From 7082c028f9ecd07726fb1c456582cc07474f5235 Mon Sep 17 00:00:00 2001 From: King Star Date: Sat, 13 Jun 2026 07:57:19 +0800 Subject: [PATCH] fix: include harness Dockerfiles in deploy hash --- .../deploy/__tests__/change-detection.test.ts | 40 +++++++++++++++++-- src/cli/operations/deploy/change-detection.ts | 13 ++++++ 2 files changed, 49 insertions(+), 4 deletions(-) diff --git a/src/cli/operations/deploy/__tests__/change-detection.test.ts b/src/cli/operations/deploy/__tests__/change-detection.test.ts index 028411d11..4ba542883 100644 --- a/src/cli/operations/deploy/__tests__/change-detection.test.ts +++ b/src/cli/operations/deploy/__tests__/change-detection.test.ts @@ -1,15 +1,35 @@ import { canSkipDeploy, computeProjectDeployHash } from '../change-detection'; -import { describe, expect, it, vi } from 'vitest'; +import { beforeEach, describe, expect, it, vi } from 'vitest'; + +const mockFiles = vi.hoisted(() => ({ + dockerfile: 'FROM public.ecr.aws/lambda/nodejs:20\n', + harnessJson: JSON.stringify({ + name: 'h1', + dockerfile: 'Dockerfile', + model: { provider: 'bedrock', modelId: 'anthropic.claude-3' }, + }), + prompt: 'You are a helpful assistant.', +})); vi.mock('node:fs/promises', () => ({ readFile: vi.fn().mockImplementation((path: string) => { - if (path.includes('harness.json')) - return Promise.resolve('{"name":"h1","model":{"provider":"bedrock","modelId":"anthropic.claude-3"}}'); - if (path.includes('system-prompt.md')) return Promise.resolve('You are a helpful assistant.'); + if (path.includes('harness.json')) return Promise.resolve(mockFiles.harnessJson); + if (path.includes('system-prompt.md')) return Promise.resolve(mockFiles.prompt); + if (path.includes('Dockerfile')) return Promise.resolve(mockFiles.dockerfile); return Promise.reject(Object.assign(new Error('ENOENT'), { code: 'ENOENT' })); }), })); +beforeEach(() => { + mockFiles.dockerfile = 'FROM public.ecr.aws/lambda/nodejs:20\n'; + mockFiles.harnessJson = JSON.stringify({ + name: 'h1', + dockerfile: 'Dockerfile', + model: { provider: 'bedrock', modelId: 'anthropic.claude-3' }, + }); + mockFiles.prompt = 'You are a helpful assistant.'; +}); + function mockConfigIO(opts: { runtimes?: any[]; harnesses?: any[]; @@ -52,6 +72,18 @@ describe('computeProjectDeployHash', () => { const hash2 = await computeProjectDeployHash(io2); expect(hash1).not.toBe(hash2); }); + + it('returns different hash when harness Dockerfile content changes', async () => { + const io = mockConfigIO({}); + + mockFiles.dockerfile = 'FROM public.ecr.aws/lambda/nodejs:20\nRUN echo first\n'; + const hash1 = await computeProjectDeployHash(io); + + mockFiles.dockerfile = 'FROM public.ecr.aws/lambda/nodejs:20\nRUN echo second\n'; + const hash2 = await computeProjectDeployHash(io); + + expect(hash1).not.toBe(hash2); + }); }); describe('canSkipDeploy', () => { diff --git a/src/cli/operations/deploy/change-detection.ts b/src/cli/operations/deploy/change-detection.ts index 65a513142..0ca6b57b9 100644 --- a/src/cli/operations/deploy/change-detection.ts +++ b/src/cli/operations/deploy/change-detection.ts @@ -25,6 +25,19 @@ export async function computeProjectDeployHash(configIO: ConfigIO): Promise 0) { + try { + const dockerfileContent = await readFile(join(harnessDir, harnessSpec.dockerfile), 'utf-8'); + hash.update(dockerfileContent); + } catch { + // Dockerfile missing — preflight validates existence before deploy. + } + } + } catch { + // Invalid harness.json is still represented by the raw content above. + } } catch { // harness.json missing — hash will differ from last deploy }