Skip to content

Commit 24b1911

Browse files
authored
Add npm run test:build script (#189)
With the newer workloads we should also check that the build steps keep on working in isolation. - Add "npm run test:build" - Include "test:build" in github actions and only run on changed workload dirs - Fix web-ssr workload building by adding missing unicode-escape-webpack-plugin
1 parent 3ec29fe commit 24b1911

File tree

7 files changed

+205
-60
lines changed

7 files changed

+205
-60
lines changed

.github/workflows/test.yml

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ on:
1010
workflow_dispatch:
1111

1212
jobs:
13-
build:
14-
name: Build
13+
test:
14+
name: Test
1515
runs-on: macos-latest
1616
env:
1717
GITHUB_ACTIONS_OUTPUT: ""
@@ -49,3 +49,30 @@ jobs:
4949
run: |
5050
echo "Running in $BROWSER"
5151
npm run test:${{ matrix.browser }}
52+
53+
build:
54+
name: Build
55+
runs-on: ubuntu-latest
56+
steps:
57+
- name: Checkout Branch
58+
uses: actions/checkout@v4
59+
with:
60+
fetch-depth: ${{ github.event_name == 'pull_request' && 2 || 0 }}
61+
62+
- name: Setup Node
63+
uses: actions/setup-node@v4
64+
with:
65+
node-version-file: package.json
66+
cache: npm
67+
68+
- name: Install Node Packages
69+
run: npm ci
70+
71+
- name: Run Build
72+
run: |
73+
if ${{ github.event_name == 'pull_request' }}; then
74+
npm run test:build -- --diff="HEAD^...HEAD"
75+
else
76+
npm run test:build -- --diff="${{ github.event.before }}...${{ github.event.after }}"
77+
fi
78+

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"test:shell": "npm run test:prepare && npm run test:v8 && npm run test:jsc && npm run test:spidermonkey",
3232
"test:v8": "npm run test:prepare && node tests/run-shell.mjs --shell v8",
3333
"test:jsc": "npm run test:prepare && node tests/run-shell.mjs --shell jsc",
34-
"test:spidermonkey": "npm run test:prepare && node tests/run-shell.mjs --shell spidermonkey"
34+
"test:spidermonkey": "npm run test:prepare && node tests/run-shell.mjs --shell spidermonkey",
35+
"test:build": "npm run test:prepare && node tests/run-build.mjs"
3536
},
3637
"devDependencies": {
3738
"@actions/core": "^1.11.1",

tests/helper.mjs

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,12 @@
2121
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
2222
// THE POSSIBILITY OF SUCH DAMAGE.
2323

24-
import { styleText } from "node:util";
2524
import core from "@actions/core";
25+
import { spawn } from "child_process";
2626
import commandLineUsage from "command-line-usage";
27+
import { styleText } from "node:util";
2728

28-
export const GITHUB_ACTIONS_OUTPUT = "GITHUB_ACTIONS_OUTPUT" in process.env;
29+
export const GITHUB_ACTIONS_OUTPUT = ("GITHUB_ACTIONS_OUTPUT" in process.env) || ("GITHUB_EVENT_PATH" in process.env);
2930

3031
export function logInfo(...args) {
3132
const text = args.join(" ")
@@ -53,6 +54,16 @@ export function logError(...args) {
5354
}
5455
}
5556

57+
export function logCommand(...args) {
58+
const cmd = args.join(" ");
59+
if (GITHUB_ACTIONS_OUTPUT) {
60+
core.notice(styleText("blue", cmd));
61+
} else {
62+
console.log(styleText("blue", cmd));
63+
}
64+
}
65+
66+
5667
export async function logGroup(name, body) {
5768
if (GITHUB_ACTIONS_OUTPUT) {
5869
core.startGroup(name);
@@ -103,3 +114,49 @@ export async function runTest(label, testFunction) {
103114
}
104115
return true;
105116
}
117+
118+
119+
export async function sh(binary, ...args) {
120+
const cmd = `${binary} ${args.join(" ")}`;
121+
if (GITHUB_ACTIONS_OUTPUT)
122+
core.startGroup(binary);
123+
logCommand(cmd);
124+
try {
125+
return await spawnCaptureStdout(binary, args);
126+
} catch(e) {
127+
logError(e.stdoutString);
128+
throw e;
129+
} finally {
130+
if (GITHUB_ACTIONS_OUTPUT)
131+
core.endGroup();
132+
}
133+
}
134+
135+
const SPAWN_OPTIONS = Object.freeze({
136+
stdio: ["inherit", "pipe", "inherit"]
137+
});
138+
139+
async function spawnCaptureStdout(binary, args, options={}) {
140+
options = Object.assign(options, SPAWN_OPTIONS);
141+
const childProcess = spawn(binary, args, options);
142+
childProcess.stdout.pipe(process.stdout);
143+
return new Promise((resolve, reject) => {
144+
childProcess.stdoutString = "";
145+
childProcess.stdio[1].on("data", (data) => {
146+
childProcess.stdoutString += data.toString();
147+
});
148+
childProcess.on("close", (code) => {
149+
if (code === 0) {
150+
resolve(childProcess);
151+
} else {
152+
// Reject the Promise with an Error on failure
153+
const error = new Error(`Command failed with exit code ${code}: ${binary} ${args.join(" ")}`);
154+
error.process = childProcess;
155+
error.stdout = childProcess.stdoutString;
156+
error.exitCode = code;
157+
reject(error);
158+
}
159+
});
160+
childProcess.on("error", reject);
161+
})
162+
}

tests/run-build.mjs

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#! /usr/bin/env node
2+
3+
import commandLineArgs from "command-line-args";
4+
import fs from "fs";
5+
import { fileURLToPath } from "url";
6+
import path from "path";
7+
8+
import { logError, logCommand, printHelp, runTest, sh, logInfo } from "./helper.mjs";
9+
10+
const FILE_PATH = fileURLToPath(import.meta.url);
11+
const SRC_DIR = path.dirname(path.dirname(FILE_PATH));
12+
13+
const optionDefinitions = [
14+
{ name: "help", alias: "h", description: "Print this help text." },
15+
{ name: "diff", type: String, description: "A git commit range to determine which builds to run (e.g. main...HEAD)." },
16+
];
17+
18+
const options = commandLineArgs(optionDefinitions);
19+
20+
if ("help" in options)
21+
printHelp(optionDefinitions);
22+
23+
24+
if (options.diff) {
25+
const { stdoutString } = await sh("git", "diff", "--name-only", options.diff);
26+
const changedDirs = new Set();
27+
const changedFiles = stdoutString.trim().split("\n");
28+
for (const file of changedFiles) {
29+
let currentDir = path.dirname(file)
30+
while (currentDir !== ".") {
31+
changedDirs.add(path.join(SRC_DIR, currentDir));
32+
currentDir = path.dirname(currentDir);
33+
}
34+
}
35+
options.changedDirs = changedDirs;
36+
} else {
37+
options.changedDirs = undefined;
38+
}
39+
40+
async function findPackageJsonFiles(dir, accumulator=[]) {
41+
const dirEntries = fs.readdirSync(dir, { withFileTypes: true });
42+
for (const dirent of dirEntries) {
43+
if (dirent.name === "node_modules" || dirent.name === ".git")
44+
continue;
45+
const fullPath = path.join(dir, dirent.name);
46+
if (dirent.isDirectory())
47+
findPackageJsonFiles(fullPath, accumulator);
48+
else if (dirent.name === "package.json")
49+
accumulator.push(fullPath)
50+
}
51+
return accumulator;
52+
}
53+
54+
async function runBuilds() {
55+
const packageJsonFiles = await findPackageJsonFiles(SRC_DIR);
56+
let success = true;
57+
58+
logInfo(`Found ${packageJsonFiles.length} package.json files`);
59+
let filteredPackageJsonFiles = packageJsonFiles;
60+
if (options.changedDirs?.size === 0) {
61+
logInfo("No file changes detected, skipping all");
62+
}
63+
if (options.changedDirs?.size > 0) {
64+
filteredPackageJsonFiles = packageJsonFiles.filter(file => {
65+
const dir = path.dirname(file);
66+
return options.changedDirs.has(dir);
67+
});
68+
logInfo(`Found ${filteredPackageJsonFiles.length} modified package.json files to build`);
69+
}
70+
71+
for (const file of filteredPackageJsonFiles) {
72+
const content = fs.readFileSync(file, "utf-8");
73+
const packageJson = JSON.parse(content);
74+
if (!packageJson.scripts?.build) {
75+
continue;
76+
}
77+
78+
const dir = path.dirname(file);
79+
const relativeDir = path.relative(SRC_DIR, dir);
80+
const testName = `Building ./${relativeDir}:`;
81+
82+
const buildTask = async () => {
83+
const oldCWD = process.cwd();
84+
try {
85+
logCommand("cd", dir);
86+
process.chdir(dir);
87+
await sh("npm", "ci");
88+
await sh("npm", "run", "build");
89+
} finally {
90+
process.chdir(oldCWD);
91+
await sh("git", "reset", "--hard");
92+
}
93+
};
94+
95+
success &&= await runTest(testName, buildTask);
96+
}
97+
98+
if (!success) {
99+
logError("One or more builds failed.");
100+
process.exit(1);
101+
}
102+
}
103+
104+
setImmediate(runBuilds);

tests/run-shell.mjs

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -24,15 +24,12 @@
2424
// THE POSSIBILITY OF SUCH DAMAGE.
2525

2626
import commandLineArgs from "command-line-args";
27-
import { spawn } from "child_process";
28-
import { fileURLToPath } from "url";
29-
import { styleText } from "node:util";
30-
import * as path from "path";
3127
import * as fs from "fs";
3228
import * as os from "os";
33-
import core from "@actions/core";
29+
import * as path from "path";
30+
import { fileURLToPath } from "url";
3431

35-
import {logInfo, logError, logGroup, printHelp, runTest, GITHUB_ACTIONS_OUTPUT} from "./helper.mjs";
32+
import { logGroup, logInfo, printHelp, runTest, sh } from "./helper.mjs";
3633

3734
const optionDefinitions = [
3835
{ name: "shell", type: String, description: "Set the shell to test, choices are [jsc, v8, spidermonkey]." },
@@ -78,55 +75,6 @@ function convertCliArgs(cli, ...cliArgs) {
7875
}
7976

8077

81-
const SPAWN_OPTIONS = {
82-
stdio: ["inherit", "inherit", "inherit"]
83-
};
84-
85-
async function sh(binary, ...args) {
86-
const cmd = `${binary} ${args.join(" ")}`;
87-
if (GITHUB_ACTIONS_OUTPUT) {
88-
core.startGroup(binary);
89-
core.notice(styleText("blue", cmd));
90-
} else {
91-
console.log(styleText("blue", cmd));
92-
}
93-
try {
94-
const result = await spawnCaptureStdout(binary, args, SPAWN_OPTIONS);
95-
if (result.status || result.error) {
96-
logError(result.error);
97-
throw new Error(`Shell CMD failed: ${binary} ${args.join(" ")}`);
98-
}
99-
return result;
100-
} finally {
101-
if (GITHUB_ACTIONS_OUTPUT)
102-
core.endGroup();
103-
}
104-
}
105-
106-
async function spawnCaptureStdout(binary, args) {
107-
const childProcess = spawn(binary, args);
108-
childProcess.stdout.pipe(process.stdout);
109-
return new Promise((resolve, reject) => {
110-
childProcess.stdoutString = "";
111-
childProcess.stdio[1].on("data", (data) => {
112-
childProcess.stdoutString += data.toString();
113-
});
114-
childProcess.on('close', (code) => {
115-
if (code === 0) {
116-
resolve(childProcess);
117-
} else {
118-
// Reject the Promise with an Error on failure
119-
const error = new Error(`Command failed with exit code ${code}: ${binary} ${args.join(" ")}`);
120-
error.process = childProcess;
121-
error.stdout = childProcess.stdoutString;
122-
error.exitCode = code;
123-
reject(error);
124-
}
125-
});
126-
childProcess.on('error', reject);
127-
})
128-
}
129-
13078
async function runTests() {
13179
const shellBinary = await logGroup(`Installing JavaScript Shell: ${SHELL_NAME}`, testSetup);
13280
let success = true;

web-ssr/package-lock.json

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

web-ssr/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@babel/node": "^7.28.0",
1919
"@babel/preset-env": "^7.28.0",
2020
"@babel/preset-react": "^7.27.1",
21+
"@dapplets/unicode-escape-webpack-plugin": "^0.1.1",
2122
"assert": "^2.1.0",
2223
"babel-loader": "^10.0.0",
2324
"babel-node": "^0.0.1-security",

0 commit comments

Comments
 (0)