From 8cd6e3e9648999eccc1119d59ba6de58bbee2d1d Mon Sep 17 00:00:00 2001 From: can1357 Date: Mon, 3 Aug 2026 15:45:44 +0200 Subject: [PATCH] chore: delete dumb tests --- package.json | 2 +- scripts/bazel-natives.test.ts | 156 -------------- scripts/ci-concurrency.test.ts | 324 ----------------------------- scripts/ci-release-notes.test.ts | 128 ------------ scripts/ci-release-publish.test.ts | 61 ------ scripts/ci-test-ts.ts | 39 +--- scripts/fix-dts-extensions.test.ts | 209 ------------------- scripts/link-omp.test.ts | 99 --------- 8 files changed, 4 insertions(+), 1014 deletions(-) delete mode 100644 scripts/bazel-natives.test.ts delete mode 100644 scripts/ci-concurrency.test.ts delete mode 100644 scripts/ci-release-notes.test.ts delete mode 100644 scripts/ci-release-publish.test.ts delete mode 100644 scripts/fix-dts-extensions.test.ts delete mode 100644 scripts/link-omp.test.ts diff --git a/package.json b/package.json index c9c8c010e..0652c1dc9 100644 --- a/package.json +++ b/package.json @@ -117,7 +117,7 @@ "build:native": "bun --cwd=packages/natives run build", "test": "bun scripts/ci-test-ts.ts local", "test:ts": "bun scripts/ci-test-ts.ts local-ts", - "test:scripts": "bun test scripts/bazel-natives.test.ts scripts/ci-concurrency.test.ts scripts/ci-release-build-binaries.test.ts scripts/ci-release-notes.test.ts scripts/ci-release-publish.test.ts scripts/fix-dts-extensions.test.ts scripts/link-omp.test.ts scripts/musl-release.test.ts", + "test:scripts": "bun test scripts/ci-release-build-binaries.test.ts scripts/musl-release.test.ts", "test:rs": "bun scripts/run-rs-task.ts test:rs", "check": "bun run --parallel check:ts check:rs", "check:ts": "bun run check:tools && bun run --workspaces --if-present check", diff --git a/scripts/bazel-natives.test.ts b/scripts/bazel-natives.test.ts deleted file mode 100644 index 4ae5acc0d..000000000 --- a/scripts/bazel-natives.test.ts +++ /dev/null @@ -1,156 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import * as fs from "node:fs/promises"; -import * as os from "node:os"; -import * as path from "node:path"; -import { $ } from "bun"; -import { - conventionOutputPaths, - type HostInfo, - hostTargetName, - parseBazelFilesOutput, - parseCliArgs, - resolveTargetLabels, -} from "./bazel-natives"; - -const linuxModern: HostInfo = { platform: "linux", arch: "x64", avx2: true }; -const linuxBaseline: HostInfo = { platform: "linux", arch: "x64", avx2: false }; -const macArm: HostInfo = { platform: "darwin", arch: "arm64", avx2: false }; - -describe("hostTargetName", () => { - test("picks the x64 variant from AVX2 support", () => { - expect(hostTargetName(linuxModern)).toBe("linux-x64-modern"); - expect(hostTargetName(linuxBaseline)).toBe("linux-x64-baseline"); - // darwin x64 ships baseline only; AVX2 must not invent a modern target. - expect(hostTargetName({ platform: "darwin", arch: "x64", avx2: true })).toBe("darwin-x64-baseline"); - }); - - test("maps non-x64 and windows hosts", () => { - expect(hostTargetName(macArm)).toBe("darwin-arm64"); - expect(hostTargetName({ platform: "linux", arch: "arm64", avx2: false })).toBe("linux-arm64"); - expect(hostTargetName({ platform: "win32", arch: "x64", avx2: true })).toBe("win32-x64-baseline"); - }); - - test("rejects hosts without an addon target", () => { - expect(() => hostTargetName({ platform: "freebsd", arch: "x64", avx2: false })).toThrow( - /No pi_natives addon target/, - ); - expect(() => hostTargetName({ platform: "win32", arch: "arm64", avx2: false })).toThrow( - /No pi_natives addon target/, - ); - }); -}); - -describe("resolveTargetLabels", () => { - test("maps explicit names, pseudo-targets, and aggregates to labels", () => { - expect(resolveTargetLabels(["linux-x64-baseline", "linux-x64-modern"], macArm)).toEqual([ - "//:natives-linux-x64-baseline", - "//:natives-linux-x64-modern", - ]); - expect(resolveTargetLabels(["host"], linuxModern)).toEqual(["//:natives-linux-x64-modern"]); - expect(resolveTargetLabels(["linux-all", "darwin-all"], macArm)).toEqual([ - "//:natives-linux-all", - "//:natives-darwin-all", - ]); - }); - - test("deduplicates and rejects unknown targets", () => { - expect(resolveTargetLabels(["host", "darwin-arm64"], macArm)).toEqual(["//:natives-darwin-arm64"]); - expect(() => resolveTargetLabels(["linux-x64"], macArm)).toThrow(/Unknown native target "linux-x64"/); - }); -}); - -describe("conventionOutputPaths", () => { - test("builds bazel-bin paths with canonical filenames (musl reuses linux names)", () => { - expect(conventionOutputPaths(["linux-musl-x64-baseline", "win32-x64-baseline"], macArm)).toEqual([ - "bazel-bin/natives-linux-musl-x64-baseline/pi_natives.linux-x64-baseline.node", - "bazel-bin/natives-win32-x64-baseline/pi_natives.win32-x64-baseline.node", - ]); - }); - - test("expands aggregates and the host pseudo-target", () => { - expect(conventionOutputPaths(["darwin-all"], macArm)).toEqual([ - "bazel-bin/natives-darwin-arm64/pi_natives.darwin-arm64.node", - "bazel-bin/natives-darwin-x64-baseline/pi_natives.darwin-x64-baseline.node", - ]); - expect(conventionOutputPaths(["host"], linuxBaseline)).toEqual([ - "bazel-bin/natives-linux-x64-baseline/pi_natives.linux-x64-baseline.node", - ]); - }); -}); - -describe("parseBazelFilesOutput", () => { - test("keeps only .node paths, trimmed and deduplicated", () => { - const output = [ - "bazel-bin/natives-linux-x64-baseline/pi_natives.linux-x64-baseline.node", - " bazel-bin/natives-linux-x64-modern/pi_natives.linux-x64-modern.node ", - "bazel-bin/natives-linux-x64-baseline/pi_natives.linux-x64-baseline.node", - "INFO: Analyzed 2 targets (0 packages loaded, 0 targets configured).", - "", - ].join("\n"); - expect(parseBazelFilesOutput(output)).toEqual([ - "bazel-bin/natives-linux-x64-baseline/pi_natives.linux-x64-baseline.node", - "bazel-bin/natives-linux-x64-modern/pi_natives.linux-x64-modern.node", - ]); - }); - - test("returns empty for output without addon files", () => { - expect(parseBazelFilesOutput("INFO: Build completed successfully\n")).toEqual([]); - }); -}); - -describe("parseCliArgs", () => { - test("splits targets, paths, and passthrough bazel args", () => { - expect( - parseCliArgs(["linux-x64-baseline", "linux-x64-modern", "--dest", "out", "--", "--config=ci", "--dest"]), - ).toEqual({ - targets: ["linux-x64-baseline", "linux-x64-modern"], - dest: "out", - source: null, - bazelArgs: ["--config=ci", "--dest"], - }); - expect(parseCliArgs(["host", "--source", "artifact"])).toEqual({ - targets: ["host"], - dest: null, - source: "artifact", - bazelArgs: [], - }); - }); - - test("rejects invalid build and artifact source combinations", () => { - expect(() => parseCliArgs([])).toThrow(/Usage:/); - expect(() => parseCliArgs(["--", "--config=ci"])).toThrow(/Usage:/); - expect(() => parseCliArgs(["host", "--config=ci"])).toThrow(/Unknown flag --config=ci/); - expect(() => parseCliArgs(["host", "--dest"])).toThrow(/--dest requires/); - expect(() => parseCliArgs(["host", "--source"])).toThrow(/--source requires/); - expect(() => parseCliArgs(["host", "--source", "artifact", "--", "--config=ci"])).toThrow( - /--source cannot be combined/, - ); - }); -}); - -describe("artifact source install", () => { - test("installs exact target outputs without invoking Bazel", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-native-artifacts-")); - const source = path.join(root, "source"); - const dest = path.join(root, "dest"); - const baseline = "pi_natives.linux-x64-baseline.node"; - const modern = "pi_natives.linux-x64-modern.node"; - try { - await fs.mkdir(path.join(source, "natives-linux-x64-baseline"), { recursive: true }); - await fs.mkdir(path.join(source, "natives-linux-x64-modern"), { recursive: true }); - await Bun.write(path.join(source, "natives-linux-x64-baseline", baseline), "baseline"); - await Bun.write(path.join(source, "natives-linux-x64-modern", modern), "modern"); - - const result = - await $`${process.execPath} ${path.join(import.meta.dir, "bazel-natives.ts")} linux-x64-baseline linux-x64-modern --source ${source} --dest ${dest}` - .quiet() - .nothrow(); - - expect(result.exitCode).toBe(0); - expect(await Bun.file(path.join(dest, baseline)).text()).toBe("baseline"); - expect(await Bun.file(path.join(dest, modern)).text()).toBe("modern"); - } finally { - await fs.rm(root, { recursive: true, force: true }); - } - }); -}); diff --git a/scripts/ci-concurrency.test.ts b/scripts/ci-concurrency.test.ts deleted file mode 100644 index 71231728f..000000000 --- a/scripts/ci-concurrency.test.ts +++ /dev/null @@ -1,324 +0,0 @@ -// Regression test for #2564: the CI workflow's `concurrency` block must route -// release runs to a per-sha group with no cancellation, so a later main push -// can't kill the in-flight release and leave the tag unpublished. The block is -// evaluated by GitHub at workflow-scheduling time (before any job can produce -// the signal), so this test re-implements the small subset of GitHub -// expression semantics the block uses and asserts the resolved group / cancel -// flag for every event shape we care about. - -import { describe, expect, it } from "bun:test"; -import * as path from "node:path"; - -const WORKFLOW_PATH = path.resolve(import.meta.dir, "..", ".github", "workflows", "ci.yml"); - -type Value = string | boolean | null; - -// `github` context fed into the evaluator. Nested objects are walked the same -// way as in real GHA expressions; missing keys resolve to `null`. -interface GhaCtx { - workflow: string; - ref: string; - sha: string; - event_name: string; - event: { - head_commit?: { message?: string }; - }; -} - -// Single-purpose, hand-rolled evaluator for the operators / functions the -// workflow's `concurrency` block uses: `startsWith`, `format`, `!`, `==`, -// `&&`, `||`, parens, single-quoted strings, dotted property access. Matches -// short-circuit semantics: `&&`/`||` return the underlying value (not a coerced -// bool), missing identifiers resolve to `null`, and `startsWith(null, …)` is -// false because the searchString coerces to `""`. -class GhaEval { - #pos = 0; - - private constructor( - private readonly src: string, - private readonly ctx: { github: GhaCtx }, - ) {} - - static run(expr: string, ctx: { github: GhaCtx }): Value { - const ev = new GhaEval(expr.trim(), ctx); - const value = ev.#or(); - ev.#skipWs(); - if (ev.#pos !== ev.src.length) { - throw new Error(`trailing input at offset ${ev.#pos}: ${ev.src.slice(ev.#pos)}`); - } - return value; - } - - // Substitute every `${{ … }}` placeholder in a workflow template string. - static template(template: string, ctx: { github: GhaCtx }): string { - let out = ""; - let i = 0; - while (i < template.length) { - const start = template.indexOf("${{", i); - if (start === -1) { - out += template.slice(i); - break; - } - out += template.slice(i, start); - const end = template.indexOf("}}", start); - if (end === -1) throw new Error("unterminated ${{ expression"); - const v = GhaEval.run(template.slice(start + 3, end), ctx); - out += v === null ? "" : String(v); - i = end + 2; - } - return out; - } - - #or(): Value { - let left = this.#and(); - while (this.#consume("||")) { - const right = this.#and(); - // Truthy left wins; only null/false/"" fall through. - if (left !== null && left !== false && left !== "") continue; - left = right; - } - return left; - } - - #and(): Value { - let left = this.#eq(); - while (this.#consume("&&")) { - const right = this.#eq(); - // Falsy left short-circuits and is returned verbatim. - if (left === null || left === false || left === "") continue; - left = right; - } - return left; - } - - #eq(): Value { - let left = this.#unary(); - while (true) { - if (this.#consume("==")) { - const right = this.#unary(); - left = left === right; - continue; - } - if (this.#consume("!=")) { - const right = this.#unary(); - left = left !== right; - continue; - } - return left; - } - } - - #unary(): Value { - this.#skipWs(); - if (this.src[this.#pos] === "!") { - this.#pos++; - const v = this.#unary(); - return v === null || v === false || v === ""; - } - return this.#primary(); - } - - #primary(): Value { - this.#skipWs(); - const ch = this.src[this.#pos]; - if (ch === "(") { - this.#pos++; - const v = this.#or(); - this.#skipWs(); - if (this.src[this.#pos] !== ")") throw new Error("expected `)`"); - this.#pos++; - return v; - } - if (ch === "'") return this.#string(); - // Identifier or function call. - const ident = this.#identifier(); - this.#skipWs(); - if (this.src[this.#pos] === "(") return this.#call(ident); - return this.#readPath(ident); - } - - #string(): string { - // GHA single-quoted: `''` is an escaped quote. - this.#pos++; // opening quote - let out = ""; - while (this.#pos < this.src.length) { - const c = this.src[this.#pos]; - if (c === "'") { - if (this.src[this.#pos + 1] === "'") { - out += "'"; - this.#pos += 2; - continue; - } - this.#pos++; - return out; - } - out += c; - this.#pos++; - } - throw new Error("unterminated string literal"); - } - - #identifier(): string { - const start = this.#pos; - while (this.#pos < this.src.length && /[A-Za-z0-9_.]/.test(this.src[this.#pos]!)) { - this.#pos++; - } - if (start === this.#pos) throw new Error(`expected identifier at ${this.#pos}`); - return this.src.slice(start, this.#pos); - } - - #call(name: string): Value { - this.#pos++; // opening paren - const args: Value[] = []; - this.#skipWs(); - if (this.src[this.#pos] !== ")") { - for (;;) { - args.push(this.#or()); - this.#skipWs(); - if (this.src[this.#pos] === ",") { - this.#pos++; - continue; - } - break; - } - } - this.#skipWs(); - if (this.src[this.#pos] !== ")") throw new Error("expected `)` closing call"); - this.#pos++; - switch (name) { - case "startsWith": { - const hay = args[0] === null || args[0] === false ? "" : String(args[0]); - const needle = args[1] === null || args[1] === false ? "" : String(args[1]); - return hay.startsWith(needle); - } - case "format": { - const tmpl = args[0] === null ? "" : String(args[0]); - return tmpl.replace(/\{(\d+)\}/g, (_, idx) => { - const v = args[Number(idx) + 1]; - return v === null || v === false ? "" : String(v); - }); - } - default: - throw new Error(`unsupported function: ${name}`); - } - } - - #readPath(dotted: string): Value { - let cur: unknown = this.ctx; - for (const seg of dotted.split(".")) { - if (cur == null || typeof cur !== "object") return null; - cur = (cur as Record)[seg]; - } - if (cur === undefined || cur === null) return null; - if (typeof cur === "object") return null; - return cur as Value; - } - - #consume(op: string): boolean { - this.#skipWs(); - if (this.src.startsWith(op, this.#pos)) { - this.#pos += op.length; - return true; - } - return false; - } - - #skipWs(): void { - while (this.#pos < this.src.length && /\s/.test(this.src[this.#pos]!)) this.#pos++; - } -} - -const workflowYaml = await Bun.file(WORKFLOW_PATH).text(); -// The block sits at indent 0 immediately under the top-level `concurrency:` -// key and uses single-line values, so a flat-line extract is unambiguous. -// Values are double-quoted in YAML (the GitHub expression contains `: ` from -// the `'chore: bump version to '` literal which would otherwise trip plain -// scalar parsing), so we unwrap the wrapping `"…"` here. -const concurrencySection = workflowYaml.slice(workflowYaml.indexOf("\nconcurrency:") + 1); -const groupRaw = /^\s*group:\s*(\S.*?)\s*$/m.exec(concurrencySection)?.[1]; -const cancelRaw = /^\s*cancel-in-progress:\s*(\S.*?)\s*$/m.exec(concurrencySection)?.[1]; -const groupTemplate = groupRaw?.startsWith('"') && groupRaw.endsWith('"') ? groupRaw.slice(1, -1) : groupRaw; -const cancelTemplate = cancelRaw?.startsWith('"') && cancelRaw.endsWith('"') ? cancelRaw.slice(1, -1) : cancelRaw; -if (!groupTemplate || !cancelTemplate) { - throw new Error("could not locate concurrency.group / cancel-in-progress in ci.yml"); -} - -const RELEASE_SUBJECT = "chore: bump version to 15.12.6"; - -const baseCtx = (overrides: Partial = {}): { github: GhaCtx } => ({ - github: { - workflow: "CI", - ref: "refs/heads/main", - sha: "deadbeefcafebabe", - event_name: "push", - event: {}, - ...overrides, - }, -}); - -describe("ci.yml concurrency", () => { - it("auto release push: per-sha group, no cancellation (#2564 root cause)", () => { - const ctx = baseCtx({ event: { head_commit: { message: `${RELEASE_SUBJECT}\n\nbody` } } }); - expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-release-deadbeefcafebabe"); - expect(GhaEval.template(cancelTemplate, ctx)).toBe("false"); - }); - - it("retry release push (release subject preserved): same per-sha behavior", () => { - const ctx = baseCtx({ - sha: "feedfacedeadbeef", - event: { head_commit: { message: `${RELEASE_SUBJECT}\n\nretry: fix sccache 100 exit` } }, - }); - expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-release-feedfacedeadbeef"); - expect(GhaEval.template(cancelTemplate, ctx)).toBe("false"); - }); - - it("workflow_dispatch from a `v*` tag ref: per-sha group, no cancellation", () => { - const ctx = baseCtx({ - ref: "refs/tags/v15.12.6", - event_name: "workflow_dispatch", - sha: "abc123", - event: {}, - }); - expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-release-abc123"); - expect(GhaEval.template(cancelTemplate, ctx)).toBe("false"); - }); - - it("workflow_dispatch from tagged main HEAD is isolated before release_metadata can inspect tags", () => { - const ctx = baseCtx({ - event_name: "workflow_dispatch", - sha: "taggedmain123", - event: {}, - }); - expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-release-taggedmain123"); - expect(GhaEval.template(cancelTemplate, ctx)).toBe("false"); - }); - - it("regular main push: branch-wide group, cancel-in-progress enabled", () => { - const ctx = baseCtx({ event: { head_commit: { message: "fix(ux): theme tweak" } } }); - expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-refs/heads/main"); - expect(GhaEval.template(cancelTemplate, ctx)).toBe("true"); - }); - - it("pull_request (no head_commit): branch-wide group, cancel enabled", () => { - const ctx = baseCtx({ ref: "refs/pull/42/merge", event_name: "pull_request", event: {} }); - expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-refs/pull/42/merge"); - expect(GhaEval.template(cancelTemplate, ctx)).toBe("true"); - }); - - it("two release commits with distinct shas land in disjoint groups", () => { - const a = baseCtx({ sha: "aaaa1111", event: { head_commit: { message: RELEASE_SUBJECT } } }); - const b = baseCtx({ sha: "bbbb2222", event: { head_commit: { message: RELEASE_SUBJECT } } }); - expect(GhaEval.template(groupTemplate, a)).not.toBe(GhaEval.template(groupTemplate, b)); - }); - - it("benign commit subject that merely contains the release prefix is not a release", () => { - // startsWith is anchored, so `revert: chore: bump version to 15.12.6` (a - // follow-up commit) keeps the cancel-on-newer-push behavior — it has no - // tag to publish. - const ctx = baseCtx({ - event: { head_commit: { message: `revert: ${RELEASE_SUBJECT}` } }, - }); - expect(GhaEval.template(groupTemplate, ctx)).toBe("CI-refs/heads/main"); - expect(GhaEval.template(cancelTemplate, ctx)).toBe("true"); - }); -}); diff --git a/scripts/ci-release-notes.test.ts b/scripts/ci-release-notes.test.ts deleted file mode 100644 index 08e329069..000000000 --- a/scripts/ci-release-notes.test.ts +++ /dev/null @@ -1,128 +0,0 @@ -import { describe, expect, it } from "bun:test"; -import { compareVersions, enumerateChangelogVersions, mergePackageSection } from "./ci-release-notes"; - -const FIXTURE = [ - "# Changelog", - "", - "## [Unreleased]", - "", - "### Added", - "", - "- Unreleased entry not in any tag yet.", - "", - "## [15.13.0] - 2026-06-14", - "", - "### Fixed", - "", - "- Fixed unknown `--`-prefixed flags being silently consumed as prompt text.", - "- Fixed something only in 15.13.0.", - "", - "### Removed", - "", - "- Removed a deprecated thing in 15.13.0.", - "", - "## [15.12.6] - 2026-06-14", - "", - "### Breaking Changes", - "", - "- Removed `writeLine`/`writeLineSync` from the public SessionStorageWriter contract.", - "", - "### Added", - "", - "- Added package-level exports for session context.", - "", - "## [15.12.5] - 2026-06-13", - "", - "### Changed", - "", - "- Changed terminal resize handling to paint only the visible viewport.", - "", - "### Fixed", - "", - "- Fixed unknown `--`-prefixed flags being silently consumed as prompt text.", - "", - "## [15.12.4] - 2026-06-13", - "", - "### Added", - "", - "- Predates the silent-tag window; must not appear when floor=15.12.4.", - "", -].join("\n"); - -describe("compareVersions", () => { - it("orders semver tags numerically across all components", () => { - expect(compareVersions("15.12.5", "15.13.0") < 0).toBe(true); - expect(compareVersions("v15.13.0", "15.12.6") > 0).toBe(true); - expect(compareVersions("15.12.6", "15.12.6") === 0).toBe(true); - // Numeric (not lexicographic) — 15.2.0 < 15.13.0. - expect(compareVersions("15.2.0", "15.13.0") < 0).toBe(true); - }); -}); - -describe("enumerateChangelogVersions", () => { - it("returns every semver heading in document order, skipping Unreleased", () => { - const spans = enumerateChangelogVersions(FIXTURE); - expect(spans.map(s => s.version)).toEqual(["15.13.0", "15.12.6", "15.12.5", "15.12.4"]); - }); - - it("bounds each span by the next `## [` heading (Unreleased included as boundary)", () => { - const spans = enumerateChangelogVersions(FIXTURE); - const lines = FIXTURE.split("\n"); - for (const span of spans) { - expect(lines[span.start]).toMatch(/^## \[\d+\.\d+\.\d+\]/); - // Body never bleeds into the next heading. - for (let i = span.start + 1; i < span.end; i++) { - expect(lines[i].startsWith("## [")).toBe(false); - } - } - }); -}); - -describe("mergePackageSection", () => { - it("includes every silent-tag section above floor up to target inclusive", () => { - const merged = mergePackageSection(FIXTURE, "15.12.4", "15.13.0"); - // 15.12.6 and 15.12.5 unique fingerprints must land. - expect(merged).toContain("Removed `writeLine`/`writeLineSync` from the public SessionStorageWriter contract."); - expect(merged).toContain("Added package-level exports for session context."); - expect(merged).toContain("Changed terminal resize handling to paint only the visible viewport."); - // 15.12.4 entry stays excluded — it is the floor. - expect(merged).not.toContain("Predates the silent-tag window"); - // Unreleased never leaks. - expect(merged).not.toContain("Unreleased entry"); - }); - - it("dedupes bullets flattened forward into multiple versions", () => { - const merged = mergePackageSection(FIXTURE, "15.12.4", "15.13.0"); - const dupRegex = /Fixed unknown `--`-prefixed flags being silently consumed as prompt text\./g; - expect(merged.match(dupRegex)?.length).toBe(1); - }); - - it("groups bullets under the canonical category order regardless of source-version order", () => { - const merged = mergePackageSection(FIXTURE, "15.12.4", "15.13.0"); - // Expected canonical order: Breaking Changes → Added → Changed → Fixed → Removed. - const headings = [...merged.matchAll(/^### (.+)$/gm)].map(m => m[1]); - expect(headings).toEqual(["Breaking Changes", "Added", "Changed", "Fixed", "Removed"]); - }); - - it("floor=null reproduces single-version (legacy) extraction for the target", () => { - const merged = mergePackageSection(FIXTURE, null, "15.13.0"); - expect(merged).toContain("Fixed something only in 15.13.0"); - expect(merged).toContain("Removed a deprecated thing in 15.13.0"); - // Anything below the target stays out when no floor is set. - expect(merged).not.toContain("writeLine"); - expect(merged).not.toContain("Added package-level exports"); - }); - - it("returns empty string when no version in the requested range carries body content", () => { - const empty = ["# Changelog", "", "## [15.13.0] - 2026-06-14", "", "## [15.12.6] - 2026-06-14"].join("\n"); - expect(mergePackageSection(empty, "15.12.5", "15.13.0")).toBe(""); - }); - - it("never emits a category with only blank/whitespace bullets after dedup", () => { - // If 15.13.0 already pulled the only Fixed bullet, an older section - // contributing the identical bullet must not produce an empty - // `### Fixed` heading by itself. - const merged = mergePackageSection(FIXTURE, "15.12.4", "15.13.0"); - expect(merged).not.toMatch(/### Fixed\s*\n\s*(### |$)/); - }); -}); diff --git a/scripts/ci-release-publish.test.ts b/scripts/ci-release-publish.test.ts deleted file mode 100644 index 70078718f..000000000 --- a/scripts/ci-release-publish.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { afterEach, describe, expect, it } from "bun:test"; -import * as fs from "node:fs/promises"; -import * as os from "node:os"; -import * as path from "node:path"; -import { $ } from "bun"; -import { inspectPackedTarball, isVersionAlreadyPublished, prepareNativeCorePackage } from "./ci-release-publish.ts"; - -const temporaryDirectories: string[] = []; - -afterEach(async () => { - await Promise.all(temporaryDirectories.splice(0).map(dir => fs.rm(dir, { recursive: true, force: true }))); -}); - -describe("release publish", () => { - it("uses the packed manifest identity for an exact-version registry preflight", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-release-publish-test-")); - temporaryDirectories.push(root); - const packageDir = path.join(root, "package"); - await fs.mkdir(packageDir); - await Bun.write( - path.join(packageDir, "package.json"), - JSON.stringify({ name: "@oh-my-pi/pi-test", version: "1.2.3" }), - ); - const tarball = path.join(root, "test.tgz"); - await $`tar -czf ${tarball} -C ${root} package`.quiet(); - - await expect(inspectPackedTarball(tarball)).resolves.toEqual({ - name: "@oh-my-pi/pi-test", - version: "1.2.3", - path: tarball, - }); - }); - - it("recognizes npm's existing-version machine codes and registry-precheck prose", () => { - expect(isVersionAlreadyPublished("npm error code E409\nnpm error Cannot publish over existing version")).toBe( - true, - ); - expect(isVersionAlreadyPublished("npm ERR! code E409")).toBe(true); - expect(isVersionAlreadyPublished("npm error code EPUBLISHCONFLICT")).toBe(true); - expect(isVersionAlreadyPublished("You cannot publish over the previously published versions: 1.2.3.")).toBe(true); - expect(isVersionAlreadyPublished("cannot publish over the previously published version")).toBe(false); - }); - - it("ships every file required by the lazy desktop export in the native core", async () => { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "omp-native-core-publish-test-")); - temporaryDirectories.push(root); - await Bun.write( - path.join(root, "package.json"), - JSON.stringify({ - name: "@oh-my-pi/pi-natives", - version: "1.2.3", - exports: { - "./desktop": { types: "./native/desktop.d.ts", import: "./native/desktop.js" }, - }, - }), - ); - - const manifest = await prepareNativeCorePackage(root, false); - expect(manifest.files).toEqual(expect.arrayContaining(["native/desktop.js", "native/desktop.d.ts"])); - }); -}); diff --git a/scripts/ci-test-ts.ts b/scripts/ci-test-ts.ts index ff2793276..4b2e25ecf 100755 --- a/scripts/ci-test-ts.ts +++ b/scripts/ci-test-ts.ts @@ -111,20 +111,6 @@ const nativeAndIntegrationPackages = [ // and is outside every CI TS bucket. const localOnlyWorkspacePackages = ["packages/mnemopi", "python/robomp/web"]; -// Repo-level script tests. CI's `workspace` bucket only runs the merge gates: -// the concurrency regression (the GHA-config guard) and the .d.ts extension -// rewrite (guards published-type resolution; hermetic temp-dir suite). A local -// full run also exercises the release-notes and link-omp tests. (A -// `ci-test-ts.test.ts` entry used to sit here but the file never existed — bun -// silently ignores unmatched filters when at least one other filter matches.) -const repoScriptTests = [ - "scripts/ci-concurrency.test.ts", - "scripts/bazel-natives.test.ts", - "scripts/ci-release-notes.test.ts", - "scripts/ci-release-publish.test.ts", - "scripts/fix-dts-extensions.test.ts", - "scripts/link-omp.test.ts", -]; const codingAgentNativePathPatterns = [ /(^|\/)[^/]*(bash|native|browser|cmux|mnemopi|hindsight|memory)[^/]*\.test\.ts$/i, @@ -342,20 +328,6 @@ async function commandsForMode(mode: Mode): Promise { case "workspace": return [ ...fastWorkspacePackages.map(pkg => workspaceTestCommand(pkg, 8)), - { - label: "scripts", - cwd: ".", - command: [ - "bun", - "test", - "--parallel=4", - ...onlyFailuresArgs, - "scripts/ci-concurrency.test.ts", - "scripts/bazel-natives.test.ts", - "scripts/ci-release-publish.test.ts", - "scripts/fix-dts-extensions.test.ts", - ], - }, ]; case "native": return nativeAndIntegrationPackages.map(pkg => workspaceTestCommand(pkg, 4, { smol: true })); @@ -382,20 +354,15 @@ async function commandsForMode(mode: Mode): Promise { ]; // `local-ts` is the full local TypeScript run that root `bun run test:ts` // drives: every package the old `--workspaces` fan-out covered (the CI - // `all` set PLUS mnemopi and robomp-web, which CI omits) and every repo - // script test, routed through this one quiet runner so the whole suite - // shares one progress stream and one failure report. + // `all` set PLUS mnemopi and robomp-web, which CI omits), routed through + // this one quiet runner so the whole suite shares one progress stream and + // one failure report. Repo script tests remain available via `test:scripts`. case "local-ts": return [ ...fastWorkspacePackages.map(pkg => workspaceTestCommand(pkg, 8, { extraArgs: onlyFailuresArgs })), ...nativeAndIntegrationPackages.map(pkg => workspaceTestCommand(pkg, 4, { extraArgs: onlyFailuresArgs })), ...localOnlyWorkspacePackages.map(pkg => workspaceTestCommand(pkg, 4, { extraArgs: onlyFailuresArgs })), ...(await commandsForMode("coding-agent-heavy")), - { - label: "scripts", - cwd: ".", - command: ["bun", "test", "--parallel=4", ...onlyFailuresArgs, ...repoScriptTests], - }, ]; // `local` is what root `bun run test` drives: the full TS suite plus the // Rust task, so a single invocation reports TS and Rust together. The Rust diff --git a/scripts/fix-dts-extensions.test.ts b/scripts/fix-dts-extensions.test.ts deleted file mode 100644 index 5028f88c0..000000000 --- a/scripts/fix-dts-extensions.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { afterEach, describe, expect, it } from "bun:test"; -import * as fs from "node:fs/promises"; -import * as os from "node:os"; -import * as path from "node:path"; -import { fixDtsExtensions, fixDtsFile, resolveDtsSpecifier } from "./fix-dts-extensions"; - -// Each test builds a real `dist/types`-shaped tree under a fresh temp dir, runs -// the transform, and reads the bytes the code wrote back off disk. We assert on -// the rewritten content / return counts (the observable contract), never on the -// source text of the helper. -const tempDirs: string[] = []; - -async function makeTree(files: Record): Promise { - const root = await fs.mkdtemp(path.join(os.tmpdir(), "fix-dts-")); - tempDirs.push(root); - for (const [rel, content] of Object.entries(files)) { - const abs = path.join(root, rel); - await fs.mkdir(path.dirname(abs), { recursive: true }); - await fs.writeFile(abs, content); - } - return root; -} - -function read(...segments: string[]): Promise { - return fs.readFile(path.join(...segments), "utf8"); -} - -afterEach(async () => { - while (tempDirs.length > 0) { - const dir = tempDirs.pop(); - if (dir) await fs.rm(dir, { recursive: true, force: true }); - } -}); - -describe("resolveDtsSpecifier", () => { - it("returns null for bare, scoped, node:, and already-suffixed specifiers", async () => { - const root = await makeTree({ "placeholder.d.ts": "export {};\n" }); - for (const spec of ["@oh-my-pi/pi-tui", "zod/v4", "node:fs", "./x.js", "./data.json", "./m.mjs", "./c.cjs"]) { - expect(await resolveDtsSpecifier(root, spec)).toBeNull(); - } - }); - - it("maps a .d.ts specifier to .js without consulting the filesystem", async () => { - // No `foo.d.ts` planted: the .d.ts → .js mapping is a pure rewrite. - const root = await makeTree({ "placeholder.d.ts": "export {};\n" }); - expect(await resolveDtsSpecifier(root, "./foo.d.ts")).toBe("./foo.js"); - }); - - it("resolves relative to the given fromDir, not the tree root", async () => { - const root = await makeTree({ - "sibling.d.ts": "export declare const s: number;\n", - "sub/index.d.ts": "export {};\n", - }); - // From the subdir, `../sibling` reaches root/sibling.d.ts. - expect(await resolveDtsSpecifier(path.join(root, "sub"), "../sibling")).toBe("../sibling.js"); - // From root, `./sibling` also reaches it (control: sibling resolution works). - expect(await resolveDtsSpecifier(root, "./sibling")).toBe("./sibling.js"); - }); -}); - -describe("fixDtsFile", () => { - it("appends .js to a sibling declaration re-export and returns count 1", async () => { - const root = await makeTree({ - "sdk.d.ts": "export declare const sdk: number;\n", - "index.d.ts": 'export * from "./sdk";\n', - }); - const count = await fixDtsFile(path.join(root, "index.d.ts")); - expect(count).toBe(1); - expect(await read(root, "index.d.ts")).toBe('export * from "./sdk.js";\n'); - }); - - it("resolves a directory barrel to its index.js", async () => { - const root = await makeTree({ - "modes/index.d.ts": "export declare const X: string;\n", - "index.d.ts": 'import { X } from "./modes";\nexport { X };\n', - }); - const count = await fixDtsFile(path.join(root, "index.d.ts")); - expect(count).toBe(1); - expect(await read(root, "index.d.ts")).toBe('import { X } from "./modes/index.js";\nexport { X };\n'); - }); - - it("rewrites `export type * from` and `export type { A } from` like value exports", async () => { - const root = await makeTree({ - "sdk.d.ts": "export type A = number;\n", - "star.d.ts": 'export type * from "./sdk";\n', - "named.d.ts": 'export type { A } from "./sdk";\n', - }); - expect(await fixDtsFile(path.join(root, "star.d.ts"))).toBe(1); - expect(await fixDtsFile(path.join(root, "named.d.ts"))).toBe(1); - expect(await read(root, "star.d.ts")).toBe('export type * from "./sdk.js";\n'); - expect(await read(root, "named.d.ts")).toBe('export type { A } from "./sdk.js";\n'); - }); - - it("rewrites inline import type references", async () => { - const inlineImport = "import" + '("./types")'; - const inlineExpected = "import" + '("./types.js")'; - const root = await makeTree({ - "types.d.ts": "export type Thing = number;\n", - "index.d.ts": `export declare const typed: ${inlineImport}.Thing;\n`, - }); - const count = await fixDtsFile(path.join(root, "index.d.ts")); - expect(count).toBe(1); - expect(await read(root, "index.d.ts")).toBe(`export declare const typed: ${inlineExpected}.Thing;\n`); - }); - - it("rewrites relative declare module specifiers", async () => { - const root = await makeTree({ - "types.d.ts": "export interface Custom {}\n", - "augment.d.ts": 'declare module "./types" {\n\tinterface CustomMessages {}\n}\n', - }); - const count = await fixDtsFile(path.join(root, "augment.d.ts")); - expect(count).toBe(1); - expect(await read(root, "augment.d.ts")).toBe( - 'declare module "./types.js" {\n\tinterface CustomMessages {}\n}\n', - ); - }); - - it("leaves a file of bare and already-suffixed specifiers byte-for-byte unchanged", async () => { - const source = - 'export * from "@oh-my-pi/pi-tui";\n' + - 'import { z } from "zod/v4";\n' + - 'import * as nodefs from "node:fs";\n' + - 'export * from "./x.js";\n' + - 'import data from "./data.json";\n'; - const root = await makeTree({ "index.d.ts": source }); - const count = await fixDtsFile(path.join(root, "index.d.ts")); - expect(count).toBe(0); - expect(await read(root, "index.d.ts")).toBe(source); - }); - - it("resolves `../sibling` against the importing file's dir at depth", async () => { - const root = await makeTree({ - "sibling.d.ts": "export declare const s: number;\n", - "sub/index.d.ts": 'export * from "../sibling";\n', - }); - const count = await fixDtsFile(path.join(root, "sub", "index.d.ts")); - expect(count).toBe(1); - expect(await read(root, "sub", "index.d.ts")).toBe('export * from "../sibling.js";\n'); - }); - - it("is idempotent — a second pass returns 0 and never doubles the extension", async () => { - const root = await makeTree({ - "sdk.d.ts": "export declare const sdk: number;\n", - "index.d.ts": 'export * from "./sdk";\n', - }); - const file = path.join(root, "index.d.ts"); - expect(await fixDtsFile(file)).toBe(1); - const afterFirst = await read(root, "index.d.ts"); - expect(await fixDtsFile(file)).toBe(0); - expect(await read(root, "index.d.ts")).toBe(afterFirst); - expect(afterFirst).toBe('export * from "./sdk.js";\n'); - expect(afterFirst).not.toContain(".js.js"); - }); - - it("leaves an unresolvable relative specifier unchanged and returns 0", async () => { - const source = 'export * from "./does-not-exist";\n'; - const root = await makeTree({ "index.d.ts": source }); - expect(await resolveDtsSpecifier(root, "./does-not-exist")).toBeNull(); - expect(await fixDtsFile(path.join(root, "index.d.ts"))).toBe(0); - expect(await read(root, "index.d.ts")).toBe(source); - }); - - it("counts only resolvable specifiers, excluding an unresolvable one in the same file", async () => { - const root = await makeTree({ - "sdk.d.ts": "export declare const sdk: number;\n", - "index.d.ts": 'export * from "./sdk";\nexport * from "./ghost";\n', - }); - const count = await fixDtsFile(path.join(root, "index.d.ts")); - expect(count).toBe(1); - expect(await read(root, "index.d.ts")).toBe('export * from "./sdk.js";\nexport * from "./ghost";\n'); - }); -}); - -describe("fixDtsExtensions", () => { - it("recursively fixes every .d.ts and totals files + specifiers", async () => { - const root = await makeTree({ - // 2 resolvable specifiers: ./sdk (sibling) + ./modes (barrel). - "index.d.ts": 'export * from "./sdk";\nexport { M } from "./modes";\n', - // 0 specifiers. - "sdk.d.ts": "export declare const sdk: number;\n", - // 1 resolvable specifier: ./config (sibling within modes/). - "modes/index.d.ts": 'export * from "./config";\nexport declare const M: string;\n', - // 0 specifiers. - "modes/config.d.ts": "export declare const config: boolean;\n", - }); - const totals = await fixDtsExtensions(root); - expect(totals).toEqual({ files: 2, specifiers: 3 }); - expect(await read(root, "index.d.ts")).toBe('export * from "./sdk.js";\nexport { M } from "./modes/index.js";\n'); - expect(await read(root, "modes/index.d.ts")).toBe( - 'export * from "./config.js";\nexport declare const M: string;\n', - ); - }); - - it("integration: leaves the root barrel fully .js-suffixed with no bare forms remaining", async () => { - const root = await makeTree({ - "index.d.ts": 'export * from "./sdk";\nexport { M } from "./modes";\n', - "sdk.d.ts": "export declare const sdk: number;\n", - "modes/index.d.ts": "export declare const M: string;\n", - }); - await fixDtsExtensions(root); - const barrel = await read(root, "index.d.ts"); - expect(barrel).toContain('from "./sdk.js"'); - expect(barrel).toContain('from "./modes/index.js"'); - // The trailing quote disambiguates: `from "./sdk"` is not a substring of - // `from "./sdk.js"`, so this proves no bare specifier survived. - expect(barrel).not.toContain('from "./sdk"'); - expect(barrel).not.toContain('from "./modes"'); - }); -}); diff --git a/scripts/link-omp.test.ts b/scripts/link-omp.test.ts deleted file mode 100644 index 49a93ac8f..000000000 --- a/scripts/link-omp.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { afterEach, describe, expect, it } from "bun:test"; -import { spawnSync } from "node:child_process"; -import * as fs from "node:fs"; -import * as os from "node:os"; -import * as path from "node:path"; - -const repoRoot = path.resolve(import.meta.dir, ".."); -const linkScript = path.join(repoRoot, "scripts", "link-omp.sh"); -const targetWrapper = path.join(repoRoot, "packages", "coding-agent", "scripts", "omp"); -const tempDirs: string[] = []; - -function makeTempDir() { - const dir = fs.mkdtempSync(path.join(os.tmpdir(), "omp-link-")); - tempDirs.push(dir); - return dir; -} - -function writeBunShim(dir: string, body: string) { - const shimDir = path.join(dir, "shim"); - fs.mkdirSync(shimDir, { recursive: true }); - const shim = path.join(shimDir, "bun"); - fs.writeFileSync(shim, `#!/bin/sh\n${body}`); - fs.chmodSync(shim, 0o755); - return shimDir; -} - -function runLinkScript(env: NodeJS.ProcessEnv) { - return spawnSync("sh", [linkScript], { - cwd: repoRoot, - env, - encoding: "utf8", - }); -} - -afterEach(() => { - while (tempDirs.length > 0) { - const dir = tempDirs.pop(); - if (dir) { - fs.rmSync(dir, { recursive: true, force: true }); - } - } -}); - -describe("scripts/link-omp.sh", () => { - it("falls back to BUN_INSTALL/bin when bun cannot resolve the global bin", () => { - const dir = makeTempDir(); - const bunInstall = path.join(dir, "bun-install"); - const shimDir = writeBunShim( - dir, - [ - 'if [ "$1" = "pm" ] && [ "$2" = "-g" ] && [ "$3" = "bin" ]; then', - " echo 'error: No package.json was found for directory' >&2", - " exit 1", - "fi", - "exit 99", - "", - ].join("\n"), - ); - - const result = runLinkScript({ - ...process.env, - HOME: path.join(dir, "home"), - BUN_INSTALL: bunInstall, - PATH: `${shimDir}${path.delimiter}${process.env.PATH ?? ""}`, - }); - - expect(result.status).toBe(0); - expect(result.stderr).toBe(""); - expect(fs.readlinkSync(path.join(bunInstall, "bin", "omp"))).toBe(targetWrapper); - }); - - it("uses bun pm -g bin when it succeeds", () => { - const dir = makeTempDir(); - const globalBin = path.join(dir, "global-bin"); - const shimDir = writeBunShim( - dir, - [ - 'if [ "$1" = "pm" ] && [ "$2" = "-g" ] && [ "$3" = "bin" ]; then', - ` echo '${globalBin}'`, - " exit 0", - "fi", - "exit 99", - "", - ].join("\n"), - ); - - const result = runLinkScript({ - ...process.env, - HOME: path.join(dir, "home"), - BUN_INSTALL: path.join(dir, "unused-bun-install"), - PATH: `${shimDir}${path.delimiter}${process.env.PATH ?? ""}`, - }); - - expect(result.status).toBe(0); - expect(result.stderr).toBe(""); - expect(fs.readlinkSync(path.join(globalBin, "omp"))).toBe(targetWrapper); - expect(fs.existsSync(path.join(dir, "unused-bun-install", "bin", "omp"))).toBe(false); - }); -});