Merge pull request #3787 from github/mbg/bundle/metadata

Generate and analyse esbuild bundle metadata
This commit is contained in:
Michael B. Gale
2026-04-01 10:29:27 +00:00
committed by GitHub
7 changed files with 78 additions and 17 deletions

2
.gitignore vendored
View File

@@ -11,3 +11,5 @@ build/
eslint.sarif
# for local incremental compilation
tsconfig.tsbuildinfo
# esbuild metadata file
meta.json

View File

@@ -1,4 +1,4 @@
import { copyFile, rm } from "node:fs/promises";
import { copyFile, rm, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
@@ -64,7 +64,11 @@ const onEndPlugin = {
const context = await esbuild.context({
// Include upload-lib.ts as an entry point for use in testing environments.
entryPoints: globSync([`${SRC_DIR}/*-action.ts`, `${SRC_DIR}/*-action-post.ts`, "src/upload-lib.ts"]),
entryPoints: globSync([
`${SRC_DIR}/*-action.ts`,
`${SRC_DIR}/*-action-post.ts`,
"src/upload-lib.ts",
]),
bundle: true,
format: "cjs",
outdir: OUT_DIR,
@@ -74,7 +78,10 @@ const context = await esbuild.context({
define: {
__CODEQL_ACTION_VERSION__: JSON.stringify(pkg.version),
},
metafile: true,
});
await context.rebuild();
const result = await context.rebuild();
await writeFile(join(__dirname, "meta.json"), JSON.stringify(result.metafile));
await context.dispose();

View File

@@ -5,7 +5,7 @@
"description": "CodeQL action",
"scripts": {
"_build_comment": "echo 'Run the full build so we typecheck the project and can reuse the transpiled files in npm test'",
"build": "./scripts/check-node-modules.sh && npm run transpile && node build.mjs",
"build": "./scripts/check-node-modules.sh && npm run transpile && node build.mjs && npx tsx ./pr-checks/bundle-metadata.ts",
"lint": "eslint --report-unused-disable-directives --max-warnings=0 .",
"lint-ci": "SARIF_ESLINT_IGNORE_SUPPRESSED=true eslint --report-unused-disable-directives --max-warnings=0 . --format @microsoft/eslint-formatter-sarif --output-file=eslint.sarif",
"lint-fix": "eslint --report-unused-disable-directives --max-warnings=0 . --fix",

13
pr-checks/api-client.ts Normal file
View File

@@ -0,0 +1,13 @@
import * as githubUtils from "@actions/github/lib/utils";
import { type Octokit } from "@octokit/core";
import { type PaginateInterface } from "@octokit/plugin-paginate-rest";
import { type Api } from "@octokit/plugin-rest-endpoint-methods";
/** The type of the Octokit client. */
export type ApiClient = Octokit & Api & { paginate: PaginateInterface };
/** Constructs an `ApiClient` using `token` for authentication. */
export function getApiClient(token: string): ApiClient {
const opts = githubUtils.getOctokitOptions(token);
return new githubUtils.GitHub(opts);
}

48
pr-checks/bundle-metadata.ts Executable file
View File

@@ -0,0 +1,48 @@
#!/usr/bin/env npx tsx
import * as fs from "node:fs/promises";
import { BUNDLE_METADATA_FILE } from "./config";
interface InputInfo {
bytesInOutput: number;
}
type Inputs = Record<string, InputInfo>;
interface Output {
bytes: number;
inputs: Inputs;
}
interface Metadata {
outputs: Record<string, Output>;
}
function toMB(bytes: number): string {
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
}
async function main() {
const fileContents = await fs.readFile(BUNDLE_METADATA_FILE);
const metadata = JSON.parse(String(fileContents)) as Metadata;
for (const [outputFile, outputData] of Object.entries(
metadata.outputs,
).reverse()) {
console.info(`${outputFile}: ${toMB(outputData.bytes)}`);
for (const [inputName, inputData] of Object.entries(outputData.inputs)) {
// Ignore any inputs that make up less than 5% of the output.
const percentage = (inputData.bytesInOutput / outputData.bytes) * 100.0;
if (percentage < 5.0) continue;
console.info(` ${inputName}: ${toMB(inputData.bytesInOutput)}`);
}
}
}
// Only call `main` if this script was run directly.
if (require.main === module) {
void main();
}

View File

@@ -8,3 +8,6 @@ export const PR_CHECKS_DIR = __dirname;
/** The path of the file configuring which checks shouldn't be required. */
export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml");
/** The path to the esbuild metadata file. */
export const BUNDLE_METADATA_FILE = path.join(PR_CHECKS_DIR, "..", "meta.json");

View File

@@ -5,12 +5,9 @@
import * as fs from "fs";
import { parseArgs } from "node:util";
import * as githubUtils from "@actions/github/lib/utils";
import { type Octokit } from "@octokit/core";
import { type PaginateInterface } from "@octokit/plugin-paginate-rest";
import { type Api } from "@octokit/plugin-rest-endpoint-methods";
import * as yaml from "yaml";
import { type ApiClient, getApiClient } from "./api-client";
import {
OLDEST_SUPPORTED_MAJOR_VERSION,
PR_CHECK_EXCLUDED_FILE,
@@ -49,15 +46,6 @@ function loadExclusions(): Exclusions {
) as Exclusions;
}
/** The type of the Octokit client. */
type ApiClient = Octokit & Api & { paginate: PaginateInterface };
/** Constructs an `ApiClient` using `token` for authentication. */
function getApiClient(token: string): ApiClient {
const opts = githubUtils.getOctokitOptions(token);
return new githubUtils.GitHub(opts);
}
/**
* Represents information about a check run. We track the `app_id` that generated the check,
* because the API will require it in addition to the name in the future.