@vyuhlabs/dxkit 2.21.2 → 2.23.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.
- package/CHANGELOG.md +94 -0
- package/dist/analyzers/correctness/run.d.ts +79 -0
- package/dist/analyzers/correctness/run.d.ts.map +1 -0
- package/dist/analyzers/correctness/run.js +173 -0
- package/dist/analyzers/correctness/run.js.map +1 -0
- package/dist/analyzers/correctness/surface-run.d.ts +73 -0
- package/dist/analyzers/correctness/surface-run.d.ts.map +1 -0
- package/dist/analyzers/correctness/surface-run.js +142 -0
- package/dist/analyzers/correctness/surface-run.js.map +1 -0
- package/dist/analyzers/correctness/surface.d.ts +69 -0
- package/dist/analyzers/correctness/surface.d.ts.map +1 -0
- package/dist/analyzers/correctness/surface.js +281 -0
- package/dist/analyzers/correctness/surface.js.map +1 -0
- package/dist/analyzers/flow/config.d.ts +10 -0
- package/dist/analyzers/flow/config.d.ts.map +1 -1
- package/dist/analyzers/flow/config.js +29 -0
- package/dist/analyzers/flow/config.js.map +1 -1
- package/dist/analyzers/flow/contract.d.ts +12 -0
- package/dist/analyzers/flow/contract.d.ts.map +1 -1
- package/dist/analyzers/flow/contract.js +20 -0
- package/dist/analyzers/flow/contract.js.map +1 -1
- package/dist/analyzers/flow/diagnose.d.ts +62 -0
- package/dist/analyzers/flow/diagnose.d.ts.map +1 -0
- package/dist/analyzers/flow/diagnose.js +120 -0
- package/dist/analyzers/flow/diagnose.js.map +1 -0
- package/dist/analyzers/flow/publish.d.ts +47 -0
- package/dist/analyzers/flow/publish.d.ts.map +1 -0
- package/dist/analyzers/flow/publish.js +146 -0
- package/dist/analyzers/flow/publish.js.map +1 -0
- package/dist/analyzers/flow/setup.d.ts +71 -0
- package/dist/analyzers/flow/setup.d.ts.map +1 -0
- package/dist/analyzers/flow/setup.js +136 -0
- package/dist/analyzers/flow/setup.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +129 -6
- package/dist/cli.js.map +1 -1
- package/dist/doctor.d.ts +7 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +38 -1
- package/dist/doctor.js.map +1 -1
- package/dist/flow-cli.d.ts +10 -0
- package/dist/flow-cli.d.ts.map +1 -1
- package/dist/flow-cli.js +46 -0
- package/dist/flow-cli.js.map +1 -1
- package/dist/generator.d.ts.map +1 -1
- package/dist/generator.js +6 -0
- package/dist/generator.js.map +1 -1
- package/dist/languages/capabilities/correctness.d.ts +54 -0
- package/dist/languages/capabilities/correctness.d.ts.map +1 -0
- package/dist/languages/capabilities/correctness.js +20 -0
- package/dist/languages/capabilities/correctness.js.map +1 -0
- package/dist/languages/csharp.d.ts.map +1 -1
- package/dist/languages/csharp.js +84 -0
- package/dist/languages/csharp.js.map +1 -1
- package/dist/languages/go.d.ts.map +1 -1
- package/dist/languages/go.js +51 -0
- package/dist/languages/go.js.map +1 -1
- package/dist/languages/index.d.ts +11 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +12 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/java.d.ts.map +1 -1
- package/dist/languages/java.js +12 -0
- package/dist/languages/java.js.map +1 -1
- package/dist/languages/jvm-build.d.ts +54 -0
- package/dist/languages/jvm-build.d.ts.map +1 -0
- package/dist/languages/jvm-build.js +245 -0
- package/dist/languages/jvm-build.js.map +1 -0
- package/dist/languages/kotlin.d.ts.map +1 -1
- package/dist/languages/kotlin.js +13 -0
- package/dist/languages/kotlin.js.map +1 -1
- package/dist/languages/python.d.ts.map +1 -1
- package/dist/languages/python.js +78 -0
- package/dist/languages/python.js.map +1 -1
- package/dist/languages/ruby.d.ts.map +1 -1
- package/dist/languages/ruby.js +64 -0
- package/dist/languages/ruby.js.map +1 -1
- package/dist/languages/rust.d.ts.map +1 -1
- package/dist/languages/rust.js +110 -0
- package/dist/languages/rust.js.map +1 -1
- package/dist/languages/types.d.ts +20 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +109 -0
- package/dist/languages/typescript.js.map +1 -1
- package/dist/loop/floor-gate.d.ts +54 -0
- package/dist/loop/floor-gate.d.ts.map +1 -0
- package/dist/loop/floor-gate.js +157 -0
- package/dist/loop/floor-gate.js.map +1 -0
- package/dist/loop/floor-state.d.ts +66 -0
- package/dist/loop/floor-state.d.ts.map +1 -0
- package/dist/loop/floor-state.js +138 -0
- package/dist/loop/floor-state.js.map +1 -0
- package/dist/loop/stop-gate.d.ts +2 -1
- package/dist/loop/stop-gate.d.ts.map +1 -1
- package/dist/loop/stop-gate.js +44 -6
- package/dist/loop/stop-gate.js.map +1 -1
- package/dist/prompts.d.ts +15 -0
- package/dist/prompts.d.ts.map +1 -1
- package/dist/prompts.js +66 -0
- package/dist/prompts.js.map +1 -1
- package/dist/workspace.d.ts +52 -0
- package/dist/workspace.d.ts.map +1 -0
- package/dist/workspace.js +130 -0
- package/dist/workspace.js.map +1 -0
- package/package.json +1 -1
- package/templates/.claude/skills/dxkit-config/SKILL.md +14 -0
- package/templates/.claude/skills/dxkit-fix/SKILL.md +2 -0
- package/templates/.claude/skills/dxkit-flow/SKILL.md +83 -0
- package/templates/.claude/skills/dxkit-hooks/SKILL.md +1 -1
- package/templates/.claude/skills/dxkit-init/SKILL.md +5 -0
- package/templates/.claude/skills/dxkit-onboard/SKILL.md +2 -0
- package/templates/.githooks/pre-push +10 -0
- package/templates/.github/workflows/dxkit-guardrails.yml +17 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Flow diagnosis — the "diagnose" surface, folded into `doctor` (there is no
|
|
4
|
+
* standalone `flow doctor`). Where the gate answers "did this PR break an
|
|
5
|
+
* integration?", diagnose answers "what is the current state of the contract?":
|
|
6
|
+
* which client calls do NOT resolve to a served route and why, which served
|
|
7
|
+
* routes nobody consumes, and how the served side is being resolved (the
|
|
8
|
+
* connection-resolution ladder).
|
|
9
|
+
*
|
|
10
|
+
* The output is deliberately agent-legible — `doctor --json` carries the whole
|
|
11
|
+
* `FlowDiagnosis`, so the `dxkit-flow` skill reads it as a thin consumer rather
|
|
12
|
+
* than scraping console prose. Reuses the shared extractor (Rule 2); fail-open
|
|
13
|
+
* (any error → `null`, and doctor simply omits the flow section).
|
|
14
|
+
*/
|
|
15
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
+
exports.diagnoseFlow = diagnoseFlow;
|
|
17
|
+
const languages_1 = require("../../languages");
|
|
18
|
+
const gather_1 = require("./gather");
|
|
19
|
+
const model_1 = require("./model");
|
|
20
|
+
const contract_1 = require("./contract");
|
|
21
|
+
const workspace_1 = require("../../workspace");
|
|
22
|
+
function suggestionFor(reason, topology) {
|
|
23
|
+
switch (reason) {
|
|
24
|
+
case 'external':
|
|
25
|
+
return 'adopt-spec'; // add the external API's OpenAPI spec to verify it
|
|
26
|
+
case 'placeholder-only':
|
|
27
|
+
return 'annotate'; // too generic to verify — annotate if intentional
|
|
28
|
+
case 'no-route':
|
|
29
|
+
// A monorepo serves its own routes, so a miss is a missing/typo'd route;
|
|
30
|
+
// a consumer-only repo's provider lives elsewhere (configure it).
|
|
31
|
+
return topology === 'monorepo' ? 'add-route' : 'configure-participant';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
/** Classify a binding into the unresolved tail, or `null` when it resolves. */
|
|
35
|
+
function classifyUnresolved(b, topology) {
|
|
36
|
+
let reason = null;
|
|
37
|
+
if (b.reason === 'external')
|
|
38
|
+
reason = 'external';
|
|
39
|
+
else if (b.reason === 'no-route')
|
|
40
|
+
reason = 'no-route';
|
|
41
|
+
else if (b.reason === 'placeholder-only')
|
|
42
|
+
reason = 'placeholder-only';
|
|
43
|
+
if (reason === null)
|
|
44
|
+
return null; // 'exact' → resolved
|
|
45
|
+
return {
|
|
46
|
+
method: b.call.method,
|
|
47
|
+
rawUrl: b.call.rawUrl,
|
|
48
|
+
path: b.call.path,
|
|
49
|
+
file: b.call.file,
|
|
50
|
+
line: b.call.line,
|
|
51
|
+
reason,
|
|
52
|
+
suggestion: suggestionFor(reason, topology),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function resolveConnection(cwd, model) {
|
|
56
|
+
if (model.routes.length > 0) {
|
|
57
|
+
return { rung: 'monorepo', note: 'This repo serves the routes its calls target.' };
|
|
58
|
+
}
|
|
59
|
+
if ((0, contract_1.readServedContract)(cwd)) {
|
|
60
|
+
return {
|
|
61
|
+
rung: 'committed-counterpart',
|
|
62
|
+
note: 'Resolving calls against the counterpart contract this repo commits.',
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
const ws = (0, workspace_1.readWorkspace)(cwd);
|
|
66
|
+
if (ws && ws.participants.length > 0) {
|
|
67
|
+
return {
|
|
68
|
+
rung: 'configured-participants',
|
|
69
|
+
note: `Configured participants: ${ws.participants.map((p) => p.name).join(', ')}.`,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
rung: 'unresolved',
|
|
74
|
+
note: 'No served side in this repo and no counterpart contract — the gate stays inert. Publish the provider contract or add a participant.',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Diagnose the repo's flow contract. Returns `null` (and doctor omits the flow
|
|
79
|
+
* section) when no flow-capable pack is active, when extraction finds nothing,
|
|
80
|
+
* or on any error.
|
|
81
|
+
*/
|
|
82
|
+
async function diagnoseFlow(cwd) {
|
|
83
|
+
if ((0, languages_1.allFlowSourceExtensions)((0, languages_1.detectActiveLanguages)(cwd)).length === 0)
|
|
84
|
+
return null;
|
|
85
|
+
let model;
|
|
86
|
+
try {
|
|
87
|
+
model = await (0, gather_1.gatherFlowModel)({ roots: [cwd] });
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const calls = model.calls.length;
|
|
93
|
+
const routes = model.routes.length;
|
|
94
|
+
if (calls === 0 && routes === 0)
|
|
95
|
+
return null;
|
|
96
|
+
const topology = calls > 0 && routes > 0 ? 'monorepo' : calls > 0 ? 'consumer-only' : 'provider-only';
|
|
97
|
+
const unresolved = model.bindings
|
|
98
|
+
.map((b) => classifyUnresolved(b, topology))
|
|
99
|
+
.filter((u) => u !== null);
|
|
100
|
+
const resolved = calls - unresolved.length;
|
|
101
|
+
// Served routes with no consuming binding. Consumed keys come from resolved
|
|
102
|
+
// bindings; the union with a committed counterpart's served set does NOT
|
|
103
|
+
// matter here — we only flag OUR served routes that nobody in scope calls.
|
|
104
|
+
const consumedKeys = new Set(model.bindings
|
|
105
|
+
.filter((b) => b.route !== null)
|
|
106
|
+
.map((b) => `${b.route.method} ${b.route.path}`));
|
|
107
|
+
const servedUnconsumed = model.routes
|
|
108
|
+
.filter((r) => !consumedKeys.has(`${r.method} ${r.path}`) && !(0, model_1.isPlaceholderOnlyPath)(r.path))
|
|
109
|
+
.map((r) => ({ method: r.method, path: r.path, file: r.file, line: r.line }));
|
|
110
|
+
return {
|
|
111
|
+
topology,
|
|
112
|
+
calls,
|
|
113
|
+
routes,
|
|
114
|
+
resolved,
|
|
115
|
+
unresolved,
|
|
116
|
+
servedUnconsumed,
|
|
117
|
+
connection: resolveConnection(cwd, model),
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=diagnose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"diagnose.js","sourceRoot":"","sources":["../../../src/analyzers/flow/diagnose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow publish — the multi-repo handshake behind `flow publish`.
|
|
3
|
+
*
|
|
4
|
+
* `flow refresh` writes ONE repo's served/consumed snapshots. `flow publish`
|
|
5
|
+
* goes further: it reads `.dxkit/workspace.json` and, for every participant,
|
|
6
|
+
* gathers that service's served routes (from its local path, optionally pinned
|
|
7
|
+
* at a git ref via `withRefWorktree`, Rule 11) and UNIONS them into this repo's
|
|
8
|
+
* `served.json`. The consuming repo then gates its calls against the whole mesh,
|
|
9
|
+
* not just the routes it happens to co-locate — the handshake that lets a
|
|
10
|
+
* frontend repo resolve calls to a backend it does not contain.
|
|
11
|
+
*
|
|
12
|
+
* With no participants it degenerates to a single-repo publish (this repo's own
|
|
13
|
+
* served ∪ nothing), the monorepo case. Fail-open per participant: a missing
|
|
14
|
+
* path or an unreachable ref drops that participant to zero routes rather than
|
|
15
|
+
* failing the publish. Reuses the canonical contract builders (Rule 2).
|
|
16
|
+
*/
|
|
17
|
+
/** Where a participant's served routes came from. */
|
|
18
|
+
export type ParticipantSource = 'local' | 'ref' | 'missing';
|
|
19
|
+
export interface PublishedParticipant {
|
|
20
|
+
readonly name: string;
|
|
21
|
+
readonly routes: number;
|
|
22
|
+
readonly source: ParticipantSource;
|
|
23
|
+
}
|
|
24
|
+
export interface PublishResult {
|
|
25
|
+
readonly servedPath: string;
|
|
26
|
+
readonly consumedPath: string;
|
|
27
|
+
/** One entry per workspace participant (empty in the single-repo case). */
|
|
28
|
+
readonly participants: readonly PublishedParticipant[];
|
|
29
|
+
/** Total served routes in the unioned mesh contract. */
|
|
30
|
+
readonly totalServedRoutes: number;
|
|
31
|
+
/** This repo's own consumed bindings (the mesh's consumed side is per-repo). */
|
|
32
|
+
readonly consumedBindings: number;
|
|
33
|
+
readonly contentHash: string;
|
|
34
|
+
}
|
|
35
|
+
export interface PublishOptions {
|
|
36
|
+
readonly stripUrlPrefixes?: readonly string[];
|
|
37
|
+
readonly specs?: readonly string[];
|
|
38
|
+
/** Stamped onto the snapshot meta (kept out of this module for testability). */
|
|
39
|
+
readonly generatedAt: string;
|
|
40
|
+
readonly commitSha?: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Publish the mesh contract: this repo's served routes ∪ every participant's,
|
|
44
|
+
* written to `.dxkit/flow/served.json`, plus this repo's own `consumed.json`.
|
|
45
|
+
*/
|
|
46
|
+
export declare function publishFlow(cwd: string, opts: PublishOptions): Promise<PublishResult>;
|
|
47
|
+
//# sourceMappingURL=publish.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/publish.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAkBH,qDAAqD;AACrD,MAAM,MAAM,iBAAiB,GAAG,OAAO,GAAG,KAAK,GAAG,SAAS,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACpC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,2EAA2E;IAC3E,QAAQ,CAAC,YAAY,EAAE,SAAS,oBAAoB,EAAE,CAAC;IACvD,wDAAwD;IACxD,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,gFAAgF;IAChF,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACnC,gFAAgF;IAChF,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAuDD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAyC3F"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Flow publish — the multi-repo handshake behind `flow publish`.
|
|
4
|
+
*
|
|
5
|
+
* `flow refresh` writes ONE repo's served/consumed snapshots. `flow publish`
|
|
6
|
+
* goes further: it reads `.dxkit/workspace.json` and, for every participant,
|
|
7
|
+
* gathers that service's served routes (from its local path, optionally pinned
|
|
8
|
+
* at a git ref via `withRefWorktree`, Rule 11) and UNIONS them into this repo's
|
|
9
|
+
* `served.json`. The consuming repo then gates its calls against the whole mesh,
|
|
10
|
+
* not just the routes it happens to co-locate — the handshake that lets a
|
|
11
|
+
* frontend repo resolve calls to a backend it does not contain.
|
|
12
|
+
*
|
|
13
|
+
* With no participants it degenerates to a single-repo publish (this repo's own
|
|
14
|
+
* served ∪ nothing), the monorepo case. Fail-open per participant: a missing
|
|
15
|
+
* path or an unreachable ref drops that participant to zero routes rather than
|
|
16
|
+
* failing the publish. Reuses the canonical contract builders (Rule 2).
|
|
17
|
+
*/
|
|
18
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
19
|
+
if (k2 === undefined) k2 = k;
|
|
20
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
21
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
22
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
23
|
+
}
|
|
24
|
+
Object.defineProperty(o, k2, desc);
|
|
25
|
+
}) : (function(o, m, k, k2) {
|
|
26
|
+
if (k2 === undefined) k2 = k;
|
|
27
|
+
o[k2] = m[k];
|
|
28
|
+
}));
|
|
29
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
30
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
31
|
+
}) : function(o, v) {
|
|
32
|
+
o["default"] = v;
|
|
33
|
+
});
|
|
34
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
35
|
+
var ownKeys = function(o) {
|
|
36
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
37
|
+
var ar = [];
|
|
38
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
39
|
+
return ar;
|
|
40
|
+
};
|
|
41
|
+
return ownKeys(o);
|
|
42
|
+
};
|
|
43
|
+
return function (mod) {
|
|
44
|
+
if (mod && mod.__esModule) return mod;
|
|
45
|
+
var result = {};
|
|
46
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
47
|
+
__setModuleDefault(result, mod);
|
|
48
|
+
return result;
|
|
49
|
+
};
|
|
50
|
+
})();
|
|
51
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
52
|
+
exports.publishFlow = publishFlow;
|
|
53
|
+
const fs = __importStar(require("fs"));
|
|
54
|
+
const path = __importStar(require("path"));
|
|
55
|
+
const workspace_1 = require("../../workspace");
|
|
56
|
+
const ref_baseline_1 = require("../../baseline/ref-baseline");
|
|
57
|
+
const gather_1 = require("./gather");
|
|
58
|
+
const contract_1 = require("./contract");
|
|
59
|
+
/** Meta with only the fields a participant gather needs — routes carry no
|
|
60
|
+
* timestamp of their own, so a clock-free placeholder keeps the gather pure. */
|
|
61
|
+
const GATHER_META = { schemaVersion: 1, generatedAt: '' };
|
|
62
|
+
/** Dedupe served routes to one per `(method, path)` — the mesh may serve the
|
|
63
|
+
* same route from this repo and a participant, or from two participants. */
|
|
64
|
+
function dedupeServed(routes) {
|
|
65
|
+
const seen = new Map();
|
|
66
|
+
for (const r of routes) {
|
|
67
|
+
const key = (0, contract_1.contractKey)(r.method, r.path);
|
|
68
|
+
if (!seen.has(key))
|
|
69
|
+
seen.set(key, r);
|
|
70
|
+
}
|
|
71
|
+
return [...seen.values()];
|
|
72
|
+
}
|
|
73
|
+
async function servedRoutesFrom(root, opts) {
|
|
74
|
+
const model = await (0, gather_1.gatherFlowModel)({
|
|
75
|
+
roots: [root],
|
|
76
|
+
...(opts.specs ? { specs: opts.specs.map((s) => path.resolve(root, s)) } : {}),
|
|
77
|
+
...(opts.stripUrlPrefixes ? { stripUrlPrefixes: [...opts.stripUrlPrefixes] } : {}),
|
|
78
|
+
relativeTo: root,
|
|
79
|
+
});
|
|
80
|
+
return (0, contract_1.buildServedContract)(model, GATHER_META).routes;
|
|
81
|
+
}
|
|
82
|
+
/** Gather one participant's served routes. Local path by default; pinned at a
|
|
83
|
+
* git ref when the participant declares one AND that ref resolves. Fail-open:
|
|
84
|
+
* a missing path → zero routes; an unresolvable ref → fall back to the tree. */
|
|
85
|
+
async function gatherParticipant(cwd, participant, opts) {
|
|
86
|
+
const abs = path.resolve(cwd, participant.path);
|
|
87
|
+
if (!fs.existsSync(abs))
|
|
88
|
+
return { routes: [], source: 'missing' };
|
|
89
|
+
if (participant.ref && (0, ref_baseline_1.resolveRefToSha)(abs, participant.ref)) {
|
|
90
|
+
try {
|
|
91
|
+
const routes = await (0, ref_baseline_1.withRefWorktree)({ cwd: abs, ref: participant.ref }, (wt) => servedRoutesFrom(wt, opts));
|
|
92
|
+
return { routes, source: 'ref' };
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Worktree/gather failure → fall through to the working tree.
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
return { routes: await servedRoutesFrom(abs, opts), source: 'local' };
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return { routes: [], source: 'missing' };
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Publish the mesh contract: this repo's served routes ∪ every participant's,
|
|
107
|
+
* written to `.dxkit/flow/served.json`, plus this repo's own `consumed.json`.
|
|
108
|
+
*/
|
|
109
|
+
async function publishFlow(cwd, opts) {
|
|
110
|
+
const baseMeta = {
|
|
111
|
+
schemaVersion: 1,
|
|
112
|
+
generatedAt: opts.generatedAt,
|
|
113
|
+
...(opts.commitSha ? { commitSha: opts.commitSha } : {}),
|
|
114
|
+
};
|
|
115
|
+
// This repo's own model — its served routes seed the mesh; its consumed side
|
|
116
|
+
// is published as-is (consumed is inherently per-repo).
|
|
117
|
+
const selfModel = await (0, gather_1.gatherFlowModel)({
|
|
118
|
+
roots: [cwd],
|
|
119
|
+
...(opts.specs ? { specs: opts.specs.map((s) => path.resolve(cwd, s)) } : {}),
|
|
120
|
+
...(opts.stripUrlPrefixes ? { stripUrlPrefixes: [...opts.stripUrlPrefixes] } : {}),
|
|
121
|
+
relativeTo: cwd,
|
|
122
|
+
});
|
|
123
|
+
const selfServed = (0, contract_1.buildServedContract)(selfModel, baseMeta);
|
|
124
|
+
const consumed = (0, contract_1.buildConsumedContract)(selfModel, baseMeta);
|
|
125
|
+
const allRoutes = [...selfServed.routes];
|
|
126
|
+
const participants = [];
|
|
127
|
+
for (const p of (0, workspace_1.readWorkspace)(cwd)?.participants ?? []) {
|
|
128
|
+
const { routes, source } = await gatherParticipant(cwd, p, opts);
|
|
129
|
+
participants.push({ name: p.name, routes: routes.length, source });
|
|
130
|
+
allRoutes.push(...routes);
|
|
131
|
+
}
|
|
132
|
+
const mesh = dedupeServed(allRoutes);
|
|
133
|
+
const contentHash = (0, contract_1.servedContentHash)(mesh);
|
|
134
|
+
const servedContract = { side: 'served', ...baseMeta, contentHash, routes: mesh };
|
|
135
|
+
const servedPath = (0, contract_1.writeServedContract)(cwd, servedContract);
|
|
136
|
+
const consumedPath = (0, contract_1.writeConsumedContract)(cwd, consumed);
|
|
137
|
+
return {
|
|
138
|
+
servedPath,
|
|
139
|
+
consumedPath,
|
|
140
|
+
participants,
|
|
141
|
+
totalServedRoutes: mesh.length,
|
|
142
|
+
consumedBindings: consumed.bindings.length,
|
|
143
|
+
contentHash,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=publish.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish.js","sourceRoot":"","sources":["../../../src/analyzers/flow/publish.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Flow setup — the detection + apply behind folding `flow init` into `init`.
|
|
3
|
+
*
|
|
4
|
+
* `init` runs `detectFlowTopology` to decide whether a repo even HAS a UI→API
|
|
5
|
+
* surface worth gating (if not, the init wizard stays silent — zero burden).
|
|
6
|
+
* When it does, init surfaces the two learnings this module computes — the
|
|
7
|
+
* dominant host-helper to strip, and any multiple backend services — as confirm
|
|
8
|
+
* prompts, then `applyFlowSetup` writes the resulting config (`.dxkit/policy.json:flow`
|
|
9
|
+
* plus `.dxkit/workspace.json` when participants are named).
|
|
10
|
+
*
|
|
11
|
+
* Detection reuses the same `gatherFlowModel` the gate runs (Rule 2 — one
|
|
12
|
+
* extractor); it never re-parses source itself. Fail-open throughout — an error
|
|
13
|
+
* degrades to "no flow detected", which the caller treats as "don't ask".
|
|
14
|
+
*/
|
|
15
|
+
import { type FlowGateMode } from './config';
|
|
16
|
+
import { type WorkspaceParticipant } from '../../workspace';
|
|
17
|
+
import type { ClientCall, RouteEndpoint } from './extract';
|
|
18
|
+
/** Which halves of the UI→API contract this repo contains. */
|
|
19
|
+
export type FlowTopology = 'monorepo' | 'consumer-only' | 'provider-only' | 'none';
|
|
20
|
+
export interface FlowDetection {
|
|
21
|
+
/** monorepo (both sides) · consumer-only (calls, no routes) · provider-only
|
|
22
|
+
* (routes, no calls) · none (nothing to gate — stay silent). */
|
|
23
|
+
readonly topology: FlowTopology;
|
|
24
|
+
readonly callCount: number;
|
|
25
|
+
readonly routeCount: number;
|
|
26
|
+
/** Calls that already bind to a served route — the healthy baseline. */
|
|
27
|
+
readonly resolvedCount: number;
|
|
28
|
+
/** Dominant host-helper prefix(es) across client calls — the strip-prefix the
|
|
29
|
+
* setup offers so a call like `${Config.api()}/x` matches a served `/x`. */
|
|
30
|
+
readonly suggestedStripPrefixes: readonly string[];
|
|
31
|
+
/** Distinct top-level directories that serve routes — a best-effort
|
|
32
|
+
* multi-service hint (robust multi-repo splitting is M4.3's `flow publish`). */
|
|
33
|
+
readonly detectedServices: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Detect the repo's flow topology by running the shared extractor. Returns
|
|
37
|
+
* `topology: 'none'` (and the caller stays silent) when no flow-capable pack is
|
|
38
|
+
* active, when extraction finds nothing, or on any error.
|
|
39
|
+
*/
|
|
40
|
+
export declare function detectFlowTopology(cwd: string): Promise<FlowDetection>;
|
|
41
|
+
/**
|
|
42
|
+
* The prefix of a raw client URL that is NOT part of the route path — an
|
|
43
|
+
* absolute `scheme://host`, or a leading `${...}` base-URL helper template. A
|
|
44
|
+
* relative URL (`/articles`) has no host prefix. This is exactly what a
|
|
45
|
+
* strip-prefix removes so a templated call matches a served route.
|
|
46
|
+
*/
|
|
47
|
+
export declare function hostPrefixOf(rawUrl: string): string | null;
|
|
48
|
+
/** Host-helper prefixes ranked by frequency across calls, most common first.
|
|
49
|
+
* Empty when calls are all relative (nothing to strip). */
|
|
50
|
+
export declare function dominantHostPrefixes(calls: readonly ClientCall[]): string[];
|
|
51
|
+
/** Distinct top-level directories that contain served routes — a coarse
|
|
52
|
+
* multi-service signal. Returns [] when routes live under one top-level dir
|
|
53
|
+
* (a single service). Deeper (nested / multi-repo) service splitting is
|
|
54
|
+
* M4.3's concern; here we only surface the obvious top-level split. */
|
|
55
|
+
export declare function servicesFromRoutes(routes: readonly RouteEndpoint[]): string[];
|
|
56
|
+
/** The confirmed setup a caller applies after the init prompts. */
|
|
57
|
+
export interface FlowSetupDecision {
|
|
58
|
+
readonly mode: FlowGateMode;
|
|
59
|
+
readonly stripUrlPrefixes: readonly string[];
|
|
60
|
+
/** Named participants → written to workspace.json (the multi-service /
|
|
61
|
+
* cross-repo case). Omitted for a single-service monorepo. */
|
|
62
|
+
readonly participants?: readonly WorkspaceParticipant[];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Apply a confirmed flow setup: write `flow.mode` (+ strip prefixes) into
|
|
66
|
+
* `.dxkit/policy.json` via the canonical writer, and — when participants are
|
|
67
|
+
* named — `.dxkit/workspace.json`. Returns the repo-relative paths written (for
|
|
68
|
+
* the init summary). Never throws on an already-current policy (idempotent).
|
|
69
|
+
*/
|
|
70
|
+
export declare function applyFlowSetup(cwd: string, decision: FlowSetupDecision): string[];
|
|
71
|
+
//# sourceMappingURL=setup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/setup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAKH,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,UAAU,CAAC;AAC9D,OAAO,EAAkB,KAAK,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC5E,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAE3D,8DAA8D;AAC9D,MAAM,MAAM,YAAY,GAAG,UAAU,GAAG,eAAe,GAAG,eAAe,GAAG,MAAM,CAAC;AAEnF,MAAM,WAAW,aAAa;IAC5B;qEACiE;IACjE,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,wEAAwE;IACxE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B;iFAC6E;IAC7E,QAAQ,CAAC,sBAAsB,EAAE,SAAS,MAAM,EAAE,CAAC;IACnD;qFACiF;IACjF,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C;AAWD;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAmC5E;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAU1D;AAED;4DAC4D;AAC5D,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,GAAG,MAAM,EAAE,CAO3E;AAED;;;wEAGwE;AACxE,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,MAAM,EAAE,CAO7E;AAED,mEAAmE;AACnE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C;mEAC+D;IAC/D,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,oBAAoB,EAAE,CAAC;CACzD;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,MAAM,EAAE,CAcjF"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Flow setup — the detection + apply behind folding `flow init` into `init`.
|
|
4
|
+
*
|
|
5
|
+
* `init` runs `detectFlowTopology` to decide whether a repo even HAS a UI→API
|
|
6
|
+
* surface worth gating (if not, the init wizard stays silent — zero burden).
|
|
7
|
+
* When it does, init surfaces the two learnings this module computes — the
|
|
8
|
+
* dominant host-helper to strip, and any multiple backend services — as confirm
|
|
9
|
+
* prompts, then `applyFlowSetup` writes the resulting config (`.dxkit/policy.json:flow`
|
|
10
|
+
* plus `.dxkit/workspace.json` when participants are named).
|
|
11
|
+
*
|
|
12
|
+
* Detection reuses the same `gatherFlowModel` the gate runs (Rule 2 — one
|
|
13
|
+
* extractor); it never re-parses source itself. Fail-open throughout — an error
|
|
14
|
+
* degrades to "no flow detected", which the caller treats as "don't ask".
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.detectFlowTopology = detectFlowTopology;
|
|
18
|
+
exports.hostPrefixOf = hostPrefixOf;
|
|
19
|
+
exports.dominantHostPrefixes = dominantHostPrefixes;
|
|
20
|
+
exports.servicesFromRoutes = servicesFromRoutes;
|
|
21
|
+
exports.applyFlowSetup = applyFlowSetup;
|
|
22
|
+
const languages_1 = require("../../languages");
|
|
23
|
+
const gather_1 = require("./gather");
|
|
24
|
+
const model_1 = require("./model");
|
|
25
|
+
const config_1 = require("./config");
|
|
26
|
+
const workspace_1 = require("../../workspace");
|
|
27
|
+
const NONE = {
|
|
28
|
+
topology: 'none',
|
|
29
|
+
callCount: 0,
|
|
30
|
+
routeCount: 0,
|
|
31
|
+
resolvedCount: 0,
|
|
32
|
+
suggestedStripPrefixes: [],
|
|
33
|
+
detectedServices: [],
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Detect the repo's flow topology by running the shared extractor. Returns
|
|
37
|
+
* `topology: 'none'` (and the caller stays silent) when no flow-capable pack is
|
|
38
|
+
* active, when extraction finds nothing, or on any error.
|
|
39
|
+
*/
|
|
40
|
+
async function detectFlowTopology(cwd) {
|
|
41
|
+
// Cheap gate first: if no active pack extracts flow, don't even run the
|
|
42
|
+
// extractor (keeps `init --yes` on a non-flow repo fast).
|
|
43
|
+
if ((0, languages_1.allFlowSourceExtensions)((0, languages_1.detectActiveLanguages)(cwd)).length === 0)
|
|
44
|
+
return NONE;
|
|
45
|
+
let model;
|
|
46
|
+
try {
|
|
47
|
+
model = await (0, gather_1.gatherFlowModel)({ roots: [cwd] });
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return NONE;
|
|
51
|
+
}
|
|
52
|
+
const callCount = model.calls.length;
|
|
53
|
+
const routeCount = model.routes.length;
|
|
54
|
+
if (callCount === 0 && routeCount === 0)
|
|
55
|
+
return NONE;
|
|
56
|
+
const resolvedCount = model.bindings.filter((b) => b.route !== null && !(0, model_1.isPlaceholderOnlyPath)(b.route.path)).length;
|
|
57
|
+
const topology = callCount > 0 && routeCount > 0
|
|
58
|
+
? 'monorepo'
|
|
59
|
+
: callCount > 0
|
|
60
|
+
? 'consumer-only'
|
|
61
|
+
: 'provider-only';
|
|
62
|
+
return {
|
|
63
|
+
topology,
|
|
64
|
+
callCount,
|
|
65
|
+
routeCount,
|
|
66
|
+
resolvedCount,
|
|
67
|
+
suggestedStripPrefixes: dominantHostPrefixes(model.calls),
|
|
68
|
+
detectedServices: servicesFromRoutes(model.routes),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The prefix of a raw client URL that is NOT part of the route path — an
|
|
73
|
+
* absolute `scheme://host`, or a leading `${...}` base-URL helper template. A
|
|
74
|
+
* relative URL (`/articles`) has no host prefix. This is exactly what a
|
|
75
|
+
* strip-prefix removes so a templated call matches a served route.
|
|
76
|
+
*/
|
|
77
|
+
function hostPrefixOf(rawUrl) {
|
|
78
|
+
// A template-literal URL is captured WITH its backticks (`${Config.api()}/x`);
|
|
79
|
+
// strip a leading one so the `${...}` head is at the string start. A plain
|
|
80
|
+
// string literal is captured without quotes, so this is a no-op for it.
|
|
81
|
+
const s = rawUrl.replace(/^`/, '');
|
|
82
|
+
const abs = /^(https?:\/\/[^/`]+)/.exec(s);
|
|
83
|
+
if (abs)
|
|
84
|
+
return abs[1];
|
|
85
|
+
const tmpl = /^(\$\{[^}]+\})/.exec(s);
|
|
86
|
+
if (tmpl)
|
|
87
|
+
return tmpl[1];
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
/** Host-helper prefixes ranked by frequency across calls, most common first.
|
|
91
|
+
* Empty when calls are all relative (nothing to strip). */
|
|
92
|
+
function dominantHostPrefixes(calls) {
|
|
93
|
+
const counts = new Map();
|
|
94
|
+
for (const c of calls) {
|
|
95
|
+
const p = hostPrefixOf(c.rawUrl);
|
|
96
|
+
if (p)
|
|
97
|
+
counts.set(p, (counts.get(p) ?? 0) + 1);
|
|
98
|
+
}
|
|
99
|
+
return [...counts.entries()].sort((a, b) => b[1] - a[1]).map(([p]) => p);
|
|
100
|
+
}
|
|
101
|
+
/** Distinct top-level directories that contain served routes — a coarse
|
|
102
|
+
* multi-service signal. Returns [] when routes live under one top-level dir
|
|
103
|
+
* (a single service). Deeper (nested / multi-repo) service splitting is
|
|
104
|
+
* M4.3's concern; here we only surface the obvious top-level split. */
|
|
105
|
+
function servicesFromRoutes(routes) {
|
|
106
|
+
const dirs = new Set();
|
|
107
|
+
for (const r of routes) {
|
|
108
|
+
const top = r.file.split(/[/\\]/)[0];
|
|
109
|
+
if (top && top !== r.file)
|
|
110
|
+
dirs.add(top); // skip files at repo root (no dir)
|
|
111
|
+
}
|
|
112
|
+
return dirs.size >= 2 ? [...dirs].sort() : [];
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Apply a confirmed flow setup: write `flow.mode` (+ strip prefixes) into
|
|
116
|
+
* `.dxkit/policy.json` via the canonical writer, and — when participants are
|
|
117
|
+
* named — `.dxkit/workspace.json`. Returns the repo-relative paths written (for
|
|
118
|
+
* the init summary). Never throws on an already-current policy (idempotent).
|
|
119
|
+
*/
|
|
120
|
+
function applyFlowSetup(cwd, decision) {
|
|
121
|
+
const written = [];
|
|
122
|
+
const changed = (0, config_1.writeFlowPolicy)(cwd, {
|
|
123
|
+
mode: decision.mode,
|
|
124
|
+
...(decision.stripUrlPrefixes.length
|
|
125
|
+
? { stripUrlPrefixes: [...decision.stripUrlPrefixes] }
|
|
126
|
+
: {}),
|
|
127
|
+
});
|
|
128
|
+
if (changed)
|
|
129
|
+
written.push('.dxkit/policy.json');
|
|
130
|
+
if (decision.participants && decision.participants.length > 0) {
|
|
131
|
+
(0, workspace_1.writeWorkspace)(cwd, { participants: [...decision.participants], external: [] });
|
|
132
|
+
written.push('.dxkit/workspace.json');
|
|
133
|
+
}
|
|
134
|
+
return written;
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../../src/analyzers/flow/setup.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AA4SA,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAikEvD"}
|