Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"build:mcp": "npm --workspace @jsonbored/gittensory-mcp run build",
"build:miner": "npm --workspace @jsonbored/gittensory-engine run build && npm --workspace @jsonbored/gittensory-miner run build",
"test:mcp-pack": "node scripts/check-mcp-package.mjs",
"test:miner-pack": "node scripts/check-miner-package.mjs",
"rees:install": "npm ci --prefix review-enrichment --prefer-offline --no-audit --no-fund",
"rees:test": "npm run rees:install && npm --prefix review-enrichment test",
"rees:metadata": "npm --prefix review-enrichment run metadata",
Expand Down Expand Up @@ -72,7 +73,7 @@
"test:smoke:observability": "node scripts/smoke-observability-traces.mjs",
"test:smoke:browser:install": "playwright install chromium",
"test:smoke:browser": "node scripts/smoke-ui-browser.mjs",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:ci": "git diff --check && npm run actionlint && npm run db:migrations:check && npm run db:schema-drift:check && npm run selfhost:env-reference:check && npm run selfhost:validate-observability && npm run cf-typegen:check && npm run typecheck && npm run test:coverage && npm run test:workers && npm run build:mcp && npm run test:mcp-pack && npm run build:miner && npm run test:miner-pack && npm run rees:test && npm run ui:openapi:check && npm run ui:openapi:settings-parity && npm run ui:version-audit && npm run docs:drift-check && npm run command-reference:check && npm run ui:lint && npm run ui:typecheck && npm run ui:test && npm run ui:build",
"test:release": "npm run test:ci && npm run changelog:check",
"test:release:mcp": "npm run test:ci && npm run changelog:check:mcp",
"test:watch": "vitest",
Expand Down
65 changes: 65 additions & 0 deletions scripts/check-miner-package.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
import { join } from "node:path";
import { spawnSync } from "node:child_process";
import { fileURLToPath } from "node:url";

const ALLOWED = [
/^bin\/gittensory-miner\.js$/,
/^lib\/[a-z0-9-]+\.(js|d\.ts)$/,
/^package\.json$/,
/^README\.md$/,
];
const FORBIDDEN_PATH = /(^|\/)(\.dev\.vars|\.env|\.npmrc|.*\.pem|.*private.*key.*|.*secret.*)$/i;
const FORBIDDEN_CONTENT =
/(BEGIN (RSA |EC |OPENSSH )?PRIVATE KEY|github_pat_[A-Za-z0-9_]+|gh[pousr]_[A-Za-z0-9_]+|gts_[0-9a-f]{64}|[A-Z0-9_]*(TOKEN|SECRET|PRIVATE_KEY)=)/;

export function validateMinerPackFileList(files, readContent) {
const paths = files.map((file) => (typeof file === "string" ? file : file.path)).sort();
for (const file of paths) {
if (FORBIDDEN_PATH.test(file)) throw new Error(`Forbidden file in miner package: ${file}`);
if (!ALLOWED.some((pattern) => pattern.test(file))) throw new Error(`Unexpected file in miner package: ${file}`);
const content = readContent(file);
if (FORBIDDEN_CONTENT.test(content)) throw new Error(`Secret-like content found in miner package file: ${file}`);
}
return paths;
}

export function runMinerPackCheck(options = {}) {
const pack = options.pack ?? loadMinerPackFromNpm();
const packageRoot = options.packageRoot ?? join(process.cwd(), "packages/gittensory-miner");
const readContent =
options.readContent ??
((file) => {
if (process.env.CHECK_MINER_PACK_TEST_CONTENT !== undefined) return process.env.CHECK_MINER_PACK_TEST_CONTENT;
return readFileSync(join(packageRoot, file), "utf8");
});
const paths = validateMinerPackFileList(pack.files, readContent);
return `Miner package dry-run ok: ${paths.join(", ")}\n`;
}

function loadMinerPackFromNpm() {
if (process.env.CHECK_MINER_PACK_TEST_FILES) {
const paths = JSON.parse(process.env.CHECK_MINER_PACK_TEST_FILES);
return { files: paths.map((path) => ({ path })) };
}
const result = spawnSync("npm", ["pack", "--workspace", "@jsonbored/gittensory-miner", "--dry-run", "--json"], {
encoding: "utf8",
});
if (result.status !== 0) {
const message = result.stderr || result.stdout || "npm pack failed";
throw new Error(message.trim());
}
return JSON.parse(result.stdout)[0];
}

function main() {
try {
process.stdout.write(runMinerPackCheck());
} catch (err) {
process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
process.exit(1);
}
}

if (process.argv[1] === fileURLToPath(import.meta.url)) main();
46 changes: 46 additions & 0 deletions test/unit/check-miner-package.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { execFileSync } from "node:child_process";
import { describe, expect, it } from "vitest";

function runChecker(env: Record<string, string | undefined> = {}): { status: number; out: string } {
try {
const stdout = execFileSync(process.execPath, ["scripts/check-miner-package.mjs"], {
encoding: "utf8",
env: { ...process.env, ...env },
});
return { status: 0, out: stdout };
} catch (err) {
const e = err as { status?: number; stdout?: string; stderr?: string };
return { status: e.status ?? 1, out: `${e.stdout ?? ""}${e.stderr ?? ""}` };
}
}

describe("check-miner-package script", () => {
it("passes on the real miner workspace package", () => {
const result = runChecker();
expect(result.status).toBe(0);
expect(result.out).toMatch(/^Miner package dry-run ok:/);
expect(result.out).toContain("bin/gittensory-miner.js");
expect(result.out).toContain("package.json");
});

it("rejects a forbidden path", () => {
const result = runChecker({ CHECK_MINER_PACK_TEST_FILES: JSON.stringify([".env"]) });
expect(result.status).toBe(1);
expect(result.out).toContain("Forbidden file in miner package: .env");
});

it("rejects an unexpected file", () => {
const result = runChecker({ CHECK_MINER_PACK_TEST_FILES: JSON.stringify(["scripts/extra.mjs"]) });
expect(result.status).toBe(1);
expect(result.out).toContain("Unexpected file in miner package: scripts/extra.mjs");
});

it("rejects secret-like content", () => {
const result = runChecker({
CHECK_MINER_PACK_TEST_FILES: JSON.stringify(["package.json"]),
CHECK_MINER_PACK_TEST_CONTENT: "github_pat_abcdefghijklmnopqrstuvwxyz0123456789",
});
expect(result.status).toBe(1);
expect(result.out).toContain("Secret-like content found in miner package file: package.json");
});
});
Loading