@vyuhlabs/dxkit 2.18.1 → 2.19.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 +23 -0
- package/dist/analyzers/flow/csv.d.ts +17 -0
- package/dist/analyzers/flow/csv.d.ts.map +1 -0
- package/dist/analyzers/flow/csv.js +64 -0
- package/dist/analyzers/flow/csv.js.map +1 -0
- package/dist/analyzers/flow/extract.d.ts +64 -0
- package/dist/analyzers/flow/extract.d.ts.map +1 -0
- package/dist/analyzers/flow/extract.js +254 -0
- package/dist/analyzers/flow/extract.js.map +1 -0
- package/dist/analyzers/flow/gather.d.ts +24 -0
- package/dist/analyzers/flow/gather.d.ts.map +1 -0
- package/dist/analyzers/flow/gather.js +50 -0
- package/dist/analyzers/flow/gather.js.map +1 -0
- package/dist/analyzers/flow/model.d.ts +63 -0
- package/dist/analyzers/flow/model.d.ts.map +1 -0
- package/dist/analyzers/flow/model.js +78 -0
- package/dist/analyzers/flow/model.js.map +1 -0
- package/dist/analyzers/flow/normalize.d.ts +76 -0
- package/dist/analyzers/flow/normalize.d.ts.map +1 -0
- package/dist/analyzers/flow/normalize.js +133 -0
- package/dist/analyzers/flow/normalize.js.map +1 -0
- package/dist/analyzers/flow/spec-source.d.ts +44 -0
- package/dist/analyzers/flow/spec-source.d.ts.map +1 -0
- package/dist/analyzers/flow/spec-source.js +75 -0
- package/dist/analyzers/flow/spec-source.js.map +1 -0
- package/dist/ast/parse.d.ts +71 -0
- package/dist/ast/parse.d.ts.map +1 -0
- package/dist/ast/parse.js +220 -0
- package/dist/ast/parse.js.map +1 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +21 -0
- package/dist/cli.js.map +1 -1
- package/dist/flow-cli.d.ts +16 -0
- package/dist/flow-cli.d.ts.map +1 -0
- package/dist/flow-cli.js +98 -0
- package/dist/flow-cli.js.map +1 -0
- package/dist/languages/index.d.ts +15 -2
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +18 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/types.d.ts +106 -0
- package/dist/languages/types.d.ts.map +1 -1
- package/dist/languages/typescript.d.ts.map +1 -1
- package/dist/languages/typescript.js +32 -0
- package/dist/languages/typescript.js.map +1 -1
- package/package.json +4 -2
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Flow model + the join — assemble extracted client calls and route
|
|
4
|
+
* declarations into a `FlowModel`, then bind each call to the route it targets
|
|
5
|
+
* on the normalized `(method, path)` key.
|
|
6
|
+
*
|
|
7
|
+
* The join is where consumed meets served. A binding carries a confidence in
|
|
8
|
+
* [0, 1] + a reason, mirroring the git-aware matcher's contract: an exact key
|
|
9
|
+
* match on a path with real static signal is full confidence; a path that is
|
|
10
|
+
* all placeholder (`/{var}`) carries no signal and is low confidence even when
|
|
11
|
+
* it happens to match a catch-all; a dynamic or unrouted call is unresolved.
|
|
12
|
+
* Confidence is what a gate thresholds on — only high-confidence bindings can
|
|
13
|
+
* block, which is what keeps the false-positive budget intact.
|
|
14
|
+
*
|
|
15
|
+
* Pure over its inputs (the file-extraction step that produces `FileFlow`s does
|
|
16
|
+
* the I/O); this module just structures + joins.
|
|
17
|
+
*/
|
|
18
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
19
|
+
exports.joinFlow = joinFlow;
|
|
20
|
+
exports.buildFlowModel = buildFlowModel;
|
|
21
|
+
exports.extractFlowModel = extractFlowModel;
|
|
22
|
+
exports.summarize = summarize;
|
|
23
|
+
const extract_1 = require("./extract");
|
|
24
|
+
/** A path made up entirely of `{var}` segments carries no static signal. */
|
|
25
|
+
function isPlaceholderOnly(path) {
|
|
26
|
+
return /^(\/\{var\})+$/.test(path);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Bind each client call to the route it targets, on the normalized
|
|
30
|
+
* `(method, path)` key. Routes are indexed once; each call resolves in O(1).
|
|
31
|
+
*/
|
|
32
|
+
function joinFlow(calls, routes) {
|
|
33
|
+
const routeIndex = new Map();
|
|
34
|
+
for (const r of routes)
|
|
35
|
+
routeIndex.set(`${r.method} ${r.path}`, r);
|
|
36
|
+
return calls.map((call) => {
|
|
37
|
+
if (call.path == null)
|
|
38
|
+
return { call, route: null, confidence: 0, reason: 'external' };
|
|
39
|
+
const route = routeIndex.get(`${call.method} ${call.path}`) ?? null;
|
|
40
|
+
if (!route)
|
|
41
|
+
return { call, route: null, confidence: 0, reason: 'no-route' };
|
|
42
|
+
if (isPlaceholderOnly(call.path)) {
|
|
43
|
+
return { call, route, confidence: 0.3, reason: 'placeholder-only' };
|
|
44
|
+
}
|
|
45
|
+
return { call, route, confidence: 1, reason: 'exact' };
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
/** Flatten per-file surfaces into one model + its bindings. */
|
|
49
|
+
function buildFlowModel(fileFlows) {
|
|
50
|
+
const calls = fileFlows.flatMap((f) => f.calls);
|
|
51
|
+
const routes = fileFlows.flatMap((f) => f.routes);
|
|
52
|
+
return { calls, routes, bindings: joinFlow(calls, routes) };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Extract + assemble a flow model from a set of files. `null` extractions
|
|
56
|
+
* (unparseable / engine unavailable) are skipped, never fatal.
|
|
57
|
+
*/
|
|
58
|
+
async function extractFlowModel(filePaths, config) {
|
|
59
|
+
const flows = [];
|
|
60
|
+
for (const path of filePaths) {
|
|
61
|
+
const flow = await (0, extract_1.extractFileFlow)(path, config);
|
|
62
|
+
if (flow)
|
|
63
|
+
flows.push(flow);
|
|
64
|
+
}
|
|
65
|
+
return buildFlowModel(flows);
|
|
66
|
+
}
|
|
67
|
+
function summarize(model) {
|
|
68
|
+
const resolved = model.bindings.filter((b) => b.route !== null).length;
|
|
69
|
+
const highConfidence = model.bindings.filter((b) => b.confidence >= 1).length;
|
|
70
|
+
return {
|
|
71
|
+
calls: model.calls.length,
|
|
72
|
+
routes: model.routes.length,
|
|
73
|
+
resolved,
|
|
74
|
+
highConfidence,
|
|
75
|
+
unresolved: model.calls.length - resolved,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/analyzers/flow/model.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;GAeG;;AAwCH,4BAgBC;AAGD,wCAIC;AAMD,4CAUC;AAWD,8BAUC;AAlGD,uCAAgG;AA6BhG,4EAA4E;AAC5E,SAAS,iBAAiB,CAAC,IAAY;IACrC,OAAO,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACrC,CAAC;AAED;;;GAGG;AACH,SAAgB,QAAQ,CACtB,KAA4B,EAC5B,MAAgC;IAEhC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAyB,CAAC;IACpD,KAAK,MAAM,CAAC,IAAI,MAAM;QAAE,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IAEnE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAe,EAAE;QACrC,IAAI,IAAI,CAAC,IAAI,IAAI,IAAI;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;QACvF,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC,IAAI,IAAI,CAAC;QACpE,IAAI,CAAC,KAAK;YAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,UAAU,EAAE,CAAC;QAC5E,IAAI,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACjC,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;QACtE,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;IACzD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,+DAA+D;AAC/D,SAAgB,cAAc,CAAC,SAA8B;IAC3D,MAAM,KAAK,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IAChD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;IAClD,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,EAAE,CAAC;AAC9D,CAAC;AAED;;;GAGG;AACI,KAAK,UAAU,gBAAgB,CACpC,SAA4B,EAC5B,MAAwB;IAExB,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,IAAA,yBAAe,EAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACjD,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IACD,OAAO,cAAc,CAAC,KAAK,CAAC,CAAC;AAC/B,CAAC;AAWD,SAAgB,SAAS,CAAC,KAAgB;IACxC,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,MAAM,CAAC;IACvE,MAAM,cAAc,GAAG,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,OAAO;QACL,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM;QACzB,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM;QAC3B,QAAQ;QACR,cAAc;QACd,UAAU,EAAE,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,QAAQ;KAC1C,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL / route-path normalization — the canonical, pure transform that lets a
|
|
3
|
+
* frontend client call and a backend route declaration JOIN even though they
|
|
4
|
+
* are written differently. It is the precision-critical core of the flow
|
|
5
|
+
* feature: this algorithm was validated at 96% precision (vs ~84% for a regex
|
|
6
|
+
* approach) on a real axios → LoopBack stack — the win comes from being
|
|
7
|
+
* structural rather than regex-mangled.
|
|
8
|
+
*
|
|
9
|
+
* The contract: turn a raw URL/route literal into a canonical path string in
|
|
10
|
+
* which path parameters are erased to `{var}` and host/prefix noise is gone,
|
|
11
|
+
* so `axios.get(`${Config.apiBase()}/articles/${slug}`)` (client) and
|
|
12
|
+
* `@get('/articles/{id}')` (server) both reduce to `/articles/{var}` and
|
|
13
|
+
* match. Method normalization (`del` → `DELETE`) lives here too.
|
|
14
|
+
*
|
|
15
|
+
* Two inputs are deliberately NOT baked in as language facts (CLAUDE.md
|
|
16
|
+
* Rule 6 boundary):
|
|
17
|
+
* - **Host helpers** (`${Config.apiBase()}`, `${apiUrl}`) are per-APP, not
|
|
18
|
+
* per-language — they arrive via `NormalizeConfig.stripUrlPrefixes`
|
|
19
|
+
* (sourced from `.dxkit/policy.json:flow.stripUrlPrefixes`).
|
|
20
|
+
* - **Param-form canonicalization** (`:id`, `{id}`, `${x}` → `{var}`) is
|
|
21
|
+
* uniform across frameworks, so it lives here rather than in a per-pack
|
|
22
|
+
* descriptor.
|
|
23
|
+
*
|
|
24
|
+
* Pure: no I/O, deterministic over its inputs. Identity-bearing (a binding's
|
|
25
|
+
* Rule 9 fingerprint is computed from the normalized path), so changes here
|
|
26
|
+
* are a normalization-scheme change — treat with the same care as a
|
|
27
|
+
* fingerprint-scheme bump.
|
|
28
|
+
*/
|
|
29
|
+
/** Canonical HTTP verbs the flow feature recognizes. */
|
|
30
|
+
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';
|
|
31
|
+
/** Per-app normalization inputs (not language facts — see module header). */
|
|
32
|
+
export interface NormalizeConfig {
|
|
33
|
+
/**
|
|
34
|
+
* Host-helper / base-URL prefixes to strip before matching, so a client
|
|
35
|
+
* call's absolute-ish URL reduces to the bare route path. Each entry is a
|
|
36
|
+
* literal substring removed wherever it appears at the head of the URL —
|
|
37
|
+
* e.g. `['${Config.apiBase()}', '${apiUrl}']`. Sourced from
|
|
38
|
+
* `.dxkit/policy.json:flow.stripUrlPrefixes`; `flow init` auto-suggests the
|
|
39
|
+
* dominant host-helper found across client calls.
|
|
40
|
+
*/
|
|
41
|
+
stripUrlPrefixes?: readonly string[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Canonicalize a raw URL / route literal to a comparable path, or `null` for
|
|
45
|
+
* an external absolute URL or empty / non-path input. A path that is entirely
|
|
46
|
+
* dynamic (`${x}`) normalizes faithfully to `/{var}` rather than being
|
|
47
|
+
* dropped here — the join (not this function) treats an all-placeholder path
|
|
48
|
+
* as unresolved (validated: precision held at 96% with this split).
|
|
49
|
+
*
|
|
50
|
+
* `raw` is the literal text as it appears in source, including any
|
|
51
|
+
* surrounding quotes/backticks (the extractor passes the node text verbatim).
|
|
52
|
+
*
|
|
53
|
+
* Steps (order matters):
|
|
54
|
+
* 1. strip surrounding quotes / backticks;
|
|
55
|
+
* 2. strip configured host-helper prefixes;
|
|
56
|
+
* 3. reject external absolute URLs (`http(s)://…`, `${host}://…`) → `null`
|
|
57
|
+
* (a call to a host we don't serve is not an internal route binding);
|
|
58
|
+
* 4. collapse template expressions `${…}` → `{var}`;
|
|
59
|
+
* 5. drop the query string (`?…`) — query params don't distinguish a route;
|
|
60
|
+
* 6. canonicalize path params `:id` and `{id}` → `{var}`;
|
|
61
|
+
* 7. ensure a single leading slash (LoopBack allows `@post('zen/x')`);
|
|
62
|
+
* 8. drop a trailing slash;
|
|
63
|
+
* 9. require a real path head (`/letter` or `/{var}`), else `null`.
|
|
64
|
+
*/
|
|
65
|
+
export declare function normalizePath(raw: string | null | undefined, config?: NormalizeConfig): string | null;
|
|
66
|
+
/**
|
|
67
|
+
* Canonicalize a matched method token (a decorator name like `get`, or a
|
|
68
|
+
* client/router member like `post`) to an uppercase {@link HttpMethod}.
|
|
69
|
+
* `aliases` carries pack-declared exceptions (LoopBack's `del` → `DELETE`);
|
|
70
|
+
* everything else upper-cases. Returns `null` for a token that doesn't map to
|
|
71
|
+
* a known verb.
|
|
72
|
+
*/
|
|
73
|
+
export declare function normalizeMethod(token: string, aliases?: Readonly<Record<string, string>>): HttpMethod | null;
|
|
74
|
+
/** A normalized binding key `"<METHOD> <path>"` — the join key + identity input. */
|
|
75
|
+
export declare function bindingKey(method: HttpMethod, path: string): string;
|
|
76
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/normalize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,wDAAwD;AACxD,MAAM,MAAM,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1F,6EAA6E;AAC7E,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,gBAAgB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACtC;AAID;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC9B,MAAM,CAAC,EAAE,eAAe,GACvB,MAAM,GAAG,IAAI,CAiDf;AAED;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,GACzC,UAAU,GAAG,IAAI,CAInB;AAgBD,oFAAoF;AACpF,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnE"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* URL / route-path normalization — the canonical, pure transform that lets a
|
|
4
|
+
* frontend client call and a backend route declaration JOIN even though they
|
|
5
|
+
* are written differently. It is the precision-critical core of the flow
|
|
6
|
+
* feature: this algorithm was validated at 96% precision (vs ~84% for a regex
|
|
7
|
+
* approach) on a real axios → LoopBack stack — the win comes from being
|
|
8
|
+
* structural rather than regex-mangled.
|
|
9
|
+
*
|
|
10
|
+
* The contract: turn a raw URL/route literal into a canonical path string in
|
|
11
|
+
* which path parameters are erased to `{var}` and host/prefix noise is gone,
|
|
12
|
+
* so `axios.get(`${Config.apiBase()}/articles/${slug}`)` (client) and
|
|
13
|
+
* `@get('/articles/{id}')` (server) both reduce to `/articles/{var}` and
|
|
14
|
+
* match. Method normalization (`del` → `DELETE`) lives here too.
|
|
15
|
+
*
|
|
16
|
+
* Two inputs are deliberately NOT baked in as language facts (CLAUDE.md
|
|
17
|
+
* Rule 6 boundary):
|
|
18
|
+
* - **Host helpers** (`${Config.apiBase()}`, `${apiUrl}`) are per-APP, not
|
|
19
|
+
* per-language — they arrive via `NormalizeConfig.stripUrlPrefixes`
|
|
20
|
+
* (sourced from `.dxkit/policy.json:flow.stripUrlPrefixes`).
|
|
21
|
+
* - **Param-form canonicalization** (`:id`, `{id}`, `${x}` → `{var}`) is
|
|
22
|
+
* uniform across frameworks, so it lives here rather than in a per-pack
|
|
23
|
+
* descriptor.
|
|
24
|
+
*
|
|
25
|
+
* Pure: no I/O, deterministic over its inputs. Identity-bearing (a binding's
|
|
26
|
+
* Rule 9 fingerprint is computed from the normalized path), so changes here
|
|
27
|
+
* are a normalization-scheme change — treat with the same care as a
|
|
28
|
+
* fingerprint-scheme bump.
|
|
29
|
+
*/
|
|
30
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
31
|
+
exports.normalizePath = normalizePath;
|
|
32
|
+
exports.normalizeMethod = normalizeMethod;
|
|
33
|
+
exports.bindingKey = bindingKey;
|
|
34
|
+
const PLACEHOLDER = '{var}';
|
|
35
|
+
/**
|
|
36
|
+
* Canonicalize a raw URL / route literal to a comparable path, or `null` for
|
|
37
|
+
* an external absolute URL or empty / non-path input. A path that is entirely
|
|
38
|
+
* dynamic (`${x}`) normalizes faithfully to `/{var}` rather than being
|
|
39
|
+
* dropped here — the join (not this function) treats an all-placeholder path
|
|
40
|
+
* as unresolved (validated: precision held at 96% with this split).
|
|
41
|
+
*
|
|
42
|
+
* `raw` is the literal text as it appears in source, including any
|
|
43
|
+
* surrounding quotes/backticks (the extractor passes the node text verbatim).
|
|
44
|
+
*
|
|
45
|
+
* Steps (order matters):
|
|
46
|
+
* 1. strip surrounding quotes / backticks;
|
|
47
|
+
* 2. strip configured host-helper prefixes;
|
|
48
|
+
* 3. reject external absolute URLs (`http(s)://…`, `${host}://…`) → `null`
|
|
49
|
+
* (a call to a host we don't serve is not an internal route binding);
|
|
50
|
+
* 4. collapse template expressions `${…}` → `{var}`;
|
|
51
|
+
* 5. drop the query string (`?…`) — query params don't distinguish a route;
|
|
52
|
+
* 6. canonicalize path params `:id` and `{id}` → `{var}`;
|
|
53
|
+
* 7. ensure a single leading slash (LoopBack allows `@post('zen/x')`);
|
|
54
|
+
* 8. drop a trailing slash;
|
|
55
|
+
* 9. require a real path head (`/letter` or `/{var}`), else `null`.
|
|
56
|
+
*/
|
|
57
|
+
function normalizePath(raw, config) {
|
|
58
|
+
if (raw == null)
|
|
59
|
+
return null;
|
|
60
|
+
let s = raw.trim();
|
|
61
|
+
if (s.length === 0)
|
|
62
|
+
return null;
|
|
63
|
+
// 1. surrounding quotes / backticks
|
|
64
|
+
if (s.length >= 2 && (s[0] === "'" || s[0] === '"' || s[0] === '`') && s[s.length - 1] === s[0]) {
|
|
65
|
+
s = s.slice(1, -1);
|
|
66
|
+
}
|
|
67
|
+
// 2. host-helper prefixes (longest first, so a more specific prefix wins)
|
|
68
|
+
for (const prefix of [...(config?.stripUrlPrefixes ?? [])].sort((a, b) => b.length - a.length)) {
|
|
69
|
+
if (prefix && s.startsWith(prefix)) {
|
|
70
|
+
s = s.slice(prefix.length);
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
// also strip when embedded at the head inside a template, e.g. a leading
|
|
74
|
+
// `${Config.apiBase()}` not at index 0 due to whitespace — rare; handled by
|
|
75
|
+
// a single replace of the first occurrence.
|
|
76
|
+
if (prefix && s.includes(prefix)) {
|
|
77
|
+
s = s.replace(prefix, '');
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// 3. external absolute URL (not one of our host helpers) → not internal
|
|
82
|
+
if (/^https?:\/\//i.test(s) || /^\$\{[^}]*\}:\/\//.test(s))
|
|
83
|
+
return null;
|
|
84
|
+
// 4. template expressions → placeholder
|
|
85
|
+
s = s.replace(/\$\{[^}]*\}/g, PLACEHOLDER);
|
|
86
|
+
// 5. drop query string
|
|
87
|
+
const q = s.indexOf('?');
|
|
88
|
+
if (q !== -1)
|
|
89
|
+
s = s.slice(0, q);
|
|
90
|
+
// 6. canonicalize path params
|
|
91
|
+
s = s.replace(/:[A-Za-z0-9_]+/g, PLACEHOLDER); // Express/Rails :id
|
|
92
|
+
s = s.replace(/\{[^}]*\}/g, PLACEHOLDER); // OpenAPI/LoopBack {id} (and already-{var})
|
|
93
|
+
// 7. single leading slash
|
|
94
|
+
if (!s.startsWith('/'))
|
|
95
|
+
s = '/' + s;
|
|
96
|
+
s = s.replace(/\/{2,}/g, '/'); // collapse accidental doubles after prefix strip
|
|
97
|
+
// 8. trailing slash
|
|
98
|
+
if (s.length > 1 && s.endsWith('/'))
|
|
99
|
+
s = s.slice(0, -1);
|
|
100
|
+
// 9. require a real path head
|
|
101
|
+
if (!/^\/([A-Za-z]|\{var\})/.test(s))
|
|
102
|
+
return null;
|
|
103
|
+
return s;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Canonicalize a matched method token (a decorator name like `get`, or a
|
|
107
|
+
* client/router member like `post`) to an uppercase {@link HttpMethod}.
|
|
108
|
+
* `aliases` carries pack-declared exceptions (LoopBack's `del` → `DELETE`);
|
|
109
|
+
* everything else upper-cases. Returns `null` for a token that doesn't map to
|
|
110
|
+
* a known verb.
|
|
111
|
+
*/
|
|
112
|
+
function normalizeMethod(token, aliases) {
|
|
113
|
+
const lower = token.toLowerCase();
|
|
114
|
+
const mapped = (aliases?.[lower] ?? lower).toUpperCase();
|
|
115
|
+
return isHttpMethod(mapped) ? mapped : null;
|
|
116
|
+
}
|
|
117
|
+
const HTTP_METHODS = new Set([
|
|
118
|
+
'GET',
|
|
119
|
+
'POST',
|
|
120
|
+
'PUT',
|
|
121
|
+
'PATCH',
|
|
122
|
+
'DELETE',
|
|
123
|
+
'HEAD',
|
|
124
|
+
'OPTIONS',
|
|
125
|
+
]);
|
|
126
|
+
function isHttpMethod(s) {
|
|
127
|
+
return HTTP_METHODS.has(s);
|
|
128
|
+
}
|
|
129
|
+
/** A normalized binding key `"<METHOD> <path>"` — the join key + identity input. */
|
|
130
|
+
function bindingKey(method, path) {
|
|
131
|
+
return `${method} ${path}`;
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../src/analyzers/flow/normalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenAPI / spec served-side source — consume an existing API spec as the
|
|
3
|
+
* authoritative served contract, rather than statically extracting routes.
|
|
4
|
+
*
|
|
5
|
+
* Build-vs-buy (CLAUDE.md Rule 5 at the served layer): where a backend ships or
|
|
6
|
+
* can emit an OpenAPI document — LoopBack's `exportOpenApiSpec`, NestJS/FastAPI
|
|
7
|
+
* generators, hand-maintained specs — that document is higher-fidelity than
|
|
8
|
+
* decorator scraping: it is the framework's own answer, and it cannot pick up a
|
|
9
|
+
* commented-out or dead route. So a spec, when present, is the PREFERRED served
|
|
10
|
+
* source; static route extraction (`extract.ts`) is the fallback for backends
|
|
11
|
+
* with no spec. The CONSUMED side (client calls) has no spec equivalent and is
|
|
12
|
+
* always AST-extracted — a spec replaces half the problem, never the engine.
|
|
13
|
+
*
|
|
14
|
+
* Output is the same `RouteEndpoint` shape the static extractor produces, so the
|
|
15
|
+
* join (`model.ts`) is indifferent to where a route came from. Identity-bearing
|
|
16
|
+
* via the normalized path (Rule 9), so paths route through the shared normalizer
|
|
17
|
+
* exactly as source-extracted ones do.
|
|
18
|
+
*
|
|
19
|
+
* JSON OpenAPI (2.0/3.x) today; YAML is a fast-follow (a parser dependency
|
|
20
|
+
* decision). Pure over its inputs; the file read is the only I/O.
|
|
21
|
+
*/
|
|
22
|
+
import type { RouteEndpoint } from './extract';
|
|
23
|
+
/** A path item holds HTTP-method operations alongside non-method siblings
|
|
24
|
+
* (`parameters`, `summary`, `$ref`), so its values are loosely typed and the
|
|
25
|
+
* method entries are narrowed at read time. */
|
|
26
|
+
type OpenApiPathItem = Record<string, unknown>;
|
|
27
|
+
interface OpenApiDoc {
|
|
28
|
+
paths?: Record<string, OpenApiPathItem | undefined>;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Routes from a parsed OpenAPI document. Each `(path, method)` pair becomes a
|
|
32
|
+
* served `RouteEndpoint` with `via: 'spec'`; the handler is the operationId when
|
|
33
|
+
* present. Paths are canonicalized through the shared normalizer so they join
|
|
34
|
+
* against client calls identically to source-extracted routes.
|
|
35
|
+
*/
|
|
36
|
+
export declare function routesFromOpenApi(doc: OpenApiDoc, sourceFile: string): RouteEndpoint[];
|
|
37
|
+
/**
|
|
38
|
+
* Read + parse a JSON OpenAPI document into served routes. Returns `[]` (never
|
|
39
|
+
* throws) when the file is unreadable or not valid JSON OpenAPI — a missing or
|
|
40
|
+
* broken spec degrades to the static fallback, it does not fail the run.
|
|
41
|
+
*/
|
|
42
|
+
export declare function loadOpenApiRoutes(filePath: string): RouteEndpoint[];
|
|
43
|
+
export {};
|
|
44
|
+
//# sourceMappingURL=spec-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-source.d.ts","sourceRoot":"","sources":["../../../src/analyzers/flow/spec-source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAC;AAK/C;;gDAEgD;AAChD,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAC/C,UAAU,UAAU;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAAC,CAAC;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,GAAG,aAAa,EAAE,CAkBtF;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,aAAa,EAAE,CASnE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* OpenAPI / spec served-side source — consume an existing API spec as the
|
|
4
|
+
* authoritative served contract, rather than statically extracting routes.
|
|
5
|
+
*
|
|
6
|
+
* Build-vs-buy (CLAUDE.md Rule 5 at the served layer): where a backend ships or
|
|
7
|
+
* can emit an OpenAPI document — LoopBack's `exportOpenApiSpec`, NestJS/FastAPI
|
|
8
|
+
* generators, hand-maintained specs — that document is higher-fidelity than
|
|
9
|
+
* decorator scraping: it is the framework's own answer, and it cannot pick up a
|
|
10
|
+
* commented-out or dead route. So a spec, when present, is the PREFERRED served
|
|
11
|
+
* source; static route extraction (`extract.ts`) is the fallback for backends
|
|
12
|
+
* with no spec. The CONSUMED side (client calls) has no spec equivalent and is
|
|
13
|
+
* always AST-extracted — a spec replaces half the problem, never the engine.
|
|
14
|
+
*
|
|
15
|
+
* Output is the same `RouteEndpoint` shape the static extractor produces, so the
|
|
16
|
+
* join (`model.ts`) is indifferent to where a route came from. Identity-bearing
|
|
17
|
+
* via the normalized path (Rule 9), so paths route through the shared normalizer
|
|
18
|
+
* exactly as source-extracted ones do.
|
|
19
|
+
*
|
|
20
|
+
* JSON OpenAPI (2.0/3.x) today; YAML is a fast-follow (a parser dependency
|
|
21
|
+
* decision). Pure over its inputs; the file read is the only I/O.
|
|
22
|
+
*/
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.routesFromOpenApi = routesFromOpenApi;
|
|
25
|
+
exports.loadOpenApiRoutes = loadOpenApiRoutes;
|
|
26
|
+
const fs_1 = require("fs");
|
|
27
|
+
const normalize_1 = require("./normalize");
|
|
28
|
+
const SPEC_METHODS = new Set(['get', 'post', 'put', 'patch', 'delete', 'head', 'options']);
|
|
29
|
+
/**
|
|
30
|
+
* Routes from a parsed OpenAPI document. Each `(path, method)` pair becomes a
|
|
31
|
+
* served `RouteEndpoint` with `via: 'spec'`; the handler is the operationId when
|
|
32
|
+
* present. Paths are canonicalized through the shared normalizer so they join
|
|
33
|
+
* against client calls identically to source-extracted routes.
|
|
34
|
+
*/
|
|
35
|
+
function routesFromOpenApi(doc, sourceFile) {
|
|
36
|
+
const out = [];
|
|
37
|
+
const paths = doc.paths ?? {};
|
|
38
|
+
for (const [rawPath, ops] of Object.entries(paths)) {
|
|
39
|
+
if (!ops || typeof ops !== 'object')
|
|
40
|
+
continue;
|
|
41
|
+
const path = (0, normalize_1.normalizePath)(rawPath);
|
|
42
|
+
if (!path)
|
|
43
|
+
continue;
|
|
44
|
+
for (const [rawMethod, op] of Object.entries(ops)) {
|
|
45
|
+
const lower = rawMethod.toLowerCase();
|
|
46
|
+
if (!SPEC_METHODS.has(lower))
|
|
47
|
+
continue; // skip parameters/summary/$ref siblings
|
|
48
|
+
const method = (0, normalize_1.normalizeMethod)(lower);
|
|
49
|
+
if (!method)
|
|
50
|
+
continue;
|
|
51
|
+
const operationId = op?.operationId;
|
|
52
|
+
const handler = typeof operationId === 'string' ? operationId : null;
|
|
53
|
+
out.push({ method, path, via: 'spec', handler, file: sourceFile, line: 0 });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Read + parse a JSON OpenAPI document into served routes. Returns `[]` (never
|
|
60
|
+
* throws) when the file is unreadable or not valid JSON OpenAPI — a missing or
|
|
61
|
+
* broken spec degrades to the static fallback, it does not fail the run.
|
|
62
|
+
*/
|
|
63
|
+
function loadOpenApiRoutes(filePath) {
|
|
64
|
+
let doc;
|
|
65
|
+
try {
|
|
66
|
+
doc = JSON.parse((0, fs_1.readFileSync)(filePath, 'utf8'));
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
if (!doc || typeof doc !== 'object' || !doc.paths)
|
|
72
|
+
return [];
|
|
73
|
+
return routesFromOpenApi(doc, filePath);
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=spec-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec-source.js","sourceRoot":"","sources":["../../../src/analyzers/flow/spec-source.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;;AAsBH,8CAkBC;AAOD,8CASC;AAtDD,2BAAkC;AAElC,2CAA8E;AAE9E,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAU3F;;;;;GAKG;AACH,SAAgB,iBAAiB,CAAC,GAAe,EAAE,UAAkB;IACnE,MAAM,GAAG,GAAoB,EAAE,CAAC;IAChC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,IAAI,EAAE,CAAC;IAC9B,KAAK,MAAM,CAAC,OAAO,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,SAAS;QAC9C,MAAM,IAAI,GAAG,IAAA,yBAAa,EAAC,OAAO,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI;YAAE,SAAS;QACpB,KAAK,MAAM,CAAC,SAAS,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAClD,MAAM,KAAK,GAAG,SAAS,CAAC,WAAW,EAAE,CAAC;YACtC,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,KAAK,CAAC;gBAAE,SAAS,CAAC,wCAAwC;YAChF,MAAM,MAAM,GAAsB,IAAA,2BAAe,EAAC,KAAK,CAAC,CAAC;YACzD,IAAI,CAAC,MAAM;gBAAE,SAAS;YACtB,MAAM,WAAW,GAAI,EAAuC,EAAE,WAAW,CAAC;YAC1E,MAAM,OAAO,GAAG,OAAO,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC;YACrE,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;GAIG;AACH,SAAgB,iBAAiB,CAAC,QAAgB;IAChD,IAAI,GAAe,CAAC;IACpB,IAAI,CAAC;QACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAA,iBAAY,EAAC,QAAQ,EAAE,MAAM,CAAC,CAAe,CAAC;IACjE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IAC7D,OAAO,iBAAiB,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;AAC1C,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical AST access — dxkit's in-process, graphify-independent parser.
|
|
3
|
+
*
|
|
4
|
+
* This is the ONE module that touches a tree-sitter engine. Every AST-based
|
|
5
|
+
* feature (flow extraction today; a future graph builder that could replace
|
|
6
|
+
* graphify; any later AST analysis) parses through here, so the engine stays
|
|
7
|
+
* swappable in a single file — the same adapter discipline graphify sits
|
|
8
|
+
* behind, but for raw parsing.
|
|
9
|
+
*
|
|
10
|
+
* Why its own layer (vs graphify): graphify is a high-level *graph builder*
|
|
11
|
+
* (functions/calls/communities) that does not expose raw call/decorator/
|
|
12
|
+
* string-literal nodes, and it runs as an optional Python subprocess. This
|
|
13
|
+
* layer is the low-level *parser*: source → concrete syntax tree, in-process
|
|
14
|
+
* (web-tree-sitter wasm), no Python, no graphify. It is also the foundation a
|
|
15
|
+
* future in-house graph builder would consume to migrate the code graph off
|
|
16
|
+
* graphify.
|
|
17
|
+
*
|
|
18
|
+
* Design:
|
|
19
|
+
* - **Lazy + graceful.** The wasm engine loads on first use (keeps startup
|
|
20
|
+
* fast) and every entry point returns `null` rather than throwing when the
|
|
21
|
+
* engine or a grammar is unavailable — AST features degrade, they don't
|
|
22
|
+
* crash (mirrors graphify's "unavailable" contract).
|
|
23
|
+
* - **Pack-driven grammars (Rule 6).** A file's grammar is resolved from the
|
|
24
|
+
* owning pack's `treeSitterGrammars[ext]`; this module never hardcodes a
|
|
25
|
+
* per-language mapping. Logical grammar names map to wasm artifacts here,
|
|
26
|
+
* so the artifact source (currently `tree-sitter-wasms`, pinned) is
|
|
27
|
+
* swappable / vendorable without touching packs.
|
|
28
|
+
* - **Cached.** Parser.init runs once; each grammar's `Language` and a bound
|
|
29
|
+
* `Parser` are cached for the process.
|
|
30
|
+
*/
|
|
31
|
+
import type { Node, Tree } from 'web-tree-sitter';
|
|
32
|
+
import type { LanguageId } from '../languages/types';
|
|
33
|
+
export type { Node, Tree } from 'web-tree-sitter';
|
|
34
|
+
/** A successfully parsed file. */
|
|
35
|
+
export interface ParsedFile {
|
|
36
|
+
readonly tree: Tree;
|
|
37
|
+
readonly source: string;
|
|
38
|
+
readonly grammar: string;
|
|
39
|
+
readonly languageId: LanguageId;
|
|
40
|
+
}
|
|
41
|
+
/** Whether the AST engine can be loaded in this environment (for doctor checks). */
|
|
42
|
+
export declare function astEngineAvailable(): Promise<boolean>;
|
|
43
|
+
/**
|
|
44
|
+
* Parse source text with a named grammar. Returns the tree, or `null` if the
|
|
45
|
+
* engine or grammar is unavailable (never throws).
|
|
46
|
+
*/
|
|
47
|
+
export declare function parseSource(source: string, grammar: string): Promise<Tree | null>;
|
|
48
|
+
/**
|
|
49
|
+
* The grammar + owning language for a file extension (with leading dot),
|
|
50
|
+
* resolved from the active pack registry — or `null` if no pack parses it.
|
|
51
|
+
*/
|
|
52
|
+
export declare function grammarForExtension(ext: string): {
|
|
53
|
+
grammar: string;
|
|
54
|
+
languageId: LanguageId;
|
|
55
|
+
} | null;
|
|
56
|
+
/**
|
|
57
|
+
* Read + parse a file, resolving its grammar from the extension via the pack
|
|
58
|
+
* registry. Returns `null` if the extension maps to no grammar, the file is
|
|
59
|
+
* unreadable, or the engine is unavailable.
|
|
60
|
+
*/
|
|
61
|
+
export declare function parseFile(filePath: string): Promise<ParsedFile | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Depth-first walk of a node and its descendants. The visitor runs on each
|
|
64
|
+
* node; return `false` to skip a node's children. Consumers use this rather
|
|
65
|
+
* than walking `node.children` directly so a future engine swap stays
|
|
66
|
+
* contained in this module.
|
|
67
|
+
*/
|
|
68
|
+
export declare function walk(node: Node, visit: (node: Node) => void | boolean): void;
|
|
69
|
+
/** Test seam: drop cached engine/grammars/parsers so a test can re-init. */
|
|
70
|
+
export declare function resetAstCachesForTest(): void;
|
|
71
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/ast/parse.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAIH,OAAO,KAAK,EAAY,IAAI,EAAU,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAEpE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAIrD,YAAY,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAElD,kCAAkC;AAClC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;CACjC;AA4ED,oFAAoF;AACpF,wBAAsB,kBAAkB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE3D;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC,CAUvF;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,MAAM,GACV;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,UAAU,CAAA;CAAE,GAAG,IAAI,CAOpD;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAY5E;AAED;;;;;GAKG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,GAAG,OAAO,GAAG,IAAI,CAM5E;AAED,4EAA4E;AAC5E,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C"}
|