Merge pull request #92 from dan-claude-bot/fix/draft-is-static-uncaptured
fix(draft): name is_static in UNCAPTURED.md when the live read cannot see it (#70)
This commit is contained in:
commit
90f63637c1
2 changed files with 140 additions and 0 deletions
21
src/draft.ts
21
src/draft.ts
|
|
@ -434,6 +434,27 @@ function applicationSpec(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// is_static lives on Coolify 4.1.2's ApplicationSetting relation, which no
|
||||||
|
// read endpoint serializes (cast#68) — so on that Coolify the key is simply
|
||||||
|
// ABSENT here, and the `static: true` emission below can never fire, however
|
||||||
|
// the box is actually configured. The same predicate `diff` uses for its
|
||||||
|
// staticNotCompared escape hatch (cli.ts: `is_static == null`), applied to
|
||||||
|
// the draft's contract: a drafted static site would silently come back as a
|
||||||
|
// plain app (the #63 crash), so when the app even LOOKS static — a
|
||||||
|
// nixpacks/static pack serving a publish_directory — UNCAPTURED.md must name
|
||||||
|
// the flag as unreadable, not let its absence pass for `false`. A real
|
||||||
|
// boolean (a future Coolify) is handled below and says nothing here.
|
||||||
|
if (
|
||||||
|
r.raw.is_static == null &&
|
||||||
|
(pack === "nixpacks" || pack === "static") &&
|
||||||
|
r.raw.publish_directory
|
||||||
|
) {
|
||||||
|
flag(
|
||||||
|
"is_static",
|
||||||
|
`this Coolify cannot say whether the app serves as a static site — is_static lives on the ApplicationSetting relation, which 4.1.2's read API never returns (cast#68) — and this app is plausibly static (${pack} pack with publish_directory ${String(r.raw.publish_directory)}). The draft carries no \`static: true\`; if the box has "Is it a static site?" checked, a rebuild from this draft would build and RUN it as a plain app (the #63 crash). Check the box in the Coolify UI (Build settings) and, if set, add \`static: true\` under \`build:\` yourself.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
source: {
|
source: {
|
||||||
repo: repo ?? String(r.raw.git_repository ?? ""),
|
repo: repo ?? String(r.raw.git_repository ?? ""),
|
||||||
|
|
|
||||||
|
|
@ -333,6 +333,125 @@ describe("planDraft — the emitted shape", () => {
|
||||||
expect(build).not.toHaveProperty("static");
|
expect(build).not.toHaveProperty("static");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// #70: Coolify 4.1.2 never returns is_static on any read (it lives on the
|
||||||
|
// ApplicationSetting relation, cast#68), so on that Coolify the key is ABSENT
|
||||||
|
// from the raw payload and the draft cannot know whether the box is a static
|
||||||
|
// site. UNCAPTURED.md is the draft's honesty contract (#27): when the app
|
||||||
|
// even looks static, the unreadable flag must be NAMED there — otherwise a
|
||||||
|
// reviewer has no cue the field exists to lose, and the #63 crash (static
|
||||||
|
// site rebuilt as a plain app) re-enters through the draft door.
|
||||||
|
it("names is_static in UNCAPTURED.md when the live read cannot see it and the app looks static", () => {
|
||||||
|
const p = project({
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
kind: "application",
|
||||||
|
name: "Landing",
|
||||||
|
uuid: "a1",
|
||||||
|
raw: {
|
||||||
|
git_repository: "https://github.com/heavy-duty/incubator",
|
||||||
|
git_branch: "main",
|
||||||
|
build_pack: "nixpacks",
|
||||||
|
base_directory: "/",
|
||||||
|
publish_directory: "/apps/landing-site/dist",
|
||||||
|
fqdn: "https://landing.example.com",
|
||||||
|
// no is_static at all — Coolify 4.1.2's read path
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const plan = planDraft([p], ctx);
|
||||||
|
const item = plan.uncaptured.find((u) => u.setting === "is_static");
|
||||||
|
expect(item).toBeDefined();
|
||||||
|
expect(item?.resource).toBe("Landing");
|
||||||
|
// The entry tells the reviewer where the truth lives: the Coolify UI.
|
||||||
|
expect(item?.detail).toContain("Coolify UI");
|
||||||
|
expect(item?.detail).toContain("static: true");
|
||||||
|
// And it reaches the page a reviewer actually reads.
|
||||||
|
const uncap = plan.files.find((f) => f.path.endsWith("UNCAPTURED.md"));
|
||||||
|
expect(uncap?.content).toContain("is_static");
|
||||||
|
// The manifest itself stays silent — absent is not `true`, and a guessed
|
||||||
|
// `static: true` would be exactly the fabrication UNCAPTURED.md exists to
|
||||||
|
// prevent.
|
||||||
|
const manifest = plan.files.find((f) => f.path.endsWith("manifest.yaml"));
|
||||||
|
const dir = mkdtempSync(join(tmpdir(), "cast-draft-"));
|
||||||
|
const path = join(dir, "manifest.yaml");
|
||||||
|
writeFileSync(path, manifest?.content ?? "");
|
||||||
|
const build =
|
||||||
|
loadManifest(path).environments.prod.applications.Landing.build;
|
||||||
|
expect(build).not.toHaveProperty("static");
|
||||||
|
});
|
||||||
|
|
||||||
|
// The flag is only worth a reviewer's attention when the app is PLAUSIBLY
|
||||||
|
// static — nixpacks/static pack serving a publish_directory (the same
|
||||||
|
// heuristic as #70). An app with nothing to serve statically gets no entry;
|
||||||
|
// flagging every application would bury the page in noise.
|
||||||
|
it("does not flag is_static for an app that does not look static", () => {
|
||||||
|
const p = project({
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
kind: "application",
|
||||||
|
name: "Api",
|
||||||
|
uuid: "a1",
|
||||||
|
raw: {
|
||||||
|
git_repository: "https://github.com/heavy-duty/incubator",
|
||||||
|
git_branch: "main",
|
||||||
|
build_pack: "nixpacks",
|
||||||
|
base_directory: "/",
|
||||||
|
fqdn: "https://api.example.com",
|
||||||
|
// no publish_directory, no is_static
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: "application",
|
||||||
|
name: "Built",
|
||||||
|
uuid: "a2",
|
||||||
|
raw: {
|
||||||
|
git_repository: "https://github.com/heavy-duty/incubator",
|
||||||
|
git_branch: "main",
|
||||||
|
build_pack: "dockerfile",
|
||||||
|
base_directory: "/",
|
||||||
|
publish_directory: "/dist",
|
||||||
|
fqdn: "https://built.example.com",
|
||||||
|
// dockerfile pack — Coolify's static toggle is a buildpack concept
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const plan = planDraft([p], ctx);
|
||||||
|
expect(plan.uncaptured.some((u) => u.setting === "is_static")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
// A future Coolify that DOES serialize is_static gets the old behavior
|
||||||
|
// untouched: a real boolean is expressed in the manifest, not flagged.
|
||||||
|
// (`static: true` emission for is_static: true is covered above; here the
|
||||||
|
// read said `false`, which is an answer, not an absence.)
|
||||||
|
it("does not flag is_static when the live read answered false", () => {
|
||||||
|
const p = project({
|
||||||
|
resources: [
|
||||||
|
{
|
||||||
|
kind: "application",
|
||||||
|
name: "Plain",
|
||||||
|
uuid: "a1",
|
||||||
|
raw: {
|
||||||
|
git_repository: "https://github.com/heavy-duty/incubator",
|
||||||
|
git_branch: "main",
|
||||||
|
build_pack: "nixpacks",
|
||||||
|
base_directory: "/",
|
||||||
|
publish_directory: "/dist",
|
||||||
|
fqdn: "https://plain.example.com",
|
||||||
|
is_static: false,
|
||||||
|
},
|
||||||
|
env: {},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
const plan = planDraft([p], ctx);
|
||||||
|
expect(plan.uncaptured.some((u) => u.setting === "is_static")).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
it("emits an env template every cast reader can parse", () => {
|
it("emits an env template every cast reader can parse", () => {
|
||||||
const plan = planDraft([project()], ctx);
|
const plan = planDraft([project()], ctx);
|
||||||
const tpl = plan.files.find((f) => f.path.endsWith(".env.template"));
|
const tpl = plan.files.find((f) => f.path.endsWith(".env.template"));
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue