@akanjs/devkit 1.0.20 → 2.1.0-rc.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.
Files changed (195) hide show
  1. package/README.ko.md +65 -0
  2. package/README.md +62 -6
  3. package/aiEditor.ts +304 -0
  4. package/akanApp/akanApp.host.ts +393 -0
  5. package/akanApp/index.ts +1 -0
  6. package/akanConfig/akanConfig.test.ts +236 -0
  7. package/akanConfig/akanConfig.ts +384 -0
  8. package/akanConfig/index.ts +2 -0
  9. package/akanConfig/types.ts +23 -0
  10. package/applicationBuildReporter.ts +69 -0
  11. package/applicationBuildRunner.ts +302 -0
  12. package/applicationReleasePackager.ts +206 -0
  13. package/artifact/implicitRootLayout.ts +155 -0
  14. package/artifact/index.ts +1 -0
  15. package/artifact/routeSeedIndex.test.ts +98 -0
  16. package/artifact/routeSeedIndex.ts +130 -0
  17. package/auth.ts +41 -0
  18. package/builder.ts +164 -0
  19. package/capacitor.base.config.ts +88 -0
  20. package/capacitorApp.ts +440 -0
  21. package/commandDecorators/argMeta.ts +102 -0
  22. package/commandDecorators/command.ts +351 -0
  23. package/commandDecorators/commandBuilder.ts +224 -0
  24. package/commandDecorators/commandDecorators.test.ts +212 -0
  25. package/commandDecorators/commandMeta.ts +7 -0
  26. package/commandDecorators/dependencyBuilder.ts +100 -0
  27. package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
  28. package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
  29. package/commandDecorators/targetMeta.ts +31 -0
  30. package/commandDecorators/types.ts +10 -0
  31. package/constants.ts +25 -0
  32. package/createTunnel.ts +36 -0
  33. package/dependencyScanner.ts +357 -0
  34. package/devkitUtils.test.ts +259 -0
  35. package/executors.test.ts +315 -0
  36. package/executors.ts +1390 -0
  37. package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
  38. package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
  39. package/fileSys.ts +39 -0
  40. package/frontendBuild/allRoutesBuilder.ts +103 -0
  41. package/frontendBuild/buildRouteClient.test.ts +190 -0
  42. package/frontendBuild/clientBuildTypes.ts +114 -0
  43. package/frontendBuild/clientEntriesBundler.ts +303 -0
  44. package/frontendBuild/clientEntryDiscovery.ts +199 -0
  45. package/frontendBuild/csrArtifactBuilder.ts +237 -0
  46. package/frontendBuild/cssCompiler.ts +286 -0
  47. package/frontendBuild/cssImportResolver.ts +116 -0
  48. package/frontendBuild/fontOptimizer.ts +427 -0
  49. package/frontendBuild/frontendBuild.test.ts +204 -0
  50. package/frontendBuild/hmrChangeClassifier.ts +28 -0
  51. package/frontendBuild/hmrWatcher.ts +102 -0
  52. package/frontendBuild/index.ts +18 -0
  53. package/frontendBuild/pagesBundleBuilder.ts +137 -0
  54. package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
  55. package/frontendBuild/precompressArtifacts.ts +59 -0
  56. package/frontendBuild/routeClientBuilder.ts +290 -0
  57. package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
  58. package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
  59. package/frontendBuild/vendorSpecifiers.ts +16 -0
  60. package/frontendBuild/watchRootResolver.ts +28 -0
  61. package/getCredentials.ts +19 -0
  62. package/getDirname.ts +3 -0
  63. package/getModelFileData.ts +59 -0
  64. package/getRelatedCnsts.ts +313 -0
  65. package/guideline.ts +19 -0
  66. package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
  67. package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
  68. package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
  69. package/incrementalBuilder/index.ts +1 -0
  70. package/{esm/src/index.js → index.ts} +28 -15
  71. package/lint/no-deep-internal-import.grit +25 -0
  72. package/lint/no-import-client-functions.grit +32 -0
  73. package/lint/no-import-external-library.grit +21 -0
  74. package/lint/no-js-private-class-method.grit +42 -0
  75. package/lint/no-use-client-in-server.grit +7 -0
  76. package/lint/non-scalar-props-restricted.grit +13 -0
  77. package/linter.ts +271 -0
  78. package/mobile/index.ts +1 -0
  79. package/mobile/mobileTarget.test.ts +53 -0
  80. package/mobile/mobileTarget.ts +88 -0
  81. package/package.json +48 -31
  82. package/prompter.ts +72 -0
  83. package/scanInfo.ts +606 -0
  84. package/selectModel.ts +11 -0
  85. package/{esm/src/spinner.js → spinner.ts} +22 -28
  86. package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
  87. package/sshTunnel.ts +152 -0
  88. package/{esm/src/streamAi.js → streamAi.ts} +18 -12
  89. package/transforms/barrelAnalyzer.ts +278 -0
  90. package/transforms/barrelImportsPlugin.ts +504 -0
  91. package/transforms/externalizeFrameworkPlugin.ts +185 -0
  92. package/transforms/index.ts +5 -0
  93. package/transforms/rscUseClientTransform.ts +59 -0
  94. package/transforms/transforms.test.ts +208 -0
  95. package/transforms/useClientBundlePlugin.ts +47 -0
  96. package/tsconfig.json +37 -0
  97. package/typeChecker.ts +264 -0
  98. package/types.ts +44 -0
  99. package/ui/MultiScrollList.tsx +242 -0
  100. package/ui/ScrollList.tsx +107 -0
  101. package/ui/index.ts +2 -0
  102. package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
  103. package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
  104. package/cjs/index.js +0 -21
  105. package/cjs/src/aiEditor.js +0 -311
  106. package/cjs/src/auth.js +0 -72
  107. package/cjs/src/builder.js +0 -114
  108. package/cjs/src/capacitorApp.js +0 -313
  109. package/cjs/src/commandDecorators/argMeta.js +0 -88
  110. package/cjs/src/commandDecorators/command.js +0 -324
  111. package/cjs/src/commandDecorators/commandMeta.js +0 -30
  112. package/cjs/src/commandDecorators/helpFormatter.js +0 -211
  113. package/cjs/src/commandDecorators/index.js +0 -31
  114. package/cjs/src/commandDecorators/targetMeta.js +0 -57
  115. package/cjs/src/commandDecorators/types.js +0 -15
  116. package/cjs/src/constants.js +0 -46
  117. package/cjs/src/createTunnel.js +0 -49
  118. package/cjs/src/dependencyScanner.js +0 -220
  119. package/cjs/src/executors.js +0 -964
  120. package/cjs/src/extractDeps.js +0 -103
  121. package/cjs/src/fileEditor.js +0 -120
  122. package/cjs/src/getCredentials.js +0 -44
  123. package/cjs/src/getDirname.js +0 -38
  124. package/cjs/src/getModelFileData.js +0 -66
  125. package/cjs/src/getRelatedCnsts.js +0 -260
  126. package/cjs/src/guideline.js +0 -15
  127. package/cjs/src/index.js +0 -65
  128. package/cjs/src/linter.js +0 -238
  129. package/cjs/src/prompter.js +0 -85
  130. package/cjs/src/scanInfo.js +0 -491
  131. package/cjs/src/selectModel.js +0 -46
  132. package/cjs/src/spinner.js +0 -93
  133. package/cjs/src/streamAi.js +0 -62
  134. package/cjs/src/typeChecker.js +0 -207
  135. package/cjs/src/types.js +0 -15
  136. package/cjs/src/uploadRelease.js +0 -112
  137. package/cjs/src/useStdoutDimensions.js +0 -43
  138. package/esm/index.js +0 -1
  139. package/esm/src/aiEditor.js +0 -282
  140. package/esm/src/auth.js +0 -42
  141. package/esm/src/builder.js +0 -81
  142. package/esm/src/commandDecorators/argMeta.js +0 -54
  143. package/esm/src/commandDecorators/command.js +0 -290
  144. package/esm/src/commandDecorators/commandMeta.js +0 -7
  145. package/esm/src/commandDecorators/targetMeta.js +0 -33
  146. package/esm/src/commandDecorators/types.js +0 -0
  147. package/esm/src/constants.js +0 -17
  148. package/esm/src/createTunnel.js +0 -26
  149. package/esm/src/dependencyScanner.js +0 -187
  150. package/esm/src/executors.js +0 -928
  151. package/esm/src/getCredentials.js +0 -11
  152. package/esm/src/getDirname.js +0 -5
  153. package/esm/src/getModelFileData.js +0 -33
  154. package/esm/src/getRelatedCnsts.js +0 -221
  155. package/esm/src/guideline.js +0 -0
  156. package/esm/src/linter.js +0 -205
  157. package/esm/src/prompter.js +0 -51
  158. package/esm/src/scanInfo.js +0 -455
  159. package/esm/src/selectModel.js +0 -13
  160. package/esm/src/typeChecker.js +0 -174
  161. package/esm/src/types.js +0 -0
  162. package/index.d.ts +0 -1
  163. package/src/aiEditor.d.ts +0 -50
  164. package/src/auth.d.ts +0 -9
  165. package/src/builder.d.ts +0 -18
  166. package/src/capacitorApp.d.ts +0 -39
  167. package/src/commandDecorators/argMeta.d.ts +0 -67
  168. package/src/commandDecorators/command.d.ts +0 -2
  169. package/src/commandDecorators/commandMeta.d.ts +0 -2
  170. package/src/commandDecorators/helpFormatter.d.ts +0 -3
  171. package/src/commandDecorators/index.d.ts +0 -6
  172. package/src/commandDecorators/targetMeta.d.ts +0 -19
  173. package/src/commandDecorators/types.d.ts +0 -1
  174. package/src/constants.d.ts +0 -26
  175. package/src/createTunnel.d.ts +0 -8
  176. package/src/dependencyScanner.d.ts +0 -23
  177. package/src/executors.d.ts +0 -296
  178. package/src/extractDeps.d.ts +0 -7
  179. package/src/fileEditor.d.ts +0 -16
  180. package/src/getCredentials.d.ts +0 -12
  181. package/src/getDirname.d.ts +0 -1
  182. package/src/getModelFileData.d.ts +0 -16
  183. package/src/getRelatedCnsts.d.ts +0 -53
  184. package/src/guideline.d.ts +0 -19
  185. package/src/index.d.ts +0 -23
  186. package/src/linter.d.ts +0 -109
  187. package/src/prompter.d.ts +0 -14
  188. package/src/scanInfo.d.ts +0 -82
  189. package/src/selectModel.d.ts +0 -1
  190. package/src/spinner.d.ts +0 -20
  191. package/src/streamAi.d.ts +0 -6
  192. package/src/typeChecker.d.ts +0 -52
  193. package/src/types.d.ts +0 -31
  194. package/src/uploadRelease.d.ts +0 -10
  195. package/src/useStdoutDimensions.d.ts +0 -1
@@ -0,0 +1,278 @@
1
+ import path from "node:path";
2
+ import { Logger } from "akanjs/common";
3
+
4
+ export interface BarrelExportTarget {
5
+ /** Subpath specifier to emit in rewritten import, e.g. `akanjs/ui/Empty`. */
6
+ subpath: string;
7
+ /** Name as it is exported by the leaf module. */
8
+ originalName: string;
9
+ }
10
+
11
+ export type BarrelExportMap = Map<string, BarrelExportTarget>;
12
+
13
+ export interface PackageEntry {
14
+ /** Package specifier (e.g. `akanjs/ui`). */
15
+ pkgName: string;
16
+ /** Absolute path of the barrel entry file. */
17
+ entryFile: string;
18
+ /** Absolute directory used as the base for subpath computation. Typically dirname(entryFile). */
19
+ pkgDir: string;
20
+ /** Preserve concrete file paths for package exports that do not support extensionless deep imports. */
21
+ preserveFilePath?: boolean;
22
+ }
23
+
24
+ export interface BarrelAnalyzerOptions {
25
+ resolvePackage: (pkgName: string) => Promise<PackageEntry | null>;
26
+ /** Resolve a relative specifier from `fromFile` to an absolute file path on disk. */
27
+ resolveRelative?: (fromFile: string, relSpec: string) => Promise<string | null>;
28
+ }
29
+
30
+ // Re-export statements with an explicit source: `export { A, B as C } from "./x"`,
31
+ // `export * from "./x"`, `export * as ns from "./x"`. We rely on `Transpiler.scan`
32
+ // for the authoritative export name set; this regex only needs to give us the
33
+ // name↔source mapping that scan doesn't expose.
34
+ const REEXPORT_RE =
35
+ /(?:^|\n)\s*export\s+(?:type\s+)?(?:(\*)(?:\s+as\s+(\w+))?|\{\s*([^}]*?)\s*\})\s+from\s+(["'])([^"']+)\4;?/g;
36
+
37
+ // Local named re-export without `from`: `export { A, B as C };`
38
+ // `Transpiler.scan` currently omits these from its `exports` list so we keep a
39
+ // small regex fallback. The lookahead sits immediately after `}` so that
40
+ // greedy whitespace matching cannot backtrack past it — otherwise it would
41
+ // also match `export type { X } from "./y"` statements.
42
+ const LOCAL_NAMED_RE = /(?:^|\n)\s*export\s+\{\s*([^}]*?)\s*\}(?!\s*from)/g;
43
+
44
+ const CANDIDATE_EXTS = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
45
+
46
+ export class BarrelAnalyzer {
47
+ readonly #logger = new Logger("BarrelAnalyzer");
48
+ readonly #opts: BarrelAnalyzerOptions;
49
+ readonly #cache = new Map<string, Promise<BarrelExportMap | null>>();
50
+ readonly #tsTranspiler = new Bun.Transpiler({ loader: "ts" });
51
+ readonly #tsxTranspiler = new Bun.Transpiler({ loader: "tsx" });
52
+
53
+ constructor(opts: BarrelAnalyzerOptions) {
54
+ this.#opts = opts;
55
+ }
56
+
57
+ analyze(pkgName: string): Promise<BarrelExportMap | null> {
58
+ const cached = this.#cache.get(pkgName);
59
+ if (cached) return cached;
60
+ const promise = this.#analyzeSafe(pkgName);
61
+ this.#cache.set(pkgName, promise);
62
+ return promise;
63
+ }
64
+
65
+ async #analyzeSafe(pkgName: string): Promise<BarrelExportMap | null> {
66
+ try {
67
+ return await this.#analyzeUncached(pkgName);
68
+ } catch (err) {
69
+ this.#logger.error(`analyze failed for ${pkgName}: ${(err as Error).message}`);
70
+ return null;
71
+ }
72
+ }
73
+
74
+ async #analyzeUncached(pkgName: string): Promise<BarrelExportMap | null> {
75
+ const pkg = await this.#opts.resolvePackage(pkgName);
76
+ if (!pkg) return null;
77
+ const map: BarrelExportMap = new Map();
78
+ const visited = new Set<string>();
79
+ await this.#walk(pkg.entryFile, pkg, map, visited);
80
+ return map;
81
+ }
82
+
83
+ async #walk(absFile: string, pkg: PackageEntry, map: BarrelExportMap, visited: Set<string>): Promise<void> {
84
+ if (visited.has(absFile)) return;
85
+ visited.add(absFile);
86
+ const source = await readIfExists(absFile);
87
+ if (source === null) return;
88
+ const currentSubpath = this.#subpathFor(pkg, absFile);
89
+ if (!currentSubpath) return;
90
+
91
+ // Authoritative export names from Bun's transpiler. Filters type-only,
92
+ // comments, strings, etc. automatically. `export *` re-exports are NOT
93
+ // flooded here — they only show up as imports, so we recurse separately.
94
+ const authoritative = this.#scanExports(source, absFile);
95
+ // `default` is intentionally skipped — barrels rarely proxy defaults and
96
+ // rewriting `import X from "pkg"` requires different semantics.
97
+ authoritative.delete("default");
98
+
99
+ // Names attributed to a specific leaf via `export ... from "./path"`.
100
+ // Everything left over in `authoritative` after this is treated as a local
101
+ // declaration at this file's own subpath.
102
+ const attributed = new Set<string>();
103
+
104
+ // Pass 1: `export { ... } from "./x"` and `export * [as ns] from "./x"`.
105
+ REEXPORT_RE.lastIndex = 0;
106
+ let m: RegExpExecArray | null = REEXPORT_RE.exec(source);
107
+ while (m !== null) {
108
+ const star = m[1];
109
+ const nsAs = m[2];
110
+ const namedList = m[3];
111
+ const spec = m[5] ?? "";
112
+ m = REEXPORT_RE.exec(source);
113
+ if (!isRelative(spec)) continue;
114
+
115
+ if (star) {
116
+ if (nsAs) {
117
+ // `export * as ns from "./x"` exposes a namespace object that we
118
+ // cannot flatten into direct subpath imports. Ensure it's not misread
119
+ // as a local declaration.
120
+ authoritative.delete(nsAs);
121
+ continue;
122
+ }
123
+ const targetAbs = await this.#resolveRel(absFile, spec);
124
+ if (!targetAbs) continue;
125
+ // Recurse: target's exports will land in `map` with target's subpath.
126
+ // These names don't appear in this file's `authoritative` set, so the
127
+ // local-declaration pass below won't misattribute them.
128
+ await this.#walk(targetAbs, pkg, map, visited);
129
+ continue;
130
+ }
131
+
132
+ if (namedList !== undefined) {
133
+ const targetAbs = await this.#resolveRel(absFile, spec);
134
+ if (!targetAbs) continue;
135
+ const targetSubpath = this.#subpathFor(pkg, targetAbs);
136
+ if (!targetSubpath) continue;
137
+ for (const item of parseNamedList(namedList)) {
138
+ if (item.isType) continue;
139
+ if (item.imported === "default") continue;
140
+ // Validate against scan — drops anything hidden behind `export type`
141
+ // or otherwise not actually exported at runtime.
142
+ if (!authoritative.has(item.local)) continue;
143
+ attributed.add(item.local);
144
+ if (!map.has(item.local)) {
145
+ map.set(item.local, { subpath: targetSubpath, originalName: item.imported });
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // Pass 2: `export { A, B as C };` (no `from`). scan omits these, so the
152
+ // regex is authoritative for this narrow case.
153
+ LOCAL_NAMED_RE.lastIndex = 0;
154
+ let n: RegExpExecArray | null = LOCAL_NAMED_RE.exec(source);
155
+ while (n !== null) {
156
+ const body = n[1] ?? "";
157
+ n = LOCAL_NAMED_RE.exec(source);
158
+ for (const item of parseNamedList(body)) {
159
+ if (item.isType) continue;
160
+ if (item.imported === "default") continue;
161
+ // Validate against scan — ensures we don't treat commented-out or
162
+ // otherwise dead code as a real local re-export.
163
+ if (!authoritative.has(item.local)) continue;
164
+ attributed.add(item.local);
165
+ if (!map.has(item.local)) {
166
+ map.set(item.local, { subpath: currentSubpath, originalName: item.imported });
167
+ }
168
+ }
169
+ }
170
+
171
+ // Pass 3: remaining authoritative names are local declarations defined in
172
+ // this file (const/function/class/...). scan already filtered out
173
+ // type-only and namespace-only exports for us.
174
+ for (const name of authoritative) {
175
+ if (attributed.has(name)) continue;
176
+ if (map.has(name)) continue;
177
+ map.set(name, { subpath: currentSubpath, originalName: name });
178
+ }
179
+ }
180
+
181
+ #scanExports(source: string, absFile: string): Set<string> {
182
+ try {
183
+ const transpiler = [".tsx", ".jsx"].includes(path.extname(absFile)) ? this.#tsxTranspiler : this.#tsTranspiler;
184
+ const { exports } = transpiler.scan(source);
185
+ return new Set(exports);
186
+ } catch (err) {
187
+ this.#logger.error(`scan failed: ${(err as Error).message}`);
188
+ return new Set();
189
+ }
190
+ }
191
+
192
+ #subpathFor(pkg: PackageEntry, absFile: string): string | null {
193
+ const rel = path.relative(pkg.pkgDir, absFile);
194
+ if (!rel || rel.startsWith("..") || path.isAbsolute(rel)) return null;
195
+ if (pkg.preserveFilePath) return `${pkg.pkgName}/${rel.split(path.sep).join("/")}`;
196
+ const noExt = stripKnownExt(rel);
197
+ // index files collapse to the directory — but the barrel entry is itself an index.
198
+ // For nested `xxx/index.*`, callers are expected to import `@pkg/xxx` (not `xxx/index`).
199
+ const tail = collapseIndex(noExt);
200
+ if (tail === "") return pkg.pkgName;
201
+ return `${pkg.pkgName}/${tail.split(path.sep).join("/")}`;
202
+ }
203
+
204
+ async #resolveRel(fromFile: string, relSpec: string): Promise<string | null> {
205
+ if (this.#opts.resolveRelative) return this.#opts.resolveRelative(fromFile, relSpec);
206
+ return defaultResolveRelative(fromFile, relSpec);
207
+ }
208
+ }
209
+
210
+ interface NamedItem {
211
+ imported: string;
212
+ local: string;
213
+ isType: boolean;
214
+ }
215
+
216
+ const parseNamedList = (listBody: string): NamedItem[] => {
217
+ const out: NamedItem[] = [];
218
+ for (const raw of listBody.split(",")) {
219
+ const s = raw.trim();
220
+ if (!s) continue;
221
+ let rest = s;
222
+ let isType = false;
223
+ if (rest.startsWith("type ")) {
224
+ isType = true;
225
+ rest = rest.slice(5).trim();
226
+ }
227
+ const asMatch = /^(\w+)\s+as\s+(\w+)$/.exec(rest);
228
+ if (asMatch) {
229
+ out.push({ imported: asMatch[1] ?? "", local: asMatch[2] ?? "", isType });
230
+ continue;
231
+ }
232
+ if (/^\w+$/.test(rest)) {
233
+ out.push({ imported: rest, local: rest, isType });
234
+ }
235
+ }
236
+ return out;
237
+ };
238
+
239
+ const isRelative = (spec: string): boolean => {
240
+ return spec.startsWith("./") || spec.startsWith("../") || spec === "." || spec === "..";
241
+ };
242
+
243
+ const readIfExists = async (absFile: string): Promise<string | null> => {
244
+ const file = Bun.file(absFile);
245
+ if (!(await file.exists())) return null;
246
+ return file.text();
247
+ };
248
+
249
+ const defaultResolveRelative = async (fromFile: string, relSpec: string): Promise<string | null> => {
250
+ const baseDir = path.dirname(fromFile);
251
+ const joined = path.resolve(baseDir, relSpec);
252
+ if (path.extname(joined)) {
253
+ if (await Bun.file(joined).exists()) return joined;
254
+ return null;
255
+ }
256
+ for (const ext of CANDIDATE_EXTS) {
257
+ const cand = joined + ext;
258
+ if (await Bun.file(cand).exists()) return cand;
259
+ }
260
+ for (const ext of CANDIDATE_EXTS) {
261
+ const cand = path.join(joined, `index${ext}`);
262
+ if (await Bun.file(cand).exists()) return cand;
263
+ }
264
+ return null;
265
+ };
266
+
267
+ const stripKnownExt = (relPath: string): string => {
268
+ for (const ext of CANDIDATE_EXTS) {
269
+ if (relPath.endsWith(ext)) return relPath.slice(0, -ext.length);
270
+ }
271
+ return relPath;
272
+ };
273
+
274
+ const collapseIndex = (relPathNoExt: string): string => {
275
+ const parts = relPathNoExt.split(path.sep);
276
+ if (parts[parts.length - 1] === "index") parts.pop();
277
+ return parts.join(path.sep);
278
+ };