@intentius/chant 0.111.0 → 0.112.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.
@@ -208,7 +208,7 @@ export declare function readinessKeys(result: unknown): {
208
208
  keys: string[];
209
209
  } | null;
210
210
  /** The form a steward takes in `env` (`local` when none is named). */
211
- export declare function stewardFormFor(steward: StewardDeclaration, env?: string): StewardForm;
211
+ export declare function stewardFormFor(steward: Pick<StewardDeclaration, "form">, env?: string): StewardForm;
212
212
  /**
213
213
  * The lease a local steward holds for as long as it runs, so a second
214
214
  * `chant operator --steward` for the same steward is refused. It sits under
@@ -1 +1 @@
1
- {"version":3,"file":"steward.d.ts","sourceRoot":"","sources":["../../src/op/steward.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"steward.d.ts","sourceRoot":"","sources":["../../src/op/steward.ts"],"names":[],"mappings":"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"}
@@ -4,7 +4,8 @@
4
4
  * its Ops with their schedules, and each Op's last run.
5
5
  *
6
6
  * The steward itself is read from its declaration, the same `*.op.ts` files
7
- * `chant operator --steward` reads (`../op/discover.ts`), and only for a member
7
+ * `chant operator --steward` reads (`../op/discover.ts`), through the index
8
+ * kept per tree (`./steward-index.ts`, #3636), and only for a member
8
9
  * of kind `chant` that is a chant project of its own (a `chant.config.ts` or
9
10
  * `.json` in its directory), so a member never lists a parent project's
10
11
  * steward. Everything
@@ -46,6 +47,7 @@
46
47
  */
47
48
  import { type StewardForm } from "../op/steward.js";
48
49
  import { type ConvergeTickRecord } from "../lifecycle/converge-ledger.js";
50
+ import { type StewardIndex } from "./steward-index.js";
49
51
  import type { OpRunRecord } from "../op/runtime.js";
50
52
  /** Why a member's stewards can't be fully listed. Closed: a new code is a contract change. */
51
53
  export declare const STEWARD_REASON_CODES: readonly ["stewards-unreadable", "stewards-conflict", "steward-runs-unreadable"];
@@ -264,5 +266,6 @@ export declare function readMemberStewards(memberDir: string, env: string, now:
264
266
  }[];
265
267
  } | null, deps?: {
266
268
  readQuestions?: ReadQuestionStates;
269
+ readIndex?: (memberDir: string) => Promise<StewardIndex>;
267
270
  }): Promise<MemberStewards>;
268
271
  //# sourceMappingURL=status-stewards.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"status-stewards.d.ts","sourceRoot":"","sources":["../../src/workspace/status-stewards.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"status-stewards.d.ts","sourceRoot":"","sources":["../../src/workspace/status-stewards.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,89 @@
1
+ /**
2
+ * What `chant workspace status` reads of a project's stewards and Ops
3
+ * (#3636), kept per tree so a status doesn't import every `*.op.ts` file
4
+ * each time it runs.
5
+ *
6
+ * Discovery (`../op/discover.ts`) has to import the Op modules, and an Op
7
+ * module may do work when it is imported: run git, read files, even start
8
+ * another chant. Status needs only plain fields of what it finds: each
9
+ * steward's name, file, form, vault, capabilities and Ops, and of each Op
10
+ * its name, schedule, labels, work lease kind and `changesCheckout`. The
11
+ * first status on a tree discovers them and writes that index to
12
+ * `<cache dir>/workspace-stewards/<checkout>/<key>.json`; later ones read
13
+ * it back, until the tree changes. The cache dir is the one the graph cache
14
+ * uses (`./graph-cache.ts`): `$CHANT_CACHE_DIR`, else
15
+ * `$XDG_CACHE_HOME/chant`, else `~/.cache/chant`, outside the workspace,
16
+ * since a read never changes the workspace it reads.
17
+ *
18
+ * The tree is the checkout's `HEAD` tree plus every changed or untracked
19
+ * file `git status` lists (its path and contents), and the chant version,
20
+ * so a commit, an edit or a new file anywhere in the checkout discovers
21
+ * again. Files git ignores (`node_modules`) are not part of it. An index is
22
+ * only written when every Op file imported, so an import that failed is
23
+ * tried again by the next status. `CHANT_STEWARD_INDEX=0` turns the index
24
+ * off, for an Op module whose declaration depends on more than its tree.
25
+ */
26
+ import { type StewardForm } from "../op/steward.js";
27
+ /** What status reads of an Op. */
28
+ export interface IndexedOp {
29
+ name: string;
30
+ schedule: {
31
+ cron: string;
32
+ } | null;
33
+ /** Absent when the Op has none. */
34
+ labels?: Record<string, string>;
35
+ workLease: {
36
+ kind: string | null;
37
+ } | null;
38
+ changesCheckout: boolean;
39
+ }
40
+ /** What status reads of a steward. */
41
+ export interface IndexedSteward {
42
+ name: string;
43
+ /** The `*.op.ts` file declaring it, absolute. */
44
+ filePath: string;
45
+ /** Every Op it runs: its turns' Ops, then those beside them. */
46
+ ops: IndexedOp[];
47
+ /** The Ops it runs beside its turns (#2861), and whether each declares a ready step. */
48
+ beside: {
49
+ op: string;
50
+ ready: boolean;
51
+ }[];
52
+ form: {
53
+ default: StewardForm;
54
+ environments: Record<string, StewardForm>;
55
+ };
56
+ capabilities: string[];
57
+ vault: string | null;
58
+ }
59
+ export interface StewardIndex {
60
+ /** In discovery's order. */
61
+ stewards: IndexedSteward[];
62
+ /** Files that could not be imported. */
63
+ errors: string[];
64
+ /** Stewards dropped as second writers. */
65
+ conflicts: string[];
66
+ /** The project's Ops that no steward lists, or null when they couldn't be discovered. */
67
+ otherOps: IndexedOp[] | null;
68
+ }
69
+ /** Discover the stewards and Ops under `memberDir`, importing every Op file. Throws as `discoverStewards` does. */
70
+ export declare function discoverStewardIndex(memberDir: string): Promise<StewardIndex>;
71
+ /**
72
+ * The key of the tree `memberDir` is in: its `HEAD` tree, each changed or
73
+ * untracked file's path and contents, the chant version and `memberDir`.
74
+ * Null outside a checkout, or when git can't say.
75
+ */
76
+ export declare function treeKey(memberDir: string): Promise<{
77
+ key: string;
78
+ dir: string;
79
+ checkout: string;
80
+ } | null>;
81
+ /**
82
+ * The stewards and Ops under `memberDir`: from the index for its tree when
83
+ * there is one, else discovered and indexed. Throws as `discoverStewards`
84
+ * does.
85
+ */
86
+ export declare function readStewardIndex(memberDir: string, deps?: {
87
+ discover?: typeof discoverStewardIndex;
88
+ }): Promise<StewardIndex>;
89
+ //# sourceMappingURL=steward-index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steward-index.d.ts","sourceRoot":"","sources":["../../src/workspace/steward-index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAUH,OAAO,EAAmB,KAAK,WAAW,EAAE,MAAM,eAAe,CAAC;AAuBlE,kCAAkC;AAClC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAClC,mCAAmC;IACnC,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,SAAS,EAAE;QAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1C,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,GAAG,EAAE,SAAS,EAAE,CAAC;IACjB,wFAAwF;IACxF,MAAM,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,EAAE,CAAC;IACzC,IAAI,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAA;KAAE,CAAC;IAC1E,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,4BAA4B;IAC5B,QAAQ,EAAE,cAAc,EAAE,CAAC;IAC3B,wCAAwC;IACxC,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,yFAAyF;IACzF,QAAQ,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;CAC9B;AAYD,mHAAmH;AACnH,wBAAsB,oBAAoB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAsBnF;AASD;;;;GAIG;AACH,wBAAsB,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAyB/G;AA8BD;;;;GAIG;AACH,wBAAsB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,OAAO,oBAAoB,CAAA;CAAO,GAAG,OAAO,CAAC,YAAY,CAAC,CA0BtI"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.111.0",
3
+ "version": "0.112.0",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
package/src/op/steward.ts CHANGED
@@ -352,7 +352,7 @@ export function readinessKeys(result: unknown): { ready: boolean; keys: string[]
352
352
  }
353
353
 
354
354
  /** The form a steward takes in `env` (`local` when none is named). */
355
- export function stewardFormFor(steward: StewardDeclaration, env: string = DEFAULT_STEWARD_ENV): StewardForm {
355
+ export function stewardFormFor(steward: Pick<StewardDeclaration, "form">, env: string = DEFAULT_STEWARD_ENV): StewardForm {
356
356
  return steward.form.environments[env] ?? steward.form.default;
357
357
  }
358
358
 
@@ -4,7 +4,8 @@
4
4
  * its Ops with their schedules, and each Op's last run.
5
5
  *
6
6
  * The steward itself is read from its declaration, the same `*.op.ts` files
7
- * `chant operator --steward` reads (`../op/discover.ts`), and only for a member
7
+ * `chant operator --steward` reads (`../op/discover.ts`), through the index
8
+ * kept per tree (`./steward-index.ts`, #3636), and only for a member
8
9
  * of kind `chant` that is a chant project of its own (a `chant.config.ts` or
9
10
  * `.json` in its directory), so a member never lists a parent project's
10
11
  * steward. Everything
@@ -47,8 +48,7 @@
47
48
 
48
49
  import { existsSync, realpathSync } from "node:fs";
49
50
  import { dirname, join, relative, resolve } from "node:path";
50
- import { discoverOps, discoverStewards } from "../op/discover";
51
- import { stewardBesideFor, stewardFormFor, stewardLeaseName, type StewardForm } from "../op/steward";
51
+ import { stewardFormFor, stewardLeaseName, type StewardForm } from "../op/steward";
52
52
  import { readRunLedger, runEnvOf } from "../lifecycle/run-ledger";
53
53
  import { readConvergeLedger, type ConvergeTickRecord } from "../lifecycle/converge-ledger";
54
54
  import { leaseRef } from "../lifecycle/lease";
@@ -56,7 +56,7 @@ import { readBlobBySha, readRefSha } from "../lifecycle/git";
56
56
  import { resolveMemberLedger } from "../lifecycle/member-ledger";
57
57
  import { listWorkLeases } from "../lifecycle/work-lease";
58
58
  import { stewardWorkHolder } from "../op/work-lease-run";
59
- import type { OpConfig } from "../op/types";
59
+ import { readStewardIndex, type IndexedOp, type IndexedSteward, type StewardIndex } from "./steward-index";
60
60
  import type { OpRunRecord } from "../op/runtime";
61
61
  import { readInFlightRun } from "../op/run-live";
62
62
  import { approveCommand } from "./status-gates";
@@ -342,19 +342,14 @@ async function readLeaseRef(leaseName: string, memberDir: string, now: string):
342
342
  }
343
343
 
344
344
  /** An Op's `beside` entry (#2861): null for an Op run as one of the steward's turns. */
345
- async function besideOf(
346
- declaration: Parameters<typeof stewardBesideFor>[0],
347
- op: string,
348
- memberDir: string,
349
- now: string,
350
- ): Promise<StatusStewardOp["beside"]> {
351
- const beside = stewardBesideFor(declaration, op);
345
+ async function besideOf(steward: IndexedSteward, op: string, memberDir: string, now: string): Promise<StatusStewardOp["beside"]> {
346
+ const beside = steward.beside.find((b) => b.op === op);
352
347
  if (!beside) return null;
353
- return { ready: beside.ready !== null && beside.ready !== undefined, lease: await readLeaseRef(op, memberDir, now) };
348
+ return { ready: beside.ready, lease: await readLeaseRef(op, memberDir, now) };
354
349
  }
355
350
 
356
351
  /** The work leases `steward`'s turns of `op` hold, from the ledger of the Op's kind or the member's. */
357
- async function readHeldWorkLeases(steward: string, op: OpConfig, memberDir: string, now: string): Promise<StatusStewardWorkLease[]> {
352
+ async function readHeldWorkLeases(steward: string, op: IndexedOp, memberDir: string, now: string): Promise<StatusStewardWorkLease[]> {
358
353
  try {
359
354
  const kind = op.workLease?.kind;
360
355
  const cwd = kind ? dirname(resolve(memberDir, kind)) : memberDir;
@@ -378,7 +373,7 @@ export async function readMemberStewards(
378
373
  now: string,
379
374
  kind = "chant",
380
375
  box: { capabilities: { name: string; broker: string | null }[] } | null = null,
381
- deps: { readQuestions?: ReadQuestionStates } = {},
376
+ deps: { readQuestions?: ReadQuestionStates; readIndex?: (memberDir: string) => Promise<StewardIndex> } = {},
382
377
  ): Promise<MemberStewards> {
383
378
  const reasons: MemberStewards["reasons"] = [];
384
379
  // Read once per member, and only when a run stopped on a question.
@@ -386,19 +381,18 @@ export async function readMemberStewards(
386
381
  const questionStates = () => (states ??= (deps.readQuestions ?? readQuestionStates)(memberDir));
387
382
  if (kind !== "chant" || !isChantProject(memberDir)) return { stewards: [], reasons };
388
383
 
389
- let discovered: Awaited<ReturnType<typeof discoverStewards>>;
384
+ let index: StewardIndex;
390
385
  try {
391
- discovered = await discoverStewards({ cwd: memberDir });
386
+ index = await (deps.readIndex ?? readStewardIndex)(memberDir);
392
387
  } catch (err) {
393
388
  reasons.push({ code: "stewards-unreadable", message: err instanceof Error ? err.message.split("\n")[0] : String(err) });
394
389
  return { stewards: [], reasons };
395
390
  }
396
- const { stewards: found, errors, conflicts } = discovered;
397
- for (const message of errors) reasons.push({ code: "stewards-unreadable", message });
398
- for (const message of conflicts) reasons.push({ code: "stewards-conflict", message });
391
+ for (const message of index.errors) reasons.push({ code: "stewards-unreadable", message });
392
+ for (const message of index.conflicts) reasons.push({ code: "stewards-conflict", message });
399
393
 
400
394
  const stewards: StatusSteward[] = [];
401
- for (const { declaration, filePath } of [...found.values()].sort((a, b) => a.declaration.name.localeCompare(b.declaration.name))) {
395
+ for (const declaration of [...index.stewards].sort((a, b) => a.name.localeCompare(b.name))) {
402
396
  const ops: StatusStewardOp[] = [];
403
397
  const waiting: StatusSteward["waiting"] = [];
404
398
  for (const op of declaration.ops) {
@@ -467,11 +461,11 @@ export async function readMemberStewards(
467
461
  }
468
462
  stewards.push({
469
463
  name: declaration.name,
470
- file: relative(realpathSync(memberDir), realpathSync(filePath)).split("\\").join("/"),
464
+ file: relative(realpathSync(memberDir), realpathSync(declaration.filePath)).split("\\").join("/"),
471
465
  form: stewardFormFor(declaration, env),
472
466
  forms: { default: declaration.form.default, environments: { ...declaration.form.environments } },
473
- vault: typeof declaration.vault === "string" ? declaration.vault : null,
474
- capabilities: (Array.isArray(declaration.capabilities) ? declaration.capabilities : []).map((name) => {
467
+ vault: declaration.vault,
468
+ capabilities: declaration.capabilities.map((name) => {
475
469
  const declared = box?.capabilities.find((c) => c.name === name);
476
470
  return { name, broker: declared?.broker ?? null, declared: declared !== undefined };
477
471
  }),
@@ -480,7 +474,7 @@ export async function readMemberStewards(
480
474
  waiting,
481
475
  });
482
476
  }
483
- if (stewards.length > 0) await addUndeclaredWaits(memberDir, stewards, found, reasons, now, questionStates);
477
+ if (stewards.length > 0 && index.otherOps) await addUndeclaredWaits(memberDir, stewards, index.otherOps, reasons, now, questionStates);
484
478
  return { stewards, reasons };
485
479
  }
486
480
 
@@ -489,26 +483,20 @@ export async function readMemberStewards(
489
483
  * steward its record names. A steward's process can start an Op itself
490
484
  * with `CHANT_STEWARD` set (studio#137), and the run ledger then records
491
485
  * the run as the steward's though the declaration does not list the Op.
492
- * Import failures are left out here: `discoverStewards` read the same files
493
- * and reported them as `stewards-unreadable`.
486
+ * `ops` are the member's Ops no steward lists, from the index; import
487
+ * failures are left out there, as discovering the stewards reported them as
488
+ * `stewards-unreadable`.
494
489
  */
495
490
  async function addUndeclaredWaits(
496
491
  memberDir: string,
497
492
  stewards: StatusSteward[],
498
- found: Awaited<ReturnType<typeof discoverStewards>>["stewards"],
493
+ ops: IndexedOp[],
499
494
  reasons: MemberStewards["reasons"],
500
495
  now: string,
501
496
  questionStates: () => Promise<Map<string, string> | null>,
502
497
  ): Promise<void> {
503
- const declared = new Set([...found.values()].flatMap(({ declaration }) => declaration.ops.map((op) => op.name)));
504
- let ops: OpConfig[];
505
- try {
506
- ops = [...(await discoverOps({ cwd: memberDir })).ops.values()].map((d) => d.config).filter((op) => !declared.has(op.name));
507
- } catch {
508
- return;
509
- }
510
498
  const byName = new Map(stewards.map((s) => [s.name, s]));
511
- for (const op of ops.sort((a, b) => a.name.localeCompare(b.name))) {
499
+ for (const op of [...ops].sort((a, b) => a.name.localeCompare(b.name))) {
512
500
  const opEnv = runEnvOf(op);
513
501
  try {
514
502
  const newest = (await readRunLedger(opEnv, op.name, { cwd: memberDir })).records.at(-1);
@@ -0,0 +1,171 @@
1
+ /**
2
+ * The steward index behind `chant workspace status` (#3636): discovered
3
+ * once per tree, read back while the tree is the same, discovered again
4
+ * after an edit, a new file or a commit, and never written into the
5
+ * checkout.
6
+ */
7
+
8
+ import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { afterAll, afterEach, beforeEach, describe, expect, test } from "vitest";
11
+ import { cleanScratch, commitAll, git, repo, scratchDir } from "./__fixtures__/contract-repo";
12
+ import { declareSteward } from "../op/steward";
13
+ import type { OpConfig } from "../op/types";
14
+ import { discoverStewardIndex, readStewardIndex, type StewardIndex } from "./steward-index";
15
+ import { readMemberStewards } from "./status-stewards";
16
+
17
+ afterAll(cleanScratch);
18
+
19
+ let cache: string;
20
+ let before: string | undefined;
21
+ beforeEach(() => {
22
+ before = process.env.CHANT_CACHE_DIR;
23
+ cache = scratchDir("chant-steward-index-cache-");
24
+ process.env.CHANT_CACHE_DIR = cache;
25
+ });
26
+ afterEach(() => {
27
+ if (before === undefined) delete process.env.CHANT_CACHE_DIR;
28
+ else process.env.CHANT_CACHE_DIR = before;
29
+ delete process.env.CHANT_STEWARD_INDEX;
30
+ });
31
+
32
+ function op(name: string, extra: Partial<OpConfig> = {}): OpConfig {
33
+ return {
34
+ name,
35
+ overview: `${name} fixture`,
36
+ phases: [{ name: "only", steps: [{ kind: "activity", fn: "noop", args: {} }] }],
37
+ ...extra,
38
+ } as OpConfig;
39
+ }
40
+
41
+ /** A chant project whose one Op file declares a steward and exports another Op it doesn't list. */
42
+ function project(): string {
43
+ const tick = op("tick", { schedule: { cron: "0 * * * *" }, labels: { Env: "box" } });
44
+ const build = op("build", { workLease: { item: "W-001", kind: "work/work.kind.mjs" }, changesCheckout: true });
45
+ const steward = declareSteward({
46
+ name: "box-steward",
47
+ ops: [tick],
48
+ beside: [{ op: build, ready: { kind: "activity", fn: "ready", args: {} } }],
49
+ form: { default: "local", environments: { k3d: "fountain" } },
50
+ capabilities: ["inference"],
51
+ });
52
+ return repo(
53
+ {
54
+ "chant.config.json": "{}\n",
55
+ "ops/steward.op.ts": `export const steward = ${JSON.stringify(steward)};\nexport const lonely = { props: ${JSON.stringify(op("lonely"))} };\n`,
56
+ },
57
+ true,
58
+ );
59
+ }
60
+
61
+ /** `readStewardIndex`, counting the discoveries it makes. */
62
+ function counted(dir: string) {
63
+ let n = 0;
64
+ const discover = async (d: string): Promise<StewardIndex> => {
65
+ n++;
66
+ return discoverStewardIndex(d);
67
+ };
68
+ return { read: () => readStewardIndex(dir, { discover }), discoveries: () => n };
69
+ }
70
+
71
+ describe("the steward index (#3636)", () => {
72
+ test("holds what status reads of the stewards and Ops", async () => {
73
+ const root = project();
74
+ const index = await discoverStewardIndex(root);
75
+ expect(index.errors).toEqual([]);
76
+ expect(index.conflicts).toEqual([]);
77
+ expect(index.stewards).toHaveLength(1);
78
+ const [s] = index.stewards;
79
+ expect(s).toMatchObject({
80
+ name: "box-steward",
81
+ beside: [{ op: "build", ready: true }],
82
+ form: { default: "local", environments: { k3d: "fountain" } },
83
+ capabilities: ["inference"],
84
+ vault: null,
85
+ });
86
+ expect(s.filePath.endsWith(join("ops", "steward.op.ts"))).toBe(true);
87
+ expect(s.ops).toEqual([
88
+ { name: "tick", schedule: { cron: "0 * * * *" }, labels: { Env: "box" }, workLease: null, changesCheckout: false },
89
+ { name: "build", schedule: null, workLease: { kind: "work/work.kind.mjs" }, changesCheckout: true },
90
+ ]);
91
+ expect(index.otherOps).toEqual([{ name: "lonely", schedule: null, workLease: null, changesCheckout: false }]);
92
+ });
93
+
94
+ test("a second read of the same tree discovers nothing", async () => {
95
+ const root = project();
96
+ const c = counted(root);
97
+ const first = await c.read();
98
+ const second = await c.read();
99
+ expect(c.discoveries()).toBe(1);
100
+ expect(second).toEqual(JSON.parse(JSON.stringify(first)));
101
+ });
102
+
103
+ test("an edit, a new file or a commit discovers again", async () => {
104
+ const root = project();
105
+ const c = counted(root);
106
+ await c.read();
107
+ // A module already imported in this process isn't read again, so only the count is checked.
108
+ writeFileSync(join(root, "ops", "steward.op.ts"), "export const nothing = 1;\n");
109
+ await c.read();
110
+ expect(c.discoveries()).toBe(2);
111
+ await c.read();
112
+ expect(c.discoveries()).toBe(2);
113
+ mkdirSync(join(root, "lib"));
114
+ writeFileSync(join(root, "lib", "helper.ts"), "export const x = 1;\n");
115
+ await c.read();
116
+ expect(c.discoveries()).toBe(3);
117
+ commitAll(root);
118
+ await c.read();
119
+ expect(c.discoveries()).toBe(4);
120
+ });
121
+
122
+ test("an Op file that fails to import is not indexed, so the next read tries again", async () => {
123
+ const root = project();
124
+ writeFileSync(join(root, "ops", "broken.op.ts"), "throw new Error('not today');\n");
125
+ const c = counted(root);
126
+ expect((await c.read()).errors).toHaveLength(1);
127
+ expect((await c.read()).errors).toHaveLength(1);
128
+ expect(c.discoveries()).toBe(2);
129
+ });
130
+
131
+ test("CHANT_STEWARD_INDEX=0 discovers every time", async () => {
132
+ const root = project();
133
+ process.env.CHANT_STEWARD_INDEX = "0";
134
+ const c = counted(root);
135
+ await c.read();
136
+ await c.read();
137
+ expect(c.discoveries()).toBe(2);
138
+ expect(existsSync(join(cache, "workspace-stewards"))).toBe(false);
139
+ });
140
+
141
+ test("the index lives in the cache dir, never in the checkout", async () => {
142
+ const root = project();
143
+ const status = git(root, "status", "--porcelain", "--ignored");
144
+ await readStewardIndex(root);
145
+ expect(git(root, "status", "--porcelain", "--ignored")).toBe(status);
146
+ const [dir] = readdirSync(join(cache, "workspace-stewards"));
147
+ expect(readdirSync(join(cache, "workspace-stewards", dir)).filter((f) => f.endsWith(".json"))).toHaveLength(1);
148
+ });
149
+
150
+ test("the indexes of a checkout that is gone go on the next write", async () => {
151
+ const gone = project();
152
+ await readStewardIndex(gone);
153
+ rmSync(gone, { recursive: true, force: true });
154
+ await readStewardIndex(project());
155
+ expect(readdirSync(join(cache, "workspace-stewards"))).toHaveLength(1);
156
+ });
157
+
158
+ test("status lists the same stewards from the index as from discovery", async () => {
159
+ const root = project();
160
+ const now = "2027-01-01T00:00:00Z";
161
+ process.env.CHANT_STEWARD_INDEX = "0";
162
+ const discovered = await readMemberStewards(root, "k3d", now);
163
+ delete process.env.CHANT_STEWARD_INDEX;
164
+ await readMemberStewards(root, "k3d", now);
165
+ const indexed = await readMemberStewards(root, "k3d", now, "chant", null, {
166
+ readIndex: (d) => readStewardIndex(d, { discover: () => Promise.reject(new Error("should have been read from the index")) }),
167
+ });
168
+ expect(indexed).toEqual(discovered);
169
+ expect(indexed.stewards[0]).toMatchObject({ name: "box-steward", form: "fountain", file: "ops/steward.op.ts" });
170
+ });
171
+ });
@@ -0,0 +1,226 @@
1
+ /**
2
+ * What `chant workspace status` reads of a project's stewards and Ops
3
+ * (#3636), kept per tree so a status doesn't import every `*.op.ts` file
4
+ * each time it runs.
5
+ *
6
+ * Discovery (`../op/discover.ts`) has to import the Op modules, and an Op
7
+ * module may do work when it is imported: run git, read files, even start
8
+ * another chant. Status needs only plain fields of what it finds: each
9
+ * steward's name, file, form, vault, capabilities and Ops, and of each Op
10
+ * its name, schedule, labels, work lease kind and `changesCheckout`. The
11
+ * first status on a tree discovers them and writes that index to
12
+ * `<cache dir>/workspace-stewards/<checkout>/<key>.json`; later ones read
13
+ * it back, until the tree changes. The cache dir is the one the graph cache
14
+ * uses (`./graph-cache.ts`): `$CHANT_CACHE_DIR`, else
15
+ * `$XDG_CACHE_HOME/chant`, else `~/.cache/chant`, outside the workspace,
16
+ * since a read never changes the workspace it reads.
17
+ *
18
+ * The tree is the checkout's `HEAD` tree plus every changed or untracked
19
+ * file `git status` lists (its path and contents), and the chant version,
20
+ * so a commit, an edit or a new file anywhere in the checkout discovers
21
+ * again. Files git ignores (`node_modules`) are not part of it. An index is
22
+ * only written when every Op file imported, so an import that failed is
23
+ * tried again by the next status. `CHANT_STEWARD_INDEX=0` turns the index
24
+ * off, for an Op module whose declaration depends on more than its tree.
25
+ */
26
+
27
+ import { execFile } from "node:child_process";
28
+ import { createHash } from "node:crypto";
29
+ import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
30
+ import { homedir } from "node:os";
31
+ import { dirname, join, resolve } from "node:path";
32
+ import { promisify } from "node:util";
33
+ import { CHANT_VERSION } from "../cli/version";
34
+ import { discoverOps, discoverStewards } from "../op/discover";
35
+ import { stewardBesideOf, type StewardForm } from "../op/steward";
36
+ import type { OpConfig } from "../op/types";
37
+
38
+ const execFileAsync = promisify(execFile);
39
+
40
+ /** Bumped when the index's shape changes, so an older index is never read. */
41
+ const INDEX_FORMAT = 1;
42
+
43
+ /** Where the indexes of the checkout at `top` live, and the checkout's real path. */
44
+ function indexDir(top: string, env: NodeJS.ProcessEnv = process.env): { dir: string; checkout: string } {
45
+ const base = env.CHANT_CACHE_DIR || join(env.XDG_CACHE_HOME || join(homedir(), ".cache"), "chant");
46
+ let checkout = resolve(top);
47
+ try {
48
+ checkout = realpathSync(checkout);
49
+ } catch {
50
+ // Keyed on the path as given.
51
+ }
52
+ return { dir: join(base, "workspace-stewards", createHash("sha256").update(checkout).digest("hex").slice(0, 16)), checkout };
53
+ }
54
+
55
+ /** How many indexes a checkout keeps: the newest, by when they were written. */
56
+ const KEPT = 32;
57
+
58
+ /** What status reads of an Op. */
59
+ export interface IndexedOp {
60
+ name: string;
61
+ schedule: { cron: string } | null;
62
+ /** Absent when the Op has none. */
63
+ labels?: Record<string, string>;
64
+ workLease: { kind: string | null } | null;
65
+ changesCheckout: boolean;
66
+ }
67
+
68
+ /** What status reads of a steward. */
69
+ export interface IndexedSteward {
70
+ name: string;
71
+ /** The `*.op.ts` file declaring it, absolute. */
72
+ filePath: string;
73
+ /** Every Op it runs: its turns' Ops, then those beside them. */
74
+ ops: IndexedOp[];
75
+ /** The Ops it runs beside its turns (#2861), and whether each declares a ready step. */
76
+ beside: { op: string; ready: boolean }[];
77
+ form: { default: StewardForm; environments: Record<string, StewardForm> };
78
+ capabilities: string[];
79
+ vault: string | null;
80
+ }
81
+
82
+ export interface StewardIndex {
83
+ /** In discovery's order. */
84
+ stewards: IndexedSteward[];
85
+ /** Files that could not be imported. */
86
+ errors: string[];
87
+ /** Stewards dropped as second writers. */
88
+ conflicts: string[];
89
+ /** The project's Ops that no steward lists, or null when they couldn't be discovered. */
90
+ otherOps: IndexedOp[] | null;
91
+ }
92
+
93
+ function indexOp(op: OpConfig): IndexedOp {
94
+ return {
95
+ name: op.name,
96
+ schedule: op.schedule ? { cron: op.schedule.cron } : null,
97
+ ...(op.labels ? { labels: { ...op.labels } } : {}),
98
+ workLease: op.workLease ? { kind: op.workLease.kind ?? null } : null,
99
+ changesCheckout: op.changesCheckout === true,
100
+ };
101
+ }
102
+
103
+ /** Discover the stewards and Ops under `memberDir`, importing every Op file. Throws as `discoverStewards` does. */
104
+ export async function discoverStewardIndex(memberDir: string): Promise<StewardIndex> {
105
+ const { stewards, errors, conflicts } = await discoverStewards({ cwd: memberDir });
106
+ const listed = [...stewards.values()].map(({ declaration, filePath }) => ({
107
+ name: declaration.name,
108
+ filePath,
109
+ ops: declaration.ops.map(indexOp),
110
+ beside: stewardBesideOf(declaration).map((b) => ({ op: b.op, ready: b.ready !== null && b.ready !== undefined })),
111
+ form: { default: declaration.form.default, environments: { ...declaration.form.environments } },
112
+ capabilities: Array.isArray(declaration.capabilities) ? [...declaration.capabilities] : [],
113
+ vault: typeof declaration.vault === "string" ? declaration.vault : null,
114
+ }));
115
+ let otherOps: IndexedOp[] | null = null;
116
+ // Only a project with a steward has waits to look for among its other Ops.
117
+ if (listed.length > 0) {
118
+ const declared = new Set(listed.flatMap((s) => s.ops.map((op) => op.name)));
119
+ try {
120
+ otherOps = [...(await discoverOps({ cwd: memberDir })).ops.values()].map((d) => indexOp(d.config)).filter((op) => !declared.has(op.name));
121
+ } catch {
122
+ otherOps = null;
123
+ }
124
+ }
125
+ return { stewards: listed, errors, conflicts, otherOps };
126
+ }
127
+
128
+ async function git(args: string[], cwd: string): Promise<string> {
129
+ // A read: `git status` must not take the index lock from a writer.
130
+ const env = { ...process.env, GIT_OPTIONAL_LOCKS: "0" };
131
+ const { stdout } = await execFileAsync("git", args, { cwd, env, maxBuffer: 64 * 1024 * 1024 });
132
+ return stdout;
133
+ }
134
+
135
+ /**
136
+ * The key of the tree `memberDir` is in: its `HEAD` tree, each changed or
137
+ * untracked file's path and contents, the chant version and `memberDir`.
138
+ * Null outside a checkout, or when git can't say.
139
+ */
140
+ export async function treeKey(memberDir: string): Promise<{ key: string; dir: string; checkout: string } | null> {
141
+ try {
142
+ const top = (await git(["rev-parse", "--show-toplevel"], memberDir)).trim();
143
+ const head = (await git(["rev-parse", "--verify", "--quiet", "HEAD^{tree}"], top).catch(() => "")).trim();
144
+ const changed = (await git(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames"], top))
145
+ .split("\0")
146
+ .filter((e) => e.length > 3)
147
+ .map((e) => e.slice(3))
148
+ .sort();
149
+ const h = createHash("sha256");
150
+ h.update(`${INDEX_FORMAT}\0${CHANT_VERSION}\0${resolve(memberDir)}\0${head}\0`);
151
+ for (const path of changed) {
152
+ h.update(`${path}\0`);
153
+ try {
154
+ const full = join(top, path);
155
+ h.update(statSync(full).isFile() ? readFileSync(full) : "dir");
156
+ } catch {
157
+ h.update("gone");
158
+ }
159
+ h.update("\0");
160
+ }
161
+ return { key: h.digest("hex"), ...indexDir(top) };
162
+ } catch {
163
+ return null;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Remove all but the {@link KEPT} newest indexes of this checkout, and the
169
+ * indexes of checkouts that are gone (each directory names its checkout in
170
+ * its `checkout` file).
171
+ */
172
+ function prune(dir: string): void {
173
+ const files = readdirSync(dir)
174
+ .filter((f) => f.endsWith(".json"))
175
+ .map((f) => ({ f, at: statSync(join(dir, f)).mtimeMs }))
176
+ .sort((a, b) => b.at - a.at);
177
+ for (const { f } of files.slice(KEPT)) rmSync(join(dir, f), { force: true });
178
+ const parent = dirname(dir);
179
+ for (const other of readdirSync(parent)) {
180
+ const at = join(parent, other);
181
+ if (at === dir) continue;
182
+ try {
183
+ if (!existsSync(readFileSync(join(at, "checkout"), "utf-8"))) rmSync(at, { recursive: true, force: true });
184
+ } catch {
185
+ // No checkout file, or being written: left alone.
186
+ }
187
+ }
188
+ }
189
+
190
+ function isIndex(v: unknown): v is StewardIndex {
191
+ const i = v as StewardIndex;
192
+ return !!i && Array.isArray(i.stewards) && Array.isArray(i.errors) && Array.isArray(i.conflicts) && (i.otherOps === null || Array.isArray(i.otherOps));
193
+ }
194
+
195
+ /**
196
+ * The stewards and Ops under `memberDir`: from the index for its tree when
197
+ * there is one, else discovered and indexed. Throws as `discoverStewards`
198
+ * does.
199
+ */
200
+ export async function readStewardIndex(memberDir: string, deps: { discover?: typeof discoverStewardIndex } = {}): Promise<StewardIndex> {
201
+ const discover = deps.discover ?? discoverStewardIndex;
202
+ if (process.env.CHANT_STEWARD_INDEX === "0") return discover(memberDir);
203
+ const tree = await treeKey(memberDir);
204
+ if (!tree) return discover(memberDir);
205
+ const file = join(tree.dir, `${tree.key}.json`);
206
+ try {
207
+ const kept = JSON.parse(readFileSync(file, "utf-8")) as unknown;
208
+ if (isIndex(kept)) return kept;
209
+ } catch {
210
+ // None yet, or unreadable: discover.
211
+ }
212
+ const index = await discover(memberDir);
213
+ if (index.errors.length === 0) {
214
+ try {
215
+ mkdirSync(tree.dir, { recursive: true });
216
+ writeFileSync(join(tree.dir, "checkout"), tree.checkout);
217
+ const tmp = `${file}.${process.pid}.tmp`;
218
+ writeFileSync(tmp, JSON.stringify(index));
219
+ renameSync(tmp, file);
220
+ prune(tree.dir);
221
+ } catch {
222
+ // A cache dir that can't be written: status still works, discovering each time.
223
+ }
224
+ }
225
+ return index;
226
+ }