domain-knowledge-kit 0.2.15 → 0.2.16
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.
- package/README.md +4 -0
- package/dist/cli.js +20 -0
- package/dist/cli.js.map +1 -1
- package/dist/features/agent/commands/init.d.ts.map +1 -1
- package/dist/features/agent/commands/init.js +141 -3
- package/dist/features/agent/commands/init.js.map +1 -1
- package/dist/features/agent/commands/prime.d.ts +11 -0
- package/dist/features/agent/commands/prime.d.ts.map +1 -1
- package/dist/features/agent/commands/prime.js +104 -8
- package/dist/features/agent/commands/prime.js.map +1 -1
- package/dist/features/federation/commands/consumers.d.ts +40 -0
- package/dist/features/federation/commands/consumers.d.ts.map +1 -0
- package/dist/features/federation/commands/consumers.js +126 -0
- package/dist/features/federation/commands/consumers.js.map +1 -0
- package/dist/features/federation/commands/peers-add.d.ts +14 -0
- package/dist/features/federation/commands/peers-add.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-add.js +79 -0
- package/dist/features/federation/commands/peers-add.js.map +1 -0
- package/dist/features/federation/commands/peers-list.d.ts +8 -0
- package/dist/features/federation/commands/peers-list.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-list.js +51 -0
- package/dist/features/federation/commands/peers-list.js.map +1 -0
- package/dist/features/federation/commands/peers-status.d.ts +8 -0
- package/dist/features/federation/commands/peers-status.d.ts.map +1 -0
- package/dist/features/federation/commands/peers-status.js +78 -0
- package/dist/features/federation/commands/peers-status.js.map +1 -0
- package/dist/features/federation/commands/pull.d.ts +18 -0
- package/dist/features/federation/commands/pull.d.ts.map +1 -0
- package/dist/features/federation/commands/pull.js +153 -0
- package/dist/features/federation/commands/pull.js.map +1 -0
- package/dist/features/federation/git-fetcher.d.ts +45 -0
- package/dist/features/federation/git-fetcher.d.ts.map +1 -0
- package/dist/features/federation/git-fetcher.js +70 -0
- package/dist/features/federation/git-fetcher.js.map +1 -0
- package/dist/features/federation/loader.d.ts +60 -0
- package/dist/features/federation/loader.d.ts.map +1 -0
- package/dist/features/federation/loader.js +193 -0
- package/dist/features/federation/loader.js.map +1 -0
- package/dist/features/federation/lock.d.ts +12 -0
- package/dist/features/federation/lock.d.ts.map +1 -0
- package/dist/features/federation/lock.js +48 -0
- package/dist/features/federation/lock.js.map +1 -0
- package/dist/features/federation/tests/git-fetcher.test.d.ts +2 -0
- package/dist/features/federation/tests/git-fetcher.test.d.ts.map +1 -0
- package/dist/features/federation/tests/git-fetcher.test.js +167 -0
- package/dist/features/federation/tests/git-fetcher.test.js.map +1 -0
- package/dist/features/federation/tests/loader.test.d.ts +2 -0
- package/dist/features/federation/tests/loader.test.d.ts.map +1 -0
- package/dist/features/federation/tests/loader.test.js +144 -0
- package/dist/features/federation/tests/loader.test.js.map +1 -0
- package/dist/features/federation/tests/phase5.test.d.ts +2 -0
- package/dist/features/federation/tests/phase5.test.d.ts.map +1 -0
- package/dist/features/federation/tests/phase5.test.js +137 -0
- package/dist/features/federation/tests/phase5.test.js.map +1 -0
- package/dist/features/federation/tests/schema-load.test.d.ts +2 -0
- package/dist/features/federation/tests/schema-load.test.d.ts.map +1 -0
- package/dist/features/federation/tests/schema-load.test.js +97 -0
- package/dist/features/federation/tests/schema-load.test.js.map +1 -0
- package/dist/features/federation/tests/validator.test.d.ts +2 -0
- package/dist/features/federation/tests/validator.test.d.ts.map +1 -0
- package/dist/features/federation/tests/validator.test.js +319 -0
- package/dist/features/federation/tests/validator.test.js.map +1 -0
- package/dist/features/mcp/commands/serve.d.ts +10 -0
- package/dist/features/mcp/commands/serve.d.ts.map +1 -0
- package/dist/features/mcp/commands/serve.js +12 -0
- package/dist/features/mcp/commands/serve.js.map +1 -0
- package/dist/features/mcp/server.d.ts +15 -0
- package/dist/features/mcp/server.d.ts.map +1 -0
- package/dist/features/mcp/server.js +438 -0
- package/dist/features/mcp/server.js.map +1 -0
- package/dist/features/pipeline/commands/validate.d.ts.map +1 -1
- package/dist/features/pipeline/commands/validate.js +7 -0
- package/dist/features/pipeline/commands/validate.js.map +1 -1
- package/dist/features/pipeline/indexer.d.ts +28 -2
- package/dist/features/pipeline/indexer.d.ts.map +1 -1
- package/dist/features/pipeline/indexer.js +82 -27
- package/dist/features/pipeline/indexer.js.map +1 -1
- package/dist/features/pipeline/validator.d.ts +10 -0
- package/dist/features/pipeline/validator.d.ts.map +1 -1
- package/dist/features/pipeline/validator.js +274 -27
- package/dist/features/pipeline/validator.js.map +1 -1
- package/dist/features/query/commands/list.d.ts +10 -0
- package/dist/features/query/commands/list.d.ts.map +1 -1
- package/dist/features/query/commands/list.js +1 -1
- package/dist/features/query/commands/list.js.map +1 -1
- package/dist/features/query/commands/locate.d.ts +1 -0
- package/dist/features/query/commands/locate.d.ts.map +1 -1
- package/dist/features/query/commands/locate.js +1 -1
- package/dist/features/query/commands/locate.js.map +1 -1
- package/dist/features/query/commands/search.d.ts.map +1 -1
- package/dist/features/query/commands/search.js +2 -0
- package/dist/features/query/commands/search.js.map +1 -1
- package/dist/features/query/commands/show.d.ts +15 -0
- package/dist/features/query/commands/show.d.ts.map +1 -1
- package/dist/features/query/commands/show.js +116 -58
- package/dist/features/query/commands/show.js.map +1 -1
- package/dist/features/query/commands/story.d.ts +70 -0
- package/dist/features/query/commands/story.d.ts.map +1 -1
- package/dist/features/query/commands/story.js +2 -2
- package/dist/features/query/commands/story.js.map +1 -1
- package/dist/features/query/commands/summary.d.ts +3 -0
- package/dist/features/query/commands/summary.d.ts.map +1 -1
- package/dist/features/query/commands/summary.js +1 -1
- package/dist/features/query/commands/summary.js.map +1 -1
- package/dist/features/query/searcher.d.ts +18 -1
- package/dist/features/query/searcher.d.ts.map +1 -1
- package/dist/features/query/searcher.js +11 -2
- package/dist/features/query/searcher.js.map +1 -1
- package/dist/features/scaffold/commands/service-init.d.ts +12 -0
- package/dist/features/scaffold/commands/service-init.d.ts.map +1 -0
- package/dist/features/scaffold/commands/service-init.js +69 -0
- package/dist/features/scaffold/commands/service-init.js.map +1 -0
- package/dist/shared/graph.d.ts +8 -0
- package/dist/shared/graph.d.ts.map +1 -1
- package/dist/shared/graph.js +180 -112
- package/dist/shared/graph.js.map +1 -1
- package/dist/shared/index.d.ts +4 -1
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/index.js +6 -1
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/loader.d.ts +22 -0
- package/dist/shared/loader.d.ts.map +1 -1
- package/dist/shared/loader.js +31 -1
- package/dist/shared/loader.js.map +1 -1
- package/dist/shared/paths.d.ts +59 -7
- package/dist/shared/paths.d.ts.map +1 -1
- package/dist/shared/paths.js +93 -11
- package/dist/shared/paths.js.map +1 -1
- package/dist/shared/refs.d.ts +96 -0
- package/dist/shared/refs.d.ts.map +1 -0
- package/dist/shared/refs.js +182 -0
- package/dist/shared/refs.js.map +1 -0
- package/dist/shared/service-id.d.ts +11 -0
- package/dist/shared/service-id.d.ts.map +1 -0
- package/dist/shared/service-id.js +64 -0
- package/dist/shared/service-id.js.map +1 -0
- package/dist/shared/tests/paths.test.d.ts +2 -0
- package/dist/shared/tests/paths.test.d.ts.map +1 -0
- package/dist/shared/tests/paths.test.js +111 -0
- package/dist/shared/tests/paths.test.js.map +1 -0
- package/dist/shared/tests/refs.test.d.ts +2 -0
- package/dist/shared/tests/refs.test.d.ts.map +1 -0
- package/dist/shared/tests/refs.test.js +104 -0
- package/dist/shared/tests/refs.test.js.map +1 -0
- package/dist/shared/types/domain.d.ts +14 -0
- package/dist/shared/types/domain.d.ts.map +1 -1
- package/dist/shared/types/federation.d.ts +60 -0
- package/dist/shared/types/federation.d.ts.map +1 -0
- package/dist/shared/types/federation.js +12 -0
- package/dist/shared/types/federation.js.map +1 -0
- package/package.json +6 -3
- package/tools/dkk/claude/agents/dkk-domain-reviewer.md +69 -0
- package/tools/dkk/claude/commands/dkk-adr.md +11 -0
- package/tools/dkk/claude/commands/dkk-impact.md +34 -0
- package/tools/dkk/claude/commands/dkk-implement.md +12 -0
- package/tools/dkk/claude/commands/dkk-prime.md +6 -0
- package/tools/dkk/claude/commands/dkk-review.md +12 -0
- package/tools/dkk/claude/commands/dkk-story.md +12 -0
- package/tools/dkk/claude/hooks/post-edit-validate.mjs +51 -0
- package/tools/dkk/claude/hooks/pre-edit-block-generated.mjs +39 -0
- package/tools/dkk/claude/hooks/session-start-prime.mjs +33 -0
- package/tools/dkk/claude/hooks/stop-validate.mjs +48 -0
- package/tools/dkk/claude/settings.json +62 -0
- package/tools/dkk/claude/skills/dkk-adr-author/SKILL.md +54 -0
- package/tools/dkk/claude/skills/dkk-flow-implementer/SKILL.md +51 -0
- package/tools/dkk/claude/skills/dkk-story-analyst/SKILL.md +108 -0
- package/tools/dkk/schema/actors.schema.json +1 -1
- package/tools/dkk/schema/adr-frontmatter.schema.json +4 -4
- package/tools/dkk/schema/aggregate.schema.json +1 -1
- package/tools/dkk/schema/command.schema.json +1 -1
- package/tools/dkk/schema/event.schema.json +1 -1
- package/tools/dkk/schema/federation.schema.json +71 -0
- package/tools/dkk/schema/glossary.schema.json +1 -1
- package/tools/dkk/schema/index.schema.json +2 -2
- package/tools/dkk/schema/policy.schema.json +1 -1
- package/tools/dkk/schema/read-model.schema.json +1 -1
- package/tools/dkk/schema/service.schema.json +30 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for {@link sparseFetch}.
|
|
3
|
+
*/
|
|
4
|
+
export interface SparseFetchOptions {
|
|
5
|
+
/** Git URL (https / ssh). */
|
|
6
|
+
url: string;
|
|
7
|
+
/** Branch to track. */
|
|
8
|
+
branch: string;
|
|
9
|
+
/**
|
|
10
|
+
* Sub-path inside the peer repo where the service lives (i.e. the
|
|
11
|
+
* directory whose child is `.dkk/`). Empty string for repos that
|
|
12
|
+
* have `.dkk/` at the root.
|
|
13
|
+
*/
|
|
14
|
+
subpath: string;
|
|
15
|
+
/**
|
|
16
|
+
* Absolute destination directory. Will be wiped if it exists.
|
|
17
|
+
* After fetch, the peer's repo content lives at `<dest>` and the
|
|
18
|
+
* service's `.dkk/` lives at `<dest>/<subpath>/.dkk` (or
|
|
19
|
+
* `<dest>/.dkk` when subpath is empty).
|
|
20
|
+
*/
|
|
21
|
+
dest: string;
|
|
22
|
+
}
|
|
23
|
+
/** Result of a successful sparse fetch. */
|
|
24
|
+
export interface SparseFetchResult {
|
|
25
|
+
/** Resolved commit SHA at HEAD of the fetched branch. */
|
|
26
|
+
sha: string;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Clone a peer repository into `dest` using a sparse-checkout of only
|
|
30
|
+
* the `.dkk/` subtree. The clone is depth-1 + blobless to keep it fast
|
|
31
|
+
* and small. Returns the resolved commit SHA.
|
|
32
|
+
*
|
|
33
|
+
* Steps:
|
|
34
|
+
* 1. Wipe `dest` if it exists (a stale partial clone is worse than no clone).
|
|
35
|
+
* 2. `git clone --filter=blob:none --depth 1 --no-checkout --branch <b> <url> <dest>`.
|
|
36
|
+
* 3. `git sparse-checkout init --no-cone` inside `dest`.
|
|
37
|
+
* 4. `git sparse-checkout set <subpath>/.dkk` (or `.dkk` when subpath empty).
|
|
38
|
+
* 5. `git checkout <branch>` to materialise the sparse set on disk.
|
|
39
|
+
* 6. `git rev-parse HEAD` to capture the SHA.
|
|
40
|
+
*
|
|
41
|
+
* Errors from `git` propagate as thrown exceptions with their stderr
|
|
42
|
+
* attached, so the caller can surface a meaningful message to the user.
|
|
43
|
+
*/
|
|
44
|
+
export declare function sparseFetch(opts: SparseFetchOptions): SparseFetchResult;
|
|
45
|
+
//# sourceMappingURL=git-fetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-fetcher.d.ts","sourceRoot":"","sources":["../../../src/features/federation/git-fetcher.ts"],"names":[],"mappings":"AAcA;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,6BAA6B;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,IAAI,EAAE,MAAM,CAAC;CACd;AAED,2CAA2C;AAC3C,MAAM,WAAW,iBAAiB;IAChC,yDAAyD;IACzD,GAAG,EAAE,MAAM,CAAC;CACb;AAcD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,kBAAkB,GAAG,iBAAiB,CAoCvE"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sparse-checkout fetcher for git-source peers.
|
|
3
|
+
*
|
|
4
|
+
* Clones a peer repository into a local cache, pulling only the
|
|
5
|
+
* subdirectory containing `.dkk/` to keep disk cost minimal — a
|
|
6
|
+
* typical peer is well under a megabyte.
|
|
7
|
+
*
|
|
8
|
+
* The user's existing git credential helper / SSH agent is what
|
|
9
|
+
* authenticates the clone. DKK never handles tokens directly.
|
|
10
|
+
*/
|
|
11
|
+
import { execFileSync } from "node:child_process";
|
|
12
|
+
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
13
|
+
import { dirname } from "node:path";
|
|
14
|
+
/**
|
|
15
|
+
* Run `git` with the given args inside `cwd`. Throws on non-zero exit.
|
|
16
|
+
* Captures stdout for callers that need it (e.g. `rev-parse`).
|
|
17
|
+
*/
|
|
18
|
+
function git(args, cwd) {
|
|
19
|
+
return execFileSync("git", args, {
|
|
20
|
+
cwd,
|
|
21
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
22
|
+
encoding: "utf-8",
|
|
23
|
+
}).trim();
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Clone a peer repository into `dest` using a sparse-checkout of only
|
|
27
|
+
* the `.dkk/` subtree. The clone is depth-1 + blobless to keep it fast
|
|
28
|
+
* and small. Returns the resolved commit SHA.
|
|
29
|
+
*
|
|
30
|
+
* Steps:
|
|
31
|
+
* 1. Wipe `dest` if it exists (a stale partial clone is worse than no clone).
|
|
32
|
+
* 2. `git clone --filter=blob:none --depth 1 --no-checkout --branch <b> <url> <dest>`.
|
|
33
|
+
* 3. `git sparse-checkout init --no-cone` inside `dest`.
|
|
34
|
+
* 4. `git sparse-checkout set <subpath>/.dkk` (or `.dkk` when subpath empty).
|
|
35
|
+
* 5. `git checkout <branch>` to materialise the sparse set on disk.
|
|
36
|
+
* 6. `git rev-parse HEAD` to capture the SHA.
|
|
37
|
+
*
|
|
38
|
+
* Errors from `git` propagate as thrown exceptions with their stderr
|
|
39
|
+
* attached, so the caller can surface a meaningful message to the user.
|
|
40
|
+
*/
|
|
41
|
+
export function sparseFetch(opts) {
|
|
42
|
+
if (existsSync(opts.dest)) {
|
|
43
|
+
rmSync(opts.dest, { recursive: true, force: true });
|
|
44
|
+
}
|
|
45
|
+
mkdirSync(dirname(opts.dest), { recursive: true });
|
|
46
|
+
// Step 2: blobless, depth-1, no checkout yet.
|
|
47
|
+
git([
|
|
48
|
+
"clone",
|
|
49
|
+
"--filter=blob:none",
|
|
50
|
+
"--depth",
|
|
51
|
+
"1",
|
|
52
|
+
"--no-checkout",
|
|
53
|
+
"--branch",
|
|
54
|
+
opts.branch,
|
|
55
|
+
opts.url,
|
|
56
|
+
opts.dest,
|
|
57
|
+
], process.cwd());
|
|
58
|
+
// Steps 3-4: enable sparse-checkout, restrict to the .dkk/ subtree.
|
|
59
|
+
git(["sparse-checkout", "init", "--no-cone"], opts.dest);
|
|
60
|
+
const sparsePattern = opts.subpath
|
|
61
|
+
? `${opts.subpath.replace(/\/$/, "")}/.dkk/*`
|
|
62
|
+
: ".dkk/*";
|
|
63
|
+
git(["sparse-checkout", "set", sparsePattern], opts.dest);
|
|
64
|
+
// Step 5: materialise.
|
|
65
|
+
git(["checkout", opts.branch], opts.dest);
|
|
66
|
+
// Step 6: capture HEAD SHA.
|
|
67
|
+
const sha = git(["rev-parse", "HEAD"], opts.dest);
|
|
68
|
+
return { sha };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=git-fetcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-fetcher.js","sourceRoot":"","sources":["../../../src/features/federation/git-fetcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA+BpC;;;GAGG;AACH,SAAS,GAAG,CAAC,IAAc,EAAE,GAAW;IACtC,OAAO,YAAY,CAAC,KAAK,EAAE,IAAI,EAAE;QAC/B,GAAG;QACH,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;QACjC,QAAQ,EAAE,OAAO;KAClB,CAAC,CAAC,IAAI,EAAE,CAAC;AACZ,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,IAAwB;IAClD,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC1B,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAEnD,8CAA8C;IAC9C,GAAG,CACD;QACE,OAAO;QACP,oBAAoB;QACpB,SAAS;QACT,GAAG;QACH,eAAe;QACf,UAAU;QACV,IAAI,CAAC,MAAM;QACX,IAAI,CAAC,GAAG;QACR,IAAI,CAAC,IAAI;KACV,EACD,OAAO,CAAC,GAAG,EAAE,CACd,CAAC;IAEF,oEAAoE;IACpE,GAAG,CAAC,CAAC,iBAAiB,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IACzD,MAAM,aAAa,GAAG,IAAI,CAAC,OAAO;QAChC,CAAC,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,SAAS;QAC7C,CAAC,CAAC,QAAQ,CAAC;IACb,GAAG,CAAC,CAAC,iBAAiB,EAAE,KAAK,EAAE,aAAa,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAE1D,uBAAuB;IACvB,GAAG,CAAC,CAAC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAE1C,4BAA4B;IAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,IAAI,CAAC,CAAC;IAElD,OAAO,EAAE,GAAG,EAAE,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { DomainModel } from "../../shared/types/domain.js";
|
|
2
|
+
import type { FederationManifest, PeerSpec } from "../../shared/types/federation.js";
|
|
3
|
+
/** Per-peer resolution outcome surfaced to the loader/caller. */
|
|
4
|
+
export interface PeerResolution {
|
|
5
|
+
/** The peer's repo root on disk (where its `.dkk/` lives), if reachable. */
|
|
6
|
+
peerRoot: string | null;
|
|
7
|
+
/** True when the peer's `.dkk/` directory exists on disk. */
|
|
8
|
+
reachable: boolean;
|
|
9
|
+
/** A short reason string used for warnings (e.g. "git cache empty"). */
|
|
10
|
+
reason?: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Read `.dkk/federation.yml` (if present), validate it against
|
|
14
|
+
* `federation.schema.json`, and return the parsed manifest. Returns
|
|
15
|
+
* `null` for unfederated repos.
|
|
16
|
+
*
|
|
17
|
+
* Throws when the file exists but does not conform to the schema —
|
|
18
|
+
* the message includes Ajv's error list so the user can fix the
|
|
19
|
+
* manifest directly.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadFederation(root?: string): FederationManifest | null;
|
|
22
|
+
/**
|
|
23
|
+
* Resolve a single peer spec into an absolute repo-root path on disk.
|
|
24
|
+
*
|
|
25
|
+
* - `local` sources resolve relative to the local repo root (so
|
|
26
|
+
* `../order-svc` in `billing-svc/.dkk/federation.yml` points at the
|
|
27
|
+
* sibling directory regardless of `cwd`). Env-var override
|
|
28
|
+
* `DKK_PEER_<SERVICE_NAME_UPPER>` (uppercase, hyphens → underscores)
|
|
29
|
+
* takes precedence over the manifest's `source.path`.
|
|
30
|
+
* - `git` sources resolve to the cache directory
|
|
31
|
+
* `.dkk/imports/<service>/` populated by `dkk pull`.
|
|
32
|
+
*
|
|
33
|
+
* The returned `peerRoot` points at the peer's repository root (so
|
|
34
|
+
* `<peerRoot>/.dkk/` is where the peer's domain lives).
|
|
35
|
+
*/
|
|
36
|
+
export declare function resolvePeerRoot(spec: PeerSpec, localRepoRoot: string): PeerResolution;
|
|
37
|
+
/**
|
|
38
|
+
* Load a peer's domain model in "peer mode": one level deep (peer's
|
|
39
|
+
* own `federation.yml` is skipped) and resilient to minor schema
|
|
40
|
+
* drift. The peer's model is structurally identical to a local model
|
|
41
|
+
* so the same `loadDomainModel` is reused; the federation pass is
|
|
42
|
+
* suppressed via the `followPeers: false` option.
|
|
43
|
+
*/
|
|
44
|
+
export declare function loadPeerModel(peerRoot: string): DomainModel;
|
|
45
|
+
/**
|
|
46
|
+
* Resolve and load every peer declared in the manifest. Unreachable
|
|
47
|
+
* peers are reported via `warnings` (each line is one peer) but never
|
|
48
|
+
* abort the load — the caller (typically the main loader) attaches
|
|
49
|
+
* the resulting map to `model.peers`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function loadAllPeers(localRepoRoot: string, manifest: FederationManifest): {
|
|
52
|
+
peers: Map<string, DomainModel>;
|
|
53
|
+
warnings: string[];
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Build the env-var key used to override a peer's source path.
|
|
57
|
+
* Exposed for tests and for the `peers status` command.
|
|
58
|
+
*/
|
|
59
|
+
export declare function peerEnvKey(serviceName: string): string;
|
|
60
|
+
//# sourceMappingURL=loader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../src/features/federation/loader.ts"],"names":[],"mappings":"AAsBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAChE,OAAO,KAAK,EACV,kBAAkB,EAClB,QAAQ,EACT,MAAM,kCAAkC,CAAC;AAuB1C,iEAAiE;AACjE,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,6DAA6D;IAC7D,SAAS,EAAE,OAAO,CAAC;IACnB,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAkBvE;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,GAAG,cAAc,CAoDrF;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,WAAW,CAE3D;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAC1B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,kBAAkB,GAC3B;IAAE,KAAK,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAoBzD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEtD"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federation loader — resolves peer services declared in
|
|
3
|
+
* `.dkk/federation.yml` and loads each peer's `.dkk/` as a read-only
|
|
4
|
+
* sub-model attached at `model.peers.get(serviceName)`.
|
|
5
|
+
*
|
|
6
|
+
* Peer loading is exactly one level deep: a peer's own `federation.yml`
|
|
7
|
+
* is intentionally NOT followed. Peers are loaded in lenient mode so
|
|
8
|
+
* that DKK schema drift across services (peer YAML using fields the
|
|
9
|
+
* local DKK version doesn't understand) degrades to a warning rather
|
|
10
|
+
* than failing the consumer's load.
|
|
11
|
+
*
|
|
12
|
+
* Two source types are supported:
|
|
13
|
+
* - `local`: a filesystem path (absolute or relative to repo root).
|
|
14
|
+
* - `git`: resolved against the cache at `.dkk/imports/<service>/`
|
|
15
|
+
* populated by `dkk pull` (Phase 3).
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
18
|
+
import { isAbsolute, resolve, join } from "node:path";
|
|
19
|
+
import { createRequire } from "node:module";
|
|
20
|
+
import { parseYaml } from "../../shared/yaml.js";
|
|
21
|
+
import { federationFile, importedServiceDir, schemaDir, repoRoot } from "../../shared/paths.js";
|
|
22
|
+
import { loadDomainModel, setFederationHook } from "../../shared/loader.js";
|
|
23
|
+
// ajv is a CJS package — use createRequire for ESM interop.
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
const Ajv = require("ajv").default;
|
|
26
|
+
const addFormats = require("ajv-formats").default;
|
|
27
|
+
/** Cached Ajv instance for federation.yml validation. */
|
|
28
|
+
let cachedAjv = null;
|
|
29
|
+
function getAjv() {
|
|
30
|
+
if (cachedAjv)
|
|
31
|
+
return cachedAjv;
|
|
32
|
+
const ajv = new Ajv({ allErrors: true, strict: true });
|
|
33
|
+
addFormats(ajv);
|
|
34
|
+
const dir = schemaDir();
|
|
35
|
+
for (const f of readdirSync(dir).filter((n) => n.endsWith(".schema.json"))) {
|
|
36
|
+
const schema = JSON.parse(readFileSync(join(dir, f), "utf-8"));
|
|
37
|
+
ajv.addSchema(schema, schema.$id);
|
|
38
|
+
}
|
|
39
|
+
cachedAjv = ajv;
|
|
40
|
+
return ajv;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read `.dkk/federation.yml` (if present), validate it against
|
|
44
|
+
* `federation.schema.json`, and return the parsed manifest. Returns
|
|
45
|
+
* `null` for unfederated repos.
|
|
46
|
+
*
|
|
47
|
+
* Throws when the file exists but does not conform to the schema —
|
|
48
|
+
* the message includes Ajv's error list so the user can fix the
|
|
49
|
+
* manifest directly.
|
|
50
|
+
*/
|
|
51
|
+
export function loadFederation(root) {
|
|
52
|
+
const path = federationFile(root);
|
|
53
|
+
if (!existsSync(path))
|
|
54
|
+
return null;
|
|
55
|
+
const text = readFileSync(path, "utf-8");
|
|
56
|
+
const parsed = parseYaml(text);
|
|
57
|
+
const ajv = getAjv();
|
|
58
|
+
const valid = ajv.validate("federation.schema.json", parsed);
|
|
59
|
+
if (!valid) {
|
|
60
|
+
const details = (ajv.errors ?? [])
|
|
61
|
+
.map((e) => ` - ${e.instancePath || "/"}: ${e.message ?? "invalid"}`)
|
|
62
|
+
.join("\n");
|
|
63
|
+
throw new Error(`Invalid ${path}:\n${details}\n\nExpected shape: { peers: [{ name, source: { type: "local" | "git", ... } }] }`);
|
|
64
|
+
}
|
|
65
|
+
return parsed;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Resolve a single peer spec into an absolute repo-root path on disk.
|
|
69
|
+
*
|
|
70
|
+
* - `local` sources resolve relative to the local repo root (so
|
|
71
|
+
* `../order-svc` in `billing-svc/.dkk/federation.yml` points at the
|
|
72
|
+
* sibling directory regardless of `cwd`). Env-var override
|
|
73
|
+
* `DKK_PEER_<SERVICE_NAME_UPPER>` (uppercase, hyphens → underscores)
|
|
74
|
+
* takes precedence over the manifest's `source.path`.
|
|
75
|
+
* - `git` sources resolve to the cache directory
|
|
76
|
+
* `.dkk/imports/<service>/` populated by `dkk pull`.
|
|
77
|
+
*
|
|
78
|
+
* The returned `peerRoot` points at the peer's repository root (so
|
|
79
|
+
* `<peerRoot>/.dkk/` is where the peer's domain lives).
|
|
80
|
+
*/
|
|
81
|
+
export function resolvePeerRoot(spec, localRepoRoot) {
|
|
82
|
+
const source = spec.source;
|
|
83
|
+
// Env-var override: applies to any source type for the convenience of
|
|
84
|
+
// developers who want to point at a local checkout regardless of
|
|
85
|
+
// what the committed manifest says.
|
|
86
|
+
const envKey = `DKK_PEER_${spec.name.toUpperCase().replace(/-/g, "_")}`;
|
|
87
|
+
const envOverride = process.env[envKey];
|
|
88
|
+
if (envOverride && envOverride.length > 0) {
|
|
89
|
+
const peerRoot = isAbsolute(envOverride)
|
|
90
|
+
? envOverride
|
|
91
|
+
: resolve(localRepoRoot, envOverride);
|
|
92
|
+
const reachable = existsSync(peerRoot + "/.dkk");
|
|
93
|
+
return {
|
|
94
|
+
peerRoot,
|
|
95
|
+
reachable,
|
|
96
|
+
reason: reachable ? undefined : `env override ${envKey} points at ${peerRoot} but it has no .dkk/`,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
if (source.type === "local") {
|
|
100
|
+
const peerRoot = isAbsolute(source.path)
|
|
101
|
+
? source.path
|
|
102
|
+
: resolve(localRepoRoot, source.path);
|
|
103
|
+
const reachable = existsSync(peerRoot + "/.dkk");
|
|
104
|
+
return {
|
|
105
|
+
peerRoot,
|
|
106
|
+
reachable,
|
|
107
|
+
reason: reachable ? undefined : `local path ${peerRoot} has no .dkk/`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (source.type === "git") {
|
|
111
|
+
const cacheRoot = importedServiceDir(spec.name, localRepoRoot);
|
|
112
|
+
// When the peer's `.dkk/` lives in a sub-directory of its repo
|
|
113
|
+
// (monorepo case), the manifest's `source.path` names that
|
|
114
|
+
// sub-directory; the sparse-checkout pulls it into the cache at
|
|
115
|
+
// the same relative location.
|
|
116
|
+
const peerRoot = source.path
|
|
117
|
+
? `${cacheRoot}/${source.path.replace(/\/$/, "")}`
|
|
118
|
+
: cacheRoot;
|
|
119
|
+
const reachable = existsSync(peerRoot + "/.dkk");
|
|
120
|
+
return {
|
|
121
|
+
peerRoot,
|
|
122
|
+
reachable,
|
|
123
|
+
reason: reachable ? undefined : `git cache empty for "${spec.name}" — run \`dkk pull ${spec.name}\``,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Exhaustiveness check — future source types should be added here.
|
|
127
|
+
const exhaustive = source;
|
|
128
|
+
return { peerRoot: null, reachable: false, reason: `unknown source type: ${JSON.stringify(exhaustive)}` };
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Load a peer's domain model in "peer mode": one level deep (peer's
|
|
132
|
+
* own `federation.yml` is skipped) and resilient to minor schema
|
|
133
|
+
* drift. The peer's model is structurally identical to a local model
|
|
134
|
+
* so the same `loadDomainModel` is reused; the federation pass is
|
|
135
|
+
* suppressed via the `followPeers: false` option.
|
|
136
|
+
*/
|
|
137
|
+
export function loadPeerModel(peerRoot) {
|
|
138
|
+
return loadDomainModel({ root: peerRoot, followPeers: false });
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Resolve and load every peer declared in the manifest. Unreachable
|
|
142
|
+
* peers are reported via `warnings` (each line is one peer) but never
|
|
143
|
+
* abort the load — the caller (typically the main loader) attaches
|
|
144
|
+
* the resulting map to `model.peers`.
|
|
145
|
+
*/
|
|
146
|
+
export function loadAllPeers(localRepoRoot, manifest) {
|
|
147
|
+
const peers = new Map();
|
|
148
|
+
const warnings = [];
|
|
149
|
+
for (const spec of manifest.peers ?? []) {
|
|
150
|
+
const resolution = resolvePeerRoot(spec, localRepoRoot);
|
|
151
|
+
if (!resolution.reachable || !resolution.peerRoot) {
|
|
152
|
+
warnings.push(`peer "${spec.name}" unreachable: ${resolution.reason ?? "unknown"}`);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
try {
|
|
156
|
+
const model = loadPeerModel(resolution.peerRoot);
|
|
157
|
+
peers.set(spec.name, model);
|
|
158
|
+
}
|
|
159
|
+
catch (err) {
|
|
160
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
161
|
+
warnings.push(`peer "${spec.name}" failed to load: ${msg}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return { peers, warnings };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Build the env-var key used to override a peer's source path.
|
|
168
|
+
* Exposed for tests and for the `peers status` command.
|
|
169
|
+
*/
|
|
170
|
+
export function peerEnvKey(serviceName) {
|
|
171
|
+
return `DKK_PEER_${serviceName.toUpperCase().replace(/-/g, "_")}`;
|
|
172
|
+
}
|
|
173
|
+
// ── Hook registration ────────────────────────────────────────────────
|
|
174
|
+
//
|
|
175
|
+
// Register the peer-hydration hook with the shared loader at module
|
|
176
|
+
// initialisation. Any CLI command that imports this slice (directly or
|
|
177
|
+
// transitively via the federation commands wired in cli.ts) will cause
|
|
178
|
+
// `loadDomainModel` to start populating `model.peers`. Scripts that
|
|
179
|
+
// import the shared loader without the federation slice get plain
|
|
180
|
+
// unfederated behaviour — no surprises, no cycles.
|
|
181
|
+
setFederationHook((root, model) => {
|
|
182
|
+
const manifest = loadFederation(root);
|
|
183
|
+
if (!manifest)
|
|
184
|
+
return;
|
|
185
|
+
const resolvedRoot = repoRoot(root);
|
|
186
|
+
const { peers, warnings } = loadAllPeers(resolvedRoot, manifest);
|
|
187
|
+
if (peers.size > 0)
|
|
188
|
+
model.peers = peers;
|
|
189
|
+
for (const w of warnings) {
|
|
190
|
+
console.warn(`dkk: ${w}`);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
//# sourceMappingURL=loader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loader.js","sourceRoot":"","sources":["../../../src/features/federation/loader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FederationLock, LockEntry } from "../../shared/types/federation.js";
|
|
2
|
+
/**
|
|
3
|
+
* Read the lockfile from disk. Returns an empty record when the file
|
|
4
|
+
* is absent. Malformed JSON throws (the file is committed, so a parse
|
|
5
|
+
* failure should be loud).
|
|
6
|
+
*/
|
|
7
|
+
export declare function readLock(root?: string): FederationLock;
|
|
8
|
+
/** Write the lockfile to disk with stable formatting. */
|
|
9
|
+
export declare function writeLock(lock: FederationLock, root?: string): void;
|
|
10
|
+
/** Build a fresh lock entry from a fetch result. */
|
|
11
|
+
export declare function makeEntry(source: LockEntry["source"], sha: string | undefined): LockEntry;
|
|
12
|
+
//# sourceMappingURL=lock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../../src/features/federation/lock.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAElF;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,cAAc,CAMtD;AAED,yDAAyD;AACzD,wBAAgB,SAAS,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CASnE;AAED,oDAAoD;AACpD,wBAAgB,SAAS,CACvB,MAAM,EAAE,SAAS,CAAC,QAAQ,CAAC,EAC3B,GAAG,EAAE,MAAM,GAAG,SAAS,GACtB,SAAS,CAKX"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Federation lockfile (.dkk/federation.lock.json).
|
|
3
|
+
*
|
|
4
|
+
* Records the resolved commit SHA each git-source peer was fetched at,
|
|
5
|
+
* along with a snapshot of the source spec used. Committing the
|
|
6
|
+
* lockfile means two developers running `dkk pull` see the same peer
|
|
7
|
+
* state until someone explicitly re-pulls with `--refresh`.
|
|
8
|
+
*
|
|
9
|
+
* Local-source peers have no lockfile entry — they're always live from
|
|
10
|
+
* disk.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
13
|
+
import { dirname } from "node:path";
|
|
14
|
+
import { federationLockFile } from "../../shared/paths.js";
|
|
15
|
+
/**
|
|
16
|
+
* Read the lockfile from disk. Returns an empty record when the file
|
|
17
|
+
* is absent. Malformed JSON throws (the file is committed, so a parse
|
|
18
|
+
* failure should be loud).
|
|
19
|
+
*/
|
|
20
|
+
export function readLock(root) {
|
|
21
|
+
const path = federationLockFile(root);
|
|
22
|
+
if (!existsSync(path))
|
|
23
|
+
return {};
|
|
24
|
+
const text = readFileSync(path, "utf-8");
|
|
25
|
+
if (text.trim().length === 0)
|
|
26
|
+
return {};
|
|
27
|
+
return JSON.parse(text);
|
|
28
|
+
}
|
|
29
|
+
/** Write the lockfile to disk with stable formatting. */
|
|
30
|
+
export function writeLock(lock, root) {
|
|
31
|
+
const path = federationLockFile(root);
|
|
32
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
33
|
+
// Stable key ordering (sorted by service name) keeps diffs minimal.
|
|
34
|
+
const sorted = {};
|
|
35
|
+
for (const key of Object.keys(lock).sort()) {
|
|
36
|
+
sorted[key] = lock[key];
|
|
37
|
+
}
|
|
38
|
+
writeFileSync(path, JSON.stringify(sorted, null, 2) + "\n", "utf-8");
|
|
39
|
+
}
|
|
40
|
+
/** Build a fresh lock entry from a fetch result. */
|
|
41
|
+
export function makeEntry(source, sha) {
|
|
42
|
+
const entry = { source };
|
|
43
|
+
if (sha)
|
|
44
|
+
entry.sha = sha;
|
|
45
|
+
entry.fetchedAt = new Date().toISOString();
|
|
46
|
+
return entry;
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=lock.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lock.js","sourceRoot":"","sources":["../../../src/features/federation/lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAG3D;;;;GAIG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAa;IACpC,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACzC,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACxC,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAmB,CAAC;AAC5C,CAAC;AAED,yDAAyD;AACzD,MAAM,UAAU,SAAS,CAAC,IAAoB,EAAE,IAAa;IAC3D,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACtC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC9C,oEAAoE;IACpE,MAAM,MAAM,GAAmB,EAAE,CAAC;IAClC,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QAC3C,MAAM,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1B,CAAC;IACD,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AACvE,CAAC;AAED,oDAAoD;AACpD,MAAM,UAAU,SAAS,CACvB,MAA2B,EAC3B,GAAuB;IAEvB,MAAM,KAAK,GAAc,EAAE,MAAM,EAAE,CAAC;IACpC,IAAI,GAAG;QAAE,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC;IACzB,KAAK,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,OAAO,KAAK,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"git-fetcher.test.d.ts","sourceRoot":"","sources":["../../../../src/features/federation/tests/git-fetcher.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the sparse-checkout git fetcher.
|
|
3
|
+
*
|
|
4
|
+
* Uses a local bare repository as the "remote" so the test runs
|
|
5
|
+
* fully offline. Verifies that:
|
|
6
|
+
* - sparseFetch clones with a `.dkk/` sparse pattern.
|
|
7
|
+
* - Only `.dkk/` content is materialised; other top-level files are
|
|
8
|
+
* excluded by the sparse rule.
|
|
9
|
+
* - The returned SHA matches `HEAD` of the configured branch.
|
|
10
|
+
* - `dkk pull` populates the cache and writes a lockfile entry.
|
|
11
|
+
* - A subsequent `dkk pull` is a no-op (the SHA in the lock matches).
|
|
12
|
+
* - `dkk pull --refresh` re-fetches.
|
|
13
|
+
*
|
|
14
|
+
* If `git` is not available on PATH, the test is skipped (we don't
|
|
15
|
+
* fail CI on environments without git).
|
|
16
|
+
*/
|
|
17
|
+
import { execFileSync, spawnSync } from "node:child_process";
|
|
18
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, realpathSync } from "node:fs";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
import { tmpdir } from "node:os";
|
|
21
|
+
import { sparseFetch } from "../git-fetcher.js";
|
|
22
|
+
let passed = 0;
|
|
23
|
+
let failed = 0;
|
|
24
|
+
let skipped = false;
|
|
25
|
+
function assert(label, condition, detail) {
|
|
26
|
+
if (condition) {
|
|
27
|
+
console.log(` OK: ${label}`);
|
|
28
|
+
passed++;
|
|
29
|
+
}
|
|
30
|
+
else {
|
|
31
|
+
console.error(`FAIL: ${label}${detail ? ` — ${detail}` : ""}`);
|
|
32
|
+
failed++;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function gitAvailable() {
|
|
36
|
+
try {
|
|
37
|
+
execFileSync("git", ["--version"], { stdio: "ignore" });
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (!gitAvailable()) {
|
|
45
|
+
console.log("git not available on PATH — skipping git-fetcher tests");
|
|
46
|
+
skipped = true;
|
|
47
|
+
}
|
|
48
|
+
if (!skipped) {
|
|
49
|
+
const RAW_TMP = join(tmpdir(), `dkk-git-${Date.now()}`);
|
|
50
|
+
mkdirSync(RAW_TMP, { recursive: true });
|
|
51
|
+
const TMP = realpathSync(RAW_TMP);
|
|
52
|
+
// Layout:
|
|
53
|
+
// <TMP>/remote/ — a bare git repo (the "remote")
|
|
54
|
+
// <TMP>/source/ — a regular repo we'll push from
|
|
55
|
+
// .dkk/ — the only content sparseFetch should pull
|
|
56
|
+
// service.yml
|
|
57
|
+
// domain/.../OrderPlaced.yml
|
|
58
|
+
// README.md — should NOT appear in the cache
|
|
59
|
+
// <TMP>/local/.dkk/imports/ordering/ — destination cache
|
|
60
|
+
const REMOTE = join(TMP, "remote");
|
|
61
|
+
const SOURCE = join(TMP, "source");
|
|
62
|
+
const LOCAL = join(TMP, "local");
|
|
63
|
+
const CACHE_DIR = join(LOCAL, ".dkk", "imports", "ordering");
|
|
64
|
+
function run(args, cwd) {
|
|
65
|
+
const result = spawnSync("git", args, { cwd, stdio: ["ignore", "pipe", "pipe"], encoding: "utf-8" });
|
|
66
|
+
if (result.status !== 0) {
|
|
67
|
+
throw new Error(`git ${args.join(" ")} failed in ${cwd}\nstderr: ${result.stderr}`);
|
|
68
|
+
}
|
|
69
|
+
return result.stdout.trim();
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
// Create the bare remote.
|
|
73
|
+
mkdirSync(REMOTE, { recursive: true });
|
|
74
|
+
run(["init", "--bare", "--initial-branch=main"], REMOTE);
|
|
75
|
+
// Create the source repo with .dkk/ content + a noise file.
|
|
76
|
+
mkdirSync(join(SOURCE, ".dkk", "domain", "contexts", "ordering", "events"), {
|
|
77
|
+
recursive: true,
|
|
78
|
+
});
|
|
79
|
+
writeFileSync(join(SOURCE, ".dkk", "service.yml"), "name: ordering\nexports:\n - ordering\n");
|
|
80
|
+
writeFileSync(join(SOURCE, ".dkk", "domain", "index.yml"), "contexts:\n - name: ordering\n description: Ordering context\nflows: []\n");
|
|
81
|
+
writeFileSync(join(SOURCE, ".dkk", "domain", "actors.yml"), "actors: []\n");
|
|
82
|
+
writeFileSync(join(SOURCE, ".dkk", "domain", "contexts", "ordering", "context.yml"), "name: ordering\ndescription: Ordering bounded context\n");
|
|
83
|
+
writeFileSync(join(SOURCE, ".dkk", "domain", "contexts", "ordering", "events", "OrderPlaced.yml"), "name: OrderPlaced\ndescription: Raised when an order is placed.\n");
|
|
84
|
+
writeFileSync(join(SOURCE, "README.md"), "# Should NOT be in the sparse checkout\n");
|
|
85
|
+
run(["init", "--initial-branch=main"], SOURCE);
|
|
86
|
+
run(["config", "user.email", "test@example.com"], SOURCE);
|
|
87
|
+
run(["config", "user.name", "Test"], SOURCE);
|
|
88
|
+
run(["config", "commit.gpgsign", "false"], SOURCE);
|
|
89
|
+
run(["add", "."], SOURCE);
|
|
90
|
+
run(["commit", "-m", "initial"], SOURCE);
|
|
91
|
+
run(["remote", "add", "origin", REMOTE], SOURCE);
|
|
92
|
+
run(["push", "-u", "origin", "main"], SOURCE);
|
|
93
|
+
const sourceHead = run(["rev-parse", "HEAD"], SOURCE);
|
|
94
|
+
// ── sparseFetch pulls only .dkk/ ──────────────────────────────────
|
|
95
|
+
console.log("\n=== sparseFetch ===");
|
|
96
|
+
const result = sparseFetch({
|
|
97
|
+
url: REMOTE,
|
|
98
|
+
branch: "main",
|
|
99
|
+
subpath: "",
|
|
100
|
+
dest: CACHE_DIR,
|
|
101
|
+
});
|
|
102
|
+
assert("returned SHA matches source HEAD", result.sha === sourceHead);
|
|
103
|
+
assert(".dkk/ exists in cache", existsSync(join(CACHE_DIR, ".dkk")));
|
|
104
|
+
assert("service.yml present", existsSync(join(CACHE_DIR, ".dkk", "service.yml")));
|
|
105
|
+
assert("OrderPlaced.yml present", existsSync(join(CACHE_DIR, ".dkk", "domain", "contexts", "ordering", "events", "OrderPlaced.yml")));
|
|
106
|
+
assert("README.md NOT in cache (sparse rule excluded it)", !existsSync(join(CACHE_DIR, "README.md")));
|
|
107
|
+
// ── dkk pull integrates the fetcher + lockfile ────────────────────
|
|
108
|
+
console.log("\n=== dkk pull (CLI integration) ===");
|
|
109
|
+
// Set up a `local` repo with a federation manifest pointing at the
|
|
110
|
+
// bare remote. Wipe the cache from the previous step so `pull` has
|
|
111
|
+
// to re-fetch.
|
|
112
|
+
rmSync(join(LOCAL, ".dkk"), { recursive: true, force: true });
|
|
113
|
+
mkdirSync(join(LOCAL, ".dkk", "domain"), { recursive: true });
|
|
114
|
+
writeFileSync(join(LOCAL, ".dkk", "service.yml"), "name: billing\nexports:\n - billing\n");
|
|
115
|
+
writeFileSync(join(LOCAL, ".dkk", "federation.yml"), [
|
|
116
|
+
"peers:",
|
|
117
|
+
" - name: ordering",
|
|
118
|
+
" source:",
|
|
119
|
+
" type: git",
|
|
120
|
+
` url: ${REMOTE}`,
|
|
121
|
+
" branch: main",
|
|
122
|
+
].join("\n") + "\n");
|
|
123
|
+
const cliEntry = join(process.cwd(), "src", "cli.ts");
|
|
124
|
+
const pullResult = spawnSync("npx", ["tsx", cliEntry, "pull", "--root", LOCAL, "--json"], { encoding: "utf-8" });
|
|
125
|
+
if (pullResult.status !== 0) {
|
|
126
|
+
throw new Error(`dkk pull failed: ${pullResult.stderr}\n${pullResult.stdout}`);
|
|
127
|
+
}
|
|
128
|
+
const pullReport = JSON.parse(pullResult.stdout);
|
|
129
|
+
assert("pull report has one peer", pullReport.peers.length === 1);
|
|
130
|
+
assert("ordering peer fetched", pullReport.peers[0].name === "ordering" && pullReport.peers[0].outcome === "fetched");
|
|
131
|
+
assert("lockfile sha matches source HEAD", pullReport.peers[0].sha === sourceHead);
|
|
132
|
+
assert("lockfile written to disk", existsSync(join(LOCAL, ".dkk", "federation.lock.json")));
|
|
133
|
+
// Subsequent pull is a no-op.
|
|
134
|
+
const pullAgain = spawnSync("npx", ["tsx", cliEntry, "pull", "--root", LOCAL, "--json"], { encoding: "utf-8" });
|
|
135
|
+
const pullAgainReport = JSON.parse(pullAgain.stdout);
|
|
136
|
+
assert("second pull is cached (no-op)", pullAgainReport.peers[0].outcome === "cached");
|
|
137
|
+
// --refresh forces re-fetch even when cached.
|
|
138
|
+
const pullRefresh = spawnSync("npx", ["tsx", cliEntry, "pull", "--root", LOCAL, "--refresh", "--json"], { encoding: "utf-8" });
|
|
139
|
+
const pullRefreshReport = JSON.parse(pullRefresh.stdout);
|
|
140
|
+
assert("--refresh re-fetches even when cached", pullRefreshReport.peers[0].outcome === "fetched");
|
|
141
|
+
// --offline with cache present is a no-op.
|
|
142
|
+
const pullOffline = spawnSync("npx", ["tsx", cliEntry, "pull", "--root", LOCAL, "--offline", "--json"], { encoding: "utf-8" });
|
|
143
|
+
const pullOfflineReport = JSON.parse(pullOffline.stdout);
|
|
144
|
+
assert("--offline with cache returns skipped-offline-cached", pullOfflineReport.peers[0].outcome === "skipped-offline-cached");
|
|
145
|
+
// Verify lockfile contents.
|
|
146
|
+
const lock = JSON.parse(readFileSync(join(LOCAL, ".dkk", "federation.lock.json"), "utf-8"));
|
|
147
|
+
assert("lockfile has ordering entry", lock.ordering !== undefined);
|
|
148
|
+
assert("lockfile entry has git source", lock.ordering.source.type === "git");
|
|
149
|
+
assert("lockfile entry has sha", typeof lock.ordering.sha === "string" && lock.ordering.sha.length > 0);
|
|
150
|
+
// ── Federation loader sees the cached peer ────────────────────────
|
|
151
|
+
console.log("\n=== loader sees cached peer ===");
|
|
152
|
+
const showResult = spawnSync("npx", ["tsx", cliEntry, "show", "ordering:OrderPlaced", "--root", LOCAL, "--json"], { encoding: "utf-8" });
|
|
153
|
+
if (showResult.status !== 0) {
|
|
154
|
+
throw new Error(`dkk show failed: ${showResult.stderr}\n${showResult.stdout}`);
|
|
155
|
+
}
|
|
156
|
+
const shown = JSON.parse(showResult.stdout);
|
|
157
|
+
assert("peer item resolved via cache", shown.data?.name === "OrderPlaced");
|
|
158
|
+
assert("peer label includes [peer: ordering]", shown.label.includes("peer: ordering"));
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
rmSync(RAW_TMP, { recursive: true, force: true });
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
console.log(`\n${passed} passed, ${failed} failed${skipped ? " (test suite skipped)" : ""}`);
|
|
165
|
+
if (failed > 0)
|
|
166
|
+
process.exit(1);
|
|
167
|
+
//# sourceMappingURL=git-fetcher.test.js.map
|