Skip to content

Commit 36111db

Browse files
committed
feat(postinstall): add verbose logging for stale file detection
Signed-off-by: leocavalcante <leo@cavalcante.dev>
1 parent 2a39073 commit 36111db

File tree

2 files changed

+167
-0
lines changed

2 files changed

+167
-0
lines changed

postinstall.mjs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,17 @@ async function main() {
190190
verbose(` Target path: ${targetPath}`)
191191

192192
try {
193+
// Check if target file exists and has different content (stale)
194+
if (existsSync(targetPath)) {
195+
const sourceContent = readFileSync(sourcePath, "utf-8")
196+
const targetContent = readFileSync(targetPath, "utf-8")
197+
if (sourceContent !== targetContent) {
198+
verbose(`Overwriting existing file: ${file} (content differs)`)
199+
} else {
200+
verbose(`Target file unchanged: ${file}`)
201+
}
202+
}
203+
193204
if (DRY_RUN) {
194205
// In dry-run mode, validate source file but don't copy
195206
verbose(` Validating source file (dry-run mode)...`)

tests/install.test.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2029,6 +2029,162 @@ The agent handles various tasks and operations in the system.
20292029
})
20302030
})
20312031

2032+
describe("stale file detection", () => {
2033+
it("should log verbose message when overwriting existing file with different content", async () => {
2034+
const { AGENTS_TARGET_DIR } = await import("../src/paths.mjs")
2035+
const agentFiles = ["opencoder.md", "opencoder-planner.md", "opencoder-builder.md"]
2036+
2037+
// Ensure target directory exists
2038+
mkdirSync(AGENTS_TARGET_DIR, { recursive: true })
2039+
2040+
// Create stale (different) content in target
2041+
writeFileSync(
2042+
join(AGENTS_TARGET_DIR, "opencoder.md"),
2043+
"# Old stale content\nThis is different from the source",
2044+
)
2045+
2046+
const proc = Bun.spawn(["node", "postinstall.mjs", "--dry-run", "--verbose"], {
2047+
cwd: process.cwd(),
2048+
stdout: "pipe",
2049+
stderr: "pipe",
2050+
})
2051+
2052+
const exitCode = await proc.exited
2053+
const stdout = await new Response(proc.stdout).text()
2054+
2055+
expect(exitCode).toBe(0)
2056+
// Should contain the stale file detection message
2057+
expect(stdout).toContain(
2058+
"[VERBOSE] Overwriting existing file: opencoder.md (content differs)",
2059+
)
2060+
2061+
// Clean up
2062+
for (const file of agentFiles) {
2063+
const targetPath = join(AGENTS_TARGET_DIR, file)
2064+
if (existsSync(targetPath)) {
2065+
rmSync(targetPath)
2066+
}
2067+
}
2068+
})
2069+
2070+
it("should log verbose message when target file has unchanged content", async () => {
2071+
const { AGENTS_TARGET_DIR } = await import("../src/paths.mjs")
2072+
const agentFiles = ["opencoder.md", "opencoder-planner.md", "opencoder-builder.md"]
2073+
2074+
// Ensure target directory exists
2075+
mkdirSync(AGENTS_TARGET_DIR, { recursive: true })
2076+
2077+
// Copy the actual source content to target (identical content)
2078+
const sourceContent = readFileSync(join(process.cwd(), "agents", "opencoder.md"), "utf-8")
2079+
writeFileSync(join(AGENTS_TARGET_DIR, "opencoder.md"), sourceContent)
2080+
2081+
const proc = Bun.spawn(["node", "postinstall.mjs", "--dry-run", "--verbose"], {
2082+
cwd: process.cwd(),
2083+
stdout: "pipe",
2084+
stderr: "pipe",
2085+
})
2086+
2087+
const exitCode = await proc.exited
2088+
const stdout = await new Response(proc.stdout).text()
2089+
2090+
expect(exitCode).toBe(0)
2091+
// Should contain the unchanged message
2092+
expect(stdout).toContain("[VERBOSE] Target file unchanged: opencoder.md")
2093+
// Should NOT contain the overwriting message for this file
2094+
expect(stdout).not.toContain("Overwriting existing file: opencoder.md")
2095+
2096+
// Clean up
2097+
for (const file of agentFiles) {
2098+
const targetPath = join(AGENTS_TARGET_DIR, file)
2099+
if (existsSync(targetPath)) {
2100+
rmSync(targetPath)
2101+
}
2102+
}
2103+
})
2104+
2105+
it("should not log stale file message without --verbose flag", async () => {
2106+
const { AGENTS_TARGET_DIR } = await import("../src/paths.mjs")
2107+
const agentFiles = ["opencoder.md", "opencoder-planner.md", "opencoder-builder.md"]
2108+
2109+
// Ensure target directory exists
2110+
mkdirSync(AGENTS_TARGET_DIR, { recursive: true })
2111+
2112+
// Create stale content in target
2113+
writeFileSync(
2114+
join(AGENTS_TARGET_DIR, "opencoder.md"),
2115+
"# Old stale content\nThis is different from the source",
2116+
)
2117+
2118+
const proc = Bun.spawn(["node", "postinstall.mjs", "--dry-run"], {
2119+
cwd: process.cwd(),
2120+
stdout: "pipe",
2121+
stderr: "pipe",
2122+
})
2123+
2124+
const exitCode = await proc.exited
2125+
const stdout = await new Response(proc.stdout).text()
2126+
2127+
expect(exitCode).toBe(0)
2128+
// Should NOT contain verbose markers or stale file message
2129+
expect(stdout).not.toContain("[VERBOSE]")
2130+
expect(stdout).not.toContain("Overwriting existing file")
2131+
expect(stdout).not.toContain("content differs")
2132+
2133+
// Clean up
2134+
for (const file of agentFiles) {
2135+
const targetPath = join(AGENTS_TARGET_DIR, file)
2136+
if (existsSync(targetPath)) {
2137+
rmSync(targetPath)
2138+
}
2139+
}
2140+
})
2141+
2142+
it("should detect stale files during actual install (not dry-run)", async () => {
2143+
const { AGENTS_TARGET_DIR } = await import("../src/paths.mjs")
2144+
const agentFiles = ["opencoder.md", "opencoder-planner.md", "opencoder-builder.md"]
2145+
2146+
// Ensure target directory exists
2147+
mkdirSync(AGENTS_TARGET_DIR, { recursive: true })
2148+
2149+
// Create stale content in target for all files
2150+
for (const file of agentFiles) {
2151+
writeFileSync(
2152+
join(AGENTS_TARGET_DIR, file),
2153+
`# Old stale content for ${file}\nThis is different`,
2154+
)
2155+
}
2156+
2157+
const proc = Bun.spawn(["node", "postinstall.mjs", "--verbose"], {
2158+
cwd: process.cwd(),
2159+
stdout: "pipe",
2160+
stderr: "pipe",
2161+
})
2162+
2163+
const exitCode = await proc.exited
2164+
const stdout = await new Response(proc.stdout).text()
2165+
2166+
expect(exitCode).toBe(0)
2167+
// Should detect stale files for all three agents
2168+
expect(stdout).toContain(
2169+
"[VERBOSE] Overwriting existing file: opencoder.md (content differs)",
2170+
)
2171+
expect(stdout).toContain(
2172+
"[VERBOSE] Overwriting existing file: opencoder-planner.md (content differs)",
2173+
)
2174+
expect(stdout).toContain(
2175+
"[VERBOSE] Overwriting existing file: opencoder-builder.md (content differs)",
2176+
)
2177+
2178+
// Clean up
2179+
for (const file of agentFiles) {
2180+
const targetPath = join(AGENTS_TARGET_DIR, file)
2181+
if (existsSync(targetPath)) {
2182+
rmSync(targetPath)
2183+
}
2184+
}
2185+
})
2186+
})
2187+
20322188
describe("shipped agent files validation", () => {
20332189
it("should validate all shipped agent files pass content validation", async () => {
20342190
const { AGENT_NAMES, validateAgentContent } = await import("../src/paths.mjs")

0 commit comments

Comments
 (0)