@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
@@ -3,17 +3,12 @@ import type { BunPlugin } from "bun";
3
3
  import ts from "typescript";
4
4
  import type { App } from "../commandDecorators";
5
5
  import { BarrelAnalyzer, type BarrelExportMap, type PackageEntry } from "./barrelAnalyzer";
6
+ import { loaderFor, type NamedItem, parseNamedList } from "./moduleSyntax";
6
7
 
7
8
  export interface BarrelImportsPluginOptions {
8
- /** Absolute paths whose content should be returned unchanged (e.g. node_modules). */
9
+ /** Absolute paths whose content is returned unchanged (e.g. node_modules). */
9
10
  skipPath?: (absPath: string) => boolean;
10
- /**
11
- * Optional transform applied after the barrel rewrite in the same onLoad
12
- * pass. Useful to chain further transforms (e.g. `"use client"` stubbing)
13
- * that would otherwise be blocked — Bun's `onLoad` cannot fall through to a
14
- * second plugin once a response is returned.
15
- * Return the transformed source, or `null` to indicate no change.
16
- */
11
+ /** Runs after the rewrite in the same onLoad (Bun's onLoad cannot fall through); null means no change. */
17
12
  pipeAfter?: (source: string, args: { path: string }) => string | Promise<string | null> | null;
18
13
  }
19
14
 
@@ -30,13 +25,8 @@ export const createBarrelImportsPlugin = async (
30
25
  return {
31
26
  name: "barrel-imports",
32
27
  setup(build) {
33
- // Exclude third-party node_modules, but keep node_modules/akanjs in generated
34
- // workspaces so framework `"use client"` modules are still stubbed for RSC.
35
- //
36
- // The optional `(\?v=\d+)?` tail lets the filter match paths that HMR
37
- // has version-tagged (`./_index.tsx?v=3`). We strip the query before
38
- // reading and normalize it away from the path we pass downstream so
39
- // only the cache-bust `pipeAfter` step (if any) knows about it.
28
+ // node_modules/akanjs stays in: framework `"use client"` modules still need RSC stubbing in generated workspaces.
29
+ // The optional `?v=N` tail matches HMR cache-busted paths; it is stripped before reading.
40
30
  build.onLoad(
41
31
  {
42
32
  filter:
@@ -45,27 +35,12 @@ export const createBarrelImportsPlugin = async (
45
35
  async (args) => {
46
36
  const realPath = args.path.replace(/\?v=\d+$/, "");
47
37
  const loader = loaderFor(realPath);
48
- if (skipPath(realPath)) {
49
- const raw = await Bun.file(realPath).text();
50
- return { contents: raw, loader };
51
- }
52
-
53
38
  let source = await Bun.file(realPath).text();
39
+ if (skipPath(realPath)) return { contents: source, loader };
54
40
 
55
- // Bun's macro evaluator has a race condition when the plugin returns
56
- // rewritten source for a module that also contains
57
- // `with { type: "macro" }` imports *and* another macro-host file is
58
- // being evaluated concurrently in the same graph: one of the macro
59
- // identifiers ends up undefined at runtime (e.g. `getSerializedSignal
60
- // is not defined`). Returning the unmodified source for macro hosts
61
- // sidesteps the race. The trade-off is that a handful of `useClient`-
62
- // style files keep their original barrel imports; the rest of the
63
- // tree still benefits from flattening.
64
- const hasMacroAttr = MACRO_ATTR_RE.test(source);
65
-
66
- if (!hasMacroAttr && barrels.length > 0) {
67
- // The pre-check that used to live here — "does this file mention a barrel at all" — now
68
- // lives inside `rewriteBarrelImports`, so every caller gets it rather than just this one.
41
+ // Bun's macro evaluator races when a rewritten module holds `with { type: "macro" }` imports while another
42
+ // macro host evaluates (a macro identifier ends up undefined), so macro hosts keep their barrel imports.
43
+ if (!MACRO_ATTR_RE.test(source) && barrels.length > 0) {
69
44
  const rewritten = await rewriteBarrelImports(source, barrels, analyzer);
70
45
  if (rewritten !== null) source = rewritten;
71
46
  }
@@ -82,24 +57,16 @@ export const createBarrelImportsPlugin = async (
82
57
  };
83
58
  };
84
59
 
85
- /**
86
- * Build a `resolvePackage` that maps a package specifier (like `akanjs/ui`)
87
- * to its barrel entry file using the workspace tsconfig `paths`. If no direct
88
- * mapping exists, falls back to node_modules resolution.
89
- */
60
+ /** Resolves a package specifier to its barrel entry via tsconfig `paths`, falling back to node_modules. */
90
61
  export const createTsconfigPackageResolver = async (
91
62
  app: App,
92
63
  ): Promise<(pkgName: string) => Promise<PackageEntry | null>> => {
93
64
  const tsconfig = await app.getTsConfig();
94
65
  const tsconfigPaths = tsconfig.compilerOptions.paths ?? {};
95
- // Pre-compute wildcard entries so we don't walk the full map per lookup.
96
- // Longer prefixes sort first so `@libs/util/*` wins over `@libs/*`.
66
+ // Longest prefix first so `@libs/util/*` wins over `@libs/*`.
97
67
  const wildcardEntries = Object.entries(tsconfigPaths)
98
68
  .filter(([k]) => k.endsWith("/*"))
99
- .map(([k, v]) => ({
100
- prefix: k.slice(0, -1), // keep trailing `/`
101
- replacements: v,
102
- }))
69
+ .map(([k, v]) => ({ prefix: k.slice(0, -1), replacements: v }))
103
70
  .sort((a, b) => b.prefix.length - a.prefix.length);
104
71
 
105
72
  return async (pkgName) => {
@@ -109,34 +76,16 @@ export const createTsconfigPackageResolver = async (
109
76
  if (!raw) return null;
110
77
  const entryFile = path.resolve(app.workspace.workspaceRoot, raw);
111
78
  if (!(await Bun.file(entryFile).exists())) return null;
112
- // Detect "facet" barrels: specifiers like `@libs/util/server` whose entry
113
- // file is a sibling inside the parent directory (`libs/util/server.ts`)
114
- // rather than an `index.*` inside a dedicated package directory
115
- // (`libs/util/server/index.ts`). For facets, the subpath the analyzer
116
- // generates for a leaf like `libs/util/lib/sig.ts` must be computed
117
- // against the parent package (`@libs/util`) so that the rewritten
118
- // import resolves via the workspace's `@libs/*` tsconfig wildcard
119
- // (`libs/util/lib/sig`). Using the raw `pkgName` (`@libs/util/server`)
120
- // would generate `@libs/util/server/lib/sig`, a path that does not exist
121
- // on disk and cannot be imported.
79
+ // A facet barrel (`@libs/util/server` -> `libs/util/server.ts`) takes subpaths from its parent package, so a leaf
80
+ // rewrites to `@libs/util/lib/sig` (resolvable via `@libs/*`), not the missing `@libs/util/server/lib/sig`.
122
81
  const parsed = path.parse(entryFile);
123
82
  const lastSlash = pkgName.lastIndexOf("/");
124
- if (parsed.name !== "index" && lastSlash !== -1) {
125
- const facet = pkgName.slice(lastSlash + 1);
126
- const parentSpec = pkgName.slice(0, lastSlash);
127
- if (facet === parsed.name && parentSpec.length > 0) {
128
- return { pkgName: parentSpec, entryFile, pkgDir: parsed.dir };
129
- }
130
- }
83
+ if (parsed.name !== "index" && lastSlash > 0 && pkgName.slice(lastSlash + 1) === parsed.name)
84
+ return { pkgName: pkgName.slice(0, lastSlash), entryFile, pkgDir: parsed.dir };
131
85
  return { pkgName, entryFile, pkgDir: path.dirname(entryFile) };
132
86
  }
133
87
 
134
- // Wildcard fallback: tsconfig entries like `@libs/*` → `./libs/*` map a
135
- // whole family of specifiers to workspace directories. Without this,
136
- // barrels such as `@libs/util/ui` or `@apps/minimal/client` never resolve
137
- // to an entry file — the analyzer returns `null`, the plugin skips them,
138
- // and the consumer falls back to Bun's default resolution which loads the
139
- // full barrel (pulling the entire transitive macro / side-effect graph).
88
+ // Without the wildcard fallback `@libs/util/ui`-style barrels never resolve and load whole, macro graph included.
140
89
  for (const { prefix, replacements } of wildcardEntries) {
141
90
  if (!pkgName.startsWith(prefix)) continue;
142
91
  const suffix = pkgName.slice(prefix.length);
@@ -144,41 +93,25 @@ export const createTsconfigPackageResolver = async (
144
93
  if (!repl) continue;
145
94
  const replPath = repl.endsWith("/*") ? repl.slice(0, -1) : repl;
146
95
  const candidate = path.resolve(app.workspace.workspaceRoot, replPath + suffix);
147
- // Try `candidate.<ext>` first (facet-barrel: sibling file like
148
- // `apps/minimal/client.ts`). If it exists we treat the PARENT directory
149
- // as `pkgDir` and the PARENT specifier (`@apps/minimal`) as `pkgName`
150
- // so a leaf at `apps/minimal/lib/useClient.ts` rewrites to
151
- // `@apps/minimal/lib/useClient` and resolves via the same `@apps/*`
152
- // wildcard. Using the raw `pkgName` would yield
153
- // `@apps/minimal/client/lib/useClient`, a path that does not exist.
96
+ // A sibling file (`apps/minimal/client.ts`) is a facet barrel too: parent specifier, as above.
154
97
  for (const ext of CANDIDATE_EXTS) {
155
98
  const file = `${candidate}${ext}`;
156
99
  if (await Bun.file(file).exists()) {
157
100
  const lastSlash = pkgName.lastIndexOf("/");
158
- if (lastSlash !== -1) {
159
- const parentSpec = pkgName.slice(0, lastSlash);
160
- if (parentSpec.length > 0) {
161
- return { pkgName: parentSpec, entryFile: file, pkgDir: path.dirname(file) };
162
- }
163
- }
164
- return { pkgName, entryFile: file, pkgDir: path.dirname(file) };
101
+ const parentSpec = lastSlash > 0 ? pkgName.slice(0, lastSlash) : pkgName;
102
+ return { pkgName: parentSpec, entryFile: file, pkgDir: path.dirname(file) };
165
103
  }
166
104
  }
167
105
  for (const ext of CANDIDATE_EXTS) {
168
106
  const file = path.join(candidate, `index${ext}`);
169
- if (await Bun.file(file).exists()) {
170
- return { pkgName, entryFile: file, pkgDir: candidate };
171
- }
107
+ if (await Bun.file(file).exists()) return { pkgName, entryFile: file, pkgDir: candidate };
172
108
  }
173
109
  }
174
- // A prefix matched but nothing on disk — stop so a shorter, less specific
175
- // prefix doesn't accidentally resolve to an unrelated location.
110
+ // A matched prefix with nothing on disk stops here, so a shorter prefix cannot resolve somewhere unrelated.
176
111
  return null;
177
112
  }
178
113
 
179
- // Fallback: resolve package exports from node_modules. This supports
180
- // single-package subpaths such as `akanjs/ui`, whose package.json lives at
181
- // node_modules/akanjs/package.json rather than node_modules/akanjs/ui.
114
+ // `akanjs/ui` resolves through node_modules/akanjs/package.json exports, not node_modules/akanjs/ui.
182
115
  const exported = await resolveNodePackageExport(app.workspace.workspaceRoot, pkgName);
183
116
  if (exported) return exported;
184
117
 
@@ -226,8 +159,7 @@ const resolveNodePackageExport = async (workspaceRoot: string, specifier: string
226
159
  if (!rel?.startsWith(".")) return null;
227
160
  const entryFile = await resolveFileCandidate(path.resolve(pkgDir, rel));
228
161
  if (!entryFile) return null;
229
- const pkgEntryName = specifier;
230
- return { pkgName: pkgEntryName, entryFile, pkgDir: path.dirname(entryFile), preserveFilePath: true };
162
+ return { pkgName: specifier, entryFile, pkgDir: path.dirname(entryFile), preserveFilePath: true };
231
163
  } catch {
232
164
  return null;
233
165
  }
@@ -290,22 +222,14 @@ const resolveFileCandidate = async (candidate: string): Promise<string | null> =
290
222
  return null;
291
223
  };
292
224
 
293
- // Matches `with { type: "macro" }` import attributes (single or double quotes,
294
- // tolerant of whitespace). Used to detect macro-host modules so the plugin
295
- // can leave their source untouched.
296
225
  const MACRO_ATTR_RE = /with\s*\{\s*type\s*:\s*["']macro["']\s*\}/;
297
226
 
298
- /** Exposed for testing. */
299
227
  export const rewriteBarrelImports = async (
300
228
  source: string,
301
229
  barrels: string[],
302
230
  analyzer: BarrelAnalyzer,
303
231
  ): Promise<string | null> => {
304
- // Establish there is something to rewrite before the TypeScript parser is involved. This runs on
305
- // every source file of every dev rebuild, and parsing was by far the most expensive thing in one:
306
- // measured across 1189 files here, 299ms and 161MB of RSS, of which **63% of files import no barrel
307
- // at all**. A static import cannot name a specifier without that specifier appearing literally in the
308
- // text, so a substring test is a sound filter and costs 4ms for the whole corpus.
232
+ // Sound pre-filter before the costly parse: a static import cannot name a specifier absent from the text.
309
233
  if (!barrels.some((barrel) => source.includes(barrel))) return null;
310
234
  const statements = findImportStatements(source);
311
235
  if (statements.length === 0) return null;
@@ -332,15 +256,11 @@ interface ImportStatement {
332
256
  end: number;
333
257
  clause: string;
334
258
  specifier: string;
335
- trailingSemicolon: boolean;
336
- raw: string;
337
259
  }
338
260
 
339
261
  const findImportStatements = (source: string): ImportStatement[] => {
340
262
  const statements: ImportStatement[] = [];
341
- // `setParentNodes: false`: nothing below reads `node.parent`, and every position comes from
342
- // `getStart(sourceFile)`, which takes the file explicitly. Building the parent links cost 132ms and
343
- // 143MB of RSS across 1189 files for no reader.
263
+ // `setParentNodes: false` holds only while nothing reads `node.parent` and positions use `getStart(sourceFile)`.
344
264
  const sourceFile = ts.createSourceFile(
345
265
  "barrel-imports.tsx",
346
266
  source,
@@ -353,31 +273,20 @@ const findImportStatements = (source: string): ImportStatement[] => {
353
273
  if (!ts.isStringLiteral(statement.moduleSpecifier)) continue;
354
274
  const importClause = statement.importClause;
355
275
  if (!importClause) continue;
356
- const statementStart = statement.getStart(sourceFile);
357
- const statementEnd = statement.end;
358
276
  statements.push({
359
- start: statementStart,
360
- end: statementEnd,
277
+ start: statement.getStart(sourceFile),
278
+ end: statement.end,
361
279
  clause: source.slice(importClause.getStart(sourceFile), importClause.end).trim(),
362
280
  specifier: statement.moduleSpecifier.text,
363
- trailingSemicolon: source.slice(statement.moduleSpecifier.end, statement.end).includes(";"),
364
- raw: source.slice(statementStart, statementEnd),
365
281
  });
366
282
  }
367
283
  return statements;
368
284
  };
369
285
 
370
- interface NamedImportItem {
371
- imported: string;
372
- local: string;
373
- isType: boolean;
374
- }
375
-
376
286
  interface ParsedClause {
377
287
  defaultImport?: string;
378
288
  namespaceImport?: string;
379
- named?: NamedImportItem[];
380
- /** Whole clause is `import type { ... } from "..."`. */
289
+ named?: NamedItem[];
381
290
  typeOnly: boolean;
382
291
  }
383
292
 
@@ -389,7 +298,6 @@ const parseImportClause = (clause: string): ParsedClause | null => {
389
298
  rest = rest.slice(5).trim();
390
299
  }
391
300
  const parsed: ParsedClause = { typeOnly };
392
- // Try pattern: default + rest
393
301
  const commaMatch = /^(\w+)\s*,\s*(.+)$/.exec(rest);
394
302
  if (commaMatch) {
395
303
  parsed.defaultImport = commaMatch[1];
@@ -407,36 +315,11 @@ const parseImportClause = (clause: string): ParsedClause | null => {
407
315
  if (rest.startsWith("{")) {
408
316
  const close = rest.indexOf("}");
409
317
  if (close === -1) return null;
410
- const inner = rest.slice(1, close);
411
- parsed.named = parseNamedImportList(inner);
412
- return parsed;
318
+ parsed.named = parseNamedList(rest.slice(1, close));
413
319
  }
414
320
  return parsed;
415
321
  };
416
322
 
417
- const parseNamedImportList = (body: string): NamedImportItem[] => {
418
- const out: NamedImportItem[] = [];
419
- for (const raw of body.split(",")) {
420
- const s = raw.trim();
421
- if (!s) continue;
422
- let isType = false;
423
- let rest = s;
424
- if (rest.startsWith("type ")) {
425
- isType = true;
426
- rest = rest.slice(5).trim();
427
- }
428
- const asMatch = /^(\w+)\s+as\s+(\w+)$/.exec(rest);
429
- if (asMatch) {
430
- out.push({ imported: asMatch[1] ?? "", local: asMatch[2] ?? "", isType });
431
- continue;
432
- }
433
- if (/^\w+$/.test(rest)) {
434
- out.push({ imported: rest, local: rest, isType });
435
- }
436
- }
437
- return out;
438
- };
439
-
440
323
  const rewriteSingleStatement = (stmt: ImportStatement, map: BarrelExportMap): string | null => {
441
324
  const clause = parseImportClause(stmt.clause);
442
325
  if (!clause) return null;
@@ -444,13 +327,10 @@ const rewriteSingleStatement = (stmt: ImportStatement, map: BarrelExportMap): st
444
327
  if (clause.namespaceImport) return null;
445
328
  // Pure type imports are erased at build; leave them alone.
446
329
  if (clause.typeOnly && !clause.defaultImport) return null;
447
- if (!clause.named || clause.named.length === 0) {
448
- // Only default import — nothing to split.
449
- return null;
450
- }
330
+ if (!clause.named?.length) return null;
451
331
 
452
- const remaining: NamedImportItem[] = [];
453
- const rewrites = new Map<string, NamedImportItem[]>();
332
+ const remaining: NamedItem[] = [];
333
+ const rewrites = new Map<string, NamedItem[]>();
454
334
  for (const item of clause.named) {
455
335
  if (item.isType) {
456
336
  remaining.push(item);
@@ -462,7 +342,6 @@ const rewriteSingleStatement = (stmt: ImportStatement, map: BarrelExportMap): st
462
342
  continue;
463
343
  }
464
344
  const list = rewrites.get(target.subpath) ?? [];
465
- // Use the leaf's original name, keeping the consumer's local alias.
466
345
  list.push({ imported: target.originalName, local: item.local, isType: false });
467
346
  rewrites.set(target.subpath, list);
468
347
  }
@@ -470,26 +349,17 @@ const rewriteSingleStatement = (stmt: ImportStatement, map: BarrelExportMap): st
470
349
  if (rewrites.size === 0) return null;
471
350
 
472
351
  const lines: string[] = [];
473
- // Always emit trailing semicolons; safe even when the source omitted them.
474
- const tail = ";";
352
+ if (shouldPreserveBarrelSideEffects(stmt.specifier)) lines.push(`import "${stmt.specifier}";`);
475
353
 
476
- if (shouldPreserveBarrelSideEffects(stmt.specifier)) {
477
- lines.push(`import "${stmt.specifier}"${tail}`);
478
- }
479
-
480
- // Re-emit an import from the original barrel that carries whatever we could
481
- // not flatten (default import, type-only items, unknown names).
482
354
  if (clause.defaultImport || remaining.length > 0) {
483
355
  const parts: string[] = [];
484
356
  if (clause.defaultImport) parts.push(clause.defaultImport);
485
- if (remaining.length > 0) {
486
- parts.push(`{ ${remaining.map(serializeNamedItem).join(", ")} }`);
487
- }
488
- lines.push(`import ${parts.join(", ")} from "${stmt.specifier}"${tail}`);
357
+ if (remaining.length > 0) parts.push(`{ ${remaining.map(serializeNamedItem).join(", ")} }`);
358
+ lines.push(`import ${parts.join(", ")} from "${stmt.specifier}";`);
489
359
  }
490
360
 
491
361
  for (const [subpath, items] of rewrites) {
492
- lines.push(`import { ${items.map(serializeNamedItem).join(", ")} } from "${subpath}"${tail}`);
362
+ lines.push(`import { ${items.map(serializeNamedItem).join(", ")} } from "${subpath}";`);
493
363
  }
494
364
 
495
365
  return lines.join("\n");
@@ -497,15 +367,8 @@ const rewriteSingleStatement = (stmt: ImportStatement, map: BarrelExportMap): st
497
367
 
498
368
  const shouldPreserveBarrelSideEffects = (specifier: string): boolean => /^@(apps|libs)\/[^/]+\/client$/.test(specifier);
499
369
 
500
- const serializeNamedItem = (item: NamedImportItem): string => {
370
+ const serializeNamedItem = (item: NamedItem): string => {
501
371
  const prefix = item.isType ? "type " : "";
502
372
  if (item.imported === item.local) return `${prefix}${item.imported}`;
503
373
  return `${prefix}${item.imported} as ${item.local}`;
504
374
  };
505
-
506
- const loaderFor = (absPath: string): "ts" | "tsx" | "js" | "jsx" => {
507
- if (absPath.endsWith(".tsx")) return "tsx";
508
- if (absPath.endsWith(".jsx")) return "jsx";
509
- if (absPath.endsWith(".ts")) return "ts";
510
- return "js";
511
- };
@@ -1,70 +1,46 @@
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";
4
2
  import path from "node:path";
3
+ import { tempDirs, writeText as write } from "../testHelpers";
5
4
  import { createExternalizeFrameworkPlugin } from "./externalizeFrameworkPlugin";
6
5
 
7
- const tempRoots: string[] = [];
6
+ const makeTempRoot = tempDirs("akan-devkit-externalize-");
8
7
 
9
- const makeTempRoot = async () => {
10
- const root = await mkdtemp(path.join(os.tmpdir(), "akan-devkit-externalize-"));
11
- tempRoots.push(root);
12
- return root;
8
+ const akanServerPaths = {
9
+ akanjs: ["./pkgs/akanjs/index.ts"],
10
+ "akanjs/*": ["./pkgs/akanjs/*"],
11
+ "akanjs/server": ["./pkgs/akanjs/server/index.ts"],
12
+ "akanjs/server/*": ["./pkgs/akanjs/server/*"],
13
13
  };
14
14
 
15
- const write = async (filePath: string, content: string) => {
16
- await mkdir(path.dirname(filePath), { recursive: true });
17
- await writeFile(filePath, content);
15
+ const bundle = async (root: string, paths: Record<string, string[]>, entryLines: string[]) => {
16
+ const entry = path.join(root, "entry.ts");
17
+ await write(entry, [...entryLines, ""].join("\n"));
18
+ const plugin = await createExternalizeFrameworkPlugin({
19
+ app: { workspace: { workspaceRoot: root }, getTsConfig: async () => ({ compilerOptions: { paths } }) } as never,
20
+ });
21
+ const result = await Bun.build({
22
+ entrypoints: [entry],
23
+ outdir: path.join(root, "out"),
24
+ target: "bun",
25
+ format: "esm",
26
+ plugins: [plugin],
27
+ });
28
+ expect(result.success).toBe(true);
29
+ return await result.outputs.find((artifact) => artifact.kind === "entry-point")?.text();
18
30
  };
19
31
 
20
- afterEach(async () => {
21
- await Promise.all(tempRoots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
22
- });
23
-
24
32
  describe("createExternalizeFrameworkPlugin", () => {
25
33
  test("bundles akanjs/server workspace sources into production pages artifacts", async () => {
26
34
  const root = await makeTempRoot();
27
- const entry = path.join(root, "entry.ts");
28
- const outdir = path.join(root, "out");
29
- await write(
30
- entry,
31
- [
32
- 'import { optionMarker } from "akanjs/server";',
33
- 'import { tryMarker } from "akanjs/server/decorators";',
34
- 'import { jsx } from "react/jsx-runtime";',
35
- "export const markers = [optionMarker, tryMarker, jsx];",
36
- "",
37
- ].join("\n"),
38
- );
39
35
  await write(path.join(root, "pkgs/akanjs/server/index.ts"), 'export const optionMarker = "akan-option";\n');
40
36
  await write(path.join(root, "pkgs/akanjs/server/decorators.ts"), 'export const tryMarker = "akan-try";\n');
41
37
 
42
- const plugin = await createExternalizeFrameworkPlugin({
43
- app: {
44
- workspace: { workspaceRoot: root },
45
- getTsConfig: async () => ({
46
- compilerOptions: {
47
- paths: {
48
- akanjs: ["./pkgs/akanjs/index.ts"],
49
- "akanjs/*": ["./pkgs/akanjs/*"],
50
- "akanjs/server": ["./pkgs/akanjs/server/index.ts"],
51
- "akanjs/server/*": ["./pkgs/akanjs/server/*"],
52
- },
53
- },
54
- }),
55
- } as never,
56
- });
57
-
58
- const result = await Bun.build({
59
- entrypoints: [entry],
60
- outdir,
61
- target: "bun",
62
- format: "esm",
63
- plugins: [plugin],
64
- });
65
-
66
- expect(result.success).toBe(true);
67
- const output = await result.outputs.find((artifact) => artifact.kind === "entry-point")?.text();
38
+ const output = await bundle(root, akanServerPaths, [
39
+ 'import { optionMarker } from "akanjs/server";',
40
+ 'import { tryMarker } from "akanjs/server/decorators";',
41
+ 'import { jsx } from "react/jsx-runtime";',
42
+ "export const markers = [optionMarker, tryMarker, jsx];",
43
+ ]);
68
44
  expect(output).toContain("akan-option");
69
45
  expect(output).toContain("akan-try");
70
46
  expect(output).not.toMatch(/from\s+["']akanjs\/server(?:\/decorators)?["']/);
@@ -73,17 +49,6 @@ describe("createExternalizeFrameworkPlugin", () => {
73
49
 
74
50
  test("externalizes optional backend dependencies reached through bundled akanjs/server sources", async () => {
75
51
  const root = await makeTempRoot();
76
- const entry = path.join(root, "entry.ts");
77
- const outdir = path.join(root, "out");
78
- await write(
79
- entry,
80
- [
81
- 'import { optionMarker, redisLoader } from "akanjs/server";',
82
- "export const marker = optionMarker;",
83
- "export const loader = redisLoader;",
84
- "",
85
- ].join("\n"),
86
- );
87
52
  await write(
88
53
  path.join(root, "pkgs/akanjs/server/index.ts"),
89
54
  [
@@ -99,32 +64,11 @@ describe("createExternalizeFrameworkPlugin", () => {
99
64
  'export const loadRedis = async () => import("ioredis");\n',
100
65
  );
101
66
 
102
- const plugin = await createExternalizeFrameworkPlugin({
103
- app: {
104
- workspace: { workspaceRoot: root },
105
- getTsConfig: async () => ({
106
- compilerOptions: {
107
- paths: {
108
- akanjs: ["./pkgs/akanjs/index.ts"],
109
- "akanjs/*": ["./pkgs/akanjs/*"],
110
- "akanjs/server": ["./pkgs/akanjs/server/index.ts"],
111
- "akanjs/server/*": ["./pkgs/akanjs/server/*"],
112
- },
113
- },
114
- }),
115
- } as never,
116
- });
117
-
118
- const result = await Bun.build({
119
- entrypoints: [entry],
120
- outdir,
121
- target: "bun",
122
- format: "esm",
123
- plugins: [plugin],
124
- });
125
-
126
- expect(result.success).toBe(true);
127
- const output = await result.outputs.find((artifact) => artifact.kind === "entry-point")?.text();
67
+ const output = await bundle(root, akanServerPaths, [
68
+ 'import { optionMarker, redisLoader } from "akanjs/server";',
69
+ "export const marker = optionMarker;",
70
+ "export const loader = redisLoader;",
71
+ ]);
128
72
  expect(output).toContain("akan-option");
129
73
  expect(output).toContain("ioredis");
130
74
  expect(output).not.toMatch(/from\s+["']akanjs\/(?:server|service)["']/);
@@ -132,17 +76,6 @@ describe("createExternalizeFrameworkPlugin", () => {
132
76
 
133
77
  test("lets published akanjs package exports resolve when workspace paths are absent", async () => {
134
78
  const root = await makeTempRoot();
135
- const entry = path.join(root, "entry.ts");
136
- const outdir = path.join(root, "out");
137
- await write(
138
- entry,
139
- [
140
- 'import { optionMarker } from "akanjs/server";',
141
- 'import { jsx } from "react/jsx-runtime";',
142
- "export const markers = [optionMarker, jsx];",
143
- "",
144
- ].join("\n"),
145
- );
146
79
  await write(
147
80
  path.join(root, "node_modules/akanjs/package.json"),
148
81
  JSON.stringify({
@@ -161,23 +94,11 @@ describe("createExternalizeFrameworkPlugin", () => {
161
94
  'export const optionMarker = "published-option";\n',
162
95
  );
163
96
 
164
- const plugin = await createExternalizeFrameworkPlugin({
165
- app: {
166
- workspace: { workspaceRoot: root },
167
- getTsConfig: async () => ({ compilerOptions: { paths: {} } }),
168
- } as never,
169
- });
170
-
171
- const result = await Bun.build({
172
- entrypoints: [entry],
173
- outdir,
174
- target: "bun",
175
- format: "esm",
176
- plugins: [plugin],
177
- });
178
-
179
- expect(result.success).toBe(true);
180
- const output = await result.outputs.find((artifact) => artifact.kind === "entry-point")?.text();
97
+ const output = await bundle(root, {}, [
98
+ 'import { optionMarker } from "akanjs/server";',
99
+ 'import { jsx } from "react/jsx-runtime";',
100
+ "export const markers = [optionMarker, jsx];",
101
+ ]);
181
102
  expect(output).toContain("published-option");
182
103
  expect(output).not.toMatch(/from\s+["']akanjs\/server["']/);
183
104
  expect(output).toContain("react/jsx-runtime");