Skip to content

Commit c266e29

Browse files
committed
feat: add script to run npm commands across all actions for improved automation
1 parent 2063f10 commit c266e29

3 files changed

Lines changed: 92 additions & 0 deletions

File tree

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@code0-tech/centaurus",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"typecheck": "node scripts/for-each-action.mjs typecheck",
8+
"build": "node scripts/for-each-action.mjs build",
9+
"test": "node scripts/for-each-action.mjs test"
10+
}
11+
}

scripts/for-each-action.mjs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/usr/bin/env node
2+
/**
3+
* Runs an npm script in every action under actions/ that defines it.
4+
*
5+
* Usage: node scripts/for-each-action.mjs <script>
6+
*
7+
* Dependencies are installed automatically: with `npm ci` when running in CI,
8+
* otherwise with `npm install` and only if node_modules is missing.
9+
*/
10+
import { readdirSync, readFileSync, existsSync } from "node:fs";
11+
import { join, dirname, resolve } from "node:path";
12+
import { fileURLToPath } from "node:url";
13+
import { spawnSync } from "node:child_process";
14+
15+
const scriptName = process.argv[2];
16+
if (!scriptName) {
17+
console.error("Usage: node scripts/for-each-action.mjs <script>");
18+
process.exit(1);
19+
}
20+
21+
const rootDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
22+
const actionsDir = join(rootDir, "actions");
23+
24+
const actions = readdirSync(actionsDir, { withFileTypes: true })
25+
.filter((entry) => entry.isDirectory())
26+
.map((entry) => join(actionsDir, entry.name))
27+
.filter((dir) => existsSync(join(dir, "package.json")));
28+
29+
if (actions.length === 0) {
30+
console.error(`No actions with a package.json found in ${actionsDir}`);
31+
process.exit(1);
32+
}
33+
34+
const run = (command, args, cwd) => {
35+
console.log(`\n> ${command} ${args.join(" ")} (${cwd})`);
36+
const result = spawnSync(command, args, { cwd, stdio: "inherit", shell: process.platform === "win32" });
37+
return result.status === 0;
38+
};
39+
40+
const failed = [];
41+
const skipped = [];
42+
43+
for (const actionDir of actions) {
44+
const pkg = JSON.parse(readFileSync(join(actionDir, "package.json"), "utf8"));
45+
46+
if (!pkg.scripts?.[scriptName]) {
47+
skipped.push(pkg.name ?? actionDir);
48+
continue;
49+
}
50+
51+
const installOk = process.env.CI
52+
? run("npm", ["ci"], actionDir)
53+
: existsSync(join(actionDir, "node_modules")) || run("npm", ["install"], actionDir);
54+
55+
if (!installOk || !run("npm", ["run", scriptName], actionDir)) {
56+
failed.push(pkg.name ?? actionDir);
57+
}
58+
}
59+
60+
console.log(`\n--- ${scriptName} summary ---`);
61+
console.log(`ran: ${actions.length - skipped.length}, skipped (no "${scriptName}" script): ${skipped.length}, failed: ${failed.length}`);
62+
for (const name of skipped) console.log(` skipped: ${name}`);
63+
for (const name of failed) console.log(` failed: ${name}`);
64+
65+
if (failed.length > 0) process.exit(1);
66+
if (actions.length === skipped.length) {
67+
console.error(`No action defines a "${scriptName}" script.`);
68+
process.exit(1);
69+
}

0 commit comments

Comments
 (0)