Skip to content

Commit

Permalink
chore(tools): add script to strip out non-production sources
Browse files Browse the repository at this point in the history
To run the script you can execute this on a shell:
    yarn tools:create-production-only-archive

The above will create a temporary clone of the project sources, then
delete a lot of the files that are documentation/test code (e.g. things
that do not go into production). Finally it zips the remaining files
together into something that includes a date & timestamp and the git
hash that the archive was generated from.

There were plans to make the generated zip file also encrypted, but this
is not supported by the library that we are using for compression. The
other library was not necessarily purely Javascript implemented and so
there was some reluctance to use it because of the huge overhead on the
the dependency installation process that we already have from packages
that use native code to satisfy their stated purpose.
So, with all that said, encrypting the .zip file is left as a to-do for
later. The issues tracking this feature are here:
1. cthackers/adm-zip#259
2. cthackers/adm-zip#398

Fixes #2652

Signed-off-by: Peter Somogyvari <[email protected]>
  • Loading branch information
petermetz committed Sep 6, 2023
1 parent f8a2131 commit 032efcb
Show file tree
Hide file tree
Showing 3 changed files with 404 additions and 0 deletions.
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"custom-checks": "TS_NODE_PROJECT=./tools/tsconfig.json node --trace-deprecation --experimental-modules --abort-on-uncaught-exception --loader ts-node/esm --experimental-specifier-resolution=node ./tools/custom-checks/run-custom-checks.ts",
"tools:validate-bundle-names": "TS_NODE_PROJECT=./tools/tsconfig.json node --trace-deprecation --experimental-modules --abort-on-uncaught-exception --loader ts-node/esm --experimental-specifier-resolution=node ./tools/validate-bundle-names.js",
"tools:bump-openapi-spec-dep-versions": "TS_NODE_PROJECT=./tools/tsconfig.json node --trace-deprecation --experimental-modules --abort-on-uncaught-exception --loader ts-node/esm --experimental-specifier-resolution=node ./tools/bump-openapi-spec-dep-versions.ts",
"tools:create-production-only-archive": "TS_NODE_PROJECT=./tools/tsconfig.json node --trace-deprecation --experimental-modules --abort-on-uncaught-exception --loader ts-node/esm --experimental-specifier-resolution=node ./tools/create-production-only-archive.ts",
"tools:get-latest-sem-ver-git-tag": "TS_NODE_PROJECT=./tools/tsconfig.json node --abort-on-uncaught-exception --loader ts-node/esm --experimental-specifier-resolution=node --no-warnings ./tools/get-latest-sem-ver-git-tag.ts",
"tools:generate-sbom": "TS_NODE_PROJECT=tools/tsconfig.json node --experimental-json-modules --trace-deprecation --experimental-modules --abort-on-uncaught-exception --loader ts-node/esm --experimental-specifier-resolution=node ./tools/generate-sbom.ts",
"tools:fix-pkg-npm-scope": "TS_NODE_PROJECT=tools/tsconfig.json node --experimental-json-modules --trace-deprecation --experimental-modules --abort-on-uncaught-exception --loader ts-node/esm --experimental-specifier-resolution=node ./tools/custom-checks/check-pkg-npm-scope.ts",
Expand Down Expand Up @@ -96,6 +97,7 @@
"@lerna-lite/list": "1.17.0",
"@lerna-lite/run": "1.17.0",
"@openapitools/openapi-generator-cli": "2.7.0",
"@types/adm-zip": "0.5.0",
"@types/fs-extra": "9.0.13",
"@types/jest": "29.5.3",
"@types/node": "16.18.41",
Expand All @@ -106,11 +108,13 @@
"@types/yargs": "17.0.24",
"@typescript-eslint/eslint-plugin": "6.4.0",
"@typescript-eslint/parser": "6.4.0",
"adm-zip": "0.5.10",
"buffer": "6.0.3",
"cpy-cli": "4.2.0",
"cross-env": "7.0.3",
"crypto-browserify": "3.12.0",
"cspell": "5.21.2",
"del": "7.1.0",
"del-cli": "4.0.1",
"es-main": "1.2.0",
"eslint": "7.32.0",
Expand Down
316 changes: 316 additions & 0 deletions tools/create-production-only-archive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,316 @@
import { tmpdir } from "os";
import path from "path";
import { dirname } from "path";
import { fileURLToPath } from "url";
import { deleteAsync } from "del";
import fs from "fs-extra";
import { globby, Options as GlobbyOptions } from "globby";
import { RuntimeError } from "run-time-error";
import yargs from "yargs";
import { hideBin } from "yargs/helpers";
import { simpleGit, SimpleGit, SimpleGitOptions } from "simple-git";
import { SimpleGitProgressEvent } from "simple-git";
import fastSafeStringify from "fast-safe-stringify";
import AdmZip from "adm-zip";

const TAG = "[tools/create-production-only-archive.ts] ";

export interface IFileDeletionV1 {
readonly temporaryClonePath: string;
readonly filePath: string;
readonly fileSizeBytes: number;
readonly includeGlobs: string[];
readonly excludeGlobs: string[];
}

export interface ICreateProductionOnlyArchiveV1Request {
readonly PROJECT_DIR: string;
readonly includeGlobs: string[];
readonly excludeGlobs: string[];
readonly fileSystemCleanUpEnabled: boolean;
}

export interface ICreateProductionOnlyArchiveV1Response {
readonly bundlePath: string;
readonly bundleSizeBytes: number;
readonly zipIsDone: boolean;
}

export const DEFAULT_DELETE_INCLUDE_GLOBS = [
"**/node_modules/",
"**/dist/",
"**/build/",
"**/out/",
"**/.nyc_output/",
"**/.build-cache",
"**/src/test/**",
"./examples/**",
"./weaver/tests/**",
"./weaver/samples/**",
"./weaver/docs/**",
"./weaver/sdks/fabric/interoperation-node-sdk/test/**",
"./weaver/core/network/corda-interop-app/test-cordapp/**",
"./weaver/core/identity-management/iin-agent/docker-testnet/**",
"./weaver/core/identity-management/iin-agent/test/**",
"./weaver/core/drivers/fabric-driver/docker-testnet-envs/**",
"./weaver/core/relay/docker/**",
"./weaver/core/relay/tests/**",
".git/**",
".devcontainer/**",
".github/**",
".husky/**",
".vscode/**",
".yarn/**",
"./docs/**",
"./images/**",
"./packages-python/**",
"./tools/**",
"./typings/**",
"./whitepaper/**",
".yarnrc.yml",
"./BUILD.md",
"./CHANGELOG.md",
"./CODEOWNERS",
"./CODE_OF_CONDUCT.md",
"./CONTRIBUTING.md",
"./FAQ.md",
"./GOVERNANCE.md",
"./MAINTAINERS.md",
"./README-cactus.md",
"./README.md",
"./ROADMAP.md",
"./SECURITY.md",
"./changelog.config.js",
"./commitlint.config.js",
"./.cspell.json",
"./.dcilintignore",
"./.dockerignore",
"./.eslintignore",
"./.eslintrc.js",
"./.gitattributes",
"./.gitguardian.yaml",
"./.gitignore",
"./.lintstagedrc.json",
"./.npmignore",
"./.nuclei-config.yaml",
"./.prettierignore",
"./.prettierrc.js",
"./.readthedocs.yaml",
"./.taprc",
"./webpack.config.js",
"./webpack.dev.node.js",
"./webpack.dev.web.js",
"./webpack.prod.node.js",
"./webpack.prod.web.js",
"./karma.conf.js",
"./jest.config.js",
"./jest.setup.console.logs.js",
];

export const DEFAULT_DELETE_EXCLUDE_GLOBS: string[] = [];

async function getDeletionList(
req: ICreateProductionOnlyArchiveV1Request & { readonly cloneDir: string },
): Promise<{ readonly deletions: string[] }> {
const { cloneDir, includeGlobs, excludeGlobs } = req;

const globbyOptions: GlobbyOptions = {
cwd: cloneDir,
absolute: true,
ignore: excludeGlobs,
};
const pathsToDelete = await globby(includeGlobs, globbyOptions);
const out = { deletions: pathsToDelete };
return out;
}

async function createTemporaryClone(req: {
readonly osTmpRootPath: string;
readonly cloneUrl: string;
}): Promise<{ readonly clonePath: string; readonly gitCommitHash: string }> {
const fn = `${TAG}:createTemporaryClone()`;
const tmpDirPrefix = "cacti_tools_create_production_only_archive_";

if (!req) {
throw new RuntimeError(`${fn} req was falsy.`);
}
if (!req.cloneUrl) {
throw new RuntimeError(`${fn} req.cloneUrl was falsy.`);
}
if (typeof req.cloneUrl !== "string") {
throw new RuntimeError(`${fn} req.cloneUrl was non-string.`);
}
if (req.cloneUrl.length <= 0) {
throw new RuntimeError(`${fn} req.cloneUrl was blank string.`);
}
if (!req.osTmpRootPath) {
throw new RuntimeError(`${fn} req.osTmpRootPath was falsy.`);
}
if (typeof req.osTmpRootPath !== "string") {
throw new RuntimeError(`${fn} req.osTmpRootPath was non-string.`);
}
if (req.osTmpRootPath.length <= 0) {
throw new RuntimeError(`${fn} req.osTmpRootPath was blank string.`);
}

console.log("%s req.osTmpRootPath=%s", fn, req.osTmpRootPath);
const tmpDirPathBase = path.join(req.osTmpRootPath, tmpDirPrefix);
console.log("%s tmpDirPathBase=%s", fn, tmpDirPathBase);

const tmpDirPath = await fs.mkdtemp(tmpDirPathBase);
console.log("%s tmpDirPath=%s", fn, tmpDirPath);
console.log("%s Cloning into a temporary directory at %s...", fn, tmpDirPath);

const options: Partial<SimpleGitOptions> = {
baseDir: tmpDirPath,
binary: "git",
maxConcurrentProcesses: 6,
trimmed: false,
progress: (data: SimpleGitProgressEvent) => {
console.log("%s SimpleGit_Progress=%s", fn, fastSafeStringify(data));
},
};

// when setting all options in a single object
const git: SimpleGit = simpleGit(options);

const cloneResponse = await git.clone(req.cloneUrl, tmpDirPath);
console.log("%s Cloned %s OK into %o", fn, req.cloneUrl, cloneResponse);

await git.fetch("origin", "main");

const gitCommitHash = await git.revparse("HEAD");
console.log("s Current git commit hash=%s", fn, gitCommitHash);

return { clonePath: tmpDirPath, gitCommitHash };
}

async function createProductionOnlyArchive(
req: ICreateProductionOnlyArchiveV1Request,
): Promise<ICreateProductionOnlyArchiveV1Response> {
const fnTag = `${TAG}:createProductionOnlyArchive(ICreateProductionOnlyArchiveV1Request)`;
if (!req) {
throw new RuntimeError(`${fnTag} req was falsy.`);
}
if (!req.excludeGlobs) {
throw new RuntimeError(`${fnTag} req.excludeGlobs was falsy.`);
}
if (!req.includeGlobs) {
throw new RuntimeError(`${fnTag} req.includeGlobs was falsy.`);
}
if (!req.PROJECT_DIR) {
throw new RuntimeError(`${fnTag} req.PROJECT_DIR was falsy.`);
}
if (
req.fileSystemCleanUpEnabled !== true &&
req.fileSystemCleanUpEnabled !== false
) {
throw new RuntimeError(`${fnTag} req.PROJECT_DIR was non-boolean.`);
}

const osTmpRootPath = tmpdir();
const cloneUrl = "https://github.com/hyperledger/cacti.git";
const tmpCloneRes = await createTemporaryClone({ cloneUrl, osTmpRootPath });
const { clonePath, gitCommitHash } = tmpCloneRes;

console.log("%s Clone OK: %s, git hash=%s", fnTag, clonePath, gitCommitHash);

console.log("%s Deleting globs %o", fnTag, req.includeGlobs);

const delResponse1 = await deleteAsync(req.includeGlobs, { cwd: clonePath });

console.log("%s delResponse1=%o", fnTag, delResponse1);

const deletionList = await getDeletionList({ ...req, cloneDir: clonePath });
console.log("%s Getting deletion list OK: %o", fnTag, deletionList);

const dateAndTime = new Date().toJSON().slice(0, 24).replaceAll(":", "-");
const filename = `hyperledger_cacti_production_sources_${dateAndTime}_main_git_hash_${gitCommitHash}.zip`;
const zipFilePath = path.join(req.PROJECT_DIR, "./.tmp/", filename);
console.log("%s Creating .zip archive at: %s", fnTag, zipFilePath);

const zipFile = new AdmZip();
await zipFile.addLocalFolderPromise(clonePath, {});
const zipIsDone = await zipFile.writeZipPromise(zipFilePath);
console.log("%s zipIsDone=%o", fnTag, zipIsDone);

return {
zipIsDone,
bundlePath: zipFilePath,
bundleSizeBytes: -1,
};
}

const nodePath = path.resolve(process.argv[1]);
const modulePath = path.resolve(fileURLToPath(import.meta.url));
const isRunningDirectlyViaCLI = nodePath === modulePath;

const main = async (argv: string[], env: NodeJS.ProcessEnv) => {
const req = await createRequest(argv, env);
await createProductionOnlyArchive(req);
};

if (isRunningDirectlyViaCLI) {
main(process.argv, process.env);
}

async function createRequest(
argv: string[],
env: NodeJS.ProcessEnv,
): Promise<ICreateProductionOnlyArchiveV1Request> {
if (!argv) {
throw new RuntimeError(`${TAG} Process argv cannot be falsy.`);
}
if (!env) {
throw new RuntimeError(`${TAG} Process env cannot be falsy.`);
}

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const SCRIPT_DIR = __dirname;
const PROJECT_DIR = path.join(SCRIPT_DIR, "../");
console.log(`SCRIPT_DIR=${SCRIPT_DIR}`);
console.log(`PROJECT_DIR=${PROJECT_DIR}`);

const OPT_DESC_INCLUDE_GLOBS =
"List of include globs to use when locating files and folders for deletion." as const;

const OPT_DESC_EXCLUDE_GLOBS =
"List of exclude globs to use when locating files and folders for deletion." as const;

const OPT_DESC_FILE_SYSTEM_CLEAN_UP_ENABLED =
"When set to false, it will skip deleting the cloned files. Defaults to true." as const;

const parsedCfg = await yargs(hideBin(argv))
.env("CACTI_")
.option("includeGlobs", {
alias: "i",
type: "array",
string: true,
default: DEFAULT_DELETE_INCLUDE_GLOBS,
description: OPT_DESC_INCLUDE_GLOBS,
})
.option("excludeGlobs", {
alias: "e",
type: "array",
string: true,
default: DEFAULT_DELETE_EXCLUDE_GLOBS,
description: OPT_DESC_EXCLUDE_GLOBS,
})
.option("fileSystemCleanUpEnabled", {
alias: "c",
boolean: true,
type: "boolean",
default: true,
description: OPT_DESC_FILE_SYSTEM_CLEAN_UP_ENABLED,
}).argv;

const req: ICreateProductionOnlyArchiveV1Request = {
PROJECT_DIR,
includeGlobs: parsedCfg.includeGlobs,
excludeGlobs: parsedCfg.excludeGlobs,
fileSystemCleanUpEnabled: parsedCfg.fileSystemCleanUpEnabled,
};

return req;
}
Loading

0 comments on commit 032efcb

Please sign in to comment.