chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { existsSync } from "node:fs";
|
||||
import { dirname, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
import {
|
||||
PACK_ARTIFACT_ALLOWED_EXACT_PATHS,
|
||||
PACK_ARTIFACT_ALLOWED_PATH_PREFIXES,
|
||||
PACK_ARTIFACT_REQUIRED_PATHS,
|
||||
findMissingArtifactPaths,
|
||||
findUnexpectedArtifactPaths,
|
||||
} from "./pack-artifact-policy.ts";
|
||||
|
||||
const __filename: string = fileURLToPath(import.meta.url);
|
||||
const __dirname: string = dirname(__filename);
|
||||
const ROOT: string = join(__dirname, "..", "..");
|
||||
const npmCommand: string = process.platform === "win32" ? "npm.cmd" : "npm";
|
||||
|
||||
function runNpm(args: string[], stdio: "inherit" | "pipe" = "pipe"): string {
|
||||
const npmExecPath = process.env.npm_execpath;
|
||||
const isBunRuntime = "Bun" in globalThis;
|
||||
const command = npmExecPath && !isBunRuntime ? process.execPath : npmCommand;
|
||||
const commandArgs = npmExecPath && !isBunRuntime ? [npmExecPath, ...args] : args;
|
||||
|
||||
return execFileSync(command, commandArgs, {
|
||||
cwd: ROOT,
|
||||
encoding: "utf8",
|
||||
stdio: stdio === "inherit" ? "inherit" : ["ignore", "pipe", "pipe"],
|
||||
maxBuffer: 64 * 1024 * 1024,
|
||||
});
|
||||
}
|
||||
|
||||
function ensureAppStagingReady(): void {
|
||||
const missingAppRequiredPaths = PACK_ARTIFACT_REQUIRED_PATHS.filter((requiredPath) =>
|
||||
requiredPath.startsWith("dist/")
|
||||
).filter((requiredPath) => !existsSync(join(ROOT, requiredPath)));
|
||||
|
||||
if (missingAppRequiredPaths.length === 0) return;
|
||||
|
||||
console.log("📦 dist/ staging is missing required runtime files; running npm run build:cli...");
|
||||
runNpm(["run", "build:cli"], "inherit");
|
||||
}
|
||||
|
||||
function runPackDryRun(): any {
|
||||
const output = runNpm(["pack", "--dry-run", "--json", "--ignore-scripts"]);
|
||||
|
||||
const jsonStart = output.indexOf("[");
|
||||
const jsonEnd = output.lastIndexOf("]");
|
||||
const jsonPayload =
|
||||
jsonStart >= 0 && jsonEnd > jsonStart ? output.slice(jsonStart, jsonEnd + 1) : output;
|
||||
const parsed = JSON.parse(jsonPayload);
|
||||
const packReport = Array.isArray(parsed) ? parsed[0] : null;
|
||||
|
||||
if (!packReport || !Array.isArray(packReport.files)) {
|
||||
throw new Error("npm pack --dry-run --json did not return the expected files[] payload.");
|
||||
}
|
||||
|
||||
return packReport;
|
||||
}
|
||||
|
||||
function formatBytes(bytes: number): string {
|
||||
if (!Number.isFinite(bytes) || bytes < 1024) {
|
||||
return `${bytes || 0} B`;
|
||||
}
|
||||
|
||||
const units = ["KB", "MB", "GB"];
|
||||
let value = bytes / 1024;
|
||||
let unitIndex = 0;
|
||||
|
||||
while (value >= 1024 && unitIndex < units.length - 1) {
|
||||
value /= 1024;
|
||||
unitIndex++;
|
||||
}
|
||||
|
||||
return `${value.toFixed(value >= 10 ? 0 : 1)} ${units[unitIndex]}`;
|
||||
}
|
||||
|
||||
// --policy-only: skip the build (ensureAppStagingReady → build:cli) and the
|
||||
// required-runtime-files check (which needs the built dist/), running ONLY the
|
||||
// unexpected-files allowlist check. The unexpected files (e.g. stray bin/*.sh) are
|
||||
// SOURCE files that `npm pack --dry-run` lists regardless of build, so this catches
|
||||
// the "new file leaked into the tarball" regression cheaply on the fast-path (PR→release),
|
||||
// instead of only on the release PR's full Package Artifact job. See incident v3.8.36 (#5029).
|
||||
const POLICY_ONLY = process.argv.includes("--policy-only");
|
||||
|
||||
try {
|
||||
if (!POLICY_ONLY) ensureAppStagingReady();
|
||||
const packReport = runPackDryRun();
|
||||
const artifactPaths: string[] = packReport.files.map((file: any) => file.path);
|
||||
const unexpectedPaths: string[] = findUnexpectedArtifactPaths(artifactPaths, {
|
||||
exactPaths: PACK_ARTIFACT_ALLOWED_EXACT_PATHS,
|
||||
prefixPaths: PACK_ARTIFACT_ALLOWED_PATH_PREFIXES,
|
||||
});
|
||||
const missingRequiredPaths: string[] = POLICY_ONLY
|
||||
? []
|
||||
: findMissingArtifactPaths(artifactPaths, PACK_ARTIFACT_REQUIRED_PATHS);
|
||||
|
||||
console.log("📦 npm pack artifact summary");
|
||||
console.log(` File: ${packReport.filename}`);
|
||||
console.log(` Entry count: ${packReport.entryCount}`);
|
||||
console.log(` Packed size: ${formatBytes(packReport.size)}`);
|
||||
console.log(` Unpacked size: ${formatBytes(packReport.unpackedSize)}`);
|
||||
|
||||
if (unexpectedPaths.length > 0) {
|
||||
console.error("\n❌ Unexpected files were found in the npm publish artifact:");
|
||||
for (const unexpectedPath of unexpectedPaths) {
|
||||
console.error(` - ${unexpectedPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingRequiredPaths.length > 0) {
|
||||
console.error("\n❌ Required runtime files are missing from the npm publish artifact:");
|
||||
for (const missingPath of missingRequiredPaths) {
|
||||
console.error(` - ${missingPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
if (unexpectedPaths.length > 0 || missingRequiredPaths.length > 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("\n✅ Pack artifact policy check passed.");
|
||||
} catch (error) {
|
||||
console.error(`\n❌ Pack artifact validation failed: ${error.message}`);
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user