diff --git a/.changeset/orange-regions-run.md b/.changeset/orange-regions-run.md new file mode 100644 index 00000000..62cf0bb1 --- /dev/null +++ b/.changeset/orange-regions-run.md @@ -0,0 +1,5 @@ +--- +"@perfect-abstractions/compose-cli": patch +--- + +fix(cli): defer build freshness to framework compiler diff --git a/cli/src/modules/compile/module.ts b/cli/src/modules/compile/module.ts index 0a874762..dc95d62a 100644 --- a/cli/src/modules/compile/module.ts +++ b/cli/src/modules/compile/module.ts @@ -1,37 +1,9 @@ -import fs from "node:fs"; import { IFrameworkAdapter } from "../../adapters/IFrameworkAdapter/interface"; -import { getNewestSourceMtime, getOldestArtifactMtime } from "./utils"; export const CompileModule = { - areArtifactsStale(projectRoot: string, adapter: IFrameworkAdapter): boolean { - const artifactDir = adapter.getArtifactDir(projectRoot); - - if (!fs.existsSync(artifactDir)) { - return true; - } - - const sourceDir = adapter.getContractSourceRoot(projectRoot); - const newestSource = getNewestSourceMtime(sourceDir); - - if (newestSource === null) { - return false; - } - - const oldestArtifact = getOldestArtifactMtime(artifactDir); - - if (oldestArtifact === null) { - return true; - } - - return newestSource > oldestArtifact; - }, - - async compileIfNeeded(projectRoot: string, adapter: IFrameworkAdapter): Promise { - if (!this.areArtifactsStale(projectRoot, adapter)) { - console.log("No file change") - return; - } - + async compileProject(projectRoot: string, adapter: IFrameworkAdapter): Promise { + // Let Foundry/Hardhat evaluate their complete incremental build graph, + // including deleted files, config changes, and imported package sources. await adapter.compile(projectRoot); - } -} + }, +}; diff --git a/cli/src/modules/compile/utils.ts b/cli/src/modules/compile/utils.ts deleted file mode 100644 index a1b7d5e3..00000000 --- a/cli/src/modules/compile/utils.ts +++ /dev/null @@ -1,46 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; - -export function getNewestSourceMtime(sourceDir: string): number | null { - if (!fs.existsSync(sourceDir)) return null; - - let newest = 0; - const entries = fs.readdirSync(sourceDir, { recursive: true }); - - for (const entry of entries) { - const fullPath = path.join(sourceDir, String(entry)); - if (!fullPath.endsWith(".sol")) continue; - - try { - const stat = fs.statSync(fullPath); - if (stat.isFile() && stat.mtimeMs > newest) { - newest = stat.mtimeMs; - } - } catch { - // skip inaccessible files - } - } - - return newest > 0 ? newest : null; -} - -export function getOldestArtifactMtime(artifactDir: string): number | null { - if (!fs.existsSync(artifactDir)) return null; - - let oldest = Infinity; - const entries = fs.readdirSync(artifactDir, { recursive: true }); - - for (const entry of entries) { - const fullPath = path.join(artifactDir, String(entry)); - try { - const stat = fs.statSync(fullPath); - if (stat.isFile() && stat.mtimeMs < oldest) { - oldest = stat.mtimeMs; - } - } catch { - // skip inaccessible files - } - } - - return oldest < Infinity ? oldest : null; -} diff --git a/cli/src/pipelines/buildPipeline.ts b/cli/src/pipelines/buildPipeline.ts index 95648471..f53608e2 100644 --- a/cli/src/pipelines/buildPipeline.ts +++ b/cli/src/pipelines/buildPipeline.ts @@ -27,7 +27,7 @@ export const BuildPipeline = { throw new Error(`${framework} adapter was not resolved.`); } - await CompileModule.compileIfNeeded(projectRoot, adapter); + await CompileModule.compileProject(projectRoot, adapter); return ctx; }, diff --git a/cli/test/modules/compile/module.test.ts b/cli/test/modules/compile/module.test.ts new file mode 100644 index 00000000..9d3277d3 --- /dev/null +++ b/cli/test/modules/compile/module.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, it, vi } from "vitest"; +import type { IFrameworkAdapter } from "../../../src/adapters/IFrameworkAdapter/interface"; +import { CompileModule } from "../../../src/modules/compile/module"; + +describe("CompileModule.compileProject", () => { + it("delegates incremental freshness checks to the framework compiler", async () => { + const compile = vi.fn().mockResolvedValue(undefined); + const adapter = { compile } as unknown as IFrameworkAdapter; + + await CompileModule.compileProject("/project", adapter); + + expect(compile).toHaveBeenCalledOnce(); + expect(compile).toHaveBeenCalledWith("/project"); + }); +});