@akanjs/devkit 3.0.0-alpha.9 → 3.0.0-alpha.91

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 (183) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/DEV_RUNTIME_KNOBS.md +27 -0
  3. package/README.ko.md +1 -1
  4. package/README.md +1 -1
  5. package/agentsIndex.test.ts +10 -0
  6. package/agentsIndex.ts +21 -1
  7. package/aiEditor.ts +1 -1
  8. package/akanApp/BackendImportGraph.test.ts +120 -0
  9. package/akanApp/BackendImportGraph.ts +167 -0
  10. package/akanApp/akanApp.host.test.ts +16 -678
  11. package/akanApp/akanApp.host.ts +113 -578
  12. package/akanApp/devHostPolicy.test.ts +542 -0
  13. package/akanApp/devHostPolicy.ts +442 -0
  14. package/akanApp/index.ts +2 -0
  15. package/akanConfig/akanConfig.test.ts +182 -14
  16. package/akanConfig/akanConfig.ts +143 -47
  17. package/akanConfig/types.ts +9 -0
  18. package/akanContext.ts +30 -14
  19. package/akanMcpContract.ts +25 -4
  20. package/appSelectionMemory.ts +28 -0
  21. package/applicationBuildRunner.test.ts +1 -1
  22. package/applicationBuildRunner.ts +45 -21
  23. package/artifact/implicitRootLayout.test.ts +67 -0
  24. package/artifact/implicitRootLayout.ts +27 -7
  25. package/artifact/routeSeedIndex.test.ts +1 -0
  26. package/biome.base.json +86 -47
  27. package/biomeBase.ts +6 -5
  28. package/capacitorApp.ts +10 -0
  29. package/cloud/globalConfig.ts +9 -2
  30. package/codegenLock.test.ts +137 -0
  31. package/codegenLock.ts +136 -0
  32. package/commandDecorators/argMeta.ts +9 -1
  33. package/commandDecorators/command.ts +94 -7
  34. package/commandDecorators/commandBuilder.ts +20 -5
  35. package/commandDecorators/commandDecorators.test.ts +115 -3
  36. package/commandDecorators/helpFormatter.ts +4 -0
  37. package/dependencyScanner.test.ts +99 -0
  38. package/dependencyScanner.ts +27 -24
  39. package/devkitUtils.test.ts +0 -104
  40. package/executors.test.ts +129 -4
  41. package/executors.ts +105 -24
  42. package/fileEditor.ts +19 -19
  43. package/fileSys.ts +19 -1
  44. package/formSetterScanner.test.ts +80 -0
  45. package/formSetterScanner.ts +92 -0
  46. package/frontendBuild/autoImportSync.test.ts +58 -0
  47. package/frontendBuild/autoImportSync.ts +7 -2
  48. package/frontendBuild/buildRouteClient.test.ts +47 -24
  49. package/frontendBuild/clientBuildTypes.ts +4 -0
  50. package/frontendBuild/clientEntriesBundler.ts +4 -1
  51. package/frontendBuild/clientEntryDiscovery.ts +2 -2
  52. package/frontendBuild/csrArtifactBuilder.ts +116 -84
  53. package/frontendBuild/cssCompiler.ts +123 -12
  54. package/frontendBuild/cssImportResolver.ts +8 -7
  55. package/frontendBuild/devGeneratedIndexSync.ts +3 -2
  56. package/frontendBuild/fontOptimizer.ts +37 -18
  57. package/frontendBuild/fontPruner.test.ts +220 -0
  58. package/frontendBuild/fontPruner.ts +206 -0
  59. package/frontendBuild/frontendBuild.test.ts +178 -10
  60. package/frontendBuild/hmrWatcher.ts +1 -1
  61. package/frontendBuild/index.ts +1 -1
  62. package/frontendBuild/pagesBundleBuilder.ts +3 -3
  63. package/frontendBuild/pagesEntrySourceGenerator.ts +11 -88
  64. package/frontendBuild/routeClientBuilder.ts +12 -5
  65. package/frontendBuild/sourceMtimeIndex.ts +1 -1
  66. package/frontendBuild/ssrBaseArtifactBuilder.ts +21 -4
  67. package/frontendBuild/styleContract.ts +14 -20
  68. package/frontendBuild/themeValidator.ts +22 -17
  69. package/frontendBuild/vendorSpecifiers.ts +1 -0
  70. package/frontendBuild/watchRootResolver.test.ts +67 -0
  71. package/frontendBuild/watchRootResolver.ts +13 -1
  72. package/incrementalBuilder/devWatchBatch.test.ts +18 -20
  73. package/incrementalBuilder/devWatchBatch.ts +6 -2
  74. package/incrementalBuilder/incrementalBuilder.host.ts +51 -7
  75. package/incrementalBuilder/incrementalBuilder.proc.ts +17 -6
  76. package/index.ts +0 -7
  77. package/integration/devStabilityHarness.ts +2 -10
  78. package/libSource.test.ts +112 -0
  79. package/libSource.ts +126 -0
  80. package/lint/__fixtures__/README.md +40 -0
  81. package/lint/__fixtures__/no-arbitrary-color/bad.tsx +3 -0
  82. package/lint/__fixtures__/no-arbitrary-color/good.tsx +4 -0
  83. package/lint/__fixtures__/no-async-component-in-ui/bad.tsx +4 -0
  84. package/lint/__fixtures__/no-async-component-in-ui/good.tsx +4 -0
  85. package/lint/__fixtures__/no-bang-comment-in-client/bad.tsx +4 -0
  86. package/lint/__fixtures__/no-bang-comment-in-client/fixture.json +1 -0
  87. package/lint/__fixtures__/no-bang-comment-in-client/good.tsx +3 -0
  88. package/lint/__fixtures__/no-daisyui-legacy-class/bad.tsx +7 -0
  89. package/lint/__fixtures__/no-daisyui-legacy-class/good.tsx +5 -0
  90. package/lint/__fixtures__/no-deep-internal-import/bad.tsx +3 -0
  91. package/lint/__fixtures__/no-deep-internal-import/fixture.json +1 -0
  92. package/lint/__fixtures__/no-deep-internal-import/good.tsx +3 -0
  93. package/lint/__fixtures__/no-deprecated-log-level/bad.tsx +4 -0
  94. package/lint/__fixtures__/no-deprecated-log-level/good.tsx +4 -0
  95. package/lint/__fixtures__/no-import-client-functions/bad.tsx +2 -0
  96. package/lint/__fixtures__/no-import-client-functions/good.tsx +4 -0
  97. package/lint/__fixtures__/no-import-client-in-server/bad.tsx +6 -0
  98. package/lint/__fixtures__/no-import-client-in-server/good.tsx +5 -0
  99. package/lint/__fixtures__/no-import-external-library/bad.tsx +4 -0
  100. package/lint/__fixtures__/no-import-external-library/good.tsx +5 -0
  101. package/lint/__fixtures__/no-import-server-in-client/bad.tsx +6 -0
  102. package/lint/__fixtures__/no-import-server-in-client/good.tsx +4 -0
  103. package/lint/__fixtures__/no-init-fetch-in-client/store-file/bad.ts +1 -0
  104. package/lint/__fixtures__/no-init-fetch-in-client/store-file/fixture.json +1 -0
  105. package/lint/__fixtures__/no-init-fetch-in-client/store-file/good.ts +1 -0
  106. package/lint/__fixtures__/no-init-fetch-in-client/use-client/bad.tsx +4 -0
  107. package/lint/__fixtures__/no-init-fetch-in-client/use-client/good.tsx +5 -0
  108. package/lint/__fixtures__/no-inline-color/bad.tsx +3 -0
  109. package/lint/__fixtures__/no-inline-color/good.tsx +3 -0
  110. package/lint/__fixtures__/no-interpolated-arbitrary-class/bad.tsx +3 -0
  111. package/lint/__fixtures__/no-interpolated-arbitrary-class/good.tsx +3 -0
  112. package/lint/__fixtures__/no-js-private-class-method/bad.tsx +3 -0
  113. package/lint/__fixtures__/no-js-private-class-method/good.tsx +3 -0
  114. package/lint/__fixtures__/no-model-type-in-util-zone/bad.tsx +3 -0
  115. package/lint/__fixtures__/no-model-type-in-util-zone/good.tsx +6 -0
  116. package/lint/__fixtures__/no-raw-palette-class/bad.tsx +6 -0
  117. package/lint/__fixtures__/no-raw-palette-class/good.tsx +8 -0
  118. package/lint/__fixtures__/no-redeclare-predefined-endpoint/bad.ts +10 -0
  119. package/lint/__fixtures__/no-redeclare-predefined-endpoint/fixture.json +1 -0
  120. package/lint/__fixtures__/no-redeclare-predefined-endpoint/good.ts +6 -0
  121. package/lint/__fixtures__/no-return-in-store-action/bad.tsx +2 -0
  122. package/lint/__fixtures__/no-return-in-store-action/good.tsx +6 -0
  123. package/lint/__fixtures__/no-throw-raw-error/bad.tsx +4 -0
  124. package/lint/__fixtures__/no-throw-raw-error/good.tsx +4 -0
  125. package/lint/__fixtures__/no-unpublished-form-setter/bad.tsx +3 -0
  126. package/lint/__fixtures__/no-unpublished-form-setter/good.tsx +5 -0
  127. package/lint/__fixtures__/no-use-client-in-server/bad.tsx +3 -0
  128. package/lint/__fixtures__/no-use-client-in-server/good.tsx +2 -0
  129. package/lint/__fixtures__/non-scalar-props-restricted/bad.tsx +5 -0
  130. package/lint/__fixtures__/non-scalar-props-restricted/good.tsx +4 -0
  131. package/lint/gritRules.test.ts +178 -0
  132. package/lint/no-arbitrary-color.grit +1 -1
  133. package/lint/no-async-component-in-ui.grit +35 -0
  134. package/lint/no-bang-comment-in-client.grit +23 -10
  135. package/lint/no-daisyui-legacy-class.grit +26 -9
  136. package/lint/no-deprecated-log-level.grit +17 -0
  137. package/lint/no-init-fetch-in-client.grit +47 -0
  138. package/lint/no-inline-color.grit +10 -8
  139. package/lint/no-interpolated-arbitrary-class.grit +3 -3
  140. package/lint/no-model-type-in-util-zone.grit +58 -0
  141. package/lint/no-raw-palette-class.grit +3 -3
  142. package/lint/no-unpublished-form-setter.grit +41 -0
  143. package/lint/non-scalar-props-restricted.grit +16 -7
  144. package/linter.test.ts +80 -0
  145. package/linter.ts +99 -25
  146. package/package.json +5 -8
  147. package/prompter.ts +9 -4
  148. package/qualityScanner.test.ts +116 -0
  149. package/qualityScanner.ts +89 -21
  150. package/recipeScanner.ts +4 -1
  151. package/scanInfo.ts +32 -26
  152. package/semver.test.ts +26 -0
  153. package/semver.ts +31 -0
  154. package/slicePlanner.test.ts +182 -0
  155. package/slicePlanner.ts +235 -0
  156. package/ssrScanner.test.ts +301 -0
  157. package/subspace.test.ts +418 -0
  158. package/subspace.ts +723 -0
  159. package/subspaceConfig.ts +76 -0
  160. package/transforms/asyncDefaultExportDetector.ts +103 -0
  161. package/transforms/barrelImportsPlugin.ts +1 -1
  162. package/transforms/externalizeFrameworkPlugin.ts +0 -1
  163. package/transforms/transforms.test.ts +5 -5
  164. package/transforms/tsconfigPackageResolver.test.ts +230 -0
  165. package/tsconfig.json +6 -2
  166. package/typeChecker.ts +1 -1
  167. package/types.ts +1 -0
  168. package/uploadRelease.ts +2 -2
  169. package/workflow/executor.test.ts +146 -0
  170. package/workspaceLayout.test.ts +56 -4
  171. package/workspaceLayout.ts +48 -2
  172. package/builder.ts +0 -164
  173. package/extractDeps.ts +0 -86
  174. package/frontendBuild/styleGuard.test.ts +0 -165
  175. package/frontendBuild/styleGuard.ts +0 -322
  176. package/getCredentials.ts +0 -19
  177. package/getModelFileData.ts +0 -62
  178. package/src/capacitorApp.ts +0 -282
  179. package/streamAi.ts +0 -45
  180. package/ui/MultiScrollList.tsx +0 -242
  181. package/ui/ScrollList.tsx +0 -107
  182. package/ui/index.ts +0 -2
  183. package/useStdoutDimensions.ts +0 -20
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import os from "node:os";
4
4
  import path from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
+ import { AppExecutor, WorkspaceExecutor } from "../executors";
6
7
  import type { PackageJson } from "../types";
7
8
  import { AkanAppConfig, AkanLibConfig, deriveDefaultAppId } from "./akanConfig";
8
9
  import type { DeepPartial, LibConfigResult } from "./types";
@@ -19,7 +20,6 @@ const packageJson: PackageJson = {
19
20
  react: "19.0.0",
20
21
  "react-dom": "19.0.0",
21
22
  "react-server-dom-webpack": "19.0.0",
22
- sharp: "1.0.0",
23
23
  "@external/runtime": "2.0.0",
24
24
  },
25
25
  };
@@ -64,7 +64,8 @@ describe("AkanAppConfig", () => {
64
64
  expect(config.barrelImports).toEqual(
65
65
  expect.arrayContaining(["@apps/portal/ui", "@libs/shared/server", "akanjs/common", "akanjs/server"]),
66
66
  );
67
- expect(config.docker.content).toContain("ENV AKAN_PUBLIC_APP_NAME=portal");
67
+ expect(config.dockerfile).toContain("ENV AKAN_PUBLIC_APP_NAME=portal");
68
+ expect(config.dockerfile).toContain("ENV AKAN_LOG_TO_FILE=0");
68
69
  expect(process.env.AKAN_PUBLIC_DEFAULT_LOCALE).toBe("en");
69
70
  });
70
71
 
@@ -128,9 +129,67 @@ describe("AkanAppConfig", () => {
128
129
  });
129
130
  expect(config.publicEnv).toEqual(["AKAN_PUBLIC_FEATURE"]);
130
131
  expect(config.optimizeImports).toContain("custom-icons");
131
- expect(config.docker.content).toContain('CMD ["bun","server.js"]');
132
- expect(config.docker.content).toContain("FROM oven/bun:amd64 AS amd64");
133
- expect(config.docker.content).toContain('RUN if [ "$TARGETARCH" = "arm64"');
132
+ expect(config.dockerfile).toContain('CMD ["bun","server.js"]');
133
+ expect(config.dockerfile).toContain("FROM oven/bun:amd64 AS amd64");
134
+ expect(config.dockerfile).toContain('RUN if [ "$TARGETARCH" = "arm64"');
135
+ });
136
+
137
+ test("defaults both web surfaces on and keeps the image free of web env overrides", () => {
138
+ const config = new AkanAppConfig(app, [], packageJson, {}, baseDevEnv);
139
+
140
+ expect(config.web).toEqual({ ssr: true, csr: true });
141
+ expect(config.dockerfile).not.toContain("AKAN_SSR");
142
+ expect(config.dockerfile).not.toContain("AKAN_CSR");
143
+ });
144
+
145
+ test("bakes the disabled surface into the image env so the default matches what was built", () => {
146
+ const ssrOnly = new AkanAppConfig(app, [], packageJson, { web: { csr: false } }, baseDevEnv);
147
+ expect(ssrOnly.web).toEqual({ ssr: true, csr: false });
148
+ expect(ssrOnly.dockerfile).toContain("ENV AKAN_CSR=false");
149
+ expect(ssrOnly.dockerfile).not.toContain("ENV AKAN_SSR=false");
150
+
151
+ const apiOnly = new AkanAppConfig(app, [], packageJson, { web: false }, baseDevEnv);
152
+ expect(apiOnly.web).toEqual({ ssr: false, csr: false });
153
+ expect(apiOnly.dockerfile).toContain("ENV AKAN_SSR=false");
154
+ expect(apiOnly.dockerfile).toContain("ENV AKAN_CSR=false");
155
+
156
+ expect(new AkanAppConfig(app, [], packageJson, { web: true }, baseDevEnv).web).toEqual({ ssr: true, csr: true });
157
+ });
158
+
159
+ test("refuses a csr-less build that ships a mobile app", () => {
160
+ expect(
161
+ () => new AkanAppConfig(app, [], packageJson, { web: { csr: false }, mobile: { appName: "portal" } }, baseDevEnv),
162
+ ).toThrow("the Capacitor build ships that bundle");
163
+ });
164
+
165
+ test("installs only ca-certificates and tzdata in the default image", () => {
166
+ const config = new AkanAppConfig(app, [], packageJson, {}, baseDevEnv);
167
+
168
+ expect(config.dockerfile).toContain(
169
+ "RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*",
170
+ );
171
+ // The Chromium/ffmpeg toolchain moved to per-app `preRuns`; keeping it here paid for it in every image.
172
+ for (const dropped of ["libnss3", "ffmpeg", "build-essential", "redis", "xdg-utils"])
173
+ expect(config.dockerfile).not.toContain(dropped);
174
+ });
175
+
176
+ test("keeps a declared Dockerfile string verbatim", () => {
177
+ const dockerfile = 'FROM oven/bun:1-slim\nCOPY . .\nCMD ["bun","main.js"]';
178
+ const config = new AkanAppConfig(app, [], packageJson, { docker: dockerfile }, baseDevEnv);
179
+
180
+ expect(config.docker).toBe(dockerfile);
181
+ expect(config.dockerfile).toBe(dockerfile);
182
+ });
183
+
184
+ test("resolves the image parts, defaulting the base image and the command", () => {
185
+ const config = new AkanAppConfig(app, [], packageJson, { docker: { preRuns: ["echo hi"] } }, baseDevEnv);
186
+
187
+ expect(config.docker).toEqual({
188
+ image: "oven/bun:1-slim",
189
+ preRuns: ["echo hi"],
190
+ postRuns: [],
191
+ command: ["bun", "main.js"],
192
+ });
134
193
  });
135
194
 
136
195
  test("creates production package json and reports missing external versions", () => {
@@ -144,8 +203,6 @@ describe("AkanAppConfig", () => {
144
203
  react: "19.0.0",
145
204
  "react-dom": "19.0.0",
146
205
  "react-server-dom-webpack": "19.0.0",
147
- croner: akanPackageJson.peerDependencies?.croner,
148
- sharp: "1.0.0",
149
206
  "@external/runtime": "2.0.0",
150
207
  },
151
208
  });
@@ -184,8 +241,6 @@ describe("AkanAppConfig", () => {
184
241
  react: runtimeDependencies.react,
185
242
  "react-dom": runtimeDependencies["react-dom"],
186
243
  "react-server-dom-webpack": runtimeDependencies["react-server-dom-webpack"],
187
- croner: runtimeDependencies.croner,
188
- sharp: runtimeDependencies.sharp,
189
244
  });
190
245
  });
191
246
 
@@ -198,9 +253,6 @@ describe("AkanAppConfig", () => {
198
253
  const multipleConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "multiple" }, baseDevEnv);
199
254
  const clusterConfig = new AkanAppConfig(app, [], packageJson, { defaultDatabaseMode: "cluster" }, baseDevEnv);
200
255
 
201
- expect(singleConfig.getProductionPackageJson().dependencies).toMatchObject({
202
- croner: runtimeDependencies.croner,
203
- });
204
256
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("ioredis");
205
257
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("bullmq");
206
258
  expect(singleConfig.getProductionPackageJson().dependencies).not.toHaveProperty("@libsql/client");
@@ -210,7 +262,6 @@ describe("AkanAppConfig", () => {
210
262
  expect(multipleConfig.getProductionPackageJson().dependencies).toMatchObject({
211
263
  "@libsql/client": runtimeDependencies["@libsql/client"],
212
264
  bullmq: runtimeDependencies.bullmq,
213
- croner: runtimeDependencies.croner,
214
265
  ioredis: runtimeDependencies.ioredis,
215
266
  protobufjs: runtimeDependencies.protobufjs,
216
267
  });
@@ -218,7 +269,6 @@ describe("AkanAppConfig", () => {
218
269
 
219
270
  expect(clusterConfig.getProductionPackageJson().dependencies).toMatchObject({
220
271
  bullmq: runtimeDependencies.bullmq,
221
- croner: runtimeDependencies.croner,
222
272
  ioredis: runtimeDependencies.ioredis,
223
273
  postgres: runtimeDependencies.postgres,
224
274
  protobufjs: runtimeDependencies.protobufjs,
@@ -392,6 +442,115 @@ describe("deriveDefaultAppId", () => {
392
442
  });
393
443
  });
394
444
 
445
+ describe("AkanAppConfig lib externalLibs", () => {
446
+ test("merges lib-declared external libs into the app's own, deduped", () => {
447
+ const libAwarePackageJson: PackageJson = {
448
+ ...packageJson,
449
+ dependencies: { ...packageJson.dependencies, puppeteer: "24.0.0" },
450
+ };
451
+ const config = new AkanAppConfig(
452
+ app,
453
+ ["shared"],
454
+ libAwarePackageJson,
455
+ { externalLibs: ["@external/runtime"] },
456
+ baseDevEnv,
457
+ [],
458
+ { externalLibs: ["@external/runtime", "puppeteer"], docker: { preRuns: [], postRuns: [] } },
459
+ );
460
+
461
+ expect(config.externalLibs).toEqual(["@external/runtime", "puppeteer"]);
462
+ expect(config.getProductionPackageJson().dependencies).toMatchObject({
463
+ "@external/runtime": "2.0.0",
464
+ puppeteer: "24.0.0",
465
+ });
466
+ });
467
+
468
+ test("reads them off every workspace lib config on load", async () => {
469
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "akan-config-libext-"));
470
+ try {
471
+ fs.mkdirSync(path.join(root, "apps/extapp"), { recursive: true });
472
+ fs.mkdirSync(path.join(root, "libs/extlib"), { recursive: true });
473
+ fs.writeFileSync(path.join(root, "apps/extapp/akan.config.ts"), "export default { externalLibs: ['shiki'] };\n");
474
+ fs.writeFileSync(
475
+ path.join(root, "libs/extlib/akan.config.ts"),
476
+ "export default { externalLibs: ['puppeteer'] };\n",
477
+ );
478
+ fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "extrepo", version: "1.0.0" }));
479
+ fs.writeFileSync(path.join(root, ".env"), "AKAN_PUBLIC_REPO_NAME=extrepo\nAKAN_PUBLIC_SERVE_DOMAIN=ext.test\n");
480
+
481
+ const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot: root, repoName: "extrepo" });
482
+ const config = await AppExecutor.from(workspace, "extapp").getConfig();
483
+
484
+ expect(config.externalLibs).toEqual(["shiki", "puppeteer"]);
485
+ } finally {
486
+ fs.rmSync(root, { recursive: true, force: true });
487
+ }
488
+ });
489
+ });
490
+
491
+ describe("AkanAppConfig lib docker runs", () => {
492
+ const libDocker = (preRuns: string[], postRuns: string[] = []) => ({
493
+ externalLibs: [],
494
+ docker: { preRuns, postRuns },
495
+ });
496
+
497
+ test("runs lib steps before the app's own, deduped", () => {
498
+ const config = new AkanAppConfig(
499
+ app,
500
+ ["shared"],
501
+ packageJson,
502
+ { docker: { preRuns: ["apt-get install -y ffmpeg", "echo app"], postRuns: ["echo app-post"] } },
503
+ baseDevEnv,
504
+ [],
505
+ libDocker(["apt-get install -y ffmpeg", "echo lib"], ["echo lib-post"]),
506
+ );
507
+
508
+ expect(config.docker).toMatchObject({
509
+ preRuns: ["apt-get install -y ffmpeg", "echo lib", "echo app"],
510
+ postRuns: ["echo lib-post", "echo app-post"],
511
+ });
512
+ expect(config.dockerfile).toContain("RUN echo lib\nRUN echo app\n");
513
+ expect(config.dockerfile.match(/RUN apt-get install -y ffmpeg/g)).toHaveLength(1);
514
+ });
515
+
516
+ test("drops them when the app hands over a whole Dockerfile", () => {
517
+ const config = new AkanAppConfig(
518
+ app,
519
+ ["shared"],
520
+ packageJson,
521
+ { docker: "FROM scratch" },
522
+ baseDevEnv,
523
+ [],
524
+ libDocker(["echo lib"]),
525
+ );
526
+
527
+ expect(config.dockerfile).toBe("FROM scratch");
528
+ });
529
+
530
+ test("reads them off every workspace lib config on load", async () => {
531
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), "akan-config-libdocker-"));
532
+ try {
533
+ fs.mkdirSync(path.join(root, "apps/extapp"), { recursive: true });
534
+ fs.mkdirSync(path.join(root, "libs/extlib"), { recursive: true });
535
+ fs.writeFileSync(path.join(root, "apps/extapp/akan.config.ts"), "export default {};\n");
536
+ fs.writeFileSync(
537
+ path.join(root, "libs/extlib/akan.config.ts"),
538
+ "export default { docker: { preRuns: ['echo from-lib'], postRuns: [{ arm64: 'echo arm-only' }] } };\n",
539
+ );
540
+ fs.writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "extrepo", version: "1.0.0" }));
541
+ fs.writeFileSync(path.join(root, ".env"), "AKAN_PUBLIC_REPO_NAME=extrepo\nAKAN_PUBLIC_SERVE_DOMAIN=ext.test\n");
542
+
543
+ const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot: root, repoName: "extrepo" });
544
+ const config = await AppExecutor.from(workspace, "extapp").getConfig();
545
+
546
+ expect(config.dockerfile).toContain("RUN echo from-lib");
547
+ expect(config.dockerfile).toContain('RUN if [ "$TARGETARCH" = "arm64"');
548
+ } finally {
549
+ fs.rmSync(root, { recursive: true, force: true });
550
+ }
551
+ });
552
+ });
553
+
395
554
  describe("AkanLibConfig", () => {
396
555
  test("uses empty external libs by default and preserves explicit libs", () => {
397
556
  const lib = { name: "shared" } as never;
@@ -402,6 +561,15 @@ describe("AkanLibConfig", () => {
402
561
  };
403
562
  expect(new AkanLibConfig(lib, config).externalLibs).toEqual(["firebase-admin"]);
404
563
  });
564
+
565
+ test("defaults docker runs to empty lists and preserves declared ones", () => {
566
+ const lib = { name: "shared" } as never;
567
+ expect(new AkanLibConfig(lib, {}).docker).toEqual({ preRuns: [], postRuns: [] });
568
+ expect(new AkanLibConfig(lib, { docker: { preRuns: ["echo lib"] } }).docker).toEqual({
569
+ preRuns: ["echo lib"],
570
+ postRuns: [],
571
+ });
572
+ });
405
573
  });
406
574
 
407
575
  describe("AkanAppConfig.importConfigModule", () => {
@@ -2,22 +2,31 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import type { AkanPlugin } from "akanjs";
5
+ import { normalizeRoutePrefix } from "akanjs/base";
5
6
  import { type AkanI18nConfig, resolveAkanI18nConfig } from "akanjs/common";
6
7
  import type { AkanImageConfig } from "akanjs/server";
7
8
  import type { App, Lib } from "../commandDecorators";
8
- import { WorkspaceExecutor } from "../executors";
9
+ import { LibExecutor, WorkspaceExecutor } from "../executors";
9
10
  import type { BaseDevEnv, PackageJson } from "../types";
10
11
  import {
12
+ type AkanApiConfig,
13
+ type AkanAssetsConfig,
11
14
  type AkanMobileConfig,
12
15
  type AkanMobileTargetConfig,
13
16
  type AkanRouteConfig,
17
+ type AkanWebConfig,
18
+ type AkanWebOption,
14
19
  type AppConfigResult,
15
20
  type Arch,
16
21
  archs,
17
22
  type DatabaseMode,
18
23
  type DeepPartial,
19
24
  type DockerConfig,
25
+ type DockerOption,
26
+ type DockerRun,
27
+ type LibAssetsConfig,
20
28
  type LibConfigResult,
29
+ type LibDockerConfig,
21
30
  } from "./types";
22
31
 
23
32
  const DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
@@ -49,9 +58,8 @@ const DEFAULT_OPTIMIZE_IMPORTS = [
49
58
  "react-icons/*",
50
59
  ];
51
60
  const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] as const;
61
+ const DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
52
62
  const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
53
- const NATIVE_RUNTIME_PACKAGES = ["sharp"] as const;
54
- const DEFAULT_BACKEND_RUNTIME_PACKAGES = ["croner"] as const;
55
63
  // The firebase client (push tokens) and the Capacitor toolchain — `@capacitor/cli` (`npx cap`),
56
64
  // `@capacitor/assets` (`npx @capacitor/assets`) plus the `@capacitor/core`/`ios`/`android` runtime
57
65
  // and native-platform packages that `npx cap add`/`sync` resolve from the workspace node_modules.
@@ -92,8 +100,6 @@ const DATABASE_MODE_RUNTIME_PACKAGES = {
92
100
  } satisfies Record<DatabaseMode, readonly string[]>;
93
101
  const AKAN_RUNTIME_PACKAGES = new Set<string>([
94
102
  ...SSR_RUNTIME_PACKAGES,
95
- ...NATIVE_RUNTIME_PACKAGES,
96
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
97
103
  ...MOBILE_RUNTIME_PACKAGES,
98
104
  ...Object.values(DATABASE_MODE_RUNTIME_PACKAGES).flat(),
99
105
  ]);
@@ -109,6 +115,7 @@ const DEFAULT_AKAN_IMAGE_CONFIG: AkanImageConfig = {
109
115
  maximumRedirects: 3,
110
116
  fetchTimeoutMs: 7000,
111
117
  maxRemoteBytes: 25 * 1024 * 1024,
118
+ maxConcurrency: 0,
112
119
  };
113
120
 
114
121
  const normalizeIndexPath = (indexPath: string | undefined): string | undefined => {
@@ -167,21 +174,57 @@ const normalizeDeepLinks = (deepLinks: DeepPartial<AkanMobileTargetConfig["deepL
167
174
  } satisfies AkanMobileTargetConfig["deepLinks"];
168
175
  };
169
176
 
177
+ /** What `akan.config.ts` may write: the resolved shape made partial, with `docker` and `web` in their unions. */
178
+ type AppConfigDeclaration = Omit<DeepPartial<AppConfigResult>, "docker" | "web"> & {
179
+ docker?: DockerOption;
180
+ web?: AkanWebOption;
181
+ };
182
+
183
+ /** What the workspace's libs add to an app's build, read off each `libs/<lib>/akan.config.ts`. */
184
+ export interface LibContributions {
185
+ externalLibs: string[];
186
+ docker: LibDockerConfig;
187
+ /** Keep globs rewritten to the app's own `public/`, where `akan sync` mounts each lib's assets. */
188
+ keepFonts?: string[];
189
+ }
190
+
191
+ const emptyLibContributions = (): LibContributions => ({
192
+ externalLibs: [],
193
+ docker: { preRuns: [], postRuns: [] },
194
+ keepFonts: [],
195
+ });
196
+
197
+ const normalizeKeepFonts = (keepFonts: string[] | undefined) => [
198
+ ...new Set((keepFonts ?? []).map((glob) => glob.trim().replace(/^\/+/, "")).filter(Boolean)),
199
+ ];
200
+
201
+ /** First occurrence wins, so a step a lib and its app both declare becomes one layer. */
202
+ const dedupeDockerRuns = (runs: DockerRun[]): DockerRun[] => {
203
+ const byKey = new Map<string, DockerRun>();
204
+ for (const run of runs) byKey.set(typeof run === "string" ? run : JSON.stringify(run), run);
205
+ return [...byKey.values()];
206
+ };
207
+
170
208
  export class AkanAppConfig implements AppConfigResult {
171
209
  app: App;
172
210
  rootPackageJson: PackageJson;
173
211
  docker: DockerConfig;
212
+ /** The Dockerfile `akan build` writes: the declared string verbatim, or one assembled from the parts. */
213
+ dockerfile: string;
174
214
  defaultDatabaseMode: DatabaseMode;
215
+ web: AkanWebConfig;
175
216
  externalLibs: string[];
176
217
  barrelImports: string[];
177
218
  optimizeImports: string[];
178
219
  images: AkanImageConfig;
179
220
  i18n: AkanI18nConfig;
221
+ api: AkanApiConfig;
180
222
  publicEnv: string[];
181
223
  mobile: AkanMobileConfig;
182
224
  /** True only when the app's akan.config.ts explicitly declares a `mobile` section (vs. the synthesized default). */
183
225
  hasMobileConfig: boolean;
184
226
  secrets: string[];
227
+ assets: AkanAssetsConfig;
185
228
  /** Raw setting; resolved against the app's lib deps at sync time (see `AppExecutor.syncPages`). */
186
229
  syncPageLibs: string[] | boolean;
187
230
  baseDevEnv: BaseDevEnv;
@@ -196,9 +239,10 @@ export class AkanAppConfig implements AppConfigResult {
196
239
  app: App,
197
240
  libs: string[],
198
241
  rootPackageJson: PackageJson,
199
- config: DeepPartial<AppConfigResult>,
242
+ config: AppConfigDeclaration,
200
243
  baseDevEnv: BaseDevEnv,
201
244
  plugins: AkanPlugin[] = [],
245
+ libContributions: LibContributions = emptyLibContributions(),
202
246
  ) {
203
247
  this.app = app;
204
248
  this.rootPackageJson = rootPackageJson;
@@ -207,7 +251,7 @@ export class AkanAppConfig implements AppConfigResult {
207
251
  this.plugins = plugins;
208
252
  this.#applyRoutes(config?.routes);
209
253
  this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
210
- this.externalLibs = config?.externalLibs ?? [];
254
+ this.externalLibs = [...new Set([...(config?.externalLibs ?? []), ...libContributions.externalLibs])];
211
255
  this.barrelImports = [
212
256
  ...DEFAULT_BARREL_IMPORTS,
213
257
  ...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
@@ -219,12 +263,36 @@ export class AkanAppConfig implements AppConfigResult {
219
263
  this.i18n = resolveAkanI18nConfig(config?.i18n);
220
264
  process.env.AKAN_PUBLIC_DEFAULT_LOCALE = this.i18n.defaultLocale;
221
265
  process.env.AKAN_PUBLIC_LOCALES = this.i18n.locales.join(",");
266
+ this.api = {
267
+ prefix: normalizeRoutePrefix(config?.api?.prefix) ?? "/api",
268
+ websocketPrefix: normalizeRoutePrefix(config?.api?.websocketPrefix) ?? "/ws",
269
+ };
270
+ process.env.AKAN_PUBLIC_API_PREFIX = this.api.prefix;
271
+ process.env.AKAN_PUBLIC_WS_PREFIX = this.api.websocketPrefix;
222
272
  this.publicEnv = (config?.publicEnv as string[] | undefined) ?? ([] as string[]);
223
273
  this.secrets = (config?.secrets as string[] | undefined) ?? ([] as string[]);
274
+ this.assets = {
275
+ pruneFonts: config?.assets?.pruneFonts ?? true,
276
+ keepFonts: [
277
+ ...normalizeKeepFonts(config?.assets?.keepFonts as string[] | undefined),
278
+ ...(libContributions.keepFonts ?? []),
279
+ ],
280
+ };
224
281
  this.syncPageLibs = (config?.syncPageLibs as string[] | boolean | undefined) ?? false;
225
282
  this.hasMobileConfig = Boolean(config.mobile);
226
283
  this.mobile = this.#resolveMobileConfig(config.mobile);
227
- this.docker = this.#makeDockerContent(config?.docker ?? {});
284
+ this.web = this.#resolveWebConfig(config.web);
285
+ this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
286
+ this.dockerfile = this.#makeDockerfile();
287
+ }
288
+ #resolveWebConfig(web: AkanWebOption | undefined): AkanWebConfig {
289
+ const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
290
+ // `akan build-ios` / `build-android` copy `dist/apps/<app>/csr/<target>.html` into the native project.
291
+ if (!resolved.csr && this.hasMobileConfig)
292
+ throw new Error(
293
+ `apps/${this.app.name}/akan.config.ts turns the CSR bundle off but declares mobile targets; the Capacitor build ships that bundle. Drop the mobile section or leave CSR on.`,
294
+ );
295
+ return resolved;
228
296
  }
229
297
  #resolveMobileConfig(mobile: DeepPartial<AkanMobileConfig> | undefined): AkanMobileConfig {
230
298
  const {
@@ -325,7 +393,7 @@ export class AkanAppConfig implements AppConfigResult {
325
393
  this.branches.forEach((domain) => void domains.add(`${basePath}-${domain}.${serveDomain}`));
326
394
  });
327
395
  }
328
- #getDockerRunScripts(runs: (string | { [key in Arch]?: string })[]) {
396
+ #getDockerRunScripts(runs: DockerRun[]) {
329
397
  return runs.map((run) => {
330
398
  if (typeof run === "string") return `RUN ${run}`;
331
399
  else
@@ -342,26 +410,34 @@ export class AkanAppConfig implements AppConfigResult {
342
410
  if (typeof image === "string") return `FROM ${image}`;
343
411
  else return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join("\n");
344
412
  }
345
- #makeDockerContent(docker: DeepPartial<DockerConfig>): DockerConfig {
346
- if (docker.content)
347
- return {
348
- content: docker.content,
349
- image: {},
350
- preRuns: [],
351
- postRuns: [],
352
- command: [],
353
- };
354
- const preRunScripts = this.#getDockerRunScripts(docker.preRuns ?? []);
355
- const postRunScripts = this.#getDockerRunScripts(docker.postRuns ?? []);
356
-
357
- const imageScript = docker.image
358
- ? this.#getDockerImageScript(docker.image, "oven/bun:1-slim")
359
- : "FROM oven/bun:1-slim";
360
- const command = docker.command ?? ["bun", "main.js"];
361
- const content = `${imageScript}
413
+ /** A declared Dockerfile string is verbatim, so a lib's steps are dropped rather than silently unapplied. */
414
+ static #resolveDocker(docker: DockerOption | undefined, libDocker: LibDockerConfig): DockerConfig {
415
+ if (typeof docker === "string") return docker;
416
+ return {
417
+ image: docker?.image ?? DEFAULT_DOCKER_IMAGE,
418
+ preRuns: dedupeDockerRuns([...libDocker.preRuns, ...(docker?.preRuns ?? [])]),
419
+ postRuns: dedupeDockerRuns([...libDocker.postRuns, ...(docker?.postRuns ?? [])]),
420
+ command: docker?.command ?? ["bun", "main.js"],
421
+ };
422
+ }
423
+ #makeDockerfile(): string {
424
+ if (typeof this.docker === "string") return this.docker;
425
+ const { image, preRuns, postRuns, command } = this.docker;
426
+ const preRunScripts = this.#getDockerRunScripts(preRuns);
427
+ const postRunScripts = this.#getDockerRunScripts(postRuns);
428
+ const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
429
+ // The image default matches what the build actually produced; a deployment narrows it further with its
430
+ // own env, and can never widen it past the artifacts that are in the image.
431
+ // File logging is off in the image: a container's writable layer is ephemeral and nothing collects a
432
+ // file from it, so the rotating files would only fill the node disk (50MB x 100 at `trace`). stdout is
433
+ // the collection path; a deployment that wants the files back sets `AKAN_LOG_TO_FILE=1`.
434
+ const webEnvLines = [
435
+ ...(this.web.ssr ? [] : ["ENV AKAN_SSR=false"]),
436
+ ...(this.web.csr ? [] : ["ENV AKAN_CSR=false"]),
437
+ ].join("\n");
438
+ return `${imageScript}
439
+ RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
362
440
  RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
363
- RUN apt-get update && apt-get upgrade -y
364
- RUN apt-get install -y --no-install-recommends git redis build-essential python3 ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils udev ffmpeg
365
441
  ARG TARGETARCH
366
442
  ${preRunScripts.join("\n")}
367
443
  RUN mkdir -p /workspace
@@ -379,16 +455,12 @@ ENV AKAN_PUBLIC_ENV=${this.baseDevEnv.env}
379
455
  ${this.basePaths.size ? `ENV AKAN_PUBLIC_BASE_PATHS=${[...this.basePaths].join(",")}` : ""}
380
456
  ENV AKAN_PUBLIC_DEFAULT_LOCALE=${this.i18n.defaultLocale}
381
457
  ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
458
+ ENV AKAN_PUBLIC_API_PREFIX=${this.api.prefix}
459
+ ENV AKAN_PUBLIC_WS_PREFIX=${this.api.websocketPrefix}
382
460
  ENV AKAN_PUBLIC_OPERATION_MODE=cloud
383
-
461
+ ENV AKAN_LOG_TO_FILE=0
462
+ ${webEnvLines}
384
463
  CMD [${command.map((c) => `"${c}"`).join(",")}]`;
385
- return {
386
- content,
387
- image: imageScript,
388
- preRuns: docker.preRuns ?? [],
389
- postRuns: docker.postRuns ?? [],
390
- command,
391
- };
392
464
  }
393
465
  static #importGeneration = 0;
394
466
  /**
@@ -415,8 +487,34 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
415
487
  app.workspace.getPackageJson(),
416
488
  ]);
417
489
  const resolved = typeof configImp === "function" ? configImp(app) : configImp;
418
- const { plugins, ...config } = (resolved ?? {}) as DeepPartial<AppConfigResult> & { plugins?: AkanPlugin[] };
419
- return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? []);
490
+ const { plugins, ...config } = (resolved ?? {}) as AppConfigDeclaration & { plugins?: AkanPlugin[] };
491
+ const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
492
+ return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
493
+ }
494
+ //* Every workspace lib is read, not just this app's lib deps: narrowing the set needs the dependency
495
+ //* scan, and the incremental page rebundle re-reads this config on every file change.
496
+ static async #collectLibContributions(app: App, libs: string[], bustImportCache: boolean): Promise<LibContributions> {
497
+ const libConfigs = await Promise.all(
498
+ libs.map(async (libName) =>
499
+ LibExecutor.from(app, libName)
500
+ .getConfig({ refresh: bustImportCache })
501
+ .catch((error: unknown) => {
502
+ app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
503
+ return null;
504
+ }),
505
+ ),
506
+ );
507
+ return {
508
+ externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
509
+ docker: {
510
+ preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
511
+ postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? []),
512
+ },
513
+ //* A lib writes the glob against its own `public/`; `akan sync` mounts that at `public/libs/<lib>`.
514
+ keepFonts: libConfigs.flatMap((libConfig) =>
515
+ (libConfig?.assets.keepFonts ?? []).map((glob) => `libs/${libConfig?.lib.name}/${glob}`),
516
+ ),
517
+ };
420
518
  }
421
519
  #resolveProductionDependencyVersion(lib: string) {
422
520
  const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
@@ -428,13 +526,7 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
428
526
  return akanPackageJson.dependencies?.[lib] ?? akanPackageJson.peerDependencies?.[lib];
429
527
  }
430
528
  #getProductionRuntimePackages() {
431
- return [
432
- ...this.externalLibs,
433
- ...SSR_RUNTIME_PACKAGES,
434
- ...NATIVE_RUNTIME_PACKAGES,
435
- ...DEFAULT_BACKEND_RUNTIME_PACKAGES,
436
- ...this.getDatabaseModeRuntimePackages(),
437
- ];
529
+ return [...this.externalLibs, ...SSR_RUNTIME_PACKAGES, ...this.getDatabaseModeRuntimePackages()];
438
530
  }
439
531
  getDatabaseModeRuntimePackages(databaseMode: DatabaseMode = this.defaultDatabaseMode) {
440
532
  return [...DATABASE_MODE_RUNTIME_PACKAGES[databaseMode]];
@@ -532,15 +624,19 @@ function mergeImageConfig(config: Partial<AkanImageConfig> = {}): AkanImageConfi
532
624
  export class AkanLibConfig implements LibConfigResult {
533
625
  lib: Lib;
534
626
  externalLibs: string[];
627
+ docker: LibDockerConfig;
628
+ assets: LibAssetsConfig;
535
629
  /** Live-only: plugins declared in this lib's `akan.config.ts` (never serialized). */
536
630
  plugins: AkanPlugin[];
537
631
  constructor(lib: Lib, config: DeepPartial<LibConfigResult>, plugins: AkanPlugin[] = []) {
538
632
  this.lib = lib;
539
633
  this.externalLibs = config?.externalLibs ?? [];
634
+ this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
635
+ this.assets = { keepFonts: normalizeKeepFonts(config?.assets?.keepFonts as string[] | undefined) };
540
636
  this.plugins = plugins;
541
637
  }
542
- static async from(lib: Lib) {
543
- const [configImp] = await Promise.all([import(`${lib.cwdPath}/akan.config.ts`).then((mod) => mod.default)]);
638
+ static async from(lib: Lib, { bustImportCache = false }: { bustImportCache?: boolean } = {}) {
639
+ const configImp = await AkanAppConfig.importConfigModule(lib.cwdPath, { bustImportCache });
544
640
  const resolved = typeof configImp === "function" ? configImp(lib) : configImp;
545
641
  const { plugins, ...config } = (resolved ?? {}) as DeepPartial<LibConfigResult> & { plugins?: AkanPlugin[] };
546
642
  return new AkanLibConfig(lib, config, plugins ?? []);
@@ -1,4 +1,6 @@
1
1
  export type {
2
+ AkanApiConfig,
3
+ AkanAssetsConfig,
2
4
  AkanConfigFile,
3
5
  AkanExecutor,
4
6
  AkanMobileConfig,
@@ -9,6 +11,8 @@ export type {
9
11
  AkanRouteConfig,
10
12
  AkanScanInfo,
11
13
  AkanSyncContext,
14
+ AkanWebConfig,
15
+ AkanWebOption,
12
16
  AppConfig,
13
17
  AppConfigInput,
14
18
  AppConfigResult,
@@ -17,10 +21,15 @@ export type {
17
21
  DatabaseMode,
18
22
  DeepPartial,
19
23
  DockerConfig,
24
+ DockerImageConfig,
25
+ DockerOption,
26
+ DockerRun,
20
27
  FileConventionScanResult,
28
+ LibAssetsConfig,
21
29
  LibConfig,
22
30
  LibConfigInput,
23
31
  LibConfigResult,
32
+ LibDockerConfig,
24
33
  LibScanResult,
25
34
  MobileEnv,
26
35
  MobilePermission,