docs: document package boundary scripts

This commit is contained in:
Peter Steinberger
2026-06-04 23:37:42 -04:00
parent 056421f4f8
commit 6b0ffa2106
17 changed files with 92 additions and 0 deletions

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env node
// Prevents runtime action paths from loading global config directly.
import { collectRuntimeActionLoadConfigViolations } from "./lib/config-boundary-guard.mjs";
function main() {

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env node
// Checks built package dist files for imports outside package boundaries.
import fs from "node:fs";
import path from "node:path";
import { collectPackageDistImportErrors } from "./lib/package-dist-imports.mjs";

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Guards pnpm package patches against unapproved additions.
import { execFileSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";
@@ -110,6 +111,9 @@ function collectPatchFileViolations(cwd, violations) {
}
}
/**
* Collects disallowed package patch declarations and patch files.
*/
export function collectPackagePatchViolations(cwd = process.cwd()) {
const violations = [];
collectWorkspacePatchViolations(cwd, violations);
@@ -119,6 +123,9 @@ export function collectPackagePatchViolations(cwd = process.cwd()) {
return violations;
}
/**
* Runs the package patch guard.
*/
export async function main() {
const violations = collectPackagePatchViolations();
if (violations.length === 0) {

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Checks pairing config logic for account-scoped allowlist handling.
import ts from "typescript";
import { createPairingGuardContext } from "./lib/pairing-guard-context.mjs";
import {

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Inventories core plugin imports that cross into bundled extension files.
import { promises as fs } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
@@ -158,6 +159,9 @@ function shouldSkipFile(filePath) {
);
}
/**
* Cached inventory of src/plugins imports that cross into bundled extensions.
*/
export const collectPluginExtensionImportBoundaryInventory = createCachedAsync(async () => {
const files = (await collectTypeScriptFilesFromRoots(scanRoots))
.filter((filePath) => !shouldSkipFile(filePath))
@@ -177,10 +181,16 @@ export const collectPluginExtensionImportBoundaryInventory = createCachedAsync(a
});
});
/**
* Cached expected plugin-extension import inventory baseline.
*/
export const readExpectedInventory = createCachedAsync(async () =>
JSON.parse(await fs.readFile(baselinePath, "utf8")),
);
/**
* Diffs expected and actual plugin-extension boundary inventory entries.
*/
export function diffInventory(expected, actual) {
return diffInventoryEntries(expected, actual, compareEntries);
}
@@ -199,6 +209,9 @@ function formatEntry(entry) {
return `${entry.file}:${entry.line} [${entry.kind}] ${entry.reason} (${entry.specifier} -> ${entry.resolvedPath})`;
}
/**
* Runs the plugin-extension import boundary baseline check.
*/
export async function runPluginExtensionImportBoundaryCheck(argv, io) {
return await runBaselineInventoryCheck({
argv: argv ?? process.argv.slice(2),
@@ -211,6 +224,9 @@ export async function runPluginExtensionImportBoundaryCheck(argv, io) {
});
}
/**
* Entrypoint wrapper for the plugin-extension import boundary check.
*/
export async function main(argv, io) {
const exitCode = await runPluginExtensionImportBoundaryCheck(argv, io);
if (!io && exitCode !== 0) {

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Runs plugin lifecycle and gateway QA gauntlet probes with timing metrics.
import { spawn } from "node:child_process";
import fs from "node:fs";
import os from "node:os";
@@ -34,6 +35,9 @@ const DEFAULT_QA_PLUGIN_CHUNK_SIZE = 12;
const COMMAND_OUTPUT_MAX_BUFFER_BYTES = 16 * 1024 * 1024;
const ANSI_PATTERN = new RegExp(String.raw`\u001B\[[0-9;]*m`, "gu");
/**
* Parses plugin gateway gauntlet CLI arguments and env defaults.
*/
export function parseArgs(argv) {
const args = stripLeadingPackageManagerSeparator(argv);
const options = {
@@ -226,6 +230,9 @@ function readOptionalNonNegativeIntEnv(name) {
return raw ? parseNonNegativeInt(raw, name) : undefined;
}
/**
* Builds the command that prepares QA runtime artifacts before gauntlet probes.
*/
export function createGauntletPrebuildCommand(repoRoot) {
return {
command: process.execPath,
@@ -255,6 +262,9 @@ function chunkArray(values, chunkSize) {
return chunks;
}
/**
* Converts an output path to a repo-relative path, rejecting paths outside the repo.
*/
export function toRepoRelativePath(repoRoot, absolutePath) {
const relativePath = path.relative(repoRoot, absolutePath);
if (!relativePath || relativePath.startsWith("..") || path.isAbsolute(relativePath)) {
@@ -303,6 +313,9 @@ function timeWrapperArgs(command, args) {
return { command: "/usr/bin/time", args: ["-v", command, ...args], mode: "gnu" };
}
/**
* Parses `/usr/bin/time` output into wall, CPU, and RSS metrics.
*/
export function parseTimedMetrics(stderr, wallMs, mode) {
let userSeconds = null;
let systemSeconds = null;
@@ -374,10 +387,16 @@ function writeCommandLog(params) {
return logPath;
}
/**
* Runs a measured command through the live process implementation.
*/
export async function runMeasuredCommand(params) {
return await runMeasuredCommandLive(params);
}
/**
* Runs one command with optional timing wrapper, bounded output, and log capture.
*/
export function runMeasuredCommandLive(params) {
const { command, args, mode } =
params.timeMode === "none"
@@ -600,6 +619,9 @@ export function runMeasuredCommandLive(params) {
});
}
/**
* Reports whether gauntlet result rows contain work beyond the prebuild step.
*/
export function hasGauntletWorkRows(rows) {
return rows.some((row) => row.phase !== "prebuild");
}

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Verifies publishable plugin packages can build their npm runtime outputs.
import fs from "node:fs";
import path from "node:path";
import { pathToFileURL } from "node:url";
@@ -30,6 +31,9 @@ function parseArgs(argv) {
return { packageDirs };
}
/**
* Builds publishable plugin npm runtimes and verifies declared outputs exist.
*/
export async function checkPluginNpmRuntimeBuilds(params = {}) {
const repoRoot = path.resolve(params.repoRoot ?? ".");
const packageDirs =

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Verifies plugin SDK subpath exports and generated entrypoint metadata.
import { readFileSync } from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Rejects wildcard plugin SDK re-exports in extension API barrels.
import fs from "node:fs/promises";
import path from "node:path";
import { fileURLToPath } from "node:url";
@@ -34,6 +35,9 @@ async function listExtensionApiFiles(rootDir = extensionsRoot) {
return files.toSorted((left, right) => left.localeCompare(right));
}
/**
* Finds wildcard plugin SDK re-export lines in an extension API barrel.
*/
export function findPluginSdkWildcardReexports(source) {
return source
.split(/\r?\n/u)
@@ -41,6 +45,9 @@ export function findPluginSdkWildcardReexports(source) {
.filter(({ text }) => WILDCARD_PLUGIN_SDK_REEXPORT_PATTERN.test(text));
}
/**
* Collects extension API barrels that wildcard re-export plugin SDK subpaths.
*/
export async function collectPluginSdkWildcardReexports(rootDir = repoRoot) {
const files = await listExtensionApiFiles(path.join(rootDir, "extensions"));
const violations = [];
@@ -57,6 +64,9 @@ export async function collectPluginSdkWildcardReexports(rootDir = repoRoot) {
return violations;
}
/**
* Runs the plugin SDK wildcard re-export guard.
*/
export async function main(argv = process.argv.slice(2), io = process) {
const json = argv.includes("--json");
const violations = await collectPluginSdkWildcardReexports();

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env node
// Validates release metadata-only changed scopes for CI routing.
import { execFileSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { RELEASE_METADATA_PATHS } from "./changed-lanes.mjs";

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Finds hidden local runtime sidecar loaders missing tsdown entries.
import { promises as fs } from "node:fs";
import path from "node:path";
import ts from "typescript";
@@ -79,6 +80,9 @@ function readObjectEntrySources(entry) {
return Object.values(entry).filter((value) => typeof value === "string");
}
/**
* Collects explicit source entry files from tsdown configuration.
*/
export function collectTsdownEntrySources(config) {
const configs = Array.isArray(config) ? config : [config];
return new Set(
@@ -86,6 +90,9 @@ export function collectTsdownEntrySources(config) {
);
}
/**
* Finds local runtime require loaders not represented as explicit tsdown entries.
*/
export function findRuntimeSidecarLoaderViolations(content, importerPath, explicitEntrySources) {
const sourceFile = ts.createSourceFile(importerPath, content, ts.ScriptTarget.Latest, true);
const createRequireNames = new Set();
@@ -220,6 +227,9 @@ export function findRuntimeSidecarLoaderViolations(content, importerPath, explic
return violations;
}
/**
* Collects runtime sidecar loader violations across configured roots.
*/
export async function collectRuntimeSidecarLoaderViolations(params) {
const files = await collectTypeScriptFilesFromRoots(params.sourceRoots, {
extraTestSuffixes: [".test-support.ts", ".test-helpers.ts"],

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Runs the SDK-package extension import boundary checker.
import { createExtensionImportBoundaryChecker } from "./lib/extension-import-boundary-checker.mjs";
import { runAsScript } from "./lib/ts-guard-utils.mjs";
@@ -15,6 +16,9 @@ const checker = createExtensionImportBoundaryChecker({
},
});
/**
* Entrypoint for the SDK-package extension import boundary checker.
*/
export const main = checker.main;
runAsScript(import.meta.url, main);

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Runs the src/** extension import boundary checker.
import { createExtensionImportBoundaryChecker } from "./lib/extension-import-boundary-checker.mjs";
import { runAsScript } from "./lib/ts-guard-utils.mjs";
@@ -25,6 +26,9 @@ const checker = createExtensionImportBoundaryChecker({
},
});
/**
* Entrypoint for the src extension import boundary checker.
*/
export const main = checker.main;
runAsScript(import.meta.url, main);

View File

@@ -1,4 +1,5 @@
#!/usr/bin/env node
// Prevents Telegram runtime imports from grammy type-only modules.
import { readdirSync, readFileSync } from "node:fs";
import path from "node:path";

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Runs the test-helper extension import boundary checker.
import { createExtensionImportBoundaryChecker } from "./lib/extension-import-boundary-checker.mjs";
import { runAsScript } from "./lib/ts-guard-utils.mjs";
@@ -11,7 +12,13 @@ const checker = createExtensionImportBoundaryChecker({
inventoryTitle: "Test-helper extension import boundary inventory:",
});
/**
* Collects test-helper extension import boundary inventory.
*/
export const collectTestHelperExtensionImportBoundaryInventory = checker.collectInventory;
/**
* Entrypoint for the test-helper extension import boundary checker.
*/
export const main = checker.main;
runAsScript(import.meta.url, main);

View File

@@ -1,3 +1,4 @@
// Wraps the aggregate check command with timing behavior.
import { main } from "./check.mjs";
await main([...process.argv.slice(2), "--timed"]);

View File

@@ -1,5 +1,6 @@
#!/usr/bin/env node
// Enforces core tsgo project boundaries and sparse-checkout safety.
import { spawnSync } from "node:child_process";
import path from "node:path";
import { createManagedCommandInvocation } from "./lib/managed-child-process.mjs";