@akanjs/devkit 3.0.0-beta.3 → 3.0.0-beta.30

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 +2 -2
  3. package/README.md +3 -2
  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 +404 -141
  19. package/akanConfig/akanConfig.ts +317 -274
  20. package/akanConfig/index.ts +1 -0
  21. package/akanConfig/types.ts +21 -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 +269 -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 +177 -0
  241. package/mobile/desktopBin.ts +143 -0
  242. package/mobile/desktopServerStage.test.ts +204 -0
  243. package/mobile/desktopServerStage.ts +138 -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 +151 -0
  250. package/mobile/nativeApp.ts +405 -0
  251. package/mobile/nativeAppLine.test.ts +69 -0
  252. package/mobile/nativeAppLine.ts +71 -0
  253. package/mobile/nativeConfig.test.ts +414 -0
  254. package/mobile/nativeConfig.ts +296 -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 +30 -25
  298. package/subspace.ts +81 -115
  299. package/subspaceConfig.ts +11 -9
  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
@@ -7,11 +7,12 @@ import { FileSys } from "./fileSys";
7
7
  import type { PackageJson, TsConfigJson } from "./types";
8
8
 
9
9
  const testFileRegex = /\.(?:test|spec)\.[cm]?[tj]sx?$/;
10
- // `__fixtures__` holds deliberately invalid sample sources for the lint-rule suites; tsconfig `exclude` and
11
- // biome's `!` override already skip them, and their imports are violations rather than real dependencies.
10
+ // `__fixtures__` holds deliberately invalid lint-rule samples, whose imports are violations, not dependencies.
12
11
  const skipDirs = ["node_modules", "dist", "build", ".git", ".next", "public", "ios", "android", "__fixtures__"];
13
- const isSkippedPath = (filePath: string) =>
14
- skipDirs.some((dir) => filePath.includes(`/${dir}/`) || filePath.startsWith(`${dir}/`));
12
+ const isSkippedPath = (filePath: string) => {
13
+ const posixPath = filePath.split(path.sep).join("/");
14
+ return skipDirs.some((dir) => posixPath.includes(`/${dir}/`) || posixPath.startsWith(`${dir}/`));
15
+ };
15
16
  const builtinModuleSet = new Set([...builtinModules, ...builtinModules.map((mod) => `node:${mod}`)]);
16
17
  const stripShebang = (source: string) => source.replace(/^#!.*(?:\r?\n|$)/, "");
17
18
 
@@ -73,11 +74,8 @@ export class TypeScriptDependencyScanner {
73
74
  ): Promise<{ npmDeps: string[]; npmDevDeps: string[]; missingDeps: string[] }> {
74
75
  const runtimeDeps = new Set<string>();
75
76
  const devDeps = new Set<string>();
76
- const sourceFiles = await this.#findTypeScriptFiles(this.#directory, {
77
- excludeBuildFiles: true,
78
- excludeTestFiles: true,
79
- });
80
- const cssFiles = await this.#findCssFiles(this.#directory);
77
+ const sourceFiles = await this.#findFiles("**/*.{ts,tsx}", { excludeBuildFiles: true, excludeTestFiles: true });
78
+ const cssFiles = await this.#findFiles("**/*.css");
81
79
 
82
80
  for (const filePath of sourceFiles) {
83
81
  const fileContent = await FileSys.readText(filePath);
@@ -133,25 +131,19 @@ export class TypeScriptDependencyScanner {
133
131
  depSets: DepSets,
134
132
  fileDependencies: Map<string, string[]>,
135
133
  ): DepSets {
136
- const importedDepSets = new Array<Set<string>>(depSets.length);
137
- for (let i = 0; i < depSets.length; i++) importedDepSets[i] = new Set<string>();
138
- fileDependencies.forEach((imps) => {
139
- imps.forEach((imp) => {
140
- if (imp.startsWith(".")) return;
141
- const moduleName = imp;
142
- const moduleNameParts = moduleName.split("/");
143
- const subModuleLength = moduleNameParts.length;
144
- for (let i = 0; i < subModuleLength; i++) {
145
- const importName = moduleNameParts.slice(0, i + 1).join("/");
146
- for (let j = 0; j < depSets.length; j++) {
147
- if (depSets[j]?.has(importName)) {
148
- importedDepSets[j]?.add(importName);
149
- return;
150
- }
151
- }
134
+ const importedDepSets = depSets.map(() => new Set<string>());
135
+ for (const imps of fileDependencies.values())
136
+ for (const imp of imps) {
137
+ if (imp.startsWith(".")) continue;
138
+ const parts = imp.split("/");
139
+ for (let length = 1; length <= parts.length; length++) {
140
+ const importName = parts.slice(0, length).join("/");
141
+ const setIndex = depSets.findIndex((depSet) => depSet.has(importName));
142
+ if (setIndex === -1) continue;
143
+ importedDepSets[setIndex]?.add(importName);
144
+ break;
152
145
  }
153
- });
154
- });
146
+ }
155
147
  return importedDepSets as DepSets;
156
148
  }
157
149
 
@@ -161,48 +153,24 @@ export class TypeScriptDependencyScanner {
161
153
  this.#fileTypeDependencies.clear();
162
154
  this.#visitedFiles.clear();
163
155
 
164
- const files = await this.#findTypeScriptFiles(this.#directory);
165
-
166
- for (const file of files) await this.#analyzeFile(file, this.#directory);
167
-
156
+ for (const file of await this.#findFiles("**/*.{ts,tsx}")) await this.#analyzeFile(file, this.#directory);
168
157
  return this.#fileDependencies;
169
158
  }
170
159
 
171
- async #findTypeScriptFiles(
172
- directory: string,
160
+ async #findFiles(
161
+ pattern: string,
173
162
  {
174
163
  excludeBuildFiles = false,
175
164
  excludeTestFiles = false,
176
165
  }: { excludeBuildFiles?: boolean; excludeTestFiles?: boolean } = {},
177
166
  ): Promise<string[]> {
178
167
  const files: string[] = [];
179
-
180
- const glob = new Bun.Glob("**/*.{ts,tsx}");
181
- for await (const filePath of glob.scan({ cwd: directory, onlyFiles: true })) {
168
+ for await (const filePath of new Bun.Glob(pattern).scan({ cwd: this.#directory, onlyFiles: true })) {
182
169
  if (isSkippedPath(filePath)) continue;
183
170
  if (excludeBuildFiles && filePath === "build.ts") continue;
184
171
  if (excludeTestFiles && testFileRegex.test(filePath)) continue;
185
-
186
- const fullPath = path.join(directory, filePath);
187
- const relativePath = path.relative(this.#workspaceRoot, fullPath);
188
- if (this.#ig.ignores(relativePath)) continue;
189
-
190
- files.push(fullPath);
191
- }
192
- return files;
193
- }
194
-
195
- async #findCssFiles(directory: string): Promise<string[]> {
196
- const files: string[] = [];
197
- const glob = new Bun.Glob("**/*.css");
198
- for await (const filePath of glob.scan({ cwd: directory, onlyFiles: true })) {
199
- if (isSkippedPath(filePath)) continue;
200
-
201
- const fullPath = path.join(directory, filePath);
202
- const relativePath = path.relative(this.#workspaceRoot, fullPath);
203
- if (this.#ig.ignores(relativePath)) continue;
204
-
205
- files.push(fullPath);
172
+ const fullPath = path.join(this.#directory, filePath);
173
+ if (!this.#ig.ignores(path.relative(this.#workspaceRoot, fullPath))) files.push(fullPath);
206
174
  }
207
175
  return files;
208
176
  }
@@ -215,12 +183,8 @@ export class TypeScriptDependencyScanner {
215
183
  try {
216
184
  const fileContent = await FileSys.readText(filePath);
217
185
  const { imports, typeImports } = this.#extractImports(fileContent, filePath);
218
-
219
- // Convert imports to absolute or relative paths
220
186
  const resolvedImports = await this.#resolveImports(imports, filePath, baseDir);
221
187
  const resolvedTypeImports = await this.#resolveImports(typeImports, filePath, baseDir);
222
-
223
- // Store the dependencies
224
188
  const relativePath = path.relative(baseDir, filePath);
225
189
  this.#fileDependencies.set(relativePath, [...new Set([...resolvedImports, ...resolvedTypeImports])]);
226
190
  this.#fileRuntimeDependencies.set(relativePath, resolvedImports);
@@ -233,12 +197,8 @@ export class TypeScriptDependencyScanner {
233
197
  async #resolveImports(imports: string[], filePath: string, baseDir: string): Promise<string[]> {
234
198
  return await Promise.all(
235
199
  imports.map(async (importPath) => {
236
- if (importPath.startsWith(".")) {
237
- // Handle relative imports
238
- const resolvedPath = `./${path.join(path.relative(baseDir, filePath), importPath)}`;
239
- return await this.#ensureExtension(resolvedPath);
240
- }
241
- return importPath; // Keep package imports as is
200
+ if (!importPath.startsWith(".")) return importPath;
201
+ return await this.#ensureExtension(`./${path.join(path.relative(baseDir, filePath), importPath)}`);
242
202
  }),
243
203
  );
244
204
  }
@@ -324,19 +284,10 @@ export class TypeScriptDependencyScanner {
324
284
  const projectImports = imports.filter((i) => !i.startsWith("react") && !i.startsWith("@"));
325
285
  const externalImports = imports.filter((i) => i.startsWith("react") || i.startsWith("@"));
326
286
 
327
- if (projectImports.length > 0) {
328
- graph += " Project dependencies:\n";
329
- projectImports.forEach((imp) => {
330
- graph += ` → ${imp}\n`;
331
- });
332
- }
333
-
334
- if (externalImports.length > 0) {
335
- graph += " External dependencies:\n";
336
- externalImports.forEach((imp) => {
337
- graph += ` → ${imp}\n`;
338
- });
339
- }
287
+ if (projectImports.length > 0)
288
+ graph += ` Project dependencies:\n${projectImports.map((imp) => ` → ${imp}\n`).join("")}`;
289
+ if (externalImports.length > 0)
290
+ graph += ` External dependencies:\n${externalImports.map((imp) => ` → ${imp}\n`).join("")}`;
340
291
 
341
292
  graph += "\n";
342
293
  }
@@ -1,29 +1,14 @@
1
- import { afterEach, describe, expect, test } from "bun:test";
2
- import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
3
- import os from "node:os";
1
+ import { describe, expect, test } from "bun:test";
2
+ import { realpath } from "node:fs/promises";
4
3
  import path from "node:path";
5
4
  import { ApplicationBuildReporter } from "./applicationBuildReporter";
6
5
  import { resolveSignalTestPreloadPath } from "./applicationTestPreload";
7
6
  import { TypeScriptDependencyScanner } from "./dependencyScanner";
8
7
  import { AppExecutor, WorkspaceExecutor } from "./executors";
8
+ import { tempDirs, writeText as write } from "./testHelpers";
9
9
  import type { PackageJson, TsConfigJson } from "./types";
10
10
 
11
- const tempRoots: string[] = [];
12
-
13
- const makeTempRoot = async () => {
14
- const root = await mkdtemp(path.join(os.tmpdir(), "akan-devkit-utils-"));
15
- tempRoots.push(root);
16
- return root;
17
- };
18
-
19
- const write = async (filePath: string, content: string) => {
20
- await mkdir(path.dirname(filePath), { recursive: true });
21
- await writeFile(filePath, content);
22
- };
23
-
24
- afterEach(async () => {
25
- await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
26
- });
11
+ const makeTempRoot = tempDirs("akan-devkit-utils-");
27
12
 
28
13
  describe("resolveSignalTestPreloadPath", () => {
29
14
  test("resolves the preload file from an installed akanjs package", async () => {
@@ -40,7 +25,7 @@ describe("resolveSignalTestPreloadPath", () => {
40
25
  await write(path.join(root, "node_modules/akanjs/test/signalTest.preload.ts"), "export {};\n");
41
26
 
42
27
  await expect(resolveSignalTestPreloadPath({ cwdPath: libDir })).resolves.toContain(
43
- "node_modules/akanjs/test/signalTest.preload.ts",
28
+ path.join("node_modules/akanjs/test/signalTest.preload.ts"),
44
29
  );
45
30
  });
46
31
  });
@@ -213,4 +198,47 @@ describe("ApplicationBuildReporter", () => {
213
198
  ["failed", " first", " second", " third"].join("\n"),
214
199
  );
215
200
  });
201
+
202
+ test("says where the bundler placed each reason, relative to the workspace root", async () => {
203
+ const root = await makeTempRoot();
204
+ const refuseWith = (message: string) => ({
205
+ name: "refuse",
206
+ setup: (build: Bun.PluginBuilder) => {
207
+ build.onLoad({ filter: /plugged\.ts$/ }, () => {
208
+ throw new Error(message);
209
+ });
210
+ },
211
+ });
212
+ const failureOf = (entry: string, plugins: Bun.BunPlugin[] = []) =>
213
+ Bun.build({ entrypoints: [path.join(root, entry)], plugins }).then(
214
+ () => null,
215
+ (error: unknown) => error,
216
+ );
217
+ await write(
218
+ path.join(root, "src/entry.ts"),
219
+ 'export const a = 1;\nimport { gone } from "./not-there";\nexport const b = gone;\n',
220
+ );
221
+ await write(path.join(root, "src/plugged.ts"), "export const b = 1;\n");
222
+
223
+ const unresolved = await failureOf("src/entry.ts");
224
+ expect(ApplicationBuildReporter.formatError(unresolved, root)).toBe(
225
+ ["Bundle failed", ' Could not resolve: "./not-there" (src/entry.ts:2:22)'].join("\n"),
226
+ );
227
+ const outside = path.join(await realpath(root), "src/entry.ts");
228
+ expect(ApplicationBuildReporter.formatError(unresolved, path.join(root, "elsewhere"))).toBe(
229
+ ["Bundle failed", ` Could not resolve: "./not-there" (${outside}:2:22)`].join("\n"),
230
+ );
231
+ expect(
232
+ ApplicationBuildReporter.formatError(
233
+ await failureOf("src/plugged.ts", [refuseWith("refused\nsecond line")]),
234
+ root,
235
+ ),
236
+ ).toBe(["Bundle failed", " refused (src/plugged.ts)", " second line"].join("\n"));
237
+ expect(
238
+ ApplicationBuildReporter.formatError(
239
+ await failureOf("src/plugged.ts", [refuseWith("src/plugged.ts is refused")]),
240
+ root,
241
+ ),
242
+ ).toBe(["Bundle failed", " src/plugged.ts is refused"].join("\n"));
243
+ });
216
244
  });
@@ -0,0 +1,103 @@
1
+ import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
+ import * as fsPromises from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+
6
+ //? The Windows VM runs as an administrator, so a refused link only ever happens here — injected, on every OS.
7
+ const realSymlink = fsPromises.symlink;
8
+ let refuseLinks = false;
9
+ mock.module("node:fs/promises", () => ({
10
+ ...fsPromises,
11
+ symlink: async (...args: Parameters<typeof fsPromises.symlink>) => {
12
+ if (refuseLinks)
13
+ throw Object.assign(new Error(`EPERM: operation not permitted, symlink '${String(args[0])}'`), {
14
+ code: "EPERM",
15
+ });
16
+ return await realSymlink(...args);
17
+ },
18
+ }));
19
+ const { AppExecutor, WorkspaceExecutor } = await import("./executors");
20
+
21
+ const { lstat, mkdir, mkdtemp, readFile, rm, writeFile } = fsPromises;
22
+ const originalEnv = { ...process.env };
23
+ const originalPlatform = process.platform;
24
+ const tempRoots: string[] = [];
25
+ const PAGE_SOURCE = "export default function Page() {\n return null;\n}\n";
26
+
27
+ const setPlatform = (platform: NodeJS.Platform) =>
28
+ Object.defineProperty(process, "platform", { value: platform, configurable: true, writable: true });
29
+
30
+ // `AppExecutor.from` memoises by name, so each test needs a name no other test has used.
31
+ const makeApp = async (appName: string) => {
32
+ const root = await mkdtemp(path.join(os.tmpdir(), "akan-link-fallback-"));
33
+ tempRoots.push(root);
34
+ process.env.AKAN_PUBLIC_REPO_NAME = "repo";
35
+ process.env.AKAN_PUBLIC_SERVE_DOMAIN = "example.com";
36
+ process.env.AKAN_PUBLIC_ENV = "local";
37
+ process.env.PORT_OFFSET = "0";
38
+ await writeFile(path.join(root, "package.json"), JSON.stringify({ name: "fixture", version: "1.0.0" }));
39
+ await mkdir(path.join(root, "libs/shared/public"), { recursive: true });
40
+ await writeFile(path.join(root, "libs/shared/public/logo.png"), "logo");
41
+ await mkdir(path.join(root, "libs/shared/page/about"), { recursive: true });
42
+ await writeFile(path.join(root, "libs/shared/page/about/_index.tsx"), PAGE_SOURCE);
43
+ await mkdir(path.join(root, "apps", appName, "page"), { recursive: true });
44
+ await writeFile(path.join(root, "apps", appName, "akan.config.ts"), "export default { syncPageLibs: true };\n");
45
+ const workspace = new WorkspaceExecutor({ workspaceRoot: root, repoName: "repo" });
46
+ return { root, app: AppExecutor.from(workspace, appName), appRoot: path.join(root, "apps", appName) };
47
+ };
48
+
49
+ beforeEach(() => {
50
+ process.env = { ...originalEnv };
51
+ refuseLinks = true;
52
+ });
53
+
54
+ afterEach(async () => {
55
+ refuseLinks = false;
56
+ setPlatform(originalPlatform);
57
+ process.env = { ...originalEnv };
58
+ await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
59
+ });
60
+
61
+ describe("lib links the OS refuses to create", () => {
62
+ test("Windows gets a copy of the lib assets, and a resync replaces it without touching the lib", async () => {
63
+ setPlatform("win32");
64
+ const { root, app, appRoot } = await makeApp("fallback-assets");
65
+
66
+ await app.syncAssets(["shared"]);
67
+ const copied = path.join(appRoot, "public/libs/shared");
68
+ expect((await lstat(copied)).isSymbolicLink()).toBe(false);
69
+ expect(await readFile(path.join(copied, "logo.png"), "utf8")).toBe("logo");
70
+
71
+ await writeFile(path.join(root, "libs/shared/public/logo.png"), "logo-v2");
72
+ await app.syncAssets(["shared"]);
73
+ expect(await readFile(path.join(copied, "logo.png"), "utf8")).toBe("logo-v2");
74
+
75
+ await app.syncAssets([]);
76
+ expect(await lstat(path.join(appRoot, "public/libs")).catch(() => null)).toBeNull();
77
+ expect(await readFile(path.join(root, "libs/shared/public/logo.png"), "utf8")).toBe("logo-v2");
78
+ });
79
+
80
+ test("Windows gets a copy of the lib pages that still routes, and a resync picks up a new lib route", async () => {
81
+ setPlatform("win32");
82
+ const { root, app, appRoot } = await makeApp("fallback-pages");
83
+
84
+ expect(await app.syncPages(["shared"])).toBe(true);
85
+ expect((await lstat(path.join(appRoot, "page/(libs)/(shared)"))).isSymbolicLink()).toBe(false);
86
+ expect(await app.getPageKeys({ refresh: true })).toEqual(["./(libs)/(shared)/about/_index.tsx"]);
87
+
88
+ await mkdir(path.join(root, "libs/shared/page/terms"), { recursive: true });
89
+ await writeFile(path.join(root, "libs/shared/page/terms/_index.tsx"), PAGE_SOURCE);
90
+ expect(await app.syncPages(["shared"])).toBe(true);
91
+ expect(await app.getPageKeys({ refresh: true })).toEqual([
92
+ "./(libs)/(shared)/about/_index.tsx",
93
+ "./(libs)/(shared)/terms/_index.tsx",
94
+ ]);
95
+ });
96
+
97
+ test("elsewhere the refusal surfaces rather than being papered over with a copy", async () => {
98
+ setPlatform(originalPlatform === "win32" ? "linux" : originalPlatform);
99
+ const { app } = await makeApp("fallback-posix");
100
+
101
+ await expect(app.syncAssets(["shared"])).rejects.toThrow("EPERM");
102
+ });
103
+ });