Skip to content

Commit 3c41d8f

Browse files
committed
Build: Generate shared entrypoint
Reduces tarred repo size from 8.5 MB to 2.2 MB
1 parent 3368848 commit 3c41d8f

13 files changed

Lines changed: 162274 additions & 1176363 deletions

build.mjs

Lines changed: 103 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { copyFile, rm, writeFile } from "node:fs/promises";
2-
import { dirname, join } from "node:path";
2+
import { basename, dirname, join } from "node:path";
33
import { fileURLToPath } from "node:url";
44

55
import * as esbuild from "esbuild";
@@ -13,6 +13,71 @@ const __dirname = dirname(__filename);
1313
const SRC_DIR = join(__dirname, "src");
1414
const OUT_DIR = join(__dirname, "lib");
1515

16+
/**
17+
* Name of the shared entrypoint file that imports each Action's code. By introducing a single
18+
* entrypoint for all the Actions, we avoid duplicating code across each Action's bundle.
19+
*/
20+
const SHARED_ENTRYPOINT = "actions-entrypoint";
21+
22+
/** The names of all the Action entry points (as referenced by `action.yml`s). */
23+
function findActionNames() {
24+
return globSync([
25+
`${SRC_DIR}/*-action.ts`,
26+
`${SRC_DIR}/*-action-post.ts`,
27+
])
28+
.map((p) => basename(p, ".ts"))
29+
.sort();
30+
}
31+
32+
const ACTION_NAMES = findActionNames();
33+
34+
/**
35+
* Generate the source for the shared entry point. The generated module dispatches at runtime to the
36+
* Action selected by `CODEQL_ACTION_ENTRYPOINT`, using `require()` to incorporate each Action's
37+
* code without executing the top-level side effects.
38+
*/
39+
function generateEntrypointTypescriptSource() {
40+
const cases = ACTION_NAMES
41+
.map(
42+
(name) =>
43+
` case ${JSON.stringify(name)}:\n require("./${name}");\n break;`,
44+
)
45+
.join("\n");
46+
return `const entrypoint = process.env.CODEQL_ACTION_ENTRYPOINT;
47+
switch (entrypoint) {
48+
${cases}
49+
default:
50+
throw new Error(
51+
\`Unknown CodeQL Action entrypoint: \${JSON.stringify(entrypoint)}. \` +
52+
"This file is intended to be invoked via the generated stubs in lib/.",
53+
);
54+
}
55+
`;
56+
}
57+
58+
/**
59+
* Resolve the virtual shared entry point and provide its generated source to esbuild without
60+
* writing it to disk.
61+
*
62+
* @type {esbuild.Plugin}
63+
*/
64+
const virtualEntrypointPlugin = {
65+
name: "virtual-actions-entrypoint",
66+
setup(build) {
67+
const namespace = "actions-entrypoint";
68+
build.onResolve({ filter: new RegExp(`^${RegExp.escape(SHARED_ENTRYPOINT)}$`) }, () => ({
69+
path: SHARED_ENTRYPOINT,
70+
namespace,
71+
}));
72+
// Restrict using the namespace. The path filter does not need to discriminate any further.
73+
build.onLoad({ filter: /.*/, namespace }, () => ({
74+
contents: generateEntrypointTypescriptSource(),
75+
resolveDir: SRC_DIR,
76+
loader: "ts",
77+
}));
78+
},
79+
};
80+
1681
/**
1782
* Clean the output directory before building.
1883
*
@@ -62,18 +127,48 @@ const onEndPlugin = {
62127
},
63128
};
64129

130+
/**
131+
* Emit a tiny stub file for each Action entrypoint. Each stub sets an environment variable
132+
* identifying which action was invoked and then `require()`s the shared bundle, which dispatches to
133+
* the correct Action's code.
134+
*
135+
* @type {esbuild.Plugin}
136+
*/
137+
const emitActionStubsPlugin = {
138+
name: "emit-action-stubs",
139+
setup(build) {
140+
build.onEnd(async () => {
141+
await Promise.all(
142+
ACTION_NAMES.map(async (name) => {
143+
const stub =
144+
`"use strict";\n` +
145+
`process.env.CODEQL_ACTION_ENTRYPOINT = ${JSON.stringify(name)};\n` +
146+
`require("./${SHARED_ENTRYPOINT}.js");\n`;
147+
await writeFile(join(OUT_DIR, `${name}.js`), stub);
148+
}),
149+
);
150+
});
151+
},
152+
};
153+
65154
const context = await esbuild.context({
66-
// Include upload-lib.ts as an entry point for use in testing environments.
67-
entryPoints: globSync([
68-
`${SRC_DIR}/*-action.ts`,
69-
`${SRC_DIR}/*-action-post.ts`,
70-
"src/upload-lib.ts",
71-
]),
155+
// Bundle every action together via the shared entry point. We also keep
156+
// `upload-lib.ts` as a separate entry point for use in testing environments.
157+
entryPoints: [
158+
{ in: SHARED_ENTRYPOINT, out: SHARED_ENTRYPOINT },
159+
join(SRC_DIR, "upload-lib.ts"),
160+
],
72161
bundle: true,
73162
format: "cjs",
74163
outdir: OUT_DIR,
75164
platform: "node",
76-
plugins: [cleanPlugin, copyDefaultsPlugin, onEndPlugin],
165+
plugins: [
166+
cleanPlugin,
167+
copyDefaultsPlugin,
168+
virtualEntrypointPlugin,
169+
emitActionStubsPlugin,
170+
onEndPlugin,
171+
],
77172
target: ["node20"],
78173
define: {
79174
__CODEQL_ACTION_VERSION__: JSON.stringify(pkg.version),

0 commit comments

Comments
 (0)