craftar 0.24.0 → 0.26.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,550 @@
1
+ /**
2
+ * Service ingredients — planning (spec 31 §4.1, §5.3) and the read-only core of `craftar services`.
3
+ * Planning: the files `sync` writes for service ingredients, called once after the target loop.
4
+ * Services core: recordedServices, upRefusal, downArgv, and the Compose/Docker helpers.
5
+ */
6
+ import { createHash } from "node:crypto";
7
+ import { promises as fs } from "node:fs";
8
+ import { execFile } from "node:child_process";
9
+ import path from "node:path";
10
+ import { outName } from "../emitters/shared.js";
11
+ import { findSecrets } from "./secrets.js";
12
+ import { isUtf8, hashNormalized, stripBom, toLf } from "./text.js";
13
+ import { parseYamlText, yamlFault } from "./yaml-read.js";
14
+ import { normalizeDeclared, COMPOSE_MAPS, ComposeFragmentShape, ComposeStackShape, ComposePsRow, NO_PROTO_KEY, } from "../schema/index.js";
15
+ /** Whether a path is listed in the binary array. */
16
+ function isBinary(meta, file) {
17
+ const normalized = normalizeDeclared(file);
18
+ return meta.binary.some((b) => normalizeDeclared(b) === normalized);
19
+ }
20
+ /** Whether a path is listed in the executable array. */
21
+ function isExecutable(meta, file) {
22
+ const normalized = normalizeDeclared(file);
23
+ return meta.executable.some((e) => normalizeDeclared(e) === normalized);
24
+ }
25
+ /** Assert that the given bytes are valid UTF-8 text (no NUL). Throws with a contextual message. */
26
+ function assertText(ref, file, bytes, hint) {
27
+ if (!isUtf8(bytes) || bytes.includes(0)) {
28
+ throw new Error(`${ref} ${file}: ${hint}`);
29
+ }
30
+ }
31
+ /**
32
+ * Parse a Compose fragment from YAML text.
33
+ *
34
+ * Returns `{ ok: true, fragment }` on success, or `{ ok: false, reason }` on failure.
35
+ * The reason does NOT include a `<ref> <compose>: ` prefix — the caller adds it.
36
+ *
37
+ * Checks:
38
+ * 1. Valid YAML
39
+ * 2. Must be a YAML mapping (not null, array, scalar)
40
+ * 3. Must not declare top-level `name`
41
+ * 4. Must not declare `include`
42
+ * 5. Each of services/volumes/networks/configs/secrets must be a mapping (via ComposeFragmentShape)
43
+ * 6. __proto__ keys are refused (via noProtoKey in the schema)
44
+ * 7. Must declare at least one service
45
+ * 8. A service must not extend a file (extends: { file: ... }) — the lock would not record it
46
+ */
47
+ export function parseFragment(text) {
48
+ // 1. Parse YAML — through parseYamlText so the error never includes the source line
49
+ // Compose honours merge keys (<<: *anchor), so parse with merge: true to see what Compose sees.
50
+ let parsed;
51
+ try {
52
+ parsed = parseYamlText("fragment", text, { merge: true });
53
+ }
54
+ catch (e) {
55
+ return { ok: false, reason: `not valid YAML after rendering — ${yamlFault(e)}` };
56
+ }
57
+ // 2. Must be a mapping
58
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
59
+ return { ok: false, reason: "a fragment must be a YAML mapping" };
60
+ }
61
+ const obj = parsed;
62
+ // 3. Must not declare top-level name
63
+ if (Object.hasOwn(obj, "name")) {
64
+ return { ok: false, reason: "a fragment must not declare a top-level name — the project name is craftar's" };
65
+ }
66
+ // 4. Must not declare include
67
+ if (Object.hasOwn(obj, "include")) {
68
+ return { ok: false, reason: "a fragment must not declare include — every file of the stack is in craftar.lock" };
69
+ }
70
+ // 5 & 6. Validate shape through ComposeFragmentShape (catches __proto__ and non-mapping maps)
71
+ const result = ComposeFragmentShape.safeParse(obj);
72
+ if (!result.success) {
73
+ const issue = result.error.issues[0];
74
+ // The failing map is in issue.path[0]
75
+ const failingMap = issue.path[0];
76
+ // Check if it's a __proto__ issue (recognised by the message noProtoKey raises)
77
+ if (issue.message === NO_PROTO_KEY) {
78
+ return { ok: false, reason: NO_PROTO_KEY };
79
+ }
80
+ // Otherwise it's a shape issue (e.g., volumes: [])
81
+ return { ok: false, reason: `${failingMap} must be a mapping` };
82
+ }
83
+ const fragment = result.data;
84
+ // 7. Must declare at least one service
85
+ if (!fragment.services || Object.keys(fragment.services).length === 0) {
86
+ return { ok: false, reason: "a fragment must declare at least one service" };
87
+ }
88
+ // 8. A service must not extend a file — the lock would not record it
89
+ // Accepted: extends: 'svc' (a string) and extends: { service: 'svc' } (no file key)
90
+ // Refused: extends: { file: ..., service: ... }
91
+ for (const svcDef of Object.values(fragment.services)) {
92
+ if (svcDef && typeof svcDef === "object" && !Array.isArray(svcDef)) {
93
+ const ext = svcDef.extends;
94
+ if (ext && typeof ext === "object" && !Array.isArray(ext) && Object.hasOwn(ext, "file")) {
95
+ return { ok: false, reason: "a service must not extend a file — the lock would not record it" };
96
+ }
97
+ }
98
+ }
99
+ return { ok: true, fragment };
100
+ }
101
+ /**
102
+ * Plans the service files: `.craftar/compose.yaml`, `.craftar/.gitattributes`,
103
+ * and each service's fragment and files under `.craftar/services/<outName>/`.
104
+ *
105
+ * Called by `plan()` once after the target loop (spec 31 §5.3, choice 24).
106
+ * Reads the resolution's `service` ingredients directly — the single declared
107
+ * exception to "a planned file comes from `ctx.aimed`".
108
+ */
109
+ export async function planServices(base) {
110
+ const { resolution, warn } = base;
111
+ // Get service ingredients from resolution (not through the walk — this is the declared exception)
112
+ const services = resolution.ingredients.filter((ing) => ing.meta.type === "service");
113
+ if (services.length === 0)
114
+ return [];
115
+ const files = [];
116
+ // Track declared keys for conflict detection across fragments
117
+ const declaredKeys = new Map(); // map name -> key -> ingredient ref
118
+ for (const mapName of COMPOSE_MAPS) {
119
+ declaredKeys.set(mapName, new Map());
120
+ }
121
+ // Track published host ports for warning (per ingredient, then across)
122
+ const publishedPorts = new Map(); // port -> ingredient ref
123
+ // Process each service
124
+ for (const ing of services) {
125
+ const meta = ing.meta;
126
+ const name = outName(meta);
127
+ const fragmentPath = meta.compose;
128
+ // Read and validate the fragment bytes first (for UTF-8 check)
129
+ const fragmentBytes = await base.bytes(ing, fragmentPath);
130
+ assertText(ing.ref, fragmentPath, fragmentBytes, "the fragment is not UTF-8 text");
131
+ // Render the fragment through ctx.text (sections, params)
132
+ const fragmentText = await base.text(ing, fragmentPath);
133
+ const fragmentContent = Buffer.from(toLf(stripBom(fragmentText)), "utf8");
134
+ // Parse the rendered fragment through parseFragment
135
+ const parseResult = parseFragment(fragmentText);
136
+ if (!parseResult.ok) {
137
+ throw new Error(`${ing.ref} ${fragmentPath}: ${parseResult.reason}`);
138
+ }
139
+ const fragment = parseResult.fragment;
140
+ // Check for duplicate keys in services, volumes, networks, configs, secrets
141
+ for (const mapName of COMPOSE_MAPS) {
142
+ const mapValue = fragment[mapName];
143
+ if (mapValue && typeof mapValue === "object" && !Array.isArray(mapValue)) {
144
+ const existing = declaredKeys.get(mapName);
145
+ for (const key of Object.keys(mapValue)) {
146
+ const prev = existing.get(key);
147
+ if (prev) {
148
+ throw new Error(`${prev} and ${ing.ref} both declare ${mapName}.${key} — Compose would keep one silently`);
149
+ }
150
+ existing.set(key, ing.ref);
151
+ }
152
+ }
153
+ }
154
+ // Check for duplicate published host ports and warn (across different ingredients only)
155
+ // fragment.services is guaranteed to exist and be non-empty by parseFragment
156
+ const svcMap = fragment.services;
157
+ for (const svcDef of Object.values(svcMap)) {
158
+ if (svcDef && typeof svcDef === "object" && !Array.isArray(svcDef)) {
159
+ const ports = svcDef.ports;
160
+ if (Array.isArray(ports)) {
161
+ for (const port of ports) {
162
+ const hostPort = extractHostPort(port);
163
+ // Compare and print only when the host port is all digits (spec §4.1: a port written
164
+ // with a variable is not compared; a range like "8080-8090" is also not compared).
165
+ if (hostPort && /^\d+$/.test(hostPort)) {
166
+ const prev = publishedPorts.get(hostPort);
167
+ // Only warn for different ingredients (same ingredient = Compose's own error)
168
+ if (prev && prev !== ing.ref) {
169
+ warn(`${prev} and ${ing.ref} both publish host port ${hostPort}`);
170
+ }
171
+ else if (!prev) {
172
+ publishedPorts.set(hostPort, ing.ref);
173
+ }
174
+ }
175
+ }
176
+ }
177
+ }
178
+ }
179
+ // Check for secrets in the rendered fragment
180
+ const findings = findSecrets(fragmentText);
181
+ for (const f of findings) {
182
+ warn(`${ing.ref} ${fragmentPath}: holds a secret-like value (${f.kind}) at line ${f.line}`);
183
+ }
184
+ // Add the fragment file
185
+ files.push({
186
+ path: path.posix.join(".craftar/services", name, fragmentPath),
187
+ content: fragmentContent,
188
+ target: "services",
189
+ ingredient: ing.ref,
190
+ });
191
+ // Process each file in the files list
192
+ for (const file of meta.files) {
193
+ const filePath = path.posix.join(".craftar/services", name, file);
194
+ if (isBinary(meta, file)) {
195
+ // Binary: copy as bytes
196
+ const bytes = await base.bytes(ing, file);
197
+ files.push({
198
+ path: filePath,
199
+ content: bytes,
200
+ target: "services",
201
+ ingredient: ing.ref,
202
+ ...(isExecutable(meta, file) ? { mode: 0o755 } : {}),
203
+ });
204
+ }
205
+ else {
206
+ // Text: validate UTF-8, render, check for secrets
207
+ const bytes = await base.bytes(ing, file);
208
+ assertText(ing.ref, file, bytes, "not UTF-8 text — list it under binary in ingredient.yaml");
209
+ const text = await base.text(ing, file);
210
+ const content = Buffer.from(toLf(stripBom(text)), "utf8");
211
+ // Check for secrets
212
+ const findings = findSecrets(text);
213
+ for (const f of findings) {
214
+ warn(`${ing.ref} ${file}: holds a secret-like value (${f.kind}) at line ${f.line}`);
215
+ }
216
+ files.push({
217
+ path: filePath,
218
+ content,
219
+ target: "services",
220
+ ingredient: ing.ref,
221
+ ...(isExecutable(meta, file) ? { mode: 0o755 } : {}),
222
+ });
223
+ }
224
+ }
225
+ }
226
+ // Generate the stack file
227
+ const stackLines = [
228
+ "# Generated by craftar from the Forge — do not edit; change the service ingredient and run `craftar sync`.",
229
+ "include:",
230
+ ];
231
+ for (const ing of services) {
232
+ const name = outName(ing.meta);
233
+ const fragmentPath = ing.meta.compose;
234
+ stackLines.push(` - path: ${path.posix.join("services", name, fragmentPath)}`);
235
+ }
236
+ const stackContent = Buffer.from(stackLines.join("\n") + "\n", "utf8");
237
+ files.push({
238
+ path: ".craftar/compose.yaml",
239
+ content: stackContent,
240
+ target: "services",
241
+ ingredient: "service/*",
242
+ });
243
+ // Generate .gitattributes
244
+ files.push({
245
+ path: ".craftar/.gitattributes",
246
+ content: Buffer.from("* -text\n", "utf8"),
247
+ target: "services",
248
+ ingredient: "service/*",
249
+ });
250
+ return files;
251
+ }
252
+ /**
253
+ * Extract the host port from a port mapping.
254
+ * Handles short syntax ("8080:80", "127.0.0.1:8080:80", "8080:80/tcp")
255
+ * and long syntax ({ published: "8080", target: "80" }).
256
+ */
257
+ function extractHostPort(port) {
258
+ if (typeof port === "string") {
259
+ // Short syntax: [IP:][HOST:]CONTAINER[/PROTOCOL]
260
+ const noProto = port.replace(/\/\w+$/, "");
261
+ const parts = noProto.split(":");
262
+ if (parts.length === 2) {
263
+ // HOST:CONTAINER
264
+ return parts[0];
265
+ }
266
+ else if (parts.length === 3) {
267
+ // IP:HOST:CONTAINER
268
+ return parts[1];
269
+ }
270
+ // Just CONTAINER, no host port
271
+ return null;
272
+ }
273
+ if (port !== null && typeof port === "object" && !Array.isArray(port)) {
274
+ const obj = port;
275
+ if (Object.hasOwn(obj, "published")) {
276
+ const published = obj.published;
277
+ return typeof published === "string" || typeof published === "number" ? String(published) : null;
278
+ }
279
+ }
280
+ return null;
281
+ }
282
+ /** The first Compose with `include` (spec 31 §4.6). */
283
+ export const MIN_COMPOSE = "2.20.0";
284
+ const STACK = ".craftar/compose.yaml";
285
+ const DOCKER_FIX = `install Docker with the Compose plugin ${MIN_COMPOSE} or later`;
286
+ /**
287
+ * The paths the planned stack includes — `.craftar/services/<name>/<compose>` for each service.
288
+ * Returns the workspace-relative POSIX paths (spec 31 §4.7: services-env scans only the planned fragments).
289
+ * Returns empty array for a stack that is not a valid craftar stack (invalid YAML, no includes, or an
290
+ * include path that is absolute or has a `..` segment).
291
+ */
292
+ export function fragmentPaths(stackText) {
293
+ let doc;
294
+ try {
295
+ doc = parseYamlText("stack", stackText);
296
+ }
297
+ catch {
298
+ return []; // invalid YAML
299
+ }
300
+ const parsed = ComposeStackShape.safeParse(doc);
301
+ if (!parsed.success)
302
+ return [];
303
+ // Refuse an include path that is absolute or has a .. segment — not a craftar stack
304
+ for (const inc of parsed.data.include) {
305
+ const p = inc.path;
306
+ if (/^\/|^[A-Za-z]:/.test(p))
307
+ return []; // absolute
308
+ if (p.split("/").includes(".."))
309
+ return []; // .. segment
310
+ }
311
+ return parsed.data.include.map((inc) => path.posix.join(".craftar", inc.path));
312
+ }
313
+ /** `craftar-<slug>-<hash>` (spec 31 §4.5): computed from the workspace's real path, never stored. */
314
+ export function projectName(realRoot) {
315
+ const slug = path.basename(realRoot).toLowerCase().replace(/[^a-z0-9_-]/g, "-").slice(0, 40);
316
+ const hash = createHash("sha256").update(realRoot).digest("hex").slice(0, 8);
317
+ return /^-*$/.test(slug) ? `craftar--${hash}` : `craftar-${slug}-${hash}`;
318
+ }
319
+ /**
320
+ * What the lock records under `.craftar/`, checked against the disk: every recorded file that is missing or
321
+ * whose normalized hash differs is pending, and so is a file the stack includes that the lock does not record.
322
+ * It reads the lock and the workspace only — no Forge, no plan, no Docker — and writes nothing.
323
+ */
324
+ export async function recordedServices(root, lock) {
325
+ const all = lock?.files ?? [];
326
+ const entries = all.filter((e) => e.target === "services");
327
+ const out = { entries: entries.length, stack: entries.some((e) => e.path === STACK), services: [], pending: [], warnings: [] };
328
+ if (entries.length === 0)
329
+ return out;
330
+ if (!out.stack)
331
+ out.pending.push({ path: STACK, reason: "unrecorded" });
332
+ const intact = new Map();
333
+ for (const e of entries) {
334
+ const bytes = await fs.readFile(path.join(root, e.path)).catch(() => null);
335
+ if (bytes === null)
336
+ out.pending.push({ path: e.path, reason: "missing" });
337
+ else if (hashNormalized(bytes) !== e.hash)
338
+ out.pending.push({ path: e.path, reason: "edited" });
339
+ else
340
+ intact.set(e.path, bytes);
341
+ }
342
+ const stack = intact.get(STACK);
343
+ if (stack === undefined)
344
+ return out;
345
+ // Read include list through fragmentPaths (spec 31 §4.7: fragment reading is one place)
346
+ const includes = fragmentPaths(stack.toString("utf8"));
347
+ if (includes.length === 0)
348
+ throw new Error(`${STACK}: recorded by craftar.lock but not a craftar stack`);
349
+ for (const rel of includes) {
350
+ const entry = entries.find((e) => e.path === rel);
351
+ if (!entry) {
352
+ // The fragment was a collision at the last sync: a user's file, never handed to Compose.
353
+ out.pending.push({ path: rel, reason: "unrecorded" });
354
+ continue;
355
+ }
356
+ const bytes = intact.get(rel);
357
+ if (bytes === undefined)
358
+ continue; // already pending
359
+ const f = parseFragment(bytes.toString("utf8"));
360
+ if (!f.ok)
361
+ out.warnings.push(`${rel}: cannot be read as a Compose fragment`);
362
+ else
363
+ for (const name of Object.keys(f.fragment.services ?? {}))
364
+ out.services.push({ name, ingredient: entry.ingredient });
365
+ }
366
+ return out;
367
+ }
368
+ /** The one test `up` (refuse) and `down` (pass `-f` or not) share (spec 31 §4.2, §4.3). */
369
+ export function canUseStack(r) {
370
+ return r.stack && r.pending.length === 0;
371
+ }
372
+ /**
373
+ * The argv for `docker compose down`. Pure function for testability.
374
+ * @param project The project name (always passed with -p)
375
+ * @param r The recorded services state, or null if the lock could not be read
376
+ * @param d Docker version info
377
+ * @param stackPath The stack file path (e.g. /workspace/.craftar/compose.yaml)
378
+ * @param envFile The .env file path, or null if it does not exist
379
+ * @param volumes Whether to pass --volumes
380
+ */
381
+ export function downArgv(project, r, d, stackPath, envFile, volumes) {
382
+ // Pass -f only when: r is not null AND canUseStack AND Compose is new enough
383
+ const useStack = r !== null && canUseStack(r) && !d.belowFloor;
384
+ const argv = ["compose", "-p", project];
385
+ if (useStack) {
386
+ argv.push("-f", stackPath);
387
+ if (envFile !== null)
388
+ argv.push("--env-file", envFile);
389
+ }
390
+ argv.push("down");
391
+ if (volumes)
392
+ argv.push("--volumes");
393
+ return argv;
394
+ }
395
+ /** One line per pending file, with the fix its reason calls for (spec 31 §4.2 step 2, §16.1). */
396
+ export function pendingLine(p) {
397
+ switch (p.reason) {
398
+ case "missing":
399
+ return `${p.path} — missing — run craftar sync`;
400
+ case "edited":
401
+ return `${p.path} — edited — run craftar drift discard ${p.path}: it regenerates the file, or removes it when the Forge no longer produces it`;
402
+ case "unrecorded":
403
+ return `${p.path} is not craftar's — run craftar status`;
404
+ }
405
+ }
406
+ /** Why `services up` will not run, before Docker is looked for; empty when it may (spec 31 §4.2 steps 1–2). */
407
+ export function upRefusal(r, names) {
408
+ const dash = names.find((n) => n.startsWith("-"));
409
+ if (dash !== undefined)
410
+ return [`service name ${JSON.stringify(dash)} starts with "-" — refused`];
411
+ if (r.entries === 0)
412
+ return ["no services in this workspace — craftar sync writes them when a recipe brings a service ingredient"];
413
+ return r.pending.map(pendingLine);
414
+ }
415
+ /** `docker compose` arguments, as an array for `execFile` / `spawn` — never a shell line. */
416
+ export function composeArgv(kind, o) {
417
+ const argv = ["compose", "-p", o.project];
418
+ if (o.stack !== null)
419
+ argv.push("-f", o.stack);
420
+ if (o.envFile !== null)
421
+ argv.push("--env-file", o.envFile);
422
+ if (kind === "ps")
423
+ return [...argv, "ps", "--all", "--format", "json"];
424
+ const names = o.services ?? [];
425
+ const dash = names.find((n) => n.startsWith("-"));
426
+ if (dash !== undefined)
427
+ throw new Error(`service name ${JSON.stringify(dash)} starts with "-" — refused`);
428
+ return [...argv, "up", "-d", ...(o.wait ? ["--wait"] : []), ...names];
429
+ }
430
+ /**
431
+ * `docker compose ps --format json`: a JSON array, or one object per line, or nothing. A row that is not a
432
+ * `ComposePsRow` is counted in `unread`, never dropped unseen; output that is neither form throws.
433
+ */
434
+ export function readPs(stdout) {
435
+ const text = stdout.trim();
436
+ if (text === "")
437
+ return { containers: [], unread: 0 };
438
+ let rows;
439
+ try {
440
+ const whole = text.startsWith("[") ? JSON.parse(text) : text.split("\n").filter((l) => l.trim() !== "").map((l) => JSON.parse(l));
441
+ if (!Array.isArray(whole))
442
+ throw new Error("not an array");
443
+ rows = whole;
444
+ }
445
+ catch {
446
+ throw new Error("docker compose ps: output not understood");
447
+ }
448
+ const containers = [];
449
+ let unread = 0;
450
+ for (const row of rows) {
451
+ const r = ComposePsRow.safeParse(row);
452
+ if (!r.success) {
453
+ unread++;
454
+ continue;
455
+ }
456
+ const ports = [];
457
+ for (const p of r.data.Publishers ?? []) {
458
+ // An unpublished port has PublishedPort 0; IPv4 and IPv6 publishers repeat one triple.
459
+ if (p.PublishedPort > 0 && !ports.some((x) => x.host === p.PublishedPort && x.container === p.TargetPort && x.protocol === p.Protocol))
460
+ ports.push({ host: p.PublishedPort, container: p.TargetPort, protocol: p.Protocol });
461
+ }
462
+ containers.push({ service: r.data.Service, state: r.data.State, health: r.data.Health ? r.data.Health : null, ports });
463
+ }
464
+ return { containers, unread };
465
+ }
466
+ /** Numeric, segment by segment: 2.9.0 is older than 2.20.0. */
467
+ export function compareVersions(a, b) {
468
+ const x = a.split(".").map(Number);
469
+ const y = b.split(".").map(Number);
470
+ for (let i = 0; i < Math.max(x.length, y.length); i++) {
471
+ const d = (x[i] ?? 0) - (y[i] ?? 0);
472
+ if (d !== 0)
473
+ return d < 0 ? -1 : 1;
474
+ }
475
+ return 0;
476
+ }
477
+ /** `docker compose version --short` (spec 31 §4.6). The runner rejects when `docker` cannot be started. */
478
+ export async function dockerVersion(run) {
479
+ let r;
480
+ try {
481
+ r = await run("docker", ["compose", "version", "--short"]);
482
+ }
483
+ catch {
484
+ return { available: false, compose: null, belowFloor: false, reason: "no docker" };
485
+ }
486
+ const m = /^v?(\d+(?:\.\d+)*)/.exec(r.stdout.trim());
487
+ if (r.code !== 0 || m === null)
488
+ return { available: false, compose: null, belowFloor: false, reason: "no compose plugin" };
489
+ const compose = m[1]; // just the numeric version, no suffix like "-desktop.1"
490
+ const belowFloor = compareVersions(compose, MIN_COMPOSE) < 0;
491
+ return { available: true, compose, belowFloor, reason: belowFloor ? "too old" : null };
492
+ }
493
+ /** The message and fix for a Docker that cannot serve the command; null when it can. `down` and `status` do not need the floor. */
494
+ export function dockerFinding(d, needFloor) {
495
+ if (d.reason === "no docker")
496
+ return { message: "docker not found", fix: DOCKER_FIX };
497
+ if (d.reason === "no compose plugin")
498
+ return { message: "the docker compose plugin is missing", fix: DOCKER_FIX };
499
+ if (d.reason === "too old" && needFloor)
500
+ return { message: `Docker Compose ${d.compose} is older than ${MIN_COMPOSE}`, fix: "upgrade Docker Compose" };
501
+ return null;
502
+ }
503
+ /** The line and fix for a Docker that cannot serve the command; null when it can. `down` and `status` do not need the floor. */
504
+ export function dockerFault(d, needFloor) {
505
+ const finding = dockerFinding(d, needFloor);
506
+ return finding ? `${finding.message} — ${finding.fix}` : null;
507
+ }
508
+ /** Runs `docker` with an argument array and no shell; resolves with the exit code, rejects when it cannot start. */
509
+ export const dockerRunner = (file, args) => new Promise((resolve, reject) => {
510
+ execFile(file, args, { encoding: "utf8", maxBuffer: 16 * 1024 * 1024 }, (error, stdout, stderr) => {
511
+ if (error === null)
512
+ return resolve({ code: 0, stdout, stderr });
513
+ // A numeric code is the child's exit code; anything else (ENOENT, EACCES, a signal) means it did not run to an exit.
514
+ if (typeof error.code === "number")
515
+ return resolve({ code: error.code, stdout, stderr });
516
+ reject(error);
517
+ });
518
+ });
519
+ /** The report of `services status`. `ps` is null when Docker was not asked or did not answer; `psFault` says why. Pure. */
520
+ export function statusReport(realRoot, r, d, ps, psFault) {
521
+ const services = [];
522
+ const rows = ps?.containers ?? [];
523
+ for (const s of r.services) {
524
+ const mine = rows.filter((c) => c.service === s.name);
525
+ if (mine.length === 0)
526
+ services.push({ name: s.name, ingredient: s.ingredient, state: "not created", health: null, ports: [] });
527
+ for (const c of mine)
528
+ services.push({ name: s.name, ingredient: s.ingredient, state: c.state, health: c.health, ports: c.ports });
529
+ }
530
+ // A container of the project whose service no recorded fragment declares.
531
+ for (const c of rows) {
532
+ if (!r.services.some((s) => s.name === c.service))
533
+ services.push({ name: c.service, ingredient: null, state: c.state, health: c.health, ports: c.ports });
534
+ }
535
+ const warnings = [...r.warnings];
536
+ if (psFault !== null)
537
+ warnings.push(psFault);
538
+ if (ps !== null && ps.unread > 0)
539
+ warnings.push(`docker compose ps: ${ps.unread} row(s) not understood`);
540
+ return {
541
+ workspace: realRoot,
542
+ project: projectName(realRoot),
543
+ composeFile: r.stack ? STACK : null,
544
+ docker: { available: d.available, compose: d.compose },
545
+ services,
546
+ pending: r.pending,
547
+ warnings,
548
+ };
549
+ }
550
+ //# sourceMappingURL=services.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"services.js","sourceRoot":"","sources":["../../src/core/services.ts"],"names":[],"mappings":"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"}
package/dist/core/sync.js CHANGED
@@ -16,6 +16,7 @@ import { claudeCode } from "../emitters/claude-code.js";
16
16
  import { kiro } from "../emitters/kiro.js";
17
17
  import { agentsMd } from "../emitters/agents-md.js";
18
18
  import { written } from "./capabilities.js";
19
+ import { planServices } from "./services.js";
19
20
  export const WORKSPACE_FILE = "craftar.yaml";
20
21
  export const LOCAL_FILE = "craftar.local.yaml";
21
22
  export const LOCK_FILE = "craftar.lock";
@@ -359,8 +360,11 @@ export function assertInsideWorkspace(files) {
359
360
  export async function plan(ws) {
360
361
  const resolution = resolve(ws.forge, ws.config);
361
362
  const warnings = [...ws.warnings, ...resolution.warnings];
363
+ const hasService = resolution.ingredients.some((ing) => ing.meta.type === "service");
362
364
  if (resolution.targets.length === 0) {
363
- warnings.push("no targets resolved — nothing will be emitted and every file in craftar.lock becomes an orphan (an empty list in craftar.local.yaml replaces the workspace's)");
365
+ warnings.push(hasService
366
+ ? "no target resolved — only the service files under .craftar/ are written"
367
+ : "no targets resolved — nothing will be emitted and every file in craftar.lock becomes an orphan (an empty list in craftar.local.yaml replaces the workspace's)");
364
368
  }
365
369
  const sections = await sectionPass(ws.forge, resolution, warnings);
366
370
  /** Unresolved placeholder → refs of the ingredients citing it. */
@@ -421,6 +425,10 @@ export async function plan(ws) {
421
425
  }
422
426
  files.push(...(await emitFor(t, em, ctx)));
423
427
  }
428
+ // Service files are planned after the target loop (spec 31 §5.3, choice 24) and before the
429
+ // containment check, so assertInsideWorkspace, the missing-param collection and the duplicate-path
430
+ // guard all cover the service files.
431
+ files.push(...(await planServices(ctx)));
424
432
  assertInsideWorkspace(files);
425
433
  const missing = [];
426
434
  const declared = declaredWithoutValue({ resolution });
@@ -603,16 +611,22 @@ export async function apply(ws, p, statuses, opts = {}) {
603
611
  break;
604
612
  }
605
613
  }
606
- // Schema 2, in this key order (spec 13 §5.2): what the sync used, not only what it wrote.
607
- const built = {
608
- schema: 2,
614
+ // Schema 3 when any entry has target `services`; otherwise schema 2 (spec 31 §5.2, spec 13 §5.2).
615
+ const sortedEntries = entries.sort((a, b) => a.path.localeCompare(b.path));
616
+ const hasServices = entries.some((e) => e.target === "services");
617
+ // Build the shared part once; the two branches differ only in `schema` and in the entry type.
618
+ const base = {
609
619
  forge: { source: ws.config.forge, ref: ws.origin.ref, commit: ws.forge.commit },
610
620
  profile: ws.config.profile,
611
621
  recipes: p.resolution.recipes,
612
622
  targets: p.resolution.targets,
613
623
  generatedAt: new Date().toISOString(),
614
- files: entries.sort((a, b) => a.path.localeCompare(b.path)),
615
624
  };
625
+ const built = hasServices
626
+ ? { schema: 3, ...base, files: sortedEntries }
627
+ // The narrowing cast is sound: hasServices is false, so no entry has target "services",
628
+ // thus every entry's target is a Target (claude-code | kiro | agents-md).
629
+ : { schema: 2, ...base, files: sortedEntries };
616
630
  // Rewritten only when its content changes: generatedAt is when it last did (Ruling 10). A schema 1
617
631
  // lock always differs, so the first sync with this craftar upgrades it.
618
632
  const onDisk = opts.dryRun ? null : await readLock(ws.root);
@@ -629,5 +643,9 @@ async function writeFile(root, f) {
629
643
  const abs = path.join(root, f.path);
630
644
  await fs.mkdir(path.dirname(abs), { recursive: true });
631
645
  await fs.writeFile(abs, f.content);
646
+ // Set mode after writing (fs.writeFile's mode applies only on creation, so chmod is needed for updates).
647
+ if (f.mode !== undefined && process.platform !== "win32") {
648
+ await fs.chmod(abs, f.mode);
649
+ }
632
650
  }
633
651
  //# sourceMappingURL=sync.js.map