@akanjs/devkit 3.0.0-beta.9 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. package/DEV_RUNTIME_KNOBS.md +44 -26
  2. package/README.ko.md +21 -7
  3. package/README.md +17 -8
  4. package/abstractDoc.test.ts +6 -15
  5. package/abstractDoc.ts +6 -8
  6. package/agentsIndex.test.ts +4 -7
  7. package/agentsIndex.ts +6 -22
  8. package/akanApp/BackendImportGraph.test.ts +8 -22
  9. package/akanApp/BackendImportGraph.ts +1 -20
  10. package/akanApp/akanApp.host.test.ts +56 -3
  11. package/akanApp/akanApp.host.ts +218 -278
  12. package/akanApp/devBootLatch.test.ts +39 -0
  13. package/akanApp/devBootLatch.ts +39 -0
  14. package/akanApp/devHostPolicy.test.ts +81 -35
  15. package/akanApp/devHostPolicy.ts +41 -102
  16. package/akanConfig/akanBin.test.ts +62 -0
  17. package/akanConfig/akanBin.ts +78 -0
  18. package/akanConfig/akanConfig.test.ts +439 -141
  19. package/akanConfig/akanConfig.ts +359 -275
  20. package/akanConfig/index.ts +1 -0
  21. package/akanConfig/types.ts +22 -8
  22. package/akanContext.ts +110 -160
  23. package/akanMcpContract.ts +28 -73
  24. package/appSelectionMemory.ts +1 -4
  25. package/applicationBuildReporter.ts +37 -5
  26. package/applicationBuildRunner.test.ts +41 -5
  27. package/applicationBuildRunner.ts +71 -63
  28. package/applicationTestPreload.ts +3 -12
  29. package/artifact/implicitRootLayout.test.ts +32 -30
  30. package/artifact/implicitRootLayout.ts +58 -106
  31. package/artifact/routeSeedIndex.test.ts +7 -6
  32. package/artifact/routeSeedIndex.ts +11 -44
  33. package/biome.base.json +35 -2
  34. package/biomeBase.ts +2 -5
  35. package/cloud/cloudApi.test.ts +202 -0
  36. package/cloud/cloudApi.ts +77 -39
  37. package/cloud/configLock.test.ts +64 -0
  38. package/cloud/configLock.ts +96 -0
  39. package/cloud/constants.ts +26 -5
  40. package/cloud/globalConfig.ts +71 -32
  41. package/codeAgent/agent/AkanCodeServices.ts +97 -0
  42. package/codeAgent/agent/AkanContextFiles.ts +94 -0
  43. package/codeAgent/agent/CodeAgent.ts +551 -0
  44. package/codeAgent/agent/CodeAgentAsks.ts +66 -0
  45. package/codeAgent/agent/CodeAgentEventMapper.ts +225 -0
  46. package/codeAgent/agent/CodeAgentGate.ts +93 -0
  47. package/codeAgent/agent/CodeAgentProxy.ts +82 -0
  48. package/codeAgent/agent/CodeAgentRpcHost.ts +103 -0
  49. package/codeAgent/agent/CodeAgentRpcListener.ts +109 -0
  50. package/codeAgent/agent/CodeAgentStreamPrinter.ts +86 -0
  51. package/codeAgent/agent/CodeAgentSuspended.ts +85 -0
  52. package/codeAgent/agent/CodeAgentUi.ts +47 -0
  53. package/codeAgent/agent/CodeMailbox.ts +148 -0
  54. package/codeAgent/agent/CodeSessionFork.ts +56 -0
  55. package/codeAgent/agent/CodeSessionIndex.ts +78 -0
  56. package/codeAgent/agent/SubagentPool.ts +214 -0
  57. package/codeAgent/agent/akanCodeModel.ts +47 -0
  58. package/codeAgent/agent/akanCodePaths.ts +31 -0
  59. package/codeAgent/agent/akanContextFiles.test.ts +114 -0
  60. package/codeAgent/agent/akanSystemPrompt.ts +46 -0
  61. package/codeAgent/agent/codeAgent.test.ts +178 -0
  62. package/codeAgent/agent/codeAgentContract.test.ts +523 -0
  63. package/codeAgent/agent/codeAgentProxy.test.ts +74 -0
  64. package/codeAgent/agent/codeAgentRpcListener.test.ts +113 -0
  65. package/codeAgent/agent/codeAgentSuspended.test.ts +47 -0
  66. package/codeAgent/agent/codeMailbox.test.ts +76 -0
  67. package/codeAgent/agent/codeSessionFork.test.ts +58 -0
  68. package/codeAgent/agent/codeSessionIndex.test.ts +77 -0
  69. package/codeAgent/agent/consoleToStderr.ts +8 -0
  70. package/codeAgent/agent/jsonLines.ts +29 -0
  71. package/codeAgent/agent/stringArg.ts +5 -0
  72. package/codeAgent/feedback/DevLogFeedback.ts +82 -0
  73. package/codeAgent/feedback/PreviewChrome.ts +82 -0
  74. package/codeAgent/feedback/PreviewFeedback.ts +46 -0
  75. package/codeAgent/feedback/PreviewView.ts +95 -0
  76. package/codeAgent/feedback/TurnFeedback.ts +98 -0
  77. package/codeAgent/feedback/VerifyFeedback.ts +40 -0
  78. package/codeAgent/feedback/previewChrome.test.ts +73 -0
  79. package/codeAgent/index.ts +34 -0
  80. package/codeAgent/skills/akan-code/SKILL.md +130 -0
  81. package/codeAgent/skills/akan-debug/SKILL.md +42 -0
  82. package/codeAgent/skills/akan-deps/SKILL.md +50 -0
  83. package/codeAgent/skills/akan-err/SKILL.md +39 -0
  84. package/codeAgent/skills/akan-module/SKILL.md +45 -0
  85. package/codeAgent/skills/akan-scaffold/SKILL.md +76 -0
  86. package/codeAgent/skills/akan-scalar/SKILL.md +52 -0
  87. package/codeAgent/skills/akan-service/SKILL.md +34 -0
  88. package/codeAgent/skills/akan-store/SKILL.md +67 -0
  89. package/codeAgent/skills/akan-ui/SKILL.md +63 -0
  90. package/codeAgent/skills/akan-validate/SKILL.md +44 -0
  91. package/codeAgent/tools/AkanCli.ts +50 -0
  92. package/codeAgent/tools/AkanCodePlugins.ts +136 -0
  93. package/codeAgent/tools/AkanEditScope.ts +48 -0
  94. package/codeAgent/tools/AkanEnvKeys.ts +50 -0
  95. package/codeAgent/tools/AkanToolPack.ts +92 -0
  96. package/codeAgent/tools/AkanVerifier.ts +48 -0
  97. package/codeAgent/tools/AskToolPack.ts +99 -0
  98. package/codeAgent/tools/MailToolPack.ts +76 -0
  99. package/codeAgent/tools/McpClient.ts +187 -0
  100. package/codeAgent/tools/McpOAuth.ts +233 -0
  101. package/codeAgent/tools/McpOAuthLoopback.ts +64 -0
  102. package/codeAgent/tools/McpServerConfig.ts +129 -0
  103. package/codeAgent/tools/McpSignIn.ts +158 -0
  104. package/codeAgent/tools/McpTokenStore.ts +81 -0
  105. package/codeAgent/tools/McpToolPack.ts +199 -0
  106. package/codeAgent/tools/SessionToolPack.ts +91 -0
  107. package/codeAgent/tools/WebToolPack.ts +147 -0
  108. package/codeAgent/tools/akanCodePlugins.test.ts +677 -0
  109. package/codeAgent/tools/askToolPack.test.ts +116 -0
  110. package/codeAgent/tools/mcpOAuth.test.ts +612 -0
  111. package/codeAgent/tools/mcpServerConfig.test.ts +212 -0
  112. package/codegenLock.test.ts +4 -13
  113. package/codegenLock.ts +3 -17
  114. package/commandDecorators/argMeta.ts +1 -5
  115. package/commandDecorators/camelToKebabCase.ts +1 -0
  116. package/commandDecorators/command.ts +46 -102
  117. package/commandDecorators/commandBuilder.ts +30 -61
  118. package/commandDecorators/commandDecorators.test.ts +2 -10
  119. package/commandDecorators/dependencyBuilder.ts +10 -1
  120. package/commandDecorators/helpFormatter.ts +68 -145
  121. package/commandDecorators/index.ts +0 -2
  122. package/commandDecorators/targetMeta.ts +9 -7
  123. package/createTunnel.ts +7 -14
  124. package/csrE2e/csrE2eHarness.fixture.ts +505 -0
  125. package/csrE2e/csrFastRefresh.e2e.test.ts +233 -0
  126. package/csrE2e/csrPageStack.e2e.test.ts +252 -0
  127. package/csrE2e/csrPatchFirst.e2e.test.ts +87 -0
  128. package/csrE2e/csrRootLayout.e2e.test.ts +134 -0
  129. package/csrE2e/devHmrLatency.e2e.test.ts +391 -0
  130. package/csrE2e/registryVendor.e2e.test.ts +221 -0
  131. package/csrE2e/ssrRegistry.e2e.test.ts +644 -0
  132. package/dependencyScanner.test.ts +4 -16
  133. package/dependencyScanner.ts +31 -80
  134. package/devkitUtils.test.ts +48 -20
  135. package/executors.linkFallback.test.ts +103 -0
  136. package/executors.test.ts +176 -106
  137. package/executors.ts +212 -498
  138. package/fileEditor.ts +9 -37
  139. package/{getDirname.test.ts → fileSys.test.ts} +5 -3
  140. package/fileSys.ts +22 -9
  141. package/formSetterScanner.ts +3 -16
  142. package/frontendBuild/allRoutesBuilder.ts +7 -21
  143. package/frontendBuild/autoImportSync.test.ts +27 -38
  144. package/frontendBuild/autoImportSync.ts +31 -65
  145. package/frontendBuild/buildRouteClient.test.ts +123 -37
  146. package/frontendBuild/bundleDefine.test.ts +12 -0
  147. package/frontendBuild/bundleDefine.ts +16 -0
  148. package/frontendBuild/clientBuildTypes.ts +5 -36
  149. package/frontendBuild/clientEntriesBundler.ts +9 -40
  150. package/frontendBuild/clientEntryDiscovery.test.ts +112 -0
  151. package/frontendBuild/clientEntryDiscovery.ts +122 -110
  152. package/frontendBuild/csrArtifactBuilder.ts +9 -31
  153. package/frontendBuild/csrDevArtifactWriter.test.ts +151 -0
  154. package/frontendBuild/csrDevArtifactWriter.ts +307 -0
  155. package/frontendBuild/csrDevBundler.test.ts +66 -0
  156. package/frontendBuild/csrDevBundler.ts +95 -0
  157. package/frontendBuild/csrDevFactoryCheck.test.ts +33 -0
  158. package/frontendBuild/csrDevFactoryCheck.ts +39 -0
  159. package/frontendBuild/csrDevModuleCompiler.test.ts +293 -0
  160. package/frontendBuild/csrDevModuleCompiler.ts +422 -0
  161. package/frontendBuild/csrDevPatcher.test.ts +572 -0
  162. package/frontendBuild/csrDevPatcher.ts +548 -0
  163. package/frontendBuild/csrDevPaths.ts +145 -0
  164. package/frontendBuild/csrDevResolver.test.ts +239 -0
  165. package/frontendBuild/csrDevResolver.ts +363 -0
  166. package/frontendBuild/csrDevTypes.ts +61 -0
  167. package/frontendBuild/csrEntryFiles.ts +24 -0
  168. package/frontendBuild/cssCandidateCache.test.ts +0 -6
  169. package/frontendBuild/cssCandidateCache.ts +6 -41
  170. package/frontendBuild/cssCompiler.ts +31 -82
  171. package/frontendBuild/cssImportResolver.ts +6 -19
  172. package/frontendBuild/devChangePlanner.ts +7 -10
  173. package/frontendBuild/devGeneratedIndexSync.test.ts +12 -27
  174. package/frontendBuild/devGeneratedIndexSync.ts +1 -4
  175. package/frontendBuild/devRegistryBundler.ts +192 -0
  176. package/frontendBuild/fontOptimizer.test.ts +6 -14
  177. package/frontendBuild/fontOptimizer.ts +15 -72
  178. package/frontendBuild/fontPruner.test.ts +5 -10
  179. package/frontendBuild/fontPruner.ts +5 -20
  180. package/frontendBuild/frontendBuild.test.ts +57 -70
  181. package/frontendBuild/hmrChangeClassifier.ts +2 -2
  182. package/frontendBuild/hmrWatcher.test.ts +59 -32
  183. package/frontendBuild/hmrWatcher.ts +31 -74
  184. package/frontendBuild/index.ts +4 -0
  185. package/frontendBuild/pagesBundleBuilder.ts +28 -30
  186. package/frontendBuild/pagesEntrySourceGenerator.ts +56 -6
  187. package/frontendBuild/precompressArtifacts.ts +5 -18
  188. package/frontendBuild/registryMetadataFingerprint.test.ts +63 -0
  189. package/frontendBuild/registryMetadataFingerprint.ts +28 -0
  190. package/frontendBuild/routeClientBuilder.ts +100 -62
  191. package/frontendBuild/routesManifestArtifactSerializer.ts +5 -36
  192. package/frontendBuild/serverGraphFile.test.ts +77 -0
  193. package/frontendBuild/serverGraphFile.ts +93 -0
  194. package/frontendBuild/sourceMtimeIndex.test.ts +41 -97
  195. package/frontendBuild/sourceMtimeIndex.ts +19 -130
  196. package/frontendBuild/ssrBaseArtifactBuilder.ts +19 -13
  197. package/frontendBuild/ssrDevBundler.ts +85 -0
  198. package/frontendBuild/styleContract.ts +1 -8
  199. package/frontendBuild/themeValidator.ts +4 -26
  200. package/frontendBuild/watchRootResolver.test.ts +23 -42
  201. package/frontendBuild/watchRootResolver.ts +5 -27
  202. package/getRelatedCnsts.ts +12 -92
  203. package/incrementalBuilder/buildBatch.proc.ts +147 -63
  204. package/incrementalBuilder/buildBatchProtocol.ts +13 -38
  205. package/incrementalBuilder/buildBatchRunner.test.ts +65 -0
  206. package/incrementalBuilder/buildBatchRunner.ts +26 -38
  207. package/incrementalBuilder/builderChannel.test.ts +24 -49
  208. package/incrementalBuilder/builderChannel.ts +6 -37
  209. package/incrementalBuilder/builderRequestRouter.test.ts +0 -8
  210. package/incrementalBuilder/builderRequestRouter.ts +5 -24
  211. package/incrementalBuilder/builderWorkQueue.test.ts +94 -0
  212. package/incrementalBuilder/builderWorkQueue.ts +126 -0
  213. package/incrementalBuilder/devWatchBatch.test.ts +21 -24
  214. package/incrementalBuilder/incrementalBuilder.host.test.ts +53 -56
  215. package/incrementalBuilder/incrementalBuilder.host.ts +68 -81
  216. package/incrementalBuilder/incrementalBuilder.proc.test.ts +270 -0
  217. package/incrementalBuilder/incrementalBuilder.proc.ts +596 -208
  218. package/index.ts +5 -19
  219. package/integration/devResourceProbe.ts +50 -56
  220. package/integration/devStability.integration.test.ts +105 -199
  221. package/integration/devStabilityHarness.test.ts +13 -28
  222. package/integration/devStabilityHarness.ts +65 -330
  223. package/integration/ssrMemoryProbe.ts +41 -167
  224. package/libSource.test.ts +4 -15
  225. package/libSource.ts +19 -31
  226. package/lint/__fixtures__/no-document-cookie/bad.tsx +4 -0
  227. package/lint/__fixtures__/no-document-cookie/good.tsx +7 -0
  228. package/lint/__fixtures__/no-web-only-api-outside-webkit/bad.tsx +7 -0
  229. package/lint/__fixtures__/no-web-only-api-outside-webkit/good.tsx +11 -0
  230. package/lint/__fixtures__/no-web-storage/bad.tsx +6 -0
  231. package/lint/__fixtures__/no-web-storage/good.tsx +7 -0
  232. package/lint/gritRules.test.ts +8 -24
  233. package/lint/no-deep-internal-import.grit +2 -2
  234. package/lint/no-document-cookie.grit +29 -0
  235. package/lint/no-redeclare-predefined-endpoint.grit +2 -1
  236. package/lint/no-web-only-api-outside-webkit.grit +44 -0
  237. package/lint/no-web-storage.grit +30 -0
  238. package/linter.test.ts +1 -2
  239. package/linter.ts +26 -116
  240. package/mobile/desktopBin.test.ts +182 -0
  241. package/mobile/desktopBin.ts +144 -0
  242. package/mobile/desktopServerStage.test.ts +252 -0
  243. package/mobile/desktopServerStage.ts +178 -0
  244. package/mobile/index.ts +9 -0
  245. package/mobile/mobileTarget.test.ts +57 -8
  246. package/mobile/mobileTarget.ts +45 -26
  247. package/mobile/nativeApi.test.ts +30 -0
  248. package/mobile/nativeApi.ts +40 -0
  249. package/mobile/nativeApp.test.ts +184 -0
  250. package/mobile/nativeApp.ts +454 -0
  251. package/mobile/nativeAppLine.test.ts +69 -0
  252. package/mobile/nativeAppLine.ts +71 -0
  253. package/mobile/nativeConfig.test.ts +438 -0
  254. package/mobile/nativeConfig.ts +294 -0
  255. package/mobile/nativePluginFolders.test.ts +81 -0
  256. package/mobile/nativePluginFolders.ts +60 -0
  257. package/mobile/nativeWebDir.test.ts +80 -0
  258. package/mobile/nativeWebDir.ts +66 -0
  259. package/mobile/placeholderAppId.test.ts +13 -0
  260. package/mobile/placeholderAppId.ts +16 -0
  261. package/mobile/usageDescriptions.test.ts +27 -0
  262. package/mobile/usageDescriptions.ts +24 -0
  263. package/package.json +11 -11
  264. package/packageExports.test.ts +3 -18
  265. package/packageExportsMap.ts +3 -19
  266. package/piDependencyPins.test.ts +46 -0
  267. package/platformTest/BunTestReport.test.ts +60 -0
  268. package/platformTest/BunTestReport.ts +65 -0
  269. package/platformTest/IdleWatchedProcess.test.ts +56 -0
  270. package/platformTest/IdleWatchedProcess.ts +73 -0
  271. package/platformTest/LinuxDockerTarget.ts +105 -0
  272. package/platformTest/PlatformTestRun.test.ts +64 -0
  273. package/platformTest/PlatformTestRun.ts +141 -0
  274. package/platformTest/PlatformTestTarget.ts +142 -0
  275. package/platformTest/TestSnapshot.test.ts +72 -0
  276. package/platformTest/TestSnapshot.ts +103 -0
  277. package/platformTest/WindowsSshTarget.ts +151 -0
  278. package/prompter.ts +3 -13
  279. package/qualityScanner.test.ts +9 -26
  280. package/qualityScanner.ts +200 -286
  281. package/recipeScanner.test.ts +7 -12
  282. package/recipeScanner.ts +9 -38
  283. package/repoIdentity.ts +2 -9
  284. package/routeSourceValidator.test.ts +0 -1
  285. package/routeSourceValidator.ts +7 -42
  286. package/scanInfo.ts +91 -225
  287. package/selectModel.ts +1 -2
  288. package/semver.ts +3 -5
  289. package/slicePlanner.test.ts +6 -24
  290. package/slicePlanner.ts +11 -32
  291. package/spinner.test.ts +3 -8
  292. package/spinner.ts +4 -16
  293. package/sshTunnel.ts +1 -6
  294. package/ssrScanner.test.ts +1 -6
  295. package/ssrScanner.ts +103 -113
  296. package/stripAnsi.ts +3 -0
  297. package/subspace.test.ts +9 -25
  298. package/subspace.ts +48 -115
  299. package/subspaceConfig.ts +3 -11
  300. package/testHelpers.ts +194 -0
  301. package/transforms/asyncDefaultExportDetector.ts +3 -11
  302. package/transforms/barrelAnalyzer.ts +43 -135
  303. package/transforms/barrelImportsPlugin.ts +38 -175
  304. package/transforms/externalizeFrameworkPlugin.test.ts +39 -118
  305. package/transforms/externalizeFrameworkPlugin.ts +13 -87
  306. package/transforms/moduleSyntax.ts +32 -0
  307. package/transforms/rscUseClientTransform.ts +42 -20
  308. package/transforms/serverEnvPlugin.test.ts +103 -0
  309. package/transforms/serverEnvPlugin.ts +41 -0
  310. package/transforms/transforms.test.ts +166 -44
  311. package/transforms/tsconfigPackageResolver.test.ts +6 -24
  312. package/transforms/useClientBundlePlugin.ts +6 -23
  313. package/typeChecker.ts +7 -63
  314. package/types.ts +2 -0
  315. package/uploadRelease.ts +10 -25
  316. package/workflow/artifacts.ts +18 -0
  317. package/workflow/executor.test.ts +1 -12
  318. package/workflow/executor.ts +101 -110
  319. package/workflow/index.ts +0 -1
  320. package/workflow/moduleIndex.test.ts +4 -15
  321. package/workflow/moduleIndex.ts +9 -49
  322. package/workflow/plan.ts +18 -25
  323. package/workflow/render.ts +80 -96
  324. package/workflow/rolloutGate.test.ts +1 -1
  325. package/workflow/source.ts +52 -69
  326. package/workflow/sourceAst.ts +50 -0
  327. package/workspaceLayout.test.ts +18 -3
  328. package/workspaceLayout.ts +22 -23
  329. package/abstractCompactor.ts +0 -112
  330. package/aiEditor.test.ts +0 -60
  331. package/aiEditor.ts +0 -387
  332. package/applicationReleasePackager.ts +0 -209
  333. package/capacitorApp.test.ts +0 -372
  334. package/capacitorApp.ts +0 -1648
  335. package/commandDecorators/commandMeta.ts +0 -7
  336. package/commandDecorators/types.ts +0 -10
  337. package/getDirname.ts +0 -4
  338. package/guideline.ts +0 -19
  339. package/local/optimize-resource/ipcprobe/child.ts +0 -1
  340. package/local/optimize-resource/ipcprobe/parent.ts +0 -11
  341. package/workflow/primitive.ts +0 -59
@@ -0,0 +1,145 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export class CsrDevPaths {
5
+ static readonly modulePrefix = "akan-module:";
6
+ static readonly stubPrefix = "stub:";
7
+ static readonly emptyStubPrefix = "stub:empty:";
8
+ static readonly vendorPrefix = "vendor:";
9
+ readonly root: string;
10
+
11
+ constructor(workspaceRoot: string) {
12
+ this.root = CsrDevPaths.realpath(workspaceRoot);
13
+ }
14
+
15
+ idOf(file: string): string {
16
+ const real = CsrDevPaths.realpath(file);
17
+ const relative = path.relative(this.root, real);
18
+ const id = relative.startsWith("..") || path.isAbsolute(relative) ? real : relative;
19
+ return id.split(path.sep).join("/");
20
+ }
21
+
22
+ fileOf(id: string): string {
23
+ return path.isAbsolute(id) ? id : path.join(this.root, id);
24
+ }
25
+
26
+ static isStub(target: string): boolean {
27
+ return target.startsWith(CsrDevPaths.stubPrefix);
28
+ }
29
+
30
+ static isVendorFile(file: string): boolean {
31
+ return file.includes(`${path.sep}node_modules${path.sep}`);
32
+ }
33
+
34
+ static isScript(file: string): boolean {
35
+ return /\.[cm]?[jt]sx?$/.test(file);
36
+ }
37
+
38
+ static mtimeOf(file: string): number {
39
+ return fs.statSync(file, { throwIfNoEntry: false })?.mtimeMs ?? -1;
40
+ }
41
+
42
+ static async hashOf(file: string): Promise<string> {
43
+ const source = Bun.file(file);
44
+ return (await source.exists()) ? Bun.hash(await source.arrayBuffer()).toString(36) : "";
45
+ }
46
+
47
+ //? `.native`: on Windows Bun's `fs.realpathSync` keeps the spelling it was given, where the native call returns the
48
+ //? disk's (as macOS's plain call does), and a module id must not depend on which spelling reached it first.
49
+ static realpath(file: string): string {
50
+ // A deleted file keeps its own path, so its id still matches the graph entry it leaves behind.
51
+ return fs.existsSync(file) ? fs.realpathSync.native(file) : path.resolve(file);
52
+ }
53
+
54
+ //? Present under this very name: on a case-insensitive disk (APFS, NTFS) `ui/card.tsx` still exists after a rename to
55
+ //? `ui/Card.tsx`, but the compiler names the module by the disk's spelling, so the old id is gone.
56
+ static isNamed(file: string): boolean {
57
+ try {
58
+ return fs.realpathSync.native(file) === file;
59
+ } catch {
60
+ // Missing is an answer here.
61
+ return false;
62
+ }
63
+ }
64
+
65
+ //? Bun's own precedence for a browser build, measured (Bun 1.4.2): the registry must pick the file every other build
66
+ //? picks, and a `require` call weighs the extensions differently from an import.
67
+ static readonly #extensions = {
68
+ import: [".tsx", ".jsx", ".mts", ".ts", ".mjs", ".js", ".cts", ".cjs"],
69
+ require: [".tsx", ".ts", ".jsx", ".cts", ".cjs", ".js", ".mjs", ".mts"],
70
+ } as const;
71
+
72
+ /** A relative import resolved against the disk as it is now: the file, a script extension, or a folder's entry. */
73
+ static resolveOnDisk(base: string, kind: "import" | "require" = "import"): string | null {
74
+ return CsrDevPaths.#asFile(base, kind) ?? CsrDevPaths.#asFolder(base, kind);
75
+ }
76
+
77
+ //? A specifier ending in `/`, `.` or `..` names the folder alone, for Bun too: `./Foo/` never takes a `Foo.tsx`.
78
+ static resolveRelative(dir: string, specifier: string, kind: "import" | "require" = "import"): string | null {
79
+ const base = path.resolve(dir, specifier);
80
+ const folder = /(^|[\\/])\.\.?$|[\\/]$/.test(specifier);
81
+ return folder ? CsrDevPaths.#asFolder(base, kind) : CsrDevPaths.resolveOnDisk(base, kind);
82
+ }
83
+
84
+ static #asFile(base: string, kind: "import" | "require"): string | null {
85
+ const isFile = (candidate: string) => fs.statSync(candidate, { throwIfNoEntry: false })?.isFile() ?? false;
86
+ return [base, ...CsrDevPaths.#extensions[kind].map((extension) => `${base}${extension}`)].find(isFile) ?? null;
87
+ }
88
+
89
+ //? A folder's package.json names its entry as a browser build reads it: `browser` (the string form), then `module`
90
+ //? (an import only), then `main`, which may itself name a folder; without one, its `index`.
91
+ static #asFolder(dir: string, kind: "import" | "require"): string | null {
92
+ const entry = CsrDevPaths.#packageEntryOf(dir, kind);
93
+ const named = entry
94
+ ? (CsrDevPaths.#asFile(path.join(dir, entry), kind) ?? CsrDevPaths.#indexOf(path.join(dir, entry), kind))
95
+ : null;
96
+ return named ?? CsrDevPaths.#indexOf(dir, kind);
97
+ }
98
+
99
+ static #indexOf(dir: string, kind: "import" | "require"): string | null {
100
+ return CsrDevPaths.#asFile(path.join(dir, "index"), kind);
101
+ }
102
+
103
+ static #packageEntryOf(dir: string, kind: "import" | "require"): string | null {
104
+ try {
105
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")) as Record<string, unknown>;
106
+ const fields = kind === "require" ? [pkg.browser, pkg.main] : [pkg.browser, pkg.module, pkg.main];
107
+ const entry = fields.find((field) => typeof field === "string");
108
+ return typeof entry === "string" ? entry : null;
109
+ } catch {
110
+ // No package.json, or one that names no entry: the folder's index answers.
111
+ return null;
112
+ }
113
+ }
114
+
115
+ //? The package folder's ctime: Bun installs by cloning (macOS) or hard-linking (Linux) its cache, so a file keeps the
116
+ //? cache's mtime, and a hard link's ctime moves whenever another project links the same version; the folder is made
117
+ //? by this project's install alone.
118
+ /** A bare specifier whose package landed in a `node_modules` at or above `from` after `since`. */
119
+ static installedSince(specifier: string, from: string, since: number): boolean {
120
+ const [first = "", second = ""] = specifier.split("/");
121
+ const name = first.startsWith("@") ? `${first}/${second}` : first;
122
+ if (!name || name.startsWith(".")) return false;
123
+ for (let dir = path.resolve(from); ; dir = path.dirname(dir)) {
124
+ const pkgDir = path.join(dir, "node_modules", name);
125
+ if (fs.existsSync(path.join(pkgDir, "package.json")))
126
+ return (fs.statSync(pkgDir, { throwIfNoEntry: false })?.ctimeMs ?? 0) > since;
127
+ if (path.dirname(dir) === dir) return false;
128
+ }
129
+ }
130
+
131
+ static tryResolve(specifier: string, from: string): string | null {
132
+ try {
133
+ return Bun.resolveSync(specifier, from);
134
+ } catch {
135
+ // Unresolvable is an answer here; the caller decides whether it is an error.
136
+ return null;
137
+ }
138
+ }
139
+
140
+ static lineCount(text: string): number {
141
+ let count = 0;
142
+ for (let index = text.indexOf("\n"); index !== -1; index = text.indexOf("\n", index + 1)) count += 1;
143
+ return count;
144
+ }
145
+ }
@@ -0,0 +1,239 @@
1
+ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
2
+ import { mkdtemp, rm } from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { CsrDevPaths } from "./csrDevPaths";
6
+ import { CsrDevResolver } from "./csrDevResolver";
7
+ import type { CsrDevContext } from "./csrDevTypes";
8
+
9
+ const context: CsrDevContext = {
10
+ pageEntries: [],
11
+ basePaths: [],
12
+ htmlBasePaths: [],
13
+ define: {},
14
+ optimizeImports: [],
15
+ configKey: "key",
16
+ refreshFile: null,
17
+ };
18
+
19
+ describe("CsrDevResolver before its resolution build", () => {
20
+ let root: string;
21
+ let paths: CsrDevPaths;
22
+ const file = (relative: string) => path.join(paths.root, relative);
23
+ const resolver = (runtimeResolved: Record<string, string[]> = {}) =>
24
+ new CsrDevResolver({
25
+ paths,
26
+ context,
27
+ entryFiles: [],
28
+ resolution: { "node_modules/pkg/a.js": { dep: "node_modules/dep/index.js" } },
29
+ runtimeResolved,
30
+ });
31
+
32
+ beforeAll(async () => {
33
+ root = await mkdtemp(path.join(os.tmpdir(), "akan-csr-dev-resolver-"));
34
+ for (const relative of [
35
+ "apps/demo/ui/Card.tsx",
36
+ "node_modules/pkg/a.js",
37
+ "node_modules/pkg/b.js",
38
+ "node_modules/dep/index.js",
39
+ "node_modules/fresh/index.js",
40
+ ])
41
+ await Bun.write(path.join(root, relative), "export {};\n");
42
+ await Bun.write(path.join(root, "node_modules/fresh/package.json"), '{ "name": "fresh", "main": "index.js" }');
43
+ paths = new CsrDevPaths(root);
44
+ });
45
+
46
+ afterAll(async () => {
47
+ await rm(root, { recursive: true, force: true });
48
+ });
49
+
50
+ test("a file of the same package reuses what the browser build resolved for a sibling", () => {
51
+ expect(resolver().resolve(file("node_modules/pkg/b.js"), "dep")).toBe(file("node_modules/dep/index.js"));
52
+ });
53
+
54
+ test("but not what Bun's runtime resolver found for it: that ignores the browser condition", () => {
55
+ const withFallback = resolver({ "node_modules/pkg/a.js": ["dep"] });
56
+ expect(withFallback.resolve(file("node_modules/pkg/b.js"), "dep")).toBeNull();
57
+ expect(withFallback.serializeRuntimeResolved()).toEqual({ "node_modules/pkg/a.js": ["dep"] });
58
+ });
59
+
60
+ test("the resolution build clears a runtime resolution it records itself, so a sibling may reuse it again", async () => {
61
+ await Bun.write(
62
+ file("apps/demo/ui/Fresh.tsx"),
63
+ 'import * as fresh from "fresh";\nexport const Fresh = () => fresh;\n',
64
+ );
65
+ const prepassed = new CsrDevResolver({
66
+ paths,
67
+ context,
68
+ entryFiles: [file("apps/demo/ui/Fresh.tsx")],
69
+ runtimeResolved: { "apps/demo/ui/Fresh.tsx": ["fresh"] },
70
+ });
71
+ await prepassed.prepass();
72
+ expect(prepassed.serializeRuntimeResolved()).toEqual({});
73
+ });
74
+
75
+ test("a relative import follows a file moved into a folder or renamed, which Bun's resolver cache does not", async () => {
76
+ const card = file("apps/demo/ui/Card.tsx");
77
+ await Bun.write(file("apps/demo/ui/Moved.tsx"), "export {};\n");
78
+ await Bun.write(file("apps/demo/ui/renamed.ts"), "export {};\n");
79
+ expect(CsrDevPaths.tryResolve("./Moved", path.dirname(card))).toBe(file("apps/demo/ui/Moved.tsx"));
80
+ expect(CsrDevPaths.tryResolve("./renamed", path.dirname(card))).toBe(file("apps/demo/ui/renamed.ts"));
81
+ await rm(file("apps/demo/ui/Moved.tsx"));
82
+ await Bun.write(file("apps/demo/ui/Moved/index.tsx"), "export {};\n");
83
+ await rm(file("apps/demo/ui/renamed.ts"));
84
+ await Bun.write(file("apps/demo/ui/renamed.tsx"), "export {};\n");
85
+
86
+ expect(resolver().resolve(card, "./Moved")).toBe(file("apps/demo/ui/Moved/index.tsx"));
87
+ expect(resolver().resolve(card, "./renamed")).toBe(file("apps/demo/ui/renamed.tsx"));
88
+ expect(() => resolver().resolve(card, "./Missing")).toThrow('cannot resolve "./Missing"');
89
+ });
90
+
91
+ test("a recorded target moved away since is reused by no sibling, nor by its importer, nor kept in the graph", () => {
92
+ const stale = new CsrDevResolver({
93
+ paths,
94
+ context,
95
+ entryFiles: [],
96
+ resolution: {
97
+ "node_modules/pkg/a.js": { dep: "node_modules/dep/old.js" },
98
+ "node_modules/pkg/b.js": { dep: "node_modules/dep/old.js" },
99
+ },
100
+ });
101
+ expect(stale.resolve(file("node_modules/pkg/b.js"), "dep")).toBeNull();
102
+ expect(stale.serialize()).toEqual({ "node_modules/pkg/a.js": {}, "node_modules/pkg/b.js": {} });
103
+ });
104
+
105
+ test("a new package in the user's code asks for the resolution build, and a typo fails like a missing file", () => {
106
+ expect(resolver().resolve(file("apps/demo/ui/Card.tsx"), "fresh")).toBeNull();
107
+ expect(() => resolver().resolve(file("apps/demo/ui/Card.tsx"), "frseh")).toThrow('cannot resolve "frseh"');
108
+ expect(resolver().resolve(file("node_modules/pkg/b.js"), "frseh")).toBeNull();
109
+ });
110
+ });
111
+
112
+ describe("CsrDevPaths.resolveOnDisk", () => {
113
+ let root: string;
114
+ const at = (relative: string) => path.join(root, relative);
115
+
116
+ beforeAll(async () => {
117
+ root = CsrDevPaths.realpath(await mkdtemp(path.join(os.tmpdir(), "akan-csr-dev-disk-")));
118
+ for (const relative of [
119
+ "pair/x.js",
120
+ "pair/x.mjs",
121
+ "fields/b.js",
122
+ "fields/m.js",
123
+ "fields/main.js",
124
+ "fields/index.js",
125
+ "dirmain/lib/index.js",
126
+ "dirmain/index.js",
127
+ "shadow/Foo/index.tsx",
128
+ "shadow/Page.tsx",
129
+ "sibling/x.ts",
130
+ "sibling/Page.tsx",
131
+ "slash/Foo/index.ts",
132
+ "slash/Foo.ts",
133
+ "slash/Page.tsx",
134
+ "req/index.js",
135
+ "req/main.js",
136
+ "req/m.js",
137
+ "modonly/index.js",
138
+ "modonly/m.js",
139
+ "dot/Foo.tsx",
140
+ "dot/Foo/index.tsx",
141
+ "dot/Foo/Sub/x.ts",
142
+ "bmap/lib/server.ts",
143
+ "bmap/lib/client.ts",
144
+ "bmap/entry.ts",
145
+ ])
146
+ await Bun.write(at(relative), "export {};\n");
147
+ await Bun.write(at("fields/package.json"), '{ "browser": "b.js", "module": "m.js", "main": "main.js" }');
148
+ await Bun.write(at("dirmain/package.json"), '{ "main": "lib" }');
149
+ await Bun.write(at("req/package.json"), '{ "module": "m.js", "main": "main.js" }');
150
+ await Bun.write(at("modonly/package.json"), '{ "module": "m.js" }');
151
+ await Bun.write(at("bmap/package.json"), '{ "browser": { "./lib/server.ts": "./lib/client.ts" } }');
152
+ });
153
+
154
+ afterAll(async () => {
155
+ await rm(root, { recursive: true, force: true });
156
+ });
157
+
158
+ test("weighs extensions as Bun's browser build does, a require call apart from an import", () => {
159
+ expect(CsrDevPaths.resolveOnDisk(at("pair/x"))).toBe(at("pair/x.mjs"));
160
+ expect(CsrDevPaths.resolveOnDisk(at("pair/x"), "require")).toBe(at("pair/x.js"));
161
+ });
162
+
163
+ test("enters a folder through browser, then module, then main (which may name a folder), then index", () => {
164
+ expect(CsrDevPaths.resolveOnDisk(at("fields"))).toBe(at("fields/b.js"));
165
+ expect(CsrDevPaths.resolveOnDisk(at("dirmain"))).toBe(at("dirmain/lib/index.js"));
166
+ });
167
+
168
+ test("a require call reads no module field: main when there is one, otherwise the index", () => {
169
+ expect(CsrDevPaths.resolveOnDisk(at("req"))).toBe(at("req/m.js"));
170
+ expect(CsrDevPaths.resolveOnDisk(at("req"), "require")).toBe(at("req/main.js"));
171
+ expect(CsrDevPaths.resolveOnDisk(at("modonly"), "require")).toBe(at("modonly/index.js"));
172
+ });
173
+
174
+ test("a bare import Bun cannot resolve here goes to a worker only when its package landed after this process began", async () => {
175
+ //? A package Bun's resolver misses in this process: it keeps node_modules listings, so one added by `bun add` while
176
+ //? the builder runs looks like this until a fresh process reads it.
177
+ await Bun.write(at("inst/node_modules/half/package.json"), '{ "name": "half", "main": "missing.js" }');
178
+ await Bun.write(at("inst/src/a.ts"), 'import "half";\n');
179
+ const started = (startedAt: number) =>
180
+ new CsrDevResolver({ paths: new CsrDevPaths(root), context, entryFiles: [], startedAt });
181
+ const before = started(Date.now() - 60_000);
182
+ expect(before.resolve(at("inst/src/a.ts"), "half")).toBeNull();
183
+ expect(before.resolve(at("inst/src/a.ts"), "half/formatt")).toBeNull();
184
+ expect(() => before.resolve(at("inst/src/a.ts"), "nowhere-to-be-found")).toThrow("cannot resolve");
185
+ //? Installed before the builder started: its listing is Bun's, so a miss is a typo or a half-typed import.
186
+ const after = started(Date.now() + 60_000);
187
+ expect(() => after.resolve(at("inst/src/a.ts"), "half/formatt")).toThrow("cannot resolve");
188
+ });
189
+
190
+ test('"." and ".." name the folder alone, as a trailing slash does', () => {
191
+ expect(CsrDevPaths.resolveRelative(at("dot/Foo"), ".")).toBe(at("dot/Foo/index.tsx"));
192
+ expect(CsrDevPaths.resolveRelative(at("dot/Foo/Sub"), "..")).toBe(at("dot/Foo/index.tsx"));
193
+ });
194
+
195
+ test("app code under a package.json that maps files with a browser object keeps the recorded answer", () => {
196
+ const resolver = new CsrDevResolver({
197
+ paths: new CsrDevPaths(root),
198
+ context,
199
+ entryFiles: [],
200
+ resolution: { "bmap/entry.ts": { "./lib/server": "bmap/lib/client.ts" } },
201
+ });
202
+ expect(resolver.resolve(at("bmap/entry.ts"), "./lib/server")).toBe(at("bmap/lib/client.ts"));
203
+ });
204
+
205
+ test("a specifier ending in a slash names the folder alone", () => {
206
+ expect(CsrDevPaths.resolveRelative(at("slash"), "./Foo/")).toBe(at("slash/Foo/index.ts"));
207
+ expect(CsrDevPaths.resolveRelative(at("slash"), "./Foo")).toBe(at("slash/Foo.ts"));
208
+ });
209
+
210
+ test("a recorded relative resolution stands while the disk resolves it the same way", async () => {
211
+ const paths = new CsrDevPaths(root);
212
+ const resolver = new CsrDevResolver({
213
+ paths,
214
+ context,
215
+ entryFiles: [],
216
+ resolution: {
217
+ "shadow/Page.tsx": { "./Foo": "shadow/Foo/index.tsx" },
218
+ "pair/entry.ts": { "./x": "pair/x.js" },
219
+ },
220
+ });
221
+ expect(resolver.resolve(at("pair/entry.ts"), "./x", "require")).toBe(at("pair/x.js"));
222
+ expect(resolver.resolve(at("pair/entry.ts"), "./x")).toBe(at("pair/x.mjs"));
223
+ expect(resolver.resolve(at("shadow/Page.tsx"), "./Foo")).toBe(at("shadow/Foo/index.tsx"));
224
+ await Bun.write(at("shadow/Foo.tsx"), "export {};\n");
225
+ expect(resolver.resolve(at("shadow/Page.tsx"), "./Foo")).toBe(at("shadow/Foo.tsx"));
226
+ });
227
+
228
+ test("a sibling with a stronger extension takes over a recorded import", async () => {
229
+ const resolver = new CsrDevResolver({
230
+ paths: new CsrDevPaths(root),
231
+ context,
232
+ entryFiles: [],
233
+ resolution: { "sibling/Page.tsx": { "./x": "sibling/x.ts" } },
234
+ });
235
+ expect(resolver.resolve(at("sibling/Page.tsx"), "./x")).toBe(at("sibling/x.ts"));
236
+ await Bun.write(at("sibling/x.tsx"), "export {};\n");
237
+ expect(resolver.resolve(at("sibling/Page.tsx"), "./x")).toBe(at("sibling/x.tsx"));
238
+ });
239
+ });