@cr8rcho/alkahest 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.ko.md +208 -0
- package/README.md +208 -0
- package/dist/assets/dashboard.html +647 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +68 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/hook.d.ts +1 -0
- package/dist/commands/hook.js +64 -0
- package/dist/commands/hook.js.map +1 -0
- package/dist/commands/login.d.ts +11 -0
- package/dist/commands/login.js +27 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/mcp.d.ts +6 -0
- package/dist/commands/mcp.js +13 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/publish.d.ts +19 -0
- package/dist/commands/publish.js +27 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/scan.d.ts +11 -0
- package/dist/commands/scan.js +29 -0
- package/dist/commands/scan.js.map +1 -0
- package/dist/commands/update.d.ts +15 -0
- package/dist/commands/update.js +41 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/commands/view.d.ts +6 -0
- package/dist/commands/view.js +21 -0
- package/dist/commands/view.js.map +1 -0
- package/dist/core/adapters/angular.d.ts +2 -0
- package/dist/core/adapters/angular.js +305 -0
- package/dist/core/adapters/angular.js.map +1 -0
- package/dist/core/adapters/astro.d.ts +3 -0
- package/dist/core/adapters/astro.js +140 -0
- package/dist/core/adapters/astro.js.map +1 -0
- package/dist/core/adapters/compose.d.ts +2 -0
- package/dist/core/adapters/compose.js +195 -0
- package/dist/core/adapters/compose.js.map +1 -0
- package/dist/core/adapters/django.d.ts +2 -0
- package/dist/core/adapters/django.js +314 -0
- package/dist/core/adapters/django.js.map +1 -0
- package/dist/core/adapters/expo-router.d.ts +2 -0
- package/dist/core/adapters/expo-router.js +60 -0
- package/dist/core/adapters/expo-router.js.map +1 -0
- package/dist/core/adapters/flask.d.ts +2 -0
- package/dist/core/adapters/flask.js +249 -0
- package/dist/core/adapters/flask.js.map +1 -0
- package/dist/core/adapters/flutter.d.ts +2 -0
- package/dist/core/adapters/flutter.js +232 -0
- package/dist/core/adapters/flutter.js.map +1 -0
- package/dist/core/adapters/index.d.ts +19 -0
- package/dist/core/adapters/index.js +59 -0
- package/dist/core/adapters/index.js.map +1 -0
- package/dist/core/adapters/next-app.d.ts +2 -0
- package/dist/core/adapters/next-app.js +62 -0
- package/dist/core/adapters/next-app.js.map +1 -0
- package/dist/core/adapters/next-pages.d.ts +2 -0
- package/dist/core/adapters/next-pages.js +70 -0
- package/dist/core/adapters/next-pages.js.map +1 -0
- package/dist/core/adapters/nuxt.d.ts +2 -0
- package/dist/core/adapters/nuxt.js +59 -0
- package/dist/core/adapters/nuxt.js.map +1 -0
- package/dist/core/adapters/rails.d.ts +2 -0
- package/dist/core/adapters/rails.js +275 -0
- package/dist/core/adapters/rails.js.map +1 -0
- package/dist/core/adapters/react-jsx.d.ts +68 -0
- package/dist/core/adapters/react-jsx.js +355 -0
- package/dist/core/adapters/react-jsx.js.map +1 -0
- package/dist/core/adapters/react-navigation.d.ts +2 -0
- package/dist/core/adapters/react-navigation.js +153 -0
- package/dist/core/adapters/react-navigation.js.map +1 -0
- package/dist/core/adapters/react-router.d.ts +2 -0
- package/dist/core/adapters/react-router.js +249 -0
- package/dist/core/adapters/react-router.js.map +1 -0
- package/dist/core/adapters/remix.d.ts +2 -0
- package/dist/core/adapters/remix.js +109 -0
- package/dist/core/adapters/remix.js.map +1 -0
- package/dist/core/adapters/static-html.d.ts +2 -0
- package/dist/core/adapters/static-html.js +157 -0
- package/dist/core/adapters/static-html.js.map +1 -0
- package/dist/core/adapters/sveltekit.d.ts +2 -0
- package/dist/core/adapters/sveltekit.js +151 -0
- package/dist/core/adapters/sveltekit.js.map +1 -0
- package/dist/core/adapters/swiftui.d.ts +2 -0
- package/dist/core/adapters/swiftui.js +216 -0
- package/dist/core/adapters/swiftui.js.map +1 -0
- package/dist/core/adapters/types.d.ts +65 -0
- package/dist/core/adapters/types.js +2 -0
- package/dist/core/adapters/types.js.map +1 -0
- package/dist/core/adapters/uikit.d.ts +2 -0
- package/dist/core/adapters/uikit.js +178 -0
- package/dist/core/adapters/uikit.js.map +1 -0
- package/dist/core/adapters/vue-router.d.ts +2 -0
- package/dist/core/adapters/vue-router.js +224 -0
- package/dist/core/adapters/vue-router.js.map +1 -0
- package/dist/core/adapters/vue-sfc.d.ts +28 -0
- package/dist/core/adapters/vue-sfc.js +132 -0
- package/dist/core/adapters/vue-sfc.js.map +1 -0
- package/dist/core/credentials.d.ts +24 -0
- package/dist/core/credentials.js +30 -0
- package/dist/core/credentials.js.map +1 -0
- package/dist/core/dashboard.d.ts +9 -0
- package/dist/core/dashboard.js +20 -0
- package/dist/core/dashboard.js.map +1 -0
- package/dist/core/emit.d.ts +7 -0
- package/dist/core/emit.js +23 -0
- package/dist/core/emit.js.map +1 -0
- package/dist/core/hash.d.ts +2 -0
- package/dist/core/hash.js +6 -0
- package/dist/core/hash.js.map +1 -0
- package/dist/core/pipeline.d.ts +25 -0
- package/dist/core/pipeline.js +122 -0
- package/dist/core/pipeline.js.map +1 -0
- package/dist/core/publish.d.ts +31 -0
- package/dist/core/publish.js +87 -0
- package/dist/core/publish.js.map +1 -0
- package/dist/core/resolve.d.ts +42 -0
- package/dist/core/resolve.js +128 -0
- package/dist/core/resolve.js.map +1 -0
- package/dist/core/serve.d.ts +5 -0
- package/dist/core/serve.js +52 -0
- package/dist/core/serve.js.map +1 -0
- package/dist/core/types.d.ts +117 -0
- package/dist/core/types.js +9 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/version.d.ts +23 -0
- package/dist/core/version.js +102 -0
- package/dist/core/version.js.map +1 -0
- package/dist/mcp/server.d.ts +7 -0
- package/dist/mcp/server.js +224 -0
- package/dist/mcp/server.js.map +1 -0
- package/package.json +57 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { readdirSync, readFileSync, existsSync, statSync } from "node:fs";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* SvelteKit adapter (file-based). `src/routes/**/+page.svelte` → routes, mirroring
|
|
5
|
+
* SvelteKit's filesystem router. Screen id = route ("/blog/[slug]"); route groups `(x)`
|
|
6
|
+
* are stripped, `[id]`/`[...rest]` kept. Entry = "/".
|
|
7
|
+
*
|
|
8
|
+
* A `.svelte` file can't reuse the JSX or Vue parsers — it's `<script>` + Svelte markup —
|
|
9
|
+
* so parsing is a zero-dependency line/regex scan in the SwiftUI/Vue-SFC style.
|
|
10
|
+
*
|
|
11
|
+
* Mapping:
|
|
12
|
+
* - nav (markup): <a href="/x"> ; (script): goto("/x") from $app/navigation
|
|
13
|
+
* - call (script): fetch("…") ; load() data fns live in +page(.server).ts (not parsed yet)
|
|
14
|
+
* - feature (markup): <button>, <input|textarea|select>, <form>, {#each} (list)
|
|
15
|
+
*/
|
|
16
|
+
const PAGE_FILE = "+page.svelte";
|
|
17
|
+
function routesDirOf(projectRoot) {
|
|
18
|
+
return ([join(projectRoot, "src", "routes"), join(projectRoot, "routes")].find((d) => existsSync(d) && statSync(d).isDirectory()) ?? null);
|
|
19
|
+
}
|
|
20
|
+
function hasSvelteKitDep(projectRoot) {
|
|
21
|
+
try {
|
|
22
|
+
const pkg = JSON.parse(readFileSync(join(projectRoot, "package.json"), "utf8"));
|
|
23
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
24
|
+
return "@sveltejs/kit" in deps;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export const svelteKitAdapter = {
|
|
31
|
+
id: "svelte",
|
|
32
|
+
router: "sveltekit",
|
|
33
|
+
detect(projectRoot) {
|
|
34
|
+
return hasSvelteKitDep(projectRoot) && routesDirOf(projectRoot) !== null;
|
|
35
|
+
},
|
|
36
|
+
discover(projectRoot) {
|
|
37
|
+
const routesDir = routesDirOf(projectRoot);
|
|
38
|
+
if (!routesDir)
|
|
39
|
+
return [];
|
|
40
|
+
const files = [];
|
|
41
|
+
walk(routesDir, (file) => {
|
|
42
|
+
if (!file.endsWith(sep + PAGE_FILE))
|
|
43
|
+
return;
|
|
44
|
+
const route = routeFromPageFile(routesDir, file);
|
|
45
|
+
files.push({
|
|
46
|
+
absPath: file,
|
|
47
|
+
relPath: relative(projectRoot, file).split(sep).join("/"),
|
|
48
|
+
id: route,
|
|
49
|
+
route,
|
|
50
|
+
title: titleFromRoute(route),
|
|
51
|
+
isEntry: route === "/",
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
files.sort((a, b) => a.route.localeCompare(b.route));
|
|
55
|
+
return files;
|
|
56
|
+
},
|
|
57
|
+
parse(file) {
|
|
58
|
+
return parseSvelte(safeRead(file.absPath));
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
// ---------- discovery ----------
|
|
62
|
+
function walk(dir, onFile) {
|
|
63
|
+
let entries;
|
|
64
|
+
try {
|
|
65
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
if (entry.name === "node_modules" || entry.name.startsWith("."))
|
|
72
|
+
continue;
|
|
73
|
+
const full = join(dir, entry.name);
|
|
74
|
+
if (entry.isDirectory())
|
|
75
|
+
walk(full, onFile);
|
|
76
|
+
else
|
|
77
|
+
onFile(full);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/** SvelteKit route dir → route. Drops the `+page.svelte` leaf, strips `(group)` segments. */
|
|
81
|
+
function routeFromPageFile(routesDir, file) {
|
|
82
|
+
const segs = relative(routesDir, file)
|
|
83
|
+
.split(sep)
|
|
84
|
+
.slice(0, -1) // drop the +page.svelte filename
|
|
85
|
+
.filter((s) => !(s.startsWith("(") && s.endsWith(")"))); // route groups are not URL segments
|
|
86
|
+
const route = "/" + segs.join("/");
|
|
87
|
+
return route.length > 1 ? route.replace(/\/+$/, "") : "/";
|
|
88
|
+
}
|
|
89
|
+
function titleFromRoute(route) {
|
|
90
|
+
if (route === "/")
|
|
91
|
+
return "Home";
|
|
92
|
+
const last = route.split("/").filter(Boolean).pop() ?? route;
|
|
93
|
+
return last
|
|
94
|
+
.replace(/^\[(\.\.\.)?(.+?)\]$/, "$2") // [id] / [...rest] → id / rest
|
|
95
|
+
.replace(/[-_]/g, " ")
|
|
96
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
97
|
+
}
|
|
98
|
+
function safeRead(file) {
|
|
99
|
+
try {
|
|
100
|
+
return readFileSync(file, "utf8");
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return "";
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// ---------- parsing (line/regex scan) ----------
|
|
107
|
+
const FEATURE_RULES = [
|
|
108
|
+
{ re: /<button\b[^>]*>([^<]*)/i, kind: "button", label: (m) => m[1].trim() || "Button" },
|
|
109
|
+
{ re: /<(?:input|textarea|select)\b[^>]*\bplaceholder=["']([^"']*)["']/i, kind: "input", label: (m) => m[1] || "Input" },
|
|
110
|
+
{ re: /<(?:input|textarea|select)\b/i, kind: "input", label: () => "Input" },
|
|
111
|
+
{ re: /<form\b/i, kind: "form", label: () => "Form" },
|
|
112
|
+
{ re: /\{#each\b/i, kind: "list", label: () => "List" },
|
|
113
|
+
];
|
|
114
|
+
const GOTO = /\bgoto\s*\(\s*["'`]([^"'`]+)["'`]/;
|
|
115
|
+
const HREF = /<a\b[^>]*?\shref=["'](\/[^"']*)["']/i;
|
|
116
|
+
const FETCH = /\bfetch\s*\(\s*["'`]([^"'`]+)["'`]/;
|
|
117
|
+
function parseSvelte(src) {
|
|
118
|
+
const navs = [];
|
|
119
|
+
const calls = [];
|
|
120
|
+
const features = [];
|
|
121
|
+
const components = new Set();
|
|
122
|
+
const lines = src.split(/\r?\n/);
|
|
123
|
+
for (let i = 0; i < lines.length; i++) {
|
|
124
|
+
const line = lines[i].trim();
|
|
125
|
+
const lineNo = i + 1;
|
|
126
|
+
const href = line.match(HREF);
|
|
127
|
+
if (href)
|
|
128
|
+
navs.push({ target: href[1], raw: snippet(line), trigger: "<a href>", line: lineNo });
|
|
129
|
+
const goto = line.match(GOTO);
|
|
130
|
+
if (goto)
|
|
131
|
+
navs.push({ target: goto[1], raw: snippet(line), trigger: "goto()", line: lineNo });
|
|
132
|
+
const fetchM = line.match(FETCH);
|
|
133
|
+
if (fetchM)
|
|
134
|
+
calls.push({ url: fetchM[1], raw: snippet(line), trigger: "fetch", line: lineNo });
|
|
135
|
+
for (const rule of FEATURE_RULES) {
|
|
136
|
+
const m = line.match(rule.re);
|
|
137
|
+
if (m) {
|
|
138
|
+
features.push({ kind: rule.kind, label: rule.label(m), detail: m[0].trim().slice(0, 40), line: lineNo });
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
for (const cm of line.matchAll(/<([A-Z]\w*)\b/g))
|
|
143
|
+
components.add(cm[1]);
|
|
144
|
+
}
|
|
145
|
+
return { navs, calls, features, components: [...components].sort(), contains: [] };
|
|
146
|
+
}
|
|
147
|
+
function snippet(text) {
|
|
148
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
149
|
+
return flat.length > 80 ? flat.slice(0, 77) + "..." : flat;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=sveltekit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sveltekit.js","sourceRoot":"","sources":["../../../src/core/adapters/sveltekit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC1E,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGhD;;;;;;;;;;;;GAYG;AACH,MAAM,SAAS,GAAG,cAAc,CAAC;AAEjC,SAAS,WAAW,CAAC,WAAmB;IACtC,OAAO,CACL,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,EAAE,IAAI,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CACpE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,IAAI,QAAQ,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAClD,IAAI,IAAI,CACV,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,WAAmB;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAG7E,CAAC;QACF,MAAM,IAAI,GAAG,EAAE,GAAG,GAAG,CAAC,YAAY,EAAE,GAAG,GAAG,CAAC,eAAe,EAAE,CAAC;QAC7D,OAAO,eAAe,IAAI,IAAI,CAAC;IACjC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,gBAAgB,GAAqB;IAChD,EAAE,EAAE,QAAQ;IACZ,MAAM,EAAE,WAAW;IAEnB,MAAM,CAAC,WAAW;QAChB,OAAO,eAAe,CAAC,WAAW,CAAC,IAAI,WAAW,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC;IAC3E,CAAC;IAED,QAAQ,CAAC,WAAW;QAClB,MAAM,SAAS,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;QAC3C,IAAI,CAAC,SAAS;YAAE,OAAO,EAAE,CAAC;QAC1B,MAAM,KAAK,GAAiB,EAAE,CAAC;QAC/B,IAAI,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,EAAE;YACvB,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,GAAG,SAAS,CAAC;gBAAE,OAAO;YAC5C,MAAM,KAAK,GAAG,iBAAiB,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACjD,KAAK,CAAC,IAAI,CAAC;gBACT,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,QAAQ,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;gBACzD,EAAE,EAAE,KAAK;gBACT,KAAK;gBACL,KAAK,EAAE,cAAc,CAAC,KAAK,CAAC;gBAC5B,OAAO,EAAE,KAAK,KAAK,GAAG;aACvB,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC;QACrD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,IAAI;QACR,OAAO,WAAW,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC;IAC7C,CAAC;CACF,CAAC;AAEF,kCAAkC;AAElC,SAAS,IAAI,CAAC,GAAW,EAAE,MAA8B;IACvD,IAAI,OAAO,CAAC;IACZ,IAAI,CAAC;QACH,OAAO,GAAG,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO;IACT,CAAC;IACD,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,KAAK,CAAC,IAAI,KAAK,cAAc,IAAI,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC1E,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACnC,IAAI,KAAK,CAAC,WAAW,EAAE;YAAE,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;;YACvC,MAAM,CAAC,IAAI,CAAC,CAAC;IACpB,CAAC;AACH,CAAC;AAED,6FAA6F;AAC7F,SAAS,iBAAiB,CAAC,SAAiB,EAAE,IAAY;IACxD,MAAM,IAAI,GAAG,QAAQ,CAAC,SAAS,EAAE,IAAI,CAAC;SACnC,KAAK,CAAC,GAAG,CAAC;SACV,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,iCAAiC;SAC9C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,oCAAoC;IAC/F,MAAM,KAAK,GAAG,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnC,OAAO,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAC5D,CAAC;AAED,SAAS,cAAc,CAAC,KAAa;IACnC,IAAI,KAAK,KAAK,GAAG;QAAE,OAAO,MAAM,CAAC;IACjC,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,IAAI,KAAK,CAAC;IAC7D,OAAO,IAAI;SACR,OAAO,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAC,+BAA+B;SACrE,OAAO,CAAC,OAAO,EAAE,GAAG,CAAC;SACrB,OAAO,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;AAC9C,CAAC;AAED,SAAS,QAAQ,CAAC,IAAY;IAC5B,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED,kDAAkD;AAElD,MAAM,aAAa,GAA4F;IAC7G,EAAE,EAAE,EAAE,yBAAyB,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,IAAI,QAAQ,EAAE;IACxF,EAAE,EAAE,EAAE,kEAAkE,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE;IACxH,EAAE,EAAE,EAAE,+BAA+B,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,EAAE;IAC5E,EAAE,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;IACrD,EAAE,EAAE,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,MAAM,EAAE;CACxD,CAAC;AAEF,MAAM,IAAI,GAAG,mCAAmC,CAAC;AACjD,MAAM,IAAI,GAAG,sCAAsC,CAAC;AACpD,MAAM,KAAK,GAAG,oCAAoC,CAAC;AAEnD,SAAS,WAAW,CAAC,GAAW;IAC9B,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,MAAM,KAAK,GAAc,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAiB,EAAE,CAAC;IAClC,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IAErC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7B,MAAM,MAAM,GAAG,CAAC,GAAG,CAAC,CAAC;QAErB,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAEhG,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC9B,IAAI,IAAI;YAAE,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAE9F,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QACjC,IAAI,MAAM;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;QAE/F,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9B,IAAI,CAAC,EAAE,CAAC;gBACN,QAAQ,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC,CAAC;gBACzG,MAAM;YACR,CAAC;QACH,CAAC;QAED,KAAK,MAAM,EAAE,IAAI,IAAI,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YAAE,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,UAAU,EAAE,CAAC,GAAG,UAAU,CAAC,CAAC,IAAI,EAAE,EAAE,QAAQ,EAAE,EAAE,EAAE,CAAC;AACrF,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAC9C,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, relative, sep, basename } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* SwiftUI adapter (first non-JS adapter, ALKAHEST.md §8).
|
|
5
|
+
* Screen = `struct X: View`. Parsing uses zero-dependency heuristics (line scan).
|
|
6
|
+
* Can later be swapped for tree-sitter-swift (same interface).
|
|
7
|
+
*
|
|
8
|
+
* Mapping:
|
|
9
|
+
* - transition: NavigationLink(destination:) / .sheet / .fullScreenCover / .navigationDestination → target View
|
|
10
|
+
* - containment: child Views instantiated by TabView Tab{}/embed → contains (turned into edges in resolve)
|
|
11
|
+
* - call: URL(string:) / "https://…" / URLRequest(url:) → endpoint
|
|
12
|
+
* - feature: Button / TextField / Toggle / Picker / List / ForEach / Form …
|
|
13
|
+
*/
|
|
14
|
+
const VIEW_STRUCT = /\bstruct\s+([A-Za-z_]\w*)\s*:\s*[^{]*\bView\b/;
|
|
15
|
+
const SKIP_DIRS = /^(deprecated|archive|archieve)$/i;
|
|
16
|
+
export const swiftUiAdapter = {
|
|
17
|
+
id: "swiftui",
|
|
18
|
+
router: "swiftui-views",
|
|
19
|
+
detect(projectRoot) {
|
|
20
|
+
let found = false;
|
|
21
|
+
walkSwift(projectRoot, (file) => {
|
|
22
|
+
if (found)
|
|
23
|
+
return;
|
|
24
|
+
const src = safeRead(file);
|
|
25
|
+
if (/\bimport\s+SwiftUI\b/.test(src.slice(0, 2000)) && VIEW_STRUCT.test(src))
|
|
26
|
+
found = true;
|
|
27
|
+
});
|
|
28
|
+
return found;
|
|
29
|
+
},
|
|
30
|
+
discover(projectRoot) {
|
|
31
|
+
const files = [];
|
|
32
|
+
let entryView = null;
|
|
33
|
+
walkSwift(projectRoot, (file) => {
|
|
34
|
+
const src = safeRead(file);
|
|
35
|
+
if (!entryView)
|
|
36
|
+
entryView = entryViewIn(src); // root View launched by the @main App
|
|
37
|
+
const primary = primaryView(file, src);
|
|
38
|
+
if (!primary)
|
|
39
|
+
return;
|
|
40
|
+
files.push({
|
|
41
|
+
absPath: file,
|
|
42
|
+
relPath: relative(projectRoot, file).split(sep).join("/"),
|
|
43
|
+
id: primary,
|
|
44
|
+
route: primary,
|
|
45
|
+
title: primary.replace(/View$/, "") || primary,
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
if (entryView) {
|
|
49
|
+
const e = files.find((f) => f.id === entryView);
|
|
50
|
+
if (e)
|
|
51
|
+
e.isEntry = true;
|
|
52
|
+
}
|
|
53
|
+
files.sort((a, b) => a.id.localeCompare(b.id));
|
|
54
|
+
return files;
|
|
55
|
+
},
|
|
56
|
+
parse(file) {
|
|
57
|
+
return parseSwift(safeRead(file.absPath));
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
// ---------- discovery helpers ----------
|
|
61
|
+
function walkSwift(dir, onFile) {
|
|
62
|
+
let entries;
|
|
63
|
+
try {
|
|
64
|
+
entries = readdirSync(dir, { withFileTypes: true, encoding: "utf8" });
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
71
|
+
continue;
|
|
72
|
+
const full = join(dir, entry.name);
|
|
73
|
+
if (entry.isDirectory()) {
|
|
74
|
+
if (SKIP_DIRS.test(entry.name))
|
|
75
|
+
continue; // skip dead-code folders
|
|
76
|
+
walkSwift(full, onFile);
|
|
77
|
+
}
|
|
78
|
+
else if (entry.name.endsWith(".swift")) {
|
|
79
|
+
onFile(full);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function safeRead(file) {
|
|
84
|
+
try {
|
|
85
|
+
return readFileSync(file, "utf8");
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return "";
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/** A file's primary View: prefer the View matching the filename, else the first View struct. Null if none. */
|
|
92
|
+
function primaryView(file, src) {
|
|
93
|
+
const names = [];
|
|
94
|
+
for (const line of src.split("\n")) {
|
|
95
|
+
const m = line.match(VIEW_STRUCT);
|
|
96
|
+
if (m)
|
|
97
|
+
names.push(m[1]);
|
|
98
|
+
}
|
|
99
|
+
if (!names.length)
|
|
100
|
+
return null;
|
|
101
|
+
const stem = basename(file, ".swift");
|
|
102
|
+
return names.includes(stem) ? stem : names[0];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* The first View instantiated in the body of `@main struct X: App` → app entry point.
|
|
106
|
+
* Example: iobookApp.body → `ContentView()` → "ContentView".
|
|
107
|
+
*/
|
|
108
|
+
function entryViewIn(src) {
|
|
109
|
+
if (!/@main/.test(src) || !/:\s*App\b/.test(src))
|
|
110
|
+
return null;
|
|
111
|
+
const bodyIdx = src.search(/var\s+body\s*:\s*some\s+Scene/);
|
|
112
|
+
const region = bodyIdx >= 0 ? src.slice(bodyIdx) : src;
|
|
113
|
+
const m = region.match(/\b([A-Z]\w*)\s*\(\s*\)/);
|
|
114
|
+
return m ? m[1] : null;
|
|
115
|
+
}
|
|
116
|
+
// ---------- parsing (line heuristics) ----------
|
|
117
|
+
const NAV_CONSTRUCTS = [
|
|
118
|
+
{ re: /NavigationLink/, trigger: "NavigationLink" },
|
|
119
|
+
{ re: /\.navigationDestination/, trigger: ".navigationDestination" },
|
|
120
|
+
{ re: /\.sheet\b/, trigger: ".sheet" },
|
|
121
|
+
{ re: /\.fullScreenCover\b/, trigger: ".fullScreenCover" },
|
|
122
|
+
{ re: /\.popover\b/, trigger: ".popover" },
|
|
123
|
+
];
|
|
124
|
+
const FEATURE_RULES = [
|
|
125
|
+
{ re: /\bButton\s*\(\s*"([^"]*)"/, kind: "button", label: (m) => m[1] || "Button" },
|
|
126
|
+
{ re: /\bButton\s*\{/, kind: "button", label: () => "Button" },
|
|
127
|
+
{ re: /\b(?:TextField|SecureField)\s*\(\s*"([^"]*)"/, kind: "input", label: (m) => m[1] || "Input" },
|
|
128
|
+
{ re: /\b(?:Toggle|Picker|Stepper|Slider|DatePicker)\s*\(\s*"([^"]*)"/, kind: "input", label: (m) => m[1] || "Input" },
|
|
129
|
+
{ re: /\bForm\s*\{/, kind: "form", label: () => "Form" },
|
|
130
|
+
{ re: /\b(?:List|ForEach)\b/, kind: "list", label: () => "List" },
|
|
131
|
+
];
|
|
132
|
+
const VIEW_INSTANCE = /\b([A-Z]\w*View)\s*\(/g;
|
|
133
|
+
const DEST_VIEW = /destination:\s*([A-Z]\w*)\s*\(/;
|
|
134
|
+
const URL_STRING = /URL\(string:\s*"([^"]+)"|"(https?:\/\/[^"]+)"/;
|
|
135
|
+
const URL_REQUEST = /URLRequest\(\s*url:/;
|
|
136
|
+
const CTOR_CALL = /\b([A-Z]\w*)\s*\(/g;
|
|
137
|
+
/** SwiftUI built-in containers/elements — excluded from contains candidates (noise). */
|
|
138
|
+
const SWIFT_BUILTINS = new Set([
|
|
139
|
+
"VStack", "HStack", "ZStack", "LazyVStack", "LazyHStack", "LazyVGrid", "LazyHGrid", "Grid", "GridRow",
|
|
140
|
+
"Text", "Image", "Button", "Label", "Link", "Spacer", "Divider", "Group", "Section", "Form", "List",
|
|
141
|
+
"ForEach", "Toggle", "Picker", "Stepper", "Slider", "DatePicker", "TextField", "SecureField", "TextEditor",
|
|
142
|
+
"NavigationStack", "NavigationView", "NavigationLink", "TabView", "Tab", "ScrollView", "ScrollViewReader",
|
|
143
|
+
"Color", "Circle", "Rectangle", "RoundedRectangle", "Capsule", "Ellipse", "Path", "Menu", "Chart",
|
|
144
|
+
"GeometryReader", "Canvas", "Gauge", "ProgressView", "Table", "DisclosureGroup", "ControlGroup", "LazyView",
|
|
145
|
+
"AnyView", "EmptyView", "ViewThatFits",
|
|
146
|
+
]);
|
|
147
|
+
function parseSwift(src) {
|
|
148
|
+
const lines = src.split("\n");
|
|
149
|
+
const navs = [];
|
|
150
|
+
const calls = [];
|
|
151
|
+
const features = [];
|
|
152
|
+
const components = new Set();
|
|
153
|
+
const contains = new Set();
|
|
154
|
+
for (let i = 0; i < lines.length; i++) {
|
|
155
|
+
const line = lines[i];
|
|
156
|
+
const lineNo = i + 1;
|
|
157
|
+
// --- transition: extract target View from the line with a nav construct (+ next few lines) ---
|
|
158
|
+
const nav = NAV_CONSTRUCTS.find((c) => c.re.test(line));
|
|
159
|
+
if (nav) {
|
|
160
|
+
const target = findNavTarget(lines, i);
|
|
161
|
+
navs.push({ target, raw: snippet(line), trigger: nav.trigger, line: lineNo });
|
|
162
|
+
if (target)
|
|
163
|
+
components.add(target);
|
|
164
|
+
}
|
|
165
|
+
// --- call: URL literal / URLRequest ---
|
|
166
|
+
const urlM = line.match(URL_STRING);
|
|
167
|
+
if (urlM) {
|
|
168
|
+
calls.push({ url: urlM[1] ?? urlM[2] ?? null, raw: snippet(line), trigger: "URL", line: lineNo });
|
|
169
|
+
}
|
|
170
|
+
else if (URL_REQUEST.test(line)) {
|
|
171
|
+
calls.push({ url: null, raw: snippet(line), trigger: "URLRequest", line: lineNo });
|
|
172
|
+
}
|
|
173
|
+
// --- feature: UI controls (one per line) ---
|
|
174
|
+
for (const rule of FEATURE_RULES) {
|
|
175
|
+
const m = line.match(rule.re);
|
|
176
|
+
if (m) {
|
|
177
|
+
features.push({ kind: rule.kind, label: rule.label(m), detail: m[0].trim().slice(0, 40), line: lineNo });
|
|
178
|
+
break;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
// --- components (for display: XxxView) ---
|
|
182
|
+
if (!nav) {
|
|
183
|
+
for (const m of line.matchAll(VIEW_INSTANCE))
|
|
184
|
+
components.add(m[1]);
|
|
185
|
+
}
|
|
186
|
+
// --- contains candidates: capitalized constructor calls (excluding built-ins). resolve intersects with screenIds. ---
|
|
187
|
+
for (const m of line.matchAll(CTOR_CALL)) {
|
|
188
|
+
if (!SWIFT_BUILTINS.has(m[1]))
|
|
189
|
+
contains.add(m[1]);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
navs,
|
|
194
|
+
calls,
|
|
195
|
+
features,
|
|
196
|
+
components: [...components].sort(),
|
|
197
|
+
contains: [...contains].sort(),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/** Target View name from a nav construct: prefer destination:, else XxxView( on the same/next 3 lines. */
|
|
201
|
+
function findNavTarget(lines, i) {
|
|
202
|
+
for (let j = i; j < Math.min(i + 4, lines.length); j++) {
|
|
203
|
+
const dest = lines[j].match(DEST_VIEW);
|
|
204
|
+
if (dest)
|
|
205
|
+
return dest[1];
|
|
206
|
+
const inst = lines[j].match(/\b([A-Z]\w*View)\s*\(/);
|
|
207
|
+
if (inst && inst.index !== undefined && !/NavigationLink|navigationDestination|sheet|fullScreenCover|popover/.test(lines[j].slice(0, inst.index)))
|
|
208
|
+
return inst[1];
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
function snippet(text) {
|
|
213
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
214
|
+
return flat.length > 80 ? flat.slice(0, 77) + "..." : flat;
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=swiftui.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"swiftui.js","sourceRoot":"","sources":["../../../src/core/adapters/swiftui.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type { Feature, Framework, Router } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Framework adapter layer (ALKAHEST.md §8 adapters).
|
|
4
|
+
* Only discover+parse differ per framework; resolve/emit/dashboard/MCP are shared.
|
|
5
|
+
* Supporting a new platform = adding one adapter. The parsing approach (AST/regex/tree-sitter) is up to the adapter.
|
|
6
|
+
*/
|
|
7
|
+
/** One discovered screen file. id/route/title are filled in by the adapter. */
|
|
8
|
+
export interface ScreenFile {
|
|
9
|
+
/** Absolute path */
|
|
10
|
+
absPath: string;
|
|
11
|
+
/** Path relative to projectRoot (posix) */
|
|
12
|
+
relPath: string;
|
|
13
|
+
/** Stable screen identifier. Next=route("/x"), SwiftUI=View name */
|
|
14
|
+
id: string;
|
|
15
|
+
/** Route notation (same as id if none) */
|
|
16
|
+
route: string;
|
|
17
|
+
/** Human-readable name */
|
|
18
|
+
title: string;
|
|
19
|
+
/** Whether this is the app entry point (root launched by @main/App, or the "/" route). */
|
|
20
|
+
isEntry?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export interface RawNav {
|
|
23
|
+
/** Statically resolved target screen identifier/URL, null if unresolved */
|
|
24
|
+
target: string | null;
|
|
25
|
+
raw: string;
|
|
26
|
+
trigger: string;
|
|
27
|
+
line: number;
|
|
28
|
+
}
|
|
29
|
+
export interface RawCall {
|
|
30
|
+
/** Statically resolved endpoint URL, null if unresolved */
|
|
31
|
+
url: string | null;
|
|
32
|
+
method?: string;
|
|
33
|
+
raw: string;
|
|
34
|
+
trigger: string;
|
|
35
|
+
line: number;
|
|
36
|
+
}
|
|
37
|
+
export interface RawFeature {
|
|
38
|
+
kind: Feature["kind"];
|
|
39
|
+
label: string;
|
|
40
|
+
detail: string;
|
|
41
|
+
line: number;
|
|
42
|
+
}
|
|
43
|
+
export interface RawScreen {
|
|
44
|
+
navs: RawNav[];
|
|
45
|
+
calls: RawCall[];
|
|
46
|
+
features: RawFeature[];
|
|
47
|
+
components: string[];
|
|
48
|
+
/**
|
|
49
|
+
* Candidate other screens this screen directly instantiates (capitalized constructor calls).
|
|
50
|
+
* resolve turns only the intersection with screenIds into "contains" (structural containment) edges.
|
|
51
|
+
* Example: SwiftUI TabView's Recents()/Assets(), child Views embedded by a parent.
|
|
52
|
+
*/
|
|
53
|
+
contains: string[];
|
|
54
|
+
}
|
|
55
|
+
/** Framework adapter. */
|
|
56
|
+
export interface FrameworkAdapter {
|
|
57
|
+
id: Framework;
|
|
58
|
+
router: Router;
|
|
59
|
+
/** Is this project a target for this adapter? (lightweight check) */
|
|
60
|
+
detect(projectRoot: string): boolean;
|
|
61
|
+
/** Enumerate screen files (fills id/route/title). */
|
|
62
|
+
discover(projectRoot: string): ScreenFile[];
|
|
63
|
+
/** Parse one screen file to extract raw signals. */
|
|
64
|
+
parse(file: ScreenFile): RawScreen;
|
|
65
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/adapters/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { join, relative, sep } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* iOS UIKit adapter (Swift, programmatic). Screen = `class X: UIViewController` (and the common
|
|
5
|
+
* subclasses). Pairs with `swiftui` as the iOS set; registered after it, so a SwiftUI project
|
|
6
|
+
* (which may import UIKit) is claimed by swiftui first and only pure-UIKit apps fall here.
|
|
7
|
+
*
|
|
8
|
+
* Parsing is a zero-dependency line scan (swiftui's style). Storyboard segues need the .storyboard
|
|
9
|
+
* XML, which we don't parse yet — programmatic navigation is covered.
|
|
10
|
+
*
|
|
11
|
+
* Mapping:
|
|
12
|
+
* - transition: pushViewController(X()) / present(X()) / instantiateViewController(...) → target VC
|
|
13
|
+
* - call: URL(string:) / "https://…" / URLRequest(url:)
|
|
14
|
+
* - feature: UIButton / UITextField / UITableView / UICollectionView / UISwitch …
|
|
15
|
+
*/
|
|
16
|
+
const VC_CLASS = /\bclass\s+([A-Za-z_]\w*)\s*:\s*[^{]*\b(UI(?:View|TableView|CollectionView|Navigation|TabBar|Page|Split)Controller)\b/;
|
|
17
|
+
const SKIP_DIRS = /^(deprecated|archive|archieve|build|\.build|Pods)$/i;
|
|
18
|
+
export const uikitAdapter = {
|
|
19
|
+
id: "uikit",
|
|
20
|
+
router: "uikit-vc",
|
|
21
|
+
detect(projectRoot) {
|
|
22
|
+
let found = false;
|
|
23
|
+
walkSwift(projectRoot, (file) => {
|
|
24
|
+
if (found)
|
|
25
|
+
return;
|
|
26
|
+
const src = safeRead(file);
|
|
27
|
+
if (/\bimport\s+UIKit\b/.test(src.slice(0, 4000)) && VC_CLASS.test(src))
|
|
28
|
+
found = true;
|
|
29
|
+
});
|
|
30
|
+
return found;
|
|
31
|
+
},
|
|
32
|
+
discover(projectRoot) {
|
|
33
|
+
const files = [];
|
|
34
|
+
walkSwift(projectRoot, (file) => {
|
|
35
|
+
const src = safeRead(file);
|
|
36
|
+
const primary = primaryVC(file, src);
|
|
37
|
+
if (!primary)
|
|
38
|
+
return;
|
|
39
|
+
files.push({
|
|
40
|
+
absPath: file,
|
|
41
|
+
relPath: relative(projectRoot, file).split(sep).join("/"),
|
|
42
|
+
id: primary,
|
|
43
|
+
route: primary,
|
|
44
|
+
title: primary.replace(/(ViewController|VC|Controller)$/, "") || primary,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
files.sort((a, b) => a.id.localeCompare(b.id));
|
|
48
|
+
return files;
|
|
49
|
+
},
|
|
50
|
+
parse(file) {
|
|
51
|
+
return parseUiKit(safeRead(file.absPath));
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
// ---------- discovery ----------
|
|
55
|
+
function walkSwift(dir, onFile) {
|
|
56
|
+
let entries;
|
|
57
|
+
try {
|
|
58
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
for (const entry of entries) {
|
|
64
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules")
|
|
65
|
+
continue;
|
|
66
|
+
const full = join(dir, entry.name);
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
if (SKIP_DIRS.test(entry.name))
|
|
69
|
+
continue;
|
|
70
|
+
walkSwift(full, onFile);
|
|
71
|
+
}
|
|
72
|
+
else if (entry.name.endsWith(".swift")) {
|
|
73
|
+
onFile(full);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function safeRead(file) {
|
|
78
|
+
try {
|
|
79
|
+
return readFileSync(file, "utf8");
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return "";
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/** A file's primary view controller: prefer the VC matching the filename, else the first VC class. */
|
|
86
|
+
function primaryVC(file, src) {
|
|
87
|
+
const names = [];
|
|
88
|
+
for (const line of src.split("\n")) {
|
|
89
|
+
const m = line.match(VC_CLASS);
|
|
90
|
+
if (m)
|
|
91
|
+
names.push(m[1]);
|
|
92
|
+
}
|
|
93
|
+
if (!names.length)
|
|
94
|
+
return null;
|
|
95
|
+
const stem = file.slice(file.lastIndexOf(sep) + 1).replace(/\.swift$/, "");
|
|
96
|
+
return names.includes(stem) ? stem : names[0];
|
|
97
|
+
}
|
|
98
|
+
// ---------- parsing (line heuristics) ----------
|
|
99
|
+
const NAV_CONSTRUCTS = [
|
|
100
|
+
{ re: /\bpushViewController\b/, trigger: "pushViewController" },
|
|
101
|
+
{ re: /\bpresent\s*\(/, trigger: "present" },
|
|
102
|
+
{ re: /\bshow\s*\(/, trigger: "show" },
|
|
103
|
+
{ re: /\binstantiateViewController\b/, trigger: "instantiateViewController" },
|
|
104
|
+
];
|
|
105
|
+
const FEATURE_RULES = [
|
|
106
|
+
{ re: /\bUIButton\b/, kind: "button", label: () => "Button" },
|
|
107
|
+
{ re: /\b(?:UITextField|UITextView|UISearchBar)\b/, kind: "input", label: () => "Input" },
|
|
108
|
+
{ re: /\b(?:UISwitch|UISlider|UIStepper|UISegmentedControl|UIPickerView|UIDatePicker)\b/, kind: "input", label: () => "Input" },
|
|
109
|
+
{ re: /\b(?:UITableView|UICollectionView)\b/, kind: "list", label: () => "List" },
|
|
110
|
+
];
|
|
111
|
+
const VC_INSTANCE = /\b([A-Z]\w*(?:ViewController|VC))\s*\(/;
|
|
112
|
+
const VC_BINDING = /\b(?:let|var)\s+(\w+)\s*=\s*([A-Z]\w*(?:ViewController|VC))\s*\(/;
|
|
113
|
+
const URL_STRING = /URL\(string:\s*"([^"]+)"|"(https?:\/\/[^"]+)"/;
|
|
114
|
+
const URL_REQUEST = /URLRequest\(\s*url:/;
|
|
115
|
+
function parseUiKit(src) {
|
|
116
|
+
const lines = src.split("\n");
|
|
117
|
+
const navs = [];
|
|
118
|
+
const calls = [];
|
|
119
|
+
const features = [];
|
|
120
|
+
const components = new Set();
|
|
121
|
+
// var name → VC type, e.g. `let vc = DetailsViewController()` so `pushViewController(vc)` resolves.
|
|
122
|
+
const bindings = new Map();
|
|
123
|
+
for (let i = 0; i < lines.length; i++) {
|
|
124
|
+
const line = lines[i];
|
|
125
|
+
const lineNo = i + 1;
|
|
126
|
+
// --- local binding: track `let/var x = XxxViewController()` for later nav-arg resolution ---
|
|
127
|
+
const bind = line.match(VC_BINDING);
|
|
128
|
+
if (bind)
|
|
129
|
+
bindings.set(bind[1], bind[2]);
|
|
130
|
+
// --- transition: nav construct → target VC (inline instance, or a bound variable) ---
|
|
131
|
+
const nav = NAV_CONSTRUCTS.find((c) => c.re.test(line));
|
|
132
|
+
if (nav) {
|
|
133
|
+
const target = findNavTarget(lines, i, bindings);
|
|
134
|
+
navs.push({ target, raw: snippet(line), trigger: nav.trigger, line: lineNo });
|
|
135
|
+
if (target)
|
|
136
|
+
components.add(target);
|
|
137
|
+
}
|
|
138
|
+
// --- call: URL literal / URLRequest ---
|
|
139
|
+
const urlM = line.match(URL_STRING);
|
|
140
|
+
if (urlM) {
|
|
141
|
+
calls.push({ url: urlM[1] ?? urlM[2] ?? null, raw: snippet(line), trigger: "URL", line: lineNo });
|
|
142
|
+
}
|
|
143
|
+
else if (URL_REQUEST.test(line)) {
|
|
144
|
+
calls.push({ url: null, raw: snippet(line), trigger: "URLRequest", line: lineNo });
|
|
145
|
+
}
|
|
146
|
+
// --- feature: UIKit controls (one per line) ---
|
|
147
|
+
for (const rule of FEATURE_RULES) {
|
|
148
|
+
const m = line.match(rule.re);
|
|
149
|
+
if (m) {
|
|
150
|
+
features.push({ kind: rule.kind, label: rule.label(m), detail: m[0].trim().slice(0, 40), line: lineNo });
|
|
151
|
+
break;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return { navs, calls, features, components: [...components].sort(), contains: [] };
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Target VC for a nav construct. Prefer an inline `XxxViewController(` on the same/next 3 lines;
|
|
159
|
+
* otherwise resolve the call's first argument identifier against the tracked `let x = VC()` bindings
|
|
160
|
+
* (the common `let vc = DetailsViewController(); push(vc)` pattern).
|
|
161
|
+
*/
|
|
162
|
+
function findNavTarget(lines, i, bindings) {
|
|
163
|
+
for (let j = i; j < Math.min(i + 4, lines.length); j++) {
|
|
164
|
+
const inst = lines[j].match(VC_INSTANCE);
|
|
165
|
+
if (inst)
|
|
166
|
+
return inst[1];
|
|
167
|
+
}
|
|
168
|
+
// No inline instance — look at the argument passed to the nav call on this line.
|
|
169
|
+
const arg = lines[i].match(/(?:pushViewController|present|show)\s*\(\s*([A-Za-z_]\w*)/);
|
|
170
|
+
if (arg && bindings.has(arg[1]))
|
|
171
|
+
return bindings.get(arg[1]);
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
function snippet(text) {
|
|
175
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
176
|
+
return flat.length > 80 ? flat.slice(0, 77) + "..." : flat;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=uikit.js.map
|