Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ci(github): add check to validate exported types being correct #3561

Merged
merged 1 commit into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"approveformyorg",
"askar",
"Askar",
"attw",
"Authz",
"authzn",
"AWSSM",
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"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",
"tools:sort-package-json": "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/sort-package-json.ts",
"tools:check-missing-node-deps": "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-missing-node-deps.ts",
"tools:are-the-types-wrong": "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-attw-on-tgz.ts",
"generate-api-server-config": "node ./tools/generate-api-server-config.js",
"sync-ts-config": "TS_NODE_PROJECT=tools/tsconfig.json node --experimental-json-modules --loader ts-node/esm ./tools/sync-npm-deps-to-tsc-projects.ts",
"start:api-server": "node ./packages/cactus-cmd-api-server/dist/lib/main/typescript/cmd/cactus-api.js --config-file=.config.json",
Expand Down Expand Up @@ -121,6 +122,7 @@
"zod": ">=3.22.3"
},
"devDependencies": {
"@arethetypeswrong/cli": "0.16.4",
"@babel/parser": "7.24.7",
"@babel/types": "7.24.7",
"@bufbuild/buf": "1.30.0",
Expand Down
76 changes: 76 additions & 0 deletions tools/custom-checks/get-all-tgz-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import path from "path";
outSH marked this conversation as resolved.
Show resolved Hide resolved
import { fileURLToPath } from "url";
import { globby, Options as GlobbyOptions } from "globby";
import lernaCfg from "../../lerna.json" assert { type: "json" };

/**
* Interface for the response of the getAllTgzPath function.
* @property {Array<string>} relativePaths - An array of relative paths to the
* package directories.
*/

export interface IGetAllTgzPathResponse {
readonly relativePaths: Readonly<Array<string>>;
}

/**
* Asynchronous function to get all tgz filepaths in a Lerna monorepo.
* @returns {Promise<IGetAllTgzPathResponse>} A promise that resolves to an
* object containing the arrays of relative paths to the all tgz files.
*/

export async function getAllTgzPath(): Promise<IGetAllTgzPathResponse> {
const TAG = "[tools/get-all-tgz-path.ts]";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const SCRIPT_DIR = __dirname;
const PROJECT_DIR = path.join(SCRIPT_DIR, "../../");

console.log(`${TAG} SCRIPT_DIR=${SCRIPT_DIR}`);
console.log(`${TAG} PROJECT_DIR=${PROJECT_DIR}`);

const globbyOpts: GlobbyOptions = {
cwd: PROJECT_DIR,
onlyFiles: true,
expandDirectories: false,
// ignore pattern is added temporarily so that the yarn custom-checks github action will pass. These packages
// are failing and needs to be fixed. Each issue ticket link is added above each ignore path. More details and
// links are in the description of the issue ticket.
ignore: [
"**/node_modules",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3623
"weaver/core/drivers/fabric-driver/hyperledger-cacti-weaver-driver-fabric-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3626
"weaver/core/identity-management/iin-agent/hyperledger-cacti-weaver-iin-agent-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3627
"weaver/sdks/fabric/interoperation-node-sdk/hyperledger-cacti-weaver-sdk-fabric-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3628
"packages/cacti-plugin-weaver-driver-fabric/src/main/typescript/hyperledger-cacti-weaver-driver-fabric-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3629
"packages/cacti-plugin-copm-fabric/hyperledger-cacti-cacti-plugin-copm-fabric-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3630
"packages/cacti-ledger-browser/hyperledger-cacti-ledger-browser-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3631
"examples/cactus-example-cbdc-bridging-frontend/hyperledger-cacti-example-cbdc-bridging-frontend-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3632
"examples/cactus-common-example-server/hyperledger-cactus-common-example-server-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3633
"packages/cactus-verifier-client/hyperledger-cactus-verifier-client-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3634
"packages/cactus-plugin-ledger-connector-polkadot/hyperledger-cactus-plugin-ledger-connector-polkadot-*.tgz",
// link for issue ticket relating to this package: https://github.com/hyperledger-cacti/cacti/issues/3635
"packages/cactus-common/hyperledger-cactus-common-*.tgz",
],
};

const tgzFilesPattern = lernaCfg.packages.map(
(pkg: string) => `${pkg}/**/hyperledger-*.tgz`,
);

const tgzFilesRelative = await globby(tgzFilesPattern, globbyOpts);
console.log("%s Found %s tgz files.", TAG, tgzFilesRelative.length);

return {
relativePaths: tgzFilesRelative,
};
}
105 changes: 105 additions & 0 deletions tools/custom-checks/run-attw-on-tgz.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import esMain from "es-main";
import { getAllTgzPath } from "./get-all-tgz-path";
import { exit } from "process";
import { rm } from "fs";
import { spawn } from "child_process";

// New function to stream subprocess output in real-time
function spawnPromise(
command: string,
args: string[],
options = {},
): Promise<string> {
return new Promise((resolve, reject) => {
const child = spawn(command, args, { shell: true, ...options });
let output = "";

// Append stdout and stderr to the output string
child.stdout.on("data", (data) => {
process.stdout.write(data);
output += data.toString();
});

child.stderr.on("data", (data) => {
process.stderr.write(data);
output += data.toString();
});

child.on("close", (code) => {
if (code === 0) {
resolve(output);
} else {
const error = new Error(`Process exited with code ${code}`);
(error as any).output = output;
reject(error);
}
});

child.on("error", (err) => reject(err));
});
}

async function cleanUpTgzFiles(): Promise<void> {
const TAG = "[tools/custom-checks/run-attw-on-tgz.ts]";
console.log(`${TAG} Cleaning up existing .tgz files...`);

const { relativePaths: tgzFilesRelative } = await getAllTgzPath();

for (const filePath of tgzFilesRelative) {
await rm(filePath, { recursive: true, force: true }, () => {});
console.log(`${TAG} Deleted ${filePath}`);
}
}

export async function runAttwOnTgz(): Promise<[boolean, string[]]> {
await cleanUpTgzFiles();

const TAG = "[tools/custom-checks/run-attw-on-tgz.ts]";
await execCommand("yarn lerna exec 'npm pack'", true);
console.log(`${TAG} Packaging .tgz files`);

console.log(`${TAG} Fetching .tgz file paths.`);
const { relativePaths: tgzFilesRelative } = await getAllTgzPath();

const attwFailedPackages: string[] = [];

for (const filePath of tgzFilesRelative) {
try {
const output = await execCommand("attw", filePath);
console.log(output);
} catch (error: any) {
attwFailedPackages.push(
`ERROR ${filePath}: ${error.message}\n${error.output || ""}`,
);
}
}

const success = attwFailedPackages.length === 0;
return [success, attwFailedPackages];
}

async function execCommand(
binaryName: string,
argument: string | boolean,
): Promise<string> {
let command;
if (typeof argument === "boolean") {
command = binaryName;
} else if (typeof argument === "string") {
command = `${binaryName} ./${argument}`;
} else {
throw new Error("Invalid arguments for execCommand");
}

return await spawnPromise(command, []);
}

if (esMain(import.meta)) {
const [success, attwFailedPackages] = await runAttwOnTgz();
if (!success) {
console.log("Types are wrong for these packages:");
console.log(attwFailedPackages);
exit(1);
}
exit(0);
}
7 changes: 7 additions & 0 deletions tools/custom-checks/run-custom-checks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
checkMissingNodeDeps,
} from "./check-missing-node-deps";
import { getAllPkgDirs } from "./get-all-pkg-dirs";
import { runAttwOnTgz } from "./run-attw-on-tgz";

export async function runCustomChecks(
argv: string[],
Expand Down Expand Up @@ -73,6 +74,12 @@ export async function runCustomChecks(
overallSuccess = overallSuccess && success;
}

{
const [success, errors] = await runAttwOnTgz();
overallErrors = overallErrors.concat(errors);
overallSuccess = overallSuccess && success;
}

if (!overallSuccess) {
overallErrors.forEach((it) => console.error(it));
} else {
Expand Down
Loading
Loading