feat(lanes): scaffold .ccam/profile/ from detected Node facts (A3)
This commit is contained in:
@@ -171,3 +171,94 @@ describe("detectNode: database + Redis + env wiring", () => {
|
||||
assert.equal(detect.detectNode(repo).database, null);
|
||||
});
|
||||
});
|
||||
|
||||
describe("scaffoldProfile", () => {
|
||||
it("writes a single-service profile with no database: bootstrap/boot/health only, zero TODOs", () => {
|
||||
const repo = makeRepo();
|
||||
writePkg(repo, { start: "node index.js" });
|
||||
const facts = detect.detectNode(repo);
|
||||
|
||||
const result = detect.scaffoldProfile(repo, facts);
|
||||
|
||||
const profileDir = path.join(repo, ".ccam", "profile");
|
||||
const env = fs.readFileSync(path.join(profileDir, "profile.env"), "utf8");
|
||||
assert.match(env, /PORTS="app"/);
|
||||
assert.match(env, /PORT_BASE_app=3000/);
|
||||
assert.doesNotMatch(env, /DB_PREFIX/);
|
||||
assert.doesNotMatch(env, /TODO/);
|
||||
|
||||
for (const hook of ["bootstrap.sh", "boot.sh", "health.sh"]) {
|
||||
const hookPath = path.join(profileDir, "hooks", hook);
|
||||
assert.ok(fs.existsSync(hookPath), hook);
|
||||
assert.ok(fs.statSync(hookPath).mode & 0o111, `${hook} must be executable`);
|
||||
}
|
||||
assert.match(
|
||||
fs.readFileSync(path.join(profileDir, "hooks", "boot.sh"), "utf8"),
|
||||
/npm run start/
|
||||
);
|
||||
assert.equal(result.todos.length, 0);
|
||||
});
|
||||
|
||||
it("writes a monorepo profile with a database: DB_PREFIX, ENV_REWRITE, copied db hooks, TODO migrate/seed", () => {
|
||||
const repo = makeRepo();
|
||||
writePkg(path.join(repo, "backend"), { start: "node server.js" });
|
||||
writePkg(path.join(repo, "frontend"), { dev: "vite" }); // no preview -> exercises dev fallback
|
||||
writeCompose(repo, " postgres:\n image: postgres:16\n redis:\n image: redis:7\n");
|
||||
fs.writeFileSync(path.join(repo, "backend", ".env.example"), "FOO=bar\n");
|
||||
const facts = detect.detectNode(repo);
|
||||
|
||||
const result = detect.scaffoldProfile(repo, facts);
|
||||
|
||||
const profileDir = path.join(repo, ".ccam", "profile");
|
||||
const env = fs.readFileSync(path.join(profileDir, "profile.env"), "utf8");
|
||||
assert.match(env, /DB_PREFIX=/);
|
||||
assert.match(env, /REDIS=1/);
|
||||
assert.match(env, /ENV_FILES="backend\/\.env"/);
|
||||
assert.match(env, /ENV_REWRITE="DATABASE_URL REDIS_URL"/);
|
||||
|
||||
for (const hook of ["db-create.sh", "db-drop.sh", "migrate.sh", "seed.sh"]) {
|
||||
assert.ok(fs.existsSync(path.join(profileDir, "hooks", hook)), hook);
|
||||
}
|
||||
assert.match(fs.readFileSync(path.join(profileDir, "hooks", "migrate.sh"), "utf8"), /exit 0/);
|
||||
assert.match(
|
||||
fs.readFileSync(path.join(profileDir, "hooks", "boot.sh"), "utf8"),
|
||||
/npm run dev/ // frontend fallback, no preview script
|
||||
);
|
||||
assert.ok(result.todos.length >= 2, "migrate and seed are both TODO");
|
||||
});
|
||||
|
||||
it("writes a TODO boot line (never a guess) when no start/dev script was found", () => {
|
||||
const repo = makeRepo();
|
||||
writePkg(repo, { test: "jest" }); // no start, no dev
|
||||
const facts = {
|
||||
layout: "single-service",
|
||||
services: { app: { dir: ".", script: null } },
|
||||
database: null,
|
||||
redis: false,
|
||||
env: null,
|
||||
};
|
||||
const result = detect.scaffoldProfile(repo, facts);
|
||||
const boot = fs.readFileSync(path.join(repo, ".ccam", "profile", "hooks", "boot.sh"), "utf8");
|
||||
assert.match(boot, /# TODO: no start\/dev script found/);
|
||||
assert.equal(result.todos.length, 1);
|
||||
});
|
||||
|
||||
it("refuses to overwrite an existing profile without force", () => {
|
||||
const repo = makeRepo();
|
||||
writePkg(repo, { start: "node index.js" });
|
||||
const facts = detect.detectNode(repo);
|
||||
detect.scaffoldProfile(repo, facts);
|
||||
assert.throws(
|
||||
() => detect.scaffoldProfile(repo, facts),
|
||||
(err) => err.code === "EPROFILEEXISTS"
|
||||
);
|
||||
});
|
||||
|
||||
it("overwrites when force is true", () => {
|
||||
const repo = makeRepo();
|
||||
writePkg(repo, { start: "node index.js" });
|
||||
const facts = detect.detectNode(repo);
|
||||
detect.scaffoldProfile(repo, facts);
|
||||
assert.doesNotThrow(() => detect.scaffoldProfile(repo, facts, { force: true }));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user