mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* Fix Windows smoke launcher payload layout
* Harden release-beta-s runner probe
* Use cmd shell for release-beta-s probe
* Add win beta build script for self-hosted lane
* Keep release-beta-s checkout lightweight
* Use explicit local beta version for release-beta-s
* Install dependencies in beta build script
* Repair Electron dist before Windows beta build
* Add release-beta-s timings and diagnostics
* Polish release-beta-s observability guardrails
* Trace tools-pack cache materialization
* Reuse materialized workspace build cache
* Trace Windows packaging segments
* Trace Windows archive process details
* Reuse installer unpacked materialization
* Preserve reusable Windows unpacked projection
* Add fast smoke telemetry for self-hosted beta
* Cache Windows archive outputs for beta lane
* Tighten fast Windows smoke lane
* Scope Windows beta artifact reporting by target
* Clean stale Windows target artifacts
* Wire Windows Authenticode signing
* Expose self-hosted beta validation modes
* Add core Windows smoke profile
* Decouple core smoke from updater fixture version
* Trace Windows smoke lifecycle steps
* Skip unpacked materialization on NSIS cache hit
* Add Nexu S3 beta publisher
* Harden self-hosted Windows beta lane
* Use Windows PowerShell for publish probe
* Run publish probe via repo script
* Require explicit self-hosted S3 public origin
* Run real installer acceptance for external feeds
* Skip ready-prompt checks for external feeds
* Relax optional self-hosted update inputs
* Tolerate BOM in self-hosted publish probe
* Converge self-hosted beta metadata and signing flow
* Run self-hosted beta metadata prep on the runner lane
* Use Windows PowerShell for self-hosted beta metadata checks
* Bypass execution policy in self-hosted beta metadata checks
* Split self-hosted beta metadata publish
* Suppress mc output in beta publish helpers
* Split Windows NSIS payload cache layers
* Require fresh tool dist metadata
* Preserve Windows overlay payload paths
* Default beta-s gate to core
* Optimize win beta packaging reuse
* simplify windows full smoke lifecycle
* preserve onboarding state after windows upgrade smoke
* fix windows tools-pack test portability
* recover stale tools-pack locks
* Allow auto signing fallback for beta builds
* Add self-hosted beta mac arm64 lane
* Quote self-hosted signing choices
* Run self-hosted beta metadata on mac lane
* Use shallow metadata checkout on self-hosted beta
* Use sparse beta metadata checkout
* Make mac release profile bash-compatible
* Allow file sparse metadata checkout
* Isolate self-hosted metadata checkout
* Clear sparse skip-worktree before mac build
* Isolate self-hosted mac build checkout
* Run self-hosted beta publish without aws cli
* Use unsigned beta prefix for mixed platform publish
* Sparse checkout self-hosted beta publish
* Publish mac beta platform before metadata
* Pass mac publish release profile
* Probe mac signing keychain import
* Expand mac signing preflight variants
* Use sudo keychain helper for self-hosted mac signing
* Route self-hosted mac codesign through sudo wrapper
* Normalize mac signing identity name
* Harden self-hosted mac notarization
* Optimize self-hosted mac release cache
* Fix mac signing helper cleanup
* Retry transient mac notarization uploads
* Keep mac notarization fail-fast
* Reduce mac symlink diagnostics noise
* Normalize self-hosted beta platform inputs
* chore: bump beta base version to 0.9.1
* chore: tune release tools-pack cache retention
* test: add temporary cache validation marker
* chore: key windows nsis base payload by content
* Revert "test: add temporary cache validation marker"
This reverts commit e8487bfd3e.
* chore: probe windows nsis payload cache before materializing
* chore: align self-hosted beta smoke modes
* chore: skip mac smoke report upload when disabled
* chore: default self-hosted beta to publish both platforms
* chore: make beta signing modes explicit
* Add release report artifacts and metatool metadata
* Fix Windows release report zip publishing
* Remove artifact handoff from release beta self-hosted lane
* Fix release beta merge follow-ups
* Fix release beta install regressions
* Restore web build dependencies
* Route Windows beta downloads through mirrors
* Use PowerShell 7 for Windows beta lane
* Fix release beta preflight gates
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Fix Windows beta smoke update reuse
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Fix release beta review follow-ups
* Refresh Nix pnpm dependency hashes
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Fix Windows signtool fallback resolution
* Reject stale beta platform manifests
* Validate beta platform manifest run identity
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Validate beta manifest commit identity
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Allow beta metadata publish reruns
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Build tools-pack fully in beta Windows bootstrap
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Emit r2 metadata for Windows beta publish
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Stabilize beta publish browser test
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Stabilize Windows beta metadata smoke test
Generated-By: looper 0.9.4 (runner=fixer, agent=codex)
* Restore multi-platform Vela CLI package
* Refresh Nix pnpm dependency hashes
Generated-By: looper 0.9.5 (runner=fixer, agent=codex)
---------
Co-authored-by: Looper <looper@noreply.github.com>
205 lines
8.1 KiB
TypeScript
205 lines
8.1 KiB
TypeScript
import { access, mkdir, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { ToolPackCache } from "../src/cache.js";
|
|
|
|
async function pathExists(path: string): Promise<boolean> {
|
|
try {
|
|
await access(path);
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
describe("ToolPackCache", () => {
|
|
it("builds once, materializes copies, and reports cache hits", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "open-design-tools-pack-cache-"));
|
|
const cacheRoot = join(root, "cache");
|
|
const firstOut = join(root, "first", "payload");
|
|
const secondOut = join(root, "second", "payload");
|
|
let builds = 0;
|
|
const cache = new ToolPackCache(cacheRoot);
|
|
const node = {
|
|
id: "test.node",
|
|
key: "key-1",
|
|
outputs: ["payload"],
|
|
invalidate: async () => null,
|
|
build: async ({ entryRoot }: { entryRoot: string }) => {
|
|
builds += 1;
|
|
await mkdir(join(entryRoot, "payload"), { recursive: true });
|
|
await writeFile(join(entryRoot, "payload", "value.txt"), `build-${builds}\n`, "utf8");
|
|
return { builds };
|
|
},
|
|
};
|
|
|
|
try {
|
|
const firstManifest = await cache.acquire({ materialize: [{ from: "payload", to: firstOut }], node });
|
|
await writeFile(join(firstOut, "value.txt"), "mutated\n", "utf8");
|
|
await cache.acquire({ materialize: [{ from: "payload", to: secondOut }], node });
|
|
|
|
expect(builds).toBe(1);
|
|
expect(firstManifest.entryPath).toContain("test.node");
|
|
expect(await readFile(join(secondOut, "value.txt"), "utf8")).toBe("build-1\n");
|
|
expect(cache.report().entries.map((entry) => entry.status)).toEqual(["miss", "hit"]);
|
|
} finally {
|
|
await rm(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("rebuilds stale entries when declared outputs are missing", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "open-design-tools-pack-cache-stale-"));
|
|
const cacheRoot = join(root, "cache");
|
|
const out = join(root, "out", "payload");
|
|
let builds = 0;
|
|
const cache = new ToolPackCache(cacheRoot);
|
|
const node = {
|
|
id: "test.stale",
|
|
key: "key-1",
|
|
outputs: ["payload"],
|
|
invalidate: async () => null,
|
|
build: async ({ entryRoot }: { entryRoot: string }) => {
|
|
builds += 1;
|
|
if (builds === 1) {
|
|
await mkdir(join(entryRoot, "payload"), { recursive: true });
|
|
await writeFile(join(entryRoot, "payload", "value.txt"), "first\n", "utf8");
|
|
return { builds };
|
|
}
|
|
await mkdir(join(entryRoot, "payload"), { recursive: true });
|
|
await writeFile(join(entryRoot, "payload", "value.txt"), "second\n", "utf8");
|
|
return { builds };
|
|
},
|
|
};
|
|
|
|
try {
|
|
await cache.acquire({ materialize: [{ from: "payload", to: out }], node });
|
|
const entryPath = cache.report().entries[0]?.entryPath;
|
|
expect(entryPath).toBeDefined();
|
|
await rm(join(entryPath!, "payload"), { force: true, recursive: true });
|
|
await cache.acquire({ materialize: [{ from: "payload", to: out }], node });
|
|
|
|
expect(builds).toBe(2);
|
|
expect(await readFile(join(out, "value.txt"), "utf8")).toBe("second\n");
|
|
expect(cache.report().entries.map((entry) => entry.status)).toEqual(["miss", "stale"]);
|
|
expect(await pathExists(join(cacheRoot, "locks", "global.lock"))).toBe(false);
|
|
} finally {
|
|
await rm(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("reads existing hits without building", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "open-design-tools-pack-cache-read-hit-"));
|
|
const cacheRoot = join(root, "cache");
|
|
const out = join(root, "out", "payload");
|
|
let builds = 0;
|
|
const cache = new ToolPackCache(cacheRoot);
|
|
const node = {
|
|
id: "test.read-hit",
|
|
key: "key-1",
|
|
outputs: ["payload"],
|
|
invalidate: async () => null,
|
|
build: async ({ entryRoot }: { entryRoot: string }) => {
|
|
builds += 1;
|
|
await mkdir(join(entryRoot, "payload"), { recursive: true });
|
|
await writeFile(join(entryRoot, "payload", "value.txt"), `build-${builds}\n`, "utf8");
|
|
return { builds };
|
|
},
|
|
};
|
|
|
|
try {
|
|
expect(await cache.readHit({ materialize: [{ from: "payload", to: out }], node })).toBeNull();
|
|
await cache.acquire({ materialize: [], node });
|
|
const hit = await cache.readHit({ materialize: [{ from: "payload", to: out }], node });
|
|
|
|
expect(hit?.payloadMetadata.builds).toBe(1);
|
|
expect(builds).toBe(1);
|
|
expect(await readFile(join(out, "value.txt"), "utf8")).toBe("build-1\n");
|
|
expect(cache.report().entries.map((entry) => entry.status)).toEqual(["miss", "hit"]);
|
|
} finally {
|
|
await rm(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("reuses stamped materialized targets until the cache key changes", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "open-design-tools-pack-cache-reuse-"));
|
|
const cacheRoot = join(root, "cache");
|
|
const out = join(root, "out", "payload");
|
|
let builds = 0;
|
|
const cache = new ToolPackCache(cacheRoot);
|
|
const createNode = (key: string) => ({
|
|
id: "test.reuse",
|
|
key,
|
|
outputs: ["payload"],
|
|
invalidate: async () => null,
|
|
build: async ({ entryRoot }: { entryRoot: string }) => {
|
|
builds += 1;
|
|
await mkdir(join(entryRoot, "payload"), { recursive: true });
|
|
await writeFile(join(entryRoot, "payload", "value.txt"), `build-${builds}\n`, "utf8");
|
|
return { builds };
|
|
},
|
|
});
|
|
|
|
try {
|
|
await cache.acquire({ materialize: [{ from: "payload", reuse: true, to: out }], node: createNode("key-1") });
|
|
await writeFile(join(out, "value.txt"), "mutated\n", "utf8");
|
|
await cache.acquire({ materialize: [{ from: "payload", reuse: true, to: out }], node: createNode("key-1") });
|
|
|
|
expect(await readFile(join(out, "value.txt"), "utf8")).toBe("mutated\n");
|
|
expect(cache.report().entries.at(-1)?.materialized[0]?.skipped).toBe(true);
|
|
|
|
await cache.acquire({ materialize: [{ from: "payload", reuse: true, to: out }], node: createNode("key-2") });
|
|
|
|
expect(await readFile(join(out, "value.txt"), "utf8")).toBe("build-2\n");
|
|
expect(cache.report().entries.at(-1)?.materialized[0]?.skipped).toBeUndefined();
|
|
} finally {
|
|
await rm(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it("seeds a miss from an alias but still builds the exact entry", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "open-design-tools-pack-cache-seed-"));
|
|
const cacheRoot = join(root, "cache");
|
|
const out = join(root, "out", "payload");
|
|
let builds = 0;
|
|
const cache = new ToolPackCache(cacheRoot);
|
|
const createNode = (key: string) => ({
|
|
id: "test.seed",
|
|
key,
|
|
outputs: ["payload"],
|
|
invalidate: async () => null,
|
|
build: async ({ entryRoot }: { entryRoot: string }) => {
|
|
builds += 1;
|
|
const seed = await readFile(join(out, "value.txt"), "utf8").catch(() => "<none>\n");
|
|
await mkdir(join(entryRoot, "payload"), { recursive: true });
|
|
await writeFile(join(entryRoot, "payload", "value.txt"), `build-${builds};seed=${seed.trim()}\n`, "utf8");
|
|
return { builds };
|
|
},
|
|
});
|
|
|
|
try {
|
|
await cache.acquire({
|
|
aliases: ["family"],
|
|
materialize: [{ from: "payload", reuse: true, to: out }],
|
|
node: createNode("key-1"),
|
|
});
|
|
await rm(out, { force: true, recursive: true });
|
|
await cache.acquire({
|
|
aliases: ["family"],
|
|
materialize: [{ from: "payload", reuse: true, to: out }],
|
|
node: createNode("key-2"),
|
|
seedFrom: [{ aliasKey: "family", materialize: [{ from: "payload", to: out }] }],
|
|
});
|
|
|
|
expect(builds).toBe(2);
|
|
expect(await readFile(join(out, "value.txt"), "utf8")).toBe("build-2;seed=build-1;seed=<none>\n");
|
|
expect(cache.report().entries.map((entry) => entry.status)).toEqual(["miss", "miss"]);
|
|
expect(cache.report().entries.at(-1)?.reason).toContain("seeded from alias");
|
|
} finally {
|
|
await rm(root, { force: true, recursive: true });
|
|
}
|
|
});
|
|
});
|