@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.
- package/README.ko.md +65 -0
- package/README.md +62 -6
- package/aiEditor.ts +304 -0
- package/akanApp/akanApp.host.ts +393 -0
- package/akanApp/index.ts +1 -0
- package/akanConfig/akanConfig.test.ts +236 -0
- package/akanConfig/akanConfig.ts +384 -0
- package/akanConfig/index.ts +2 -0
- package/akanConfig/types.ts +23 -0
- package/applicationBuildReporter.ts +69 -0
- package/applicationBuildRunner.ts +302 -0
- package/applicationReleasePackager.ts +206 -0
- package/artifact/implicitRootLayout.ts +155 -0
- package/artifact/index.ts +1 -0
- package/artifact/routeSeedIndex.test.ts +98 -0
- package/artifact/routeSeedIndex.ts +130 -0
- package/auth.ts +41 -0
- package/builder.ts +164 -0
- package/capacitor.base.config.ts +88 -0
- package/capacitorApp.ts +440 -0
- package/commandDecorators/argMeta.ts +102 -0
- package/commandDecorators/command.ts +351 -0
- package/commandDecorators/commandBuilder.ts +224 -0
- package/commandDecorators/commandDecorators.test.ts +212 -0
- package/commandDecorators/commandMeta.ts +7 -0
- package/commandDecorators/dependencyBuilder.ts +100 -0
- package/{esm/src/commandDecorators/helpFormatter.js → commandDecorators/helpFormatter.ts} +100 -47
- package/{esm/src/commandDecorators/index.js → commandDecorators/index.ts} +4 -2
- package/commandDecorators/targetMeta.ts +31 -0
- package/commandDecorators/types.ts +10 -0
- package/constants.ts +25 -0
- package/createTunnel.ts +36 -0
- package/dependencyScanner.ts +357 -0
- package/devkitUtils.test.ts +259 -0
- package/executors.test.ts +315 -0
- package/executors.ts +1390 -0
- package/{esm/src/extractDeps.js → extractDeps.ts} +26 -20
- package/{esm/src/fileEditor.js → fileEditor.ts} +51 -32
- package/fileSys.ts +39 -0
- package/frontendBuild/allRoutesBuilder.ts +103 -0
- package/frontendBuild/buildRouteClient.test.ts +190 -0
- package/frontendBuild/clientBuildTypes.ts +114 -0
- package/frontendBuild/clientEntriesBundler.ts +303 -0
- package/frontendBuild/clientEntryDiscovery.ts +199 -0
- package/frontendBuild/csrArtifactBuilder.ts +237 -0
- package/frontendBuild/cssCompiler.ts +286 -0
- package/frontendBuild/cssImportResolver.ts +116 -0
- package/frontendBuild/fontOptimizer.ts +427 -0
- package/frontendBuild/frontendBuild.test.ts +204 -0
- package/frontendBuild/hmrChangeClassifier.ts +28 -0
- package/frontendBuild/hmrWatcher.ts +102 -0
- package/frontendBuild/index.ts +18 -0
- package/frontendBuild/pagesBundleBuilder.ts +137 -0
- package/frontendBuild/pagesEntrySourceGenerator.ts +37 -0
- package/frontendBuild/precompressArtifacts.ts +59 -0
- package/frontendBuild/routeClientBuilder.ts +290 -0
- package/frontendBuild/routesManifestArtifactSerializer.ts +62 -0
- package/frontendBuild/ssrBaseArtifactBuilder.ts +139 -0
- package/frontendBuild/vendorSpecifiers.ts +16 -0
- package/frontendBuild/watchRootResolver.ts +28 -0
- package/getCredentials.ts +19 -0
- package/getDirname.ts +3 -0
- package/getModelFileData.ts +59 -0
- package/getRelatedCnsts.ts +313 -0
- package/guideline.ts +19 -0
- package/incrementalBuilder/incrementalBuilder.host.test.ts +51 -0
- package/incrementalBuilder/incrementalBuilder.host.ts +152 -0
- package/incrementalBuilder/incrementalBuilder.proc.ts +331 -0
- package/incrementalBuilder/index.ts +1 -0
- package/{esm/src/index.js → index.ts} +28 -15
- package/lint/no-deep-internal-import.grit +25 -0
- package/lint/no-import-client-functions.grit +32 -0
- package/lint/no-import-external-library.grit +21 -0
- package/lint/no-js-private-class-method.grit +42 -0
- package/lint/no-use-client-in-server.grit +7 -0
- package/lint/non-scalar-props-restricted.grit +13 -0
- package/linter.ts +271 -0
- package/mobile/index.ts +1 -0
- package/mobile/mobileTarget.test.ts +53 -0
- package/mobile/mobileTarget.ts +88 -0
- package/package.json +48 -31
- package/prompter.ts +72 -0
- package/scanInfo.ts +606 -0
- package/selectModel.ts +11 -0
- package/{esm/src/spinner.js → spinner.ts} +22 -28
- package/{esm/src/capacitorApp.js → src/capacitorApp.ts} +82 -81
- package/sshTunnel.ts +152 -0
- package/{esm/src/streamAi.js → streamAi.ts} +18 -12
- package/transforms/barrelAnalyzer.ts +278 -0
- package/transforms/barrelImportsPlugin.ts +504 -0
- package/transforms/externalizeFrameworkPlugin.ts +185 -0
- package/transforms/index.ts +5 -0
- package/transforms/rscUseClientTransform.ts +59 -0
- package/transforms/transforms.test.ts +208 -0
- package/transforms/useClientBundlePlugin.ts +47 -0
- package/tsconfig.json +37 -0
- package/typeChecker.ts +264 -0
- package/types.ts +44 -0
- package/ui/MultiScrollList.tsx +242 -0
- package/ui/ScrollList.tsx +107 -0
- package/ui/index.ts +2 -0
- package/{esm/src/uploadRelease.js → uploadRelease.ts} +50 -34
- package/{esm/src/useStdoutDimensions.js → useStdoutDimensions.ts} +5 -5
- package/cjs/index.js +0 -21
- package/cjs/src/aiEditor.js +0 -311
- package/cjs/src/auth.js +0 -72
- package/cjs/src/builder.js +0 -114
- package/cjs/src/capacitorApp.js +0 -313
- package/cjs/src/commandDecorators/argMeta.js +0 -88
- package/cjs/src/commandDecorators/command.js +0 -324
- package/cjs/src/commandDecorators/commandMeta.js +0 -30
- package/cjs/src/commandDecorators/helpFormatter.js +0 -211
- package/cjs/src/commandDecorators/index.js +0 -31
- package/cjs/src/commandDecorators/targetMeta.js +0 -57
- package/cjs/src/commandDecorators/types.js +0 -15
- package/cjs/src/constants.js +0 -46
- package/cjs/src/createTunnel.js +0 -49
- package/cjs/src/dependencyScanner.js +0 -220
- package/cjs/src/executors.js +0 -964
- package/cjs/src/extractDeps.js +0 -103
- package/cjs/src/fileEditor.js +0 -120
- package/cjs/src/getCredentials.js +0 -44
- package/cjs/src/getDirname.js +0 -38
- package/cjs/src/getModelFileData.js +0 -66
- package/cjs/src/getRelatedCnsts.js +0 -260
- package/cjs/src/guideline.js +0 -15
- package/cjs/src/index.js +0 -65
- package/cjs/src/linter.js +0 -238
- package/cjs/src/prompter.js +0 -85
- package/cjs/src/scanInfo.js +0 -491
- package/cjs/src/selectModel.js +0 -46
- package/cjs/src/spinner.js +0 -93
- package/cjs/src/streamAi.js +0 -62
- package/cjs/src/typeChecker.js +0 -207
- package/cjs/src/types.js +0 -15
- package/cjs/src/uploadRelease.js +0 -112
- package/cjs/src/useStdoutDimensions.js +0 -43
- package/esm/index.js +0 -1
- package/esm/src/aiEditor.js +0 -282
- package/esm/src/auth.js +0 -42
- package/esm/src/builder.js +0 -81
- package/esm/src/commandDecorators/argMeta.js +0 -54
- package/esm/src/commandDecorators/command.js +0 -290
- package/esm/src/commandDecorators/commandMeta.js +0 -7
- package/esm/src/commandDecorators/targetMeta.js +0 -33
- package/esm/src/commandDecorators/types.js +0 -0
- package/esm/src/constants.js +0 -17
- package/esm/src/createTunnel.js +0 -26
- package/esm/src/dependencyScanner.js +0 -187
- package/esm/src/executors.js +0 -928
- package/esm/src/getCredentials.js +0 -11
- package/esm/src/getDirname.js +0 -5
- package/esm/src/getModelFileData.js +0 -33
- package/esm/src/getRelatedCnsts.js +0 -221
- package/esm/src/guideline.js +0 -0
- package/esm/src/linter.js +0 -205
- package/esm/src/prompter.js +0 -51
- package/esm/src/scanInfo.js +0 -455
- package/esm/src/selectModel.js +0 -13
- package/esm/src/typeChecker.js +0 -174
- package/esm/src/types.js +0 -0
- package/index.d.ts +0 -1
- package/src/aiEditor.d.ts +0 -50
- package/src/auth.d.ts +0 -9
- package/src/builder.d.ts +0 -18
- package/src/capacitorApp.d.ts +0 -39
- package/src/commandDecorators/argMeta.d.ts +0 -67
- package/src/commandDecorators/command.d.ts +0 -2
- package/src/commandDecorators/commandMeta.d.ts +0 -2
- package/src/commandDecorators/helpFormatter.d.ts +0 -3
- package/src/commandDecorators/index.d.ts +0 -6
- package/src/commandDecorators/targetMeta.d.ts +0 -19
- package/src/commandDecorators/types.d.ts +0 -1
- package/src/constants.d.ts +0 -26
- package/src/createTunnel.d.ts +0 -8
- package/src/dependencyScanner.d.ts +0 -23
- package/src/executors.d.ts +0 -296
- package/src/extractDeps.d.ts +0 -7
- package/src/fileEditor.d.ts +0 -16
- package/src/getCredentials.d.ts +0 -12
- package/src/getDirname.d.ts +0 -1
- package/src/getModelFileData.d.ts +0 -16
- package/src/getRelatedCnsts.d.ts +0 -53
- package/src/guideline.d.ts +0 -19
- package/src/index.d.ts +0 -23
- package/src/linter.d.ts +0 -109
- package/src/prompter.d.ts +0 -14
- package/src/scanInfo.d.ts +0 -82
- package/src/selectModel.d.ts +0 -1
- package/src/spinner.d.ts +0 -20
- package/src/streamAi.d.ts +0 -6
- package/src/typeChecker.d.ts +0 -52
- package/src/types.d.ts +0 -31
- package/src/uploadRelease.d.ts +0 -10
- 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
|
+
};
|