diff --git a/scripts/ci-plan.test.mjs b/scripts/ci-plan.test.mjs index bc00ad35..981643f5 100644 --- a/scripts/ci-plan.test.mjs +++ b/scripts/ci-plan.test.mjs @@ -1641,6 +1641,29 @@ test("v03 planner ignores Go launcher comment-only changes for env identity", as } }); +test("v03 planner rebuilds env images when the env image generator changes", async () => { + const repo = await createFixtureRepo(); + await writeFile(path.join(repo, "scripts/artifact-publish.mjs"), "export const envImageGenerator = 2;\n"); + await git(repo, ["add", "scripts/artifact-publish.mjs"]); + await git(repo, ["commit", "-m", "change env image generator"]); + + const plan = await createCiPlan({ + repoRoot: repo, + lane: "v03", + baseRef: "HEAD~1", + targetRef: "HEAD", + artifactCatalogPath: "deploy/artifact-catalog.v03.json", + services: ["hwlab-cloud-api", "hwlab-cloud-web"] + }); + + assert.deepEqual(plan.affectedServices, ["hwlab-cloud-api", "hwlab-cloud-web"]); + assert.deepEqual(plan.buildServices, ["hwlab-cloud-api", "hwlab-cloud-web"]); + for (const service of plan.services) { + assert.equal(service.environmentInputChanged, true, service.serviceId); + assert.equal(service.buildRequired, true, service.serviceId); + } +}); + test("Go env build path declares base image, stable timing, and late volatile labels", async () => { const deploy = await readStructuredFile(process.cwd(), path.join(process.cwd(), "deploy/deploy.yaml")); const recipe = envReuseRecipeForLane(deploy, "v03"); @@ -1688,6 +1711,7 @@ async function createFixtureRepo(options = {}) { await mkdir(path.join(repo, "deploy/runtime/boot"), { recursive: true }); await mkdir(path.join(repo, "deploy/runtime/launcher"), { recursive: true }); await mkdir(path.join(repo, "deploy"), { recursive: true }); + await mkdir(path.join(repo, "scripts"), { recursive: true }); await writeStructuredFile(repo, "deploy/deploy.yaml", createDeployFixture({ serviceIds: laneServiceIds })); const catalogMode = options.catalog ?? "ready"; await writeFile(path.join(repo, "package.json"), JSON.stringify({ type: "module" }, null, 2)); @@ -1724,6 +1748,7 @@ async function createFixtureRepo(options = {}) { await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-edge-proxy.sh"), "#!/bin/sh\nexec node cmd/hwlab-edge-proxy/main.mjs\n"); await writeFile(path.join(repo, "deploy/runtime/boot/hwlab-agent-skills.sh"), "#!/bin/sh\nexec bun .hwlab-agent-skills-runtime.mjs\n"); await writeFile(path.join(repo, "deploy/runtime/launcher/hwlab-env-reuse-launcher.ts"), "console.log('launcher');\n"); + await writeFile(path.join(repo, "scripts/artifact-publish.mjs"), "export const envImageGenerator = 1;\n"); await writeFile(path.join(repo, "skills/hwlab-agent-runtime/SKILL.md"), "agent runtime skill\n"); await git(repo, ["init"]); await git(repo, ["add", "."]); diff --git a/scripts/src/ci-plan-lib.mjs b/scripts/src/ci-plan-lib.mjs index 1adc21a8..c225a7fb 100644 --- a/scripts/src/ci-plan-lib.mjs +++ b/scripts/src/ci-plan-lib.mjs @@ -551,6 +551,7 @@ function envReuseRecipeForLaneConfig(laneConfig, lane = "v02") { if (!runtimeNodeModulesPath) throw new Error(`deploy.lanes.${lane}.envRecipe.runtimeNodeModulesPath must be absolute`); const additionalEnvPaths = uniqueSorted(normalizeStringList(configured.additionalEnvPaths, []).map(normalizeRepoPath)); const launcherInputPaths = uniqueSorted([ + "scripts/artifact-publish.mjs", launcherPath, ...additionalEnvPaths ]);