lockstep-cli 0.0.5 → 0.1.1
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 +29 -0
- package/dist/adapters/claude.js +26 -7
- package/dist/adapters/claude.js.map +1 -1
- package/dist/adapters/install.test.js +53 -2
- package/dist/adapters/install.test.js.map +1 -1
- package/dist/adapters/merge.d.ts +11 -0
- package/dist/adapters/merge.js +41 -0
- package/dist/adapters/merge.js.map +1 -1
- package/dist/adapters/templates.d.ts +3 -2
- package/dist/adapters/templates.js +92 -3
- package/dist/adapters/templates.js.map +1 -1
- package/dist/capture/classify-edge.test.js +9 -7
- package/dist/capture/classify-edge.test.js.map +1 -1
- package/dist/capture/classify.d.ts +6 -4
- package/dist/capture/classify.js +9 -8
- package/dist/capture/classify.js.map +1 -1
- package/dist/capture/classify.test.js +7 -2
- package/dist/capture/classify.test.js.map +1 -1
- package/dist/capture/diff.d.ts +2 -0
- package/dist/capture/diff.js +15 -0
- package/dist/capture/diff.js.map +1 -1
- package/dist/capture/extract.d.ts +5 -0
- package/dist/capture/extract.js +17 -0
- package/dist/capture/extract.js.map +1 -0
- package/dist/capture/feature-context.d.ts +3 -0
- package/dist/capture/feature-context.js +43 -0
- package/dist/capture/feature-context.js.map +1 -0
- package/dist/capture/feature-context.test.d.ts +1 -0
- package/dist/capture/feature-context.test.js +45 -0
- package/dist/capture/feature-context.test.js.map +1 -0
- package/dist/capture/index.d.ts +59 -0
- package/dist/capture/index.js +101 -21
- package/dist/capture/index.js.map +1 -1
- package/dist/capture/manifest.d.ts +26 -0
- package/dist/capture/manifest.js +98 -0
- package/dist/capture/manifest.js.map +1 -0
- package/dist/capture/manifest.test.d.ts +1 -0
- package/dist/capture/manifest.test.js +40 -0
- package/dist/capture/manifest.test.js.map +1 -0
- package/dist/capture/outbound.d.ts +10 -0
- package/dist/capture/outbound.js +139 -0
- package/dist/capture/outbound.js.map +1 -0
- package/dist/capture/outbound.test.d.ts +1 -0
- package/dist/capture/outbound.test.js +89 -0
- package/dist/capture/outbound.test.js.map +1 -0
- package/dist/capture/python.d.ts +5 -0
- package/dist/capture/python.js +127 -0
- package/dist/capture/python.js.map +1 -0
- package/dist/capture/python.test.d.ts +1 -0
- package/dist/capture/python.test.js +99 -0
- package/dist/capture/python.test.js.map +1 -0
- package/dist/capture/replay.test.d.ts +1 -0
- package/dist/capture/replay.test.js +101 -0
- package/dist/capture/replay.test.js.map +1 -0
- package/dist/capture/surface.d.ts +23 -0
- package/dist/capture/surface.js +83 -0
- package/dist/capture/surface.js.map +1 -0
- package/dist/capture/surface.test.d.ts +1 -0
- package/dist/capture/surface.test.js +37 -0
- package/dist/capture/surface.test.js.map +1 -0
- package/dist/connect.d.ts +1 -1
- package/dist/connect.js +23 -12
- package/dist/connect.js.map +1 -1
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -1
- package/dist/init.js +3 -1
- package/dist/init.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -1
- package/dist/mcp/server.js +26 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/scan.d.ts +64 -0
- package/dist/scan.js +176 -0
- package/dist/scan.js.map +1 -0
- package/dist/scan.test.d.ts +1 -0
- package/dist/scan.test.js +258 -0
- package/dist/scan.test.js.map +1 -0
- package/dist/statusline.d.ts +6 -0
- package/dist/statusline.js +108 -0
- package/dist/statusline.js.map +1 -0
- package/package.json +8 -4
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.test.js","sourceRoot":"","sources":["../../src/capture/manifest.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface OutboundRef {
|
|
2
|
+
via: "fetch" | "axios" | "grpc-client" | "import" | "python-http";
|
|
3
|
+
ref: string;
|
|
4
|
+
surface?: string;
|
|
5
|
+
hint?: string;
|
|
6
|
+
}
|
|
7
|
+
/** Build a canonical `http:METHOD /path` from a URL literal, or null if it's too dynamic/generic to name. */
|
|
8
|
+
export declare function httpSurface(rawUrl: string, method?: string): string | null;
|
|
9
|
+
/** Extract every outbound-call candidate a file makes. `[]` for files with no detectable client calls. */
|
|
10
|
+
export declare function extractOutbound(path: string, content?: string): OutboundRef[];
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Outbound-call detection — the *consumer* side of the graph. Where `surface.ts` finds what a file
|
|
3
|
+
* SERVES (produces), this finds what it CALLS (consumes): `fetch`/`axios` requests, gRPC client stubs,
|
|
4
|
+
* and generated-client imports. It is deliberately best-effort and never authoritative on its own —
|
|
5
|
+
* a detected call is a *candidate*. `lockstep scan` resolves each candidate against the project's
|
|
6
|
+
* produced-surface catalog (closed-world matching): a candidate that matches a sibling repo's produce
|
|
7
|
+
* is a real intra-org dependency; one that doesn't is treated as external. The human always approves.
|
|
8
|
+
*
|
|
9
|
+
* Kept in its own module (not folded into surface.ts) so the served-side extractor — which is vendored
|
|
10
|
+
* to the GitHub Action's `surface.mjs` — stays untouched. Pure + testable.
|
|
11
|
+
*/
|
|
12
|
+
import { normalizePath } from "./surface.js";
|
|
13
|
+
/** Build a canonical `http:METHOD /path` from a URL literal, or null if it's too dynamic/generic to name. */
|
|
14
|
+
export function httpSurface(rawUrl, method) {
|
|
15
|
+
let url = rawUrl.trim();
|
|
16
|
+
url = url.replace(/^[a-z][a-z0-9+.-]*:\/\/[^/]+/i, ""); // drop scheme://host (keep the path)
|
|
17
|
+
url = url.replace(/\$\{[^}]*\}/g, ":param"); // template-literal interpolation → path param
|
|
18
|
+
url = url.split("?")[0].split("#")[0];
|
|
19
|
+
if (!url || url === ":param")
|
|
20
|
+
return null; // wholly dynamic — can't name it
|
|
21
|
+
const path = normalizePath(url);
|
|
22
|
+
if (path === "/")
|
|
23
|
+
return null; // too generic to be a useful surface
|
|
24
|
+
return `http:${(method ?? "GET").toUpperCase()} ${path}`;
|
|
25
|
+
}
|
|
26
|
+
/** Strip a leading org scope and common client suffixes → a bare service hint (e.g. `@acme/billing-client` → `billing`). */
|
|
27
|
+
function pkgHint(specifier) {
|
|
28
|
+
const base = specifier.replace(/^@[^/]+\//, "").split("/")[0];
|
|
29
|
+
return base.replace(/[-_.](client|sdk|api|grpc|proto|rpc)$/i, "").toLowerCase();
|
|
30
|
+
}
|
|
31
|
+
const FETCH = /\bfetch\s*\(\s*(['"`])([^'"`]*?)\1\s*(?:,\s*\{([\s\S]*?)\})?/g;
|
|
32
|
+
const AXIOS_VERB = /\baxios\s*\.\s*(get|post|put|patch|delete|head|options)\s*\(\s*(['"`])([^'"`]*?)\2/gi;
|
|
33
|
+
const AXIOS_CONFIG = /\baxios(?:\s*\.\s*request)?\s*\(\s*\{([\s\S]*?)\}\s*\)/g;
|
|
34
|
+
const AXIOS_BARE = /\baxios\s*\(\s*(['"`])([^'"`]*?)\1/g;
|
|
35
|
+
const GRPC_CLIENT = /new\s+(\w+?)Client\s*\(/g;
|
|
36
|
+
const IMPORT = /(?:import\b[^'"]*?from\s*|\brequire\s*\(\s*)(['"])([^'"]+)\1/g;
|
|
37
|
+
// Wrapped-client resolution (IMPROVEMENTS #1): same-file `const` bindings only — an imported client
|
|
38
|
+
// or base URL is never followed. First binding wins; `let`/reassignment is deliberately ignored.
|
|
39
|
+
const AXIOS_CREATE = /\bconst\s+(\w+)\s*=\s*axios\.create\s*\(\s*\{([\s\S]*?)\}\s*\)/g;
|
|
40
|
+
const CONST_STR = /\bconst\s+(\w+)\s*=\s*(['"`])([^'"`\n]*)\2/g;
|
|
41
|
+
const FETCH_CONCAT = /\bfetch\s*\(\s*(\w+)\s*\+\s*(['"`])([^'"`]*?)\2\s*(?:,\s*\{([\s\S]*?)\})?/g;
|
|
42
|
+
// A verb call on an unresolved-but-client-looking instance (`api.get("/x")` where `api` is imported).
|
|
43
|
+
const CLIENTISH_VERB = /\b(\w*(?:api|client|http|sdk)\w*)\s*\.\s*(get|post|put|patch|delete|head|options)\s*\(\s*(['"`])([^'"`]*?)\3/gi;
|
|
44
|
+
const HTTP_VERBS_RE = /^(get|post|put|patch|delete|head|options)$/i;
|
|
45
|
+
const methodOf = (opts) => opts?.match(/method\s*:\s*['"]([a-zA-Z]+)['"]/)?.[1];
|
|
46
|
+
const urlOf = (config) => config.match(/\burl\s*:\s*['"`]([^'"`]+)['"`]/)?.[1];
|
|
47
|
+
const joinUrl = (base, p) => base.replace(/\/+$/, "") + (p.startsWith("/") ? p : "/" + p);
|
|
48
|
+
/** Extract every outbound-call candidate a file makes. `[]` for files with no detectable client calls. */
|
|
49
|
+
export function extractOutbound(path, content) {
|
|
50
|
+
if (!content || !/\.(ts|tsx|js|jsx|mjs|cjs)$/i.test(path))
|
|
51
|
+
return [];
|
|
52
|
+
const out = [];
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
const add = (r) => {
|
|
55
|
+
const key = `${r.via} ${r.surface ?? r.hint ?? r.ref}`;
|
|
56
|
+
if (!seen.has(key)) {
|
|
57
|
+
seen.add(key);
|
|
58
|
+
out.push(r);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
// Pre-pass: same-file string consts + axios.create instances (from the ORIGINAL content), then
|
|
62
|
+
// substitute known `${NAME}` interpolations so the literal matchers below see resolved paths.
|
|
63
|
+
const constStr = new Map();
|
|
64
|
+
for (const m of content.matchAll(CONST_STR)) {
|
|
65
|
+
if (!m[3].includes("${") && !constStr.has(m[1]))
|
|
66
|
+
constStr.set(m[1], m[3]);
|
|
67
|
+
}
|
|
68
|
+
const clientBase = new Map();
|
|
69
|
+
for (const m of content.matchAll(AXIOS_CREATE)) {
|
|
70
|
+
const base = m[2].match(/\bbaseURL\s*:\s*['"`]([^'"`]+)['"`]/)?.[1];
|
|
71
|
+
if (base && !clientBase.has(m[1]))
|
|
72
|
+
clientBase.set(m[1], base);
|
|
73
|
+
}
|
|
74
|
+
let src = content;
|
|
75
|
+
for (const [name, val] of constStr)
|
|
76
|
+
src = src.split("${" + name + "}").join(val);
|
|
77
|
+
for (const m of src.matchAll(FETCH)) {
|
|
78
|
+
add({ via: "fetch", ref: m[2], surface: httpSurface(m[2], methodOf(m[3])) ?? undefined });
|
|
79
|
+
}
|
|
80
|
+
// `fetch(BASE + "/x")` — string-concat form the literal matcher can't see.
|
|
81
|
+
for (const m of src.matchAll(FETCH_CONCAT)) {
|
|
82
|
+
const base = constStr.get(m[1]);
|
|
83
|
+
if (base)
|
|
84
|
+
add({ via: "fetch", ref: `${m[1]} + ${m[3]}`, surface: httpSurface(joinUrl(base, m[3]), methodOf(m[4])) ?? undefined });
|
|
85
|
+
}
|
|
86
|
+
for (const m of src.matchAll(AXIOS_VERB)) {
|
|
87
|
+
add({ via: "axios", ref: m[3], surface: httpSurface(m[3], m[1]) ?? undefined });
|
|
88
|
+
}
|
|
89
|
+
for (const m of src.matchAll(AXIOS_CONFIG)) {
|
|
90
|
+
const url = urlOf(m[1]);
|
|
91
|
+
if (url)
|
|
92
|
+
add({ via: "axios", ref: url, surface: httpSurface(url, methodOf(m[1])) ?? undefined });
|
|
93
|
+
}
|
|
94
|
+
for (const m of src.matchAll(AXIOS_BARE)) {
|
|
95
|
+
add({ via: "axios", ref: m[2], surface: httpSurface(m[2]) ?? undefined });
|
|
96
|
+
}
|
|
97
|
+
// Resolved axios.create instances: verb calls compose baseURL + path.
|
|
98
|
+
if (clientBase.size > 0) {
|
|
99
|
+
const instRe = new RegExp(`\\b(${[...clientBase.keys()].join("|")})\\s*\\.\\s*(get|post|put|patch|delete|head|options)\\s*\\(\\s*(['"\`])([^'"\`]*?)\\3`, "gi");
|
|
100
|
+
for (const m of src.matchAll(instRe)) {
|
|
101
|
+
const base = clientBase.get(m[1]);
|
|
102
|
+
if (base)
|
|
103
|
+
add({ via: "axios", ref: `${m[1]}.${m[2]} ${m[4]}`, surface: httpSurface(joinUrl(base, m[4]), m[2]) ?? undefined });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Unresolvable client-looking instances: literal /-paths still make exact candidates; anything
|
|
107
|
+
// else degrades to a service hint so classify() routes it to the review tier.
|
|
108
|
+
for (const m of src.matchAll(CLIENTISH_VERB)) {
|
|
109
|
+
const name = m[1];
|
|
110
|
+
if (name.toLowerCase() === "axios" || clientBase.has(name) || HTTP_VERBS_RE.test(name))
|
|
111
|
+
continue;
|
|
112
|
+
if (m[4].startsWith("/")) {
|
|
113
|
+
add({ via: "axios", ref: `${name}.${m[2]} ${m[4]}`, surface: httpSurface(m[4], m[2]) ?? undefined });
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
const hint = name.replace(/[-_.]?(api|client|http|sdk)$/i, "").toLowerCase();
|
|
117
|
+
if (hint)
|
|
118
|
+
add({ via: "axios", ref: `${name}.${m[2]}(…)`, hint });
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
for (const m of src.matchAll(GRPC_CLIENT)) {
|
|
122
|
+
const svc = m[1].replace(/Service$/, "");
|
|
123
|
+
if (svc)
|
|
124
|
+
add({ via: "grpc-client", ref: `${m[1]}Client`, hint: svc.toLowerCase() });
|
|
125
|
+
}
|
|
126
|
+
for (const m of src.matchAll(IMPORT)) {
|
|
127
|
+
const spec = m[2];
|
|
128
|
+
// Only client-ish or org-scoped specifiers are worth a dependency hint; relative/plain deps are noise.
|
|
129
|
+
if (/^\.|^node:/.test(spec))
|
|
130
|
+
continue;
|
|
131
|
+
if (!(spec.startsWith("@") || /[-_.](client|sdk|grpc|proto|rpc)$|grpc|proto/i.test(spec)))
|
|
132
|
+
continue;
|
|
133
|
+
const hint = pkgHint(spec);
|
|
134
|
+
if (hint)
|
|
135
|
+
add({ via: "import", ref: spec, hint });
|
|
136
|
+
}
|
|
137
|
+
return out;
|
|
138
|
+
}
|
|
139
|
+
//# sourceMappingURL=outbound.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.js","sourceRoot":"","sources":["../../src/capture/outbound.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { extractOutbound } from "./outbound.js";
|
|
4
|
+
const surfaces = (path, content) => extractOutbound(path, content)
|
|
5
|
+
.map((o) => o.surface)
|
|
6
|
+
.filter((s) => Boolean(s))
|
|
7
|
+
.sort();
|
|
8
|
+
const hints = (path, content) => extractOutbound(path, content)
|
|
9
|
+
.map((o) => o.hint)
|
|
10
|
+
.filter((h) => Boolean(h))
|
|
11
|
+
.sort();
|
|
12
|
+
test("detects fetch() calls as canonical http surfaces (default GET)", () => {
|
|
13
|
+
assert.deepEqual(surfaces("src/client.ts", `const r = await fetch("/orders/123")`), ["http:GET /orders/123"]);
|
|
14
|
+
});
|
|
15
|
+
test("reads the method from a fetch options object", () => {
|
|
16
|
+
const s = surfaces("src/client.ts", `fetch("/auth/session", { method: "POST", body })`);
|
|
17
|
+
assert.deepEqual(s, ["http:POST /auth/session"]);
|
|
18
|
+
});
|
|
19
|
+
test("strips scheme+host and querystring from absolute URLs", () => {
|
|
20
|
+
assert.deepEqual(surfaces("src/pay.ts", `fetch("https://api.stripe.com/v1/charges?x=1")`), ["http:GET /v1/charges"]);
|
|
21
|
+
});
|
|
22
|
+
test("normalizes template-literal interpolation to a path param", () => {
|
|
23
|
+
const s = surfaces("src/client.ts", "axios.get(`/orders/${id}/items`)");
|
|
24
|
+
assert.deepEqual(s, ["http:GET /orders/:param/items"]);
|
|
25
|
+
});
|
|
26
|
+
test("detects axios verb, bare, and config-object calls", () => {
|
|
27
|
+
const content = `
|
|
28
|
+
axios.post("/a/b", body);
|
|
29
|
+
axios("/c/d");
|
|
30
|
+
axios({ url: "/e/f", method: "put" });
|
|
31
|
+
`;
|
|
32
|
+
assert.deepEqual(surfaces("src/api.ts", content), ["http:POST /a/b", "http:GET /c/d", "http:PUT /e/f"].sort());
|
|
33
|
+
});
|
|
34
|
+
test("drops wholly-dynamic and root-only URLs (no phantom surfaces)", () => {
|
|
35
|
+
assert.deepEqual(surfaces("src/client.ts", "fetch(`${base}`); fetch(url); fetch(`/`)"), []);
|
|
36
|
+
});
|
|
37
|
+
test("gRPC client stub → service hint", () => {
|
|
38
|
+
assert.deepEqual(hints("src/rpc.ts", "const c = new BillingServiceClient(addr)"), ["billing"]);
|
|
39
|
+
});
|
|
40
|
+
test("org-scoped / generated-client imports → package hints; noise imports ignored", () => {
|
|
41
|
+
const content = `
|
|
42
|
+
import { Client } from "@acme/inventory-client";
|
|
43
|
+
import fs from "node:fs";
|
|
44
|
+
import { x } from "./local";
|
|
45
|
+
import z from "lodash";
|
|
46
|
+
`;
|
|
47
|
+
assert.deepEqual(hints("src/deps.ts", content), ["inventory"]);
|
|
48
|
+
});
|
|
49
|
+
test("dedupes repeated calls and ignores non-source files", () => {
|
|
50
|
+
assert.deepEqual(surfaces("src/a.ts", `fetch("/x/y"); fetch("/x/y")`), ["http:GET /x/y"]);
|
|
51
|
+
assert.deepEqual(extractOutbound("README.md", `fetch("/x/y")`), []);
|
|
52
|
+
});
|
|
53
|
+
test("wrapped clients: axios.create baseURL instances resolve base + path (same-file)", () => {
|
|
54
|
+
const content = `
|
|
55
|
+
const api = axios.create({ baseURL: "https://inventory.internal/v1" });
|
|
56
|
+
api.get("/items/42");
|
|
57
|
+
api.post(\`/items/\${sku}/reserve\`);
|
|
58
|
+
`;
|
|
59
|
+
assert.deepEqual(surfaces("src/client.ts", content), [
|
|
60
|
+
"http:GET /v1/items/42",
|
|
61
|
+
"http:POST /v1/items/:param/reserve",
|
|
62
|
+
]);
|
|
63
|
+
});
|
|
64
|
+
test("wrapped clients: fetch with same-file const base — concat and template forms", () => {
|
|
65
|
+
const content = `
|
|
66
|
+
const BASE = "https://orders.internal";
|
|
67
|
+
fetch(BASE + "/orders/7");
|
|
68
|
+
fetch(\`\${BASE}/orders/\${id}/items\`, { method: "POST" });
|
|
69
|
+
`;
|
|
70
|
+
assert.deepEqual(surfaces("src/client.ts", content), ["http:GET /orders/7", "http:POST /orders/:param/items"]);
|
|
71
|
+
});
|
|
72
|
+
test("wrapped clients: unresolved client-ish instance with a literal /-path still yields an exact surface", () => {
|
|
73
|
+
const content = `
|
|
74
|
+
import { api } from "./api";
|
|
75
|
+
api.put("/profiles/me");
|
|
76
|
+
`;
|
|
77
|
+
assert.deepEqual(surfaces("src/settings.ts", content), ["http:PUT /profiles/me"]);
|
|
78
|
+
});
|
|
79
|
+
test("wrapped clients: unresolved client-ish instance without a literal path degrades to a service hint", () => {
|
|
80
|
+
const content = `
|
|
81
|
+
import { billingClient } from "@acme/billing-client";
|
|
82
|
+
billingClient.post(endpoint);
|
|
83
|
+
`;
|
|
84
|
+
assert.deepEqual(hints("src/pay.ts", content), ["billing"]);
|
|
85
|
+
});
|
|
86
|
+
test("wrapped clients: non-client-ish instance verbs are ignored (no phantom consumes)", () => {
|
|
87
|
+
assert.deepEqual(surfaces("src/map.ts", `cache.get("/looks/like/a/path")`), []);
|
|
88
|
+
});
|
|
89
|
+
//# sourceMappingURL=outbound.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound.test.js","sourceRoot":"","sources":["../../src/capture/outbound.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAEhD,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,OAAe,EAAY,EAAE,CAC3D,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC;KAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC;KACrB,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;KACtC,IAAI,EAAE,CAAC;AAEZ,MAAM,KAAK,GAAG,CAAC,IAAY,EAAE,OAAe,EAAY,EAAE,CACxD,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC;KAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;KAClB,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;KACtC,IAAI,EAAE,CAAC;AAEZ,IAAI,CAAC,gEAAgE,EAAE,GAAG,EAAE;IAC1E,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,sCAAsC,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAC;AAChH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8CAA8C,EAAE,GAAG,EAAE;IACxD,MAAM,CAAC,GAAG,QAAQ,CAAC,eAAe,EAAE,kDAAkD,CAAC,CAAC;IACxF,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,yBAAyB,CAAC,CAAC,CAAC;AACnD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,uDAAuD,EAAE,GAAG,EAAE;IACjE,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,gDAAgD,CAAC,EAAE,CAAC,sBAAsB,CAAC,CAAC,CAAC;AACvH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2DAA2D,EAAE,GAAG,EAAE;IACrE,MAAM,CAAC,GAAG,QAAQ,CAAC,eAAe,EAAE,kCAAkC,CAAC,CAAC;IACxE,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,+BAA+B,CAAC,CAAC,CAAC;AACzD,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mDAAmD,EAAE,GAAG,EAAE;IAC7D,MAAM,OAAO,GAAG;;;;GAIf,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC,gBAAgB,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;AACjH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,+DAA+D,EAAE,GAAG,EAAE;IACzE,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,0CAA0C,CAAC,EAAE,EAAE,CAAC,CAAC;AAC9F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iCAAiC,EAAE,GAAG,EAAE;IAC3C,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY,EAAE,0CAA0C,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;AACjG,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8EAA8E,EAAE,GAAG,EAAE;IACxF,MAAM,OAAO,GAAG;;;;;GAKf,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,EAAE,OAAO,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC;AACjE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qDAAqD,EAAE,GAAG,EAAE;IAC/D,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,UAAU,EAAE,8BAA8B,CAAC,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC;IAC1F,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,WAAW,EAAE,eAAe,CAAC,EAAE,EAAE,CAAC,CAAC;AACtE,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,iFAAiF,EAAE,GAAG,EAAE;IAC3F,MAAM,OAAO,GAAG;;;;GAIf,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,EAAE;QACnD,uBAAuB;QACvB,oCAAoC;KACrC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,8EAA8E,EAAE,GAAG,EAAE;IACxF,MAAM,OAAO,GAAG;;;;GAIf,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC,EAAE,CAAC,oBAAoB,EAAE,gCAAgC,CAAC,CAAC,CAAC;AACjH,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,qGAAqG,EAAE,GAAG,EAAE;IAC/G,MAAM,OAAO,GAAG;;;GAGf,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC,EAAE,CAAC,uBAAuB,CAAC,CAAC,CAAC;AACpF,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,mGAAmG,EAAE,GAAG,EAAE;IAC7G,MAAM,OAAO,GAAG;;;GAGf,CAAC;IACF,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,EAAE,CAAC,SAAS,CAAC,CAAC,CAAC;AAC9D,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,kFAAkF,EAAE,GAAG,EAAE;IAC5F,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,YAAY,EAAE,iCAAiC,CAAC,EAAE,EAAE,CAAC,CAAC;AAClF,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type OutboundRef } from "./outbound.js";
|
|
2
|
+
/** Extract outbound HTTP candidates from a Python file (`requests`/`httpx`/`aiohttp`). */
|
|
3
|
+
export declare function extractPythonOutbound(path: string, content?: string): OutboundRef[];
|
|
4
|
+
/** Extract served HTTP surfaces from a Python file (FastAPI/Flask). Same-file prefixes only. */
|
|
5
|
+
export declare function extractPythonSurfaces(path: string, content?: string): string[];
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Python extractors — the first non-JS/TS language (IMPROVEMENTS #1). Same philosophy as
|
|
3
|
+
* `surface.ts`/`outbound.ts`: regex-based, best-effort, candidates not authority — `lockstep scan`
|
|
4
|
+
* resolves them against the org catalog and a human approves.
|
|
5
|
+
*
|
|
6
|
+
* BOUNDARY (deliberate): all resolution is SAME-FILE only. A router/client defined in another
|
|
7
|
+
* module is not followed; cross-file mounting/imports stay invisible. The PR-check gate
|
|
8
|
+
* (`actions/pr-check/surface.mjs`, vendored from surface.ts) remains JS/TS-only — Python contract
|
|
9
|
+
* surfaces do not trip the Tier-2 gate until that copy is extended.
|
|
10
|
+
*/
|
|
11
|
+
import { normalizePath } from "./surface.js";
|
|
12
|
+
import { httpSurface } from "./outbound.js";
|
|
13
|
+
const VERBS = "get|post|put|patch|delete|head|options";
|
|
14
|
+
/** Outbound URL literal: f-string `{expr}` is arbitrary interpolation → `:param` (matching is param-insensitive anyway). */
|
|
15
|
+
const pyPath = (raw) => raw.replace(/\{[^}]*\}/g, ":param").replace(/<(?:\w+\s*:\s*)?(\w+)>/g, ":$1");
|
|
16
|
+
/** Served route template: keep the param NAME — `{card_id}` → `:card_id` (via normalizePath), `<int:id>` → `:id`. */
|
|
17
|
+
const httpId = (method, path) => `http:${method.toUpperCase()} ${normalizePath(path.replace(/<(?:\w+\s*:\s*)?(\w+)>/g, ":$1"))}`;
|
|
18
|
+
/* ── outbound (consumes) ── */
|
|
19
|
+
const REQUESTS_VERB = new RegExp(`\\brequests\\.(${VERBS})\\s*\\(\\s*[fF]?(['"])([^'"\\n]*)\\2`, "g");
|
|
20
|
+
const REQUESTS_REQUEST = /\brequests\.request\s*\(\s*['"](\w+)['"]\s*,\s*[fF]?(['"])([^'"\n]*)\2/g;
|
|
21
|
+
const HTTPX_VERB = new RegExp(`\\bhttpx\\.(${VERBS})\\s*\\(\\s*[fF]?(['"])([^'"\\n]*)\\2`, "g");
|
|
22
|
+
const HTTPX_CLIENT = /\b(\w+)\s*=\s*httpx\.(?:Async)?Client\s*\(([^)]*)\)/g;
|
|
23
|
+
const AIOHTTP_WITH = /\basync\s+with\s+aiohttp\.ClientSession\s*\([^)]*\)\s*as\s+(\w+)/g;
|
|
24
|
+
const AIOHTTP_ASSIGN = /\b(\w+)\s*=\s*aiohttp\.ClientSession\s*\(/g;
|
|
25
|
+
const BASE_URL = /\bbase_url\s*=\s*[fF]?['"]([^'"]+)['"]/;
|
|
26
|
+
const joinUrl = (base, p) => base.replace(/\/+$/, "") + (p.startsWith("/") ? p : "/" + p);
|
|
27
|
+
/** Extract outbound HTTP candidates from a Python file (`requests`/`httpx`/`aiohttp`). */
|
|
28
|
+
export function extractPythonOutbound(path, content) {
|
|
29
|
+
if (!content || !/\.py$/i.test(path))
|
|
30
|
+
return [];
|
|
31
|
+
const out = [];
|
|
32
|
+
const seen = new Set();
|
|
33
|
+
const add = (ref, surface) => {
|
|
34
|
+
if (!surface)
|
|
35
|
+
return;
|
|
36
|
+
const key = `python-http ${surface}`;
|
|
37
|
+
if (!seen.has(key)) {
|
|
38
|
+
seen.add(key);
|
|
39
|
+
out.push({ via: "python-http", ref, surface });
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
for (const m of content.matchAll(REQUESTS_VERB))
|
|
43
|
+
add(m[3], httpSurface(pyPath(m[3]), m[1]));
|
|
44
|
+
for (const m of content.matchAll(REQUESTS_REQUEST))
|
|
45
|
+
add(m[3], httpSurface(pyPath(m[3]), m[1]));
|
|
46
|
+
for (const m of content.matchAll(HTTPX_VERB))
|
|
47
|
+
add(m[3], httpSurface(pyPath(m[3]), m[1]));
|
|
48
|
+
// Same-file client instances: httpx.Client(base_url=…) verbs resolve base+path; aiohttp
|
|
49
|
+
// sessions (no base) resolve the literal path. Conservative: only names bound in THIS file.
|
|
50
|
+
const clientBase = new Map(); // name → base_url ("" when none)
|
|
51
|
+
for (const m of content.matchAll(HTTPX_CLIENT)) {
|
|
52
|
+
if (!clientBase.has(m[1]))
|
|
53
|
+
clientBase.set(m[1], m[2].match(BASE_URL)?.[1] ?? "");
|
|
54
|
+
}
|
|
55
|
+
for (const m of content.matchAll(AIOHTTP_WITH))
|
|
56
|
+
if (!clientBase.has(m[1]))
|
|
57
|
+
clientBase.set(m[1], "");
|
|
58
|
+
for (const m of content.matchAll(AIOHTTP_ASSIGN))
|
|
59
|
+
if (!clientBase.has(m[1]))
|
|
60
|
+
clientBase.set(m[1], "");
|
|
61
|
+
if (clientBase.size > 0) {
|
|
62
|
+
const names = [...clientBase.keys()].join("|");
|
|
63
|
+
const instRe = new RegExp(`\\b(${names})\\.(${VERBS})\\s*\\(\\s*[fF]?(['"])([^'"\\n]*)\\3`, "g");
|
|
64
|
+
for (const m of content.matchAll(instRe)) {
|
|
65
|
+
const base = clientBase.get(m[1]);
|
|
66
|
+
const raw = base ? joinUrl(base, pyPath(m[4])) : pyPath(m[4]);
|
|
67
|
+
add(`${m[1]}.${m[2]} ${m[4]}`, httpSurface(raw, m[2]));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return out;
|
|
71
|
+
}
|
|
72
|
+
/* ── produces (serves) ── */
|
|
73
|
+
const FASTAPI_APP = /\b(\w+)\s*=\s*FastAPI\s*\(/g;
|
|
74
|
+
const APIROUTER = /\b(\w+)\s*=\s*APIRouter\s*\(([^)]*)\)/g;
|
|
75
|
+
const INCLUDE_ROUTER = /\.include_router\s*\(\s*(\w+)([^)]*)\)/g;
|
|
76
|
+
const PREFIX = /\bprefix\s*=\s*[fF]?['"]([^'"]+)['"]/;
|
|
77
|
+
const FLASK_APP = /\b(\w+)\s*=\s*Flask\s*\(/g;
|
|
78
|
+
const BLUEPRINT = /\b(\w+)\s*=\s*Blueprint\s*\(([^)]*)\)/g;
|
|
79
|
+
const URL_PREFIX = /\burl_prefix\s*=\s*[fF]?['"]([^'"]+)['"]/;
|
|
80
|
+
const FLASK_ROUTE = /@(\w+)\.route\s*\(\s*[fF]?(['"])([^'"\n]*)\2([^)]*)\)/g;
|
|
81
|
+
const METHODS = /\bmethods\s*=\s*\[([^\]]*)\]/;
|
|
82
|
+
/** Extract served HTTP surfaces from a Python file (FastAPI/Flask). Same-file prefixes only. */
|
|
83
|
+
export function extractPythonSurfaces(path, content) {
|
|
84
|
+
if (!content || !/\.py$/i.test(path))
|
|
85
|
+
return [];
|
|
86
|
+
const out = new Set();
|
|
87
|
+
// Known handler owners: FastAPI apps + routers (with same-file prefix composition). "app"/"router"
|
|
88
|
+
// are accepted as fallbacks — the overwhelmingly common names when the binding is off-screen.
|
|
89
|
+
const prefixes = new Map();
|
|
90
|
+
for (const m of content.matchAll(FASTAPI_APP))
|
|
91
|
+
prefixes.set(m[1], "");
|
|
92
|
+
for (const m of content.matchAll(APIROUTER))
|
|
93
|
+
prefixes.set(m[1], m[2].match(PREFIX)?.[1] ?? "");
|
|
94
|
+
for (const m of content.matchAll(INCLUDE_ROUTER)) {
|
|
95
|
+
const extra = m[2].match(PREFIX)?.[1];
|
|
96
|
+
if (extra && prefixes.has(m[1]))
|
|
97
|
+
prefixes.set(m[1], extra + (prefixes.get(m[1]) ?? ""));
|
|
98
|
+
}
|
|
99
|
+
const decoRe = new RegExp(`@(\\w+)\\.(${VERBS})\\s*\\(\\s*[fF]?(['"])([^'"\\n]*)\\3`, "g");
|
|
100
|
+
for (const m of content.matchAll(decoRe)) {
|
|
101
|
+
const name = m[1];
|
|
102
|
+
if (!prefixes.has(name) && name !== "app" && name !== "router")
|
|
103
|
+
continue;
|
|
104
|
+
const prefix = prefixes.get(name) ?? "";
|
|
105
|
+
out.add(httpId(m[2], prefix + m[4]));
|
|
106
|
+
}
|
|
107
|
+
// Flask: @app.route / @blueprint.route with methods=[…] (default GET) + Blueprint url_prefix.
|
|
108
|
+
const flaskPrefixes = new Map();
|
|
109
|
+
for (const m of content.matchAll(FLASK_APP))
|
|
110
|
+
flaskPrefixes.set(m[1], "");
|
|
111
|
+
for (const m of content.matchAll(BLUEPRINT))
|
|
112
|
+
flaskPrefixes.set(m[1], m[2].match(URL_PREFIX)?.[1] ?? "");
|
|
113
|
+
for (const m of content.matchAll(FLASK_ROUTE)) {
|
|
114
|
+
const name = m[1];
|
|
115
|
+
if (!flaskPrefixes.has(name) && name !== "app" && name !== "bp")
|
|
116
|
+
continue;
|
|
117
|
+
const prefix = flaskPrefixes.get(name) ?? "";
|
|
118
|
+
const methods = m[4].match(METHODS)?.[1];
|
|
119
|
+
const verbs = methods
|
|
120
|
+
? methods.split(",").map((v) => v.trim().replace(/['"]/g, "")).filter(Boolean)
|
|
121
|
+
: ["GET"];
|
|
122
|
+
for (const v of verbs)
|
|
123
|
+
out.add(httpId(v, prefix + m[3]));
|
|
124
|
+
}
|
|
125
|
+
return [...out];
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=python.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.js","sourceRoot":"","sources":["../../src/capture/python.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { test } from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { extractPythonOutbound, extractPythonSurfaces } from "./python.js";
|
|
4
|
+
const surfaces = (content) => extractPythonOutbound("src/client.py", content)
|
|
5
|
+
.map((o) => o.surface)
|
|
6
|
+
.filter((s) => Boolean(s))
|
|
7
|
+
.sort();
|
|
8
|
+
test("python outbound: requests verb calls with literal and f-string URLs", () => {
|
|
9
|
+
const content = `
|
|
10
|
+
import requests
|
|
11
|
+
r = requests.get("https://api.acme.com/inventory/42")
|
|
12
|
+
requests.post(f"/orders/{order_id}/items", json=payload)
|
|
13
|
+
`;
|
|
14
|
+
assert.deepEqual(surfaces(content), ["http:GET /inventory/42", "http:POST /orders/:param/items"]);
|
|
15
|
+
});
|
|
16
|
+
test("python outbound: requests.request with explicit verb", () => {
|
|
17
|
+
assert.deepEqual(surfaces(`requests.request("PUT", "/users/7")`), ["http:PUT /users/7"]);
|
|
18
|
+
});
|
|
19
|
+
test("python outbound: httpx module verbs and base_url client instances", () => {
|
|
20
|
+
const content = `
|
|
21
|
+
import httpx
|
|
22
|
+
httpx.get("/health")
|
|
23
|
+
client = httpx.Client(base_url="https://billing.internal/v1")
|
|
24
|
+
client.post("/charges")
|
|
25
|
+
async_client = httpx.AsyncClient(base_url="/inventory")
|
|
26
|
+
resp = await async_client.get(f"/items/{sku}")
|
|
27
|
+
`;
|
|
28
|
+
assert.deepEqual(surfaces(content), [
|
|
29
|
+
"http:GET /health",
|
|
30
|
+
"http:GET /inventory/items/:param",
|
|
31
|
+
"http:POST /v1/charges",
|
|
32
|
+
]);
|
|
33
|
+
});
|
|
34
|
+
test("python outbound: aiohttp session verbs (no base URL)", () => {
|
|
35
|
+
const content = `
|
|
36
|
+
async with aiohttp.ClientSession() as session:
|
|
37
|
+
await session.get("/payments/status")
|
|
38
|
+
`;
|
|
39
|
+
assert.deepEqual(surfaces(content), ["http:GET /payments/status"]);
|
|
40
|
+
});
|
|
41
|
+
test("python outbound: non-python paths and unknown instances yield nothing", () => {
|
|
42
|
+
assert.deepEqual(extractPythonOutbound("src/client.ts", `requests.get("/x")`), []);
|
|
43
|
+
assert.deepEqual(surfaces(`mystery.get("/x")`), [], "instances not bound in this file are ignored");
|
|
44
|
+
});
|
|
45
|
+
test("python produces: FastAPI app + APIRouter prefix + include_router prefix compose", () => {
|
|
46
|
+
const content = `
|
|
47
|
+
from fastapi import FastAPI, APIRouter
|
|
48
|
+
app = FastAPI()
|
|
49
|
+
router = APIRouter(prefix="/cards")
|
|
50
|
+
|
|
51
|
+
@app.get("/health")
|
|
52
|
+
def health(): ...
|
|
53
|
+
|
|
54
|
+
@router.post("/{card_id}/activate")
|
|
55
|
+
def activate(card_id: int): ...
|
|
56
|
+
|
|
57
|
+
app.include_router(admin_router, prefix="/admin")
|
|
58
|
+
`;
|
|
59
|
+
const out = extractPythonSurfaces("src/main.py", content).sort();
|
|
60
|
+
assert.deepEqual(out, ["http:GET /health", "http:POST /cards/:card_id/activate"]);
|
|
61
|
+
});
|
|
62
|
+
test("python produces: include_router prefix prepends to a same-file router", () => {
|
|
63
|
+
const content = `
|
|
64
|
+
router = APIRouter(prefix="/cards")
|
|
65
|
+
app.include_router(router, prefix="/api")
|
|
66
|
+
|
|
67
|
+
@router.get("/{id}")
|
|
68
|
+
def get_card(id: int): ...
|
|
69
|
+
`;
|
|
70
|
+
assert.deepEqual(extractPythonSurfaces("src/api.py", content), ["http:GET /api/cards/:id"]);
|
|
71
|
+
});
|
|
72
|
+
test("python produces: bare 'router' decorators are accepted (common off-screen binding)", () => {
|
|
73
|
+
assert.deepEqual(extractPythonSurfaces("src/routes.py", `@router.delete("/sessions/{sid}")\ndef d(): ...`), [
|
|
74
|
+
"http:DELETE /sessions/:sid",
|
|
75
|
+
]);
|
|
76
|
+
});
|
|
77
|
+
test("python produces: Flask routes with methods list, Blueprint url_prefix, and <converter:param>", () => {
|
|
78
|
+
const content = `
|
|
79
|
+
from flask import Flask, Blueprint
|
|
80
|
+
app = Flask(__name__)
|
|
81
|
+
bp = Blueprint("billing", __name__, url_prefix="/billing")
|
|
82
|
+
|
|
83
|
+
@app.route("/ping")
|
|
84
|
+
def ping(): ...
|
|
85
|
+
|
|
86
|
+
@bp.route("/invoices/<int:invoice_id>", methods=["GET", "POST"])
|
|
87
|
+
def invoices(invoice_id): ...
|
|
88
|
+
`;
|
|
89
|
+
const out = extractPythonSurfaces("src/app.py", content).sort();
|
|
90
|
+
assert.deepEqual(out, [
|
|
91
|
+
"http:GET /billing/invoices/:invoice_id",
|
|
92
|
+
"http:GET /ping",
|
|
93
|
+
"http:POST /billing/invoices/:invoice_id",
|
|
94
|
+
]);
|
|
95
|
+
});
|
|
96
|
+
test("python produces: decorators on unknown owners yield nothing", () => {
|
|
97
|
+
assert.deepEqual(extractPythonSurfaces("src/x.py", `@celery.get("/not-a-route")\ndef t(): ...`), []);
|
|
98
|
+
});
|
|
99
|
+
//# sourceMappingURL=python.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"python.test.js","sourceRoot":"","sources":["../../src/capture/python.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|