@akanjs/devkit 3.0.0-alpha.65 → 3.0.0-alpha.66

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.
@@ -64,7 +64,7 @@ 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
68
  expect(process.env.AKAN_PUBLIC_DEFAULT_LOCALE).toBe("en");
69
69
  });
70
70
 
@@ -128,29 +128,29 @@ describe("AkanAppConfig", () => {
128
128
  });
129
129
  expect(config.publicEnv).toEqual(["AKAN_PUBLIC_FEATURE"]);
130
130
  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"');
131
+ expect(config.dockerfile).toContain('CMD ["bun","server.js"]');
132
+ expect(config.dockerfile).toContain("FROM oven/bun:amd64 AS amd64");
133
+ expect(config.dockerfile).toContain('RUN if [ "$TARGETARCH" = "arm64"');
134
134
  });
135
135
 
136
136
  test("defaults both web surfaces on and keeps the image free of web env overrides", () => {
137
137
  const config = new AkanAppConfig(app, [], packageJson, {}, baseDevEnv);
138
138
 
139
139
  expect(config.web).toEqual({ ssr: true, csr: true });
140
- expect(config.docker.content).not.toContain("AKAN_SSR");
141
- expect(config.docker.content).not.toContain("AKAN_CSR");
140
+ expect(config.dockerfile).not.toContain("AKAN_SSR");
141
+ expect(config.dockerfile).not.toContain("AKAN_CSR");
142
142
  });
143
143
 
144
144
  test("bakes the disabled surface into the image env so the default matches what was built", () => {
145
145
  const ssrOnly = new AkanAppConfig(app, [], packageJson, { web: { csr: false } }, baseDevEnv);
146
146
  expect(ssrOnly.web).toEqual({ ssr: true, csr: false });
147
- expect(ssrOnly.docker.content).toContain("ENV AKAN_CSR=false");
148
- expect(ssrOnly.docker.content).not.toContain("ENV AKAN_SSR=false");
147
+ expect(ssrOnly.dockerfile).toContain("ENV AKAN_CSR=false");
148
+ expect(ssrOnly.dockerfile).not.toContain("ENV AKAN_SSR=false");
149
149
 
150
150
  const apiOnly = new AkanAppConfig(app, [], packageJson, { web: false }, baseDevEnv);
151
151
  expect(apiOnly.web).toEqual({ ssr: false, csr: false });
152
- expect(apiOnly.docker.content).toContain("ENV AKAN_SSR=false");
153
- expect(apiOnly.docker.content).toContain("ENV AKAN_CSR=false");
152
+ expect(apiOnly.dockerfile).toContain("ENV AKAN_SSR=false");
153
+ expect(apiOnly.dockerfile).toContain("ENV AKAN_CSR=false");
154
154
 
155
155
  expect(new AkanAppConfig(app, [], packageJson, { web: true }, baseDevEnv).web).toEqual({ ssr: true, csr: true });
156
156
  });
@@ -164,12 +164,31 @@ describe("AkanAppConfig", () => {
164
164
  test("installs only ca-certificates and tzdata in the default image", () => {
165
165
  const config = new AkanAppConfig(app, [], packageJson, {}, baseDevEnv);
166
166
 
167
- expect(config.docker.content).toContain(
167
+ expect(config.dockerfile).toContain(
168
168
  "RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*",
169
169
  );
170
170
  // The Chromium/ffmpeg toolchain moved to per-app `preRuns`; keeping it here paid for it in every image.
171
171
  for (const dropped of ["libnss3", "ffmpeg", "build-essential", "redis", "xdg-utils"])
172
- expect(config.docker.content).not.toContain(dropped);
172
+ expect(config.dockerfile).not.toContain(dropped);
173
+ });
174
+
175
+ test("keeps a declared Dockerfile string verbatim", () => {
176
+ const dockerfile = "FROM oven/bun:1-slim\nCOPY . .\nCMD [\"bun\",\"main.js\"]";
177
+ const config = new AkanAppConfig(app, [], packageJson, { docker: dockerfile }, baseDevEnv);
178
+
179
+ expect(config.docker).toBe(dockerfile);
180
+ expect(config.dockerfile).toBe(dockerfile);
181
+ });
182
+
183
+ test("resolves the image parts, defaulting the base image and the command", () => {
184
+ const config = new AkanAppConfig(app, [], packageJson, { docker: { preRuns: ["echo hi"] } }, baseDevEnv);
185
+
186
+ expect(config.docker).toEqual({
187
+ image: "oven/bun:1-slim",
188
+ preRuns: ["echo hi"],
189
+ postRuns: [],
190
+ command: ["bun", "main.js"],
191
+ });
173
192
  });
174
193
 
175
194
  test("creates production package json and reports missing external versions", () => {
@@ -435,7 +454,7 @@ describe("AkanAppConfig lib externalLibs", () => {
435
454
  { externalLibs: ["@external/runtime"] },
436
455
  baseDevEnv,
437
456
  [],
438
- ["@external/runtime", "puppeteer"],
457
+ { externalLibs: ["@external/runtime", "puppeteer"], docker: { preRuns: [], postRuns: [] } },
439
458
  );
440
459
 
441
460
  expect(config.externalLibs).toEqual(["@external/runtime", "puppeteer"]);
@@ -468,6 +487,70 @@ describe("AkanAppConfig lib externalLibs", () => {
468
487
  });
469
488
  });
470
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
+
471
554
  describe("AkanLibConfig", () => {
472
555
  test("uses empty external libs by default and preserves explicit libs", () => {
473
556
  const lib = { name: "shared" } as never;
@@ -478,6 +561,15 @@ describe("AkanLibConfig", () => {
478
561
  };
479
562
  expect(new AkanLibConfig(lib, config).externalLibs).toEqual(["firebase-admin"]);
480
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
+ });
481
573
  });
482
574
 
483
575
  describe("AkanAppConfig.importConfigModule", () => {
@@ -19,7 +19,10 @@ import {
19
19
  type DatabaseMode,
20
20
  type DeepPartial,
21
21
  type DockerConfig,
22
+ type DockerOption,
23
+ type DockerRun,
22
24
  type LibConfigResult,
25
+ type LibDockerConfig,
23
26
  } from "./types";
24
27
 
25
28
  const DEFAULT_BARREL_IMPORTS = ["akanjs/webkit", "akanjs/common", "akanjs/ui", "akanjs/server"];
@@ -51,6 +54,7 @@ const DEFAULT_OPTIMIZE_IMPORTS = [
51
54
  "react-icons/*",
52
55
  ];
53
56
  const WORKSPACE_BARREL_FACETS = ["ui", "webkit", "common", "client", "server"] as const;
57
+ const DEFAULT_DOCKER_IMAGE = "oven/bun:1-slim";
54
58
  const SSR_RUNTIME_PACKAGES = ["react", "react-dom", "react-server-dom-webpack"] as const;
55
59
  // The firebase client (push tokens) and the Capacitor toolchain — `@capacitor/cli` (`npx cap`),
56
60
  // `@capacitor/assets` (`npx @capacitor/assets`) plus the `@capacitor/core`/`ios`/`android` runtime
@@ -165,13 +169,36 @@ const normalizeDeepLinks = (deepLinks: DeepPartial<AkanMobileTargetConfig["deepL
165
169
  } satisfies AkanMobileTargetConfig["deepLinks"];
166
170
  };
167
171
 
168
- /** What `akan.config.ts` may write: the resolved shape made partial, with `web` in its authoring union. */
169
- type AppConfigDeclaration = Omit<DeepPartial<AppConfigResult>, "web"> & { web?: AkanWebOption };
172
+ /** What `akan.config.ts` may write: the resolved shape made partial, with `docker` and `web` in their unions. */
173
+ type AppConfigDeclaration = Omit<DeepPartial<AppConfigResult>, "docker" | "web"> & {
174
+ docker?: DockerOption;
175
+ web?: AkanWebOption;
176
+ };
177
+
178
+ /** What the workspace's libs add to an app's build, read off each `libs/<lib>/akan.config.ts`. */
179
+ export interface LibContributions {
180
+ externalLibs: string[];
181
+ docker: LibDockerConfig;
182
+ }
183
+
184
+ const emptyLibContributions = (): LibContributions => ({
185
+ externalLibs: [],
186
+ docker: { preRuns: [], postRuns: [] },
187
+ });
188
+
189
+ /** First occurrence wins, so a step a lib and its app both declare becomes one layer. */
190
+ const dedupeDockerRuns = (runs: DockerRun[]): DockerRun[] => {
191
+ const byKey = new Map<string, DockerRun>();
192
+ for (const run of runs) byKey.set(typeof run === "string" ? run : JSON.stringify(run), run);
193
+ return [...byKey.values()];
194
+ };
170
195
 
171
196
  export class AkanAppConfig implements AppConfigResult {
172
197
  app: App;
173
198
  rootPackageJson: PackageJson;
174
199
  docker: DockerConfig;
200
+ /** The Dockerfile `akan build` writes: the declared string verbatim, or one assembled from the parts. */
201
+ dockerfile: string;
175
202
  defaultDatabaseMode: DatabaseMode;
176
203
  web: AkanWebConfig;
177
204
  externalLibs: string[];
@@ -201,7 +228,7 @@ export class AkanAppConfig implements AppConfigResult {
201
228
  config: AppConfigDeclaration,
202
229
  baseDevEnv: BaseDevEnv,
203
230
  plugins: AkanPlugin[] = [],
204
- libExternalLibs: readonly string[] = [],
231
+ libContributions: LibContributions = emptyLibContributions(),
205
232
  ) {
206
233
  this.app = app;
207
234
  this.rootPackageJson = rootPackageJson;
@@ -210,7 +237,7 @@ export class AkanAppConfig implements AppConfigResult {
210
237
  this.plugins = plugins;
211
238
  this.#applyRoutes(config?.routes);
212
239
  this.defaultDatabaseMode = config?.defaultDatabaseMode ?? "single";
213
- this.externalLibs = [...new Set([...(config?.externalLibs ?? []), ...libExternalLibs])];
240
+ this.externalLibs = [...new Set([...(config?.externalLibs ?? []), ...libContributions.externalLibs])];
214
241
  this.barrelImports = [
215
242
  ...DEFAULT_BARREL_IMPORTS,
216
243
  ...WORKSPACE_BARREL_FACETS.map((facet) => `@apps/${app.name}/${facet}`),
@@ -228,7 +255,8 @@ export class AkanAppConfig implements AppConfigResult {
228
255
  this.hasMobileConfig = Boolean(config.mobile);
229
256
  this.mobile = this.#resolveMobileConfig(config.mobile);
230
257
  this.web = this.#resolveWebConfig(config.web);
231
- this.docker = this.#makeDockerContent(config?.docker ?? {});
258
+ this.docker = AkanAppConfig.#resolveDocker(config.docker, libContributions.docker);
259
+ this.dockerfile = this.#makeDockerfile();
232
260
  }
233
261
  #resolveWebConfig(web: AkanWebOption | undefined): AkanWebConfig {
234
262
  const resolved = typeof web === "object" ? { ssr: true, csr: web.csr } : { ssr: web ?? true, csr: web ?? true };
@@ -338,7 +366,7 @@ export class AkanAppConfig implements AppConfigResult {
338
366
  this.branches.forEach((domain) => void domains.add(`${basePath}-${domain}.${serveDomain}`));
339
367
  });
340
368
  }
341
- #getDockerRunScripts(runs: (string | { [key in Arch]?: string })[]) {
369
+ #getDockerRunScripts(runs: DockerRun[]) {
342
370
  return runs.map((run) => {
343
371
  if (typeof run === "string") return `RUN ${run}`;
344
372
  else
@@ -355,29 +383,29 @@ export class AkanAppConfig implements AppConfigResult {
355
383
  if (typeof image === "string") return `FROM ${image}`;
356
384
  else return archs.map((arch) => `FROM ${image[arch] ?? defaultImage} AS ${arch}`).join("\n");
357
385
  }
358
- #makeDockerContent(docker: DeepPartial<DockerConfig>): DockerConfig {
359
- if (docker.content)
360
- return {
361
- content: docker.content,
362
- image: {},
363
- preRuns: [],
364
- postRuns: [],
365
- command: [],
366
- };
367
- const preRunScripts = this.#getDockerRunScripts(docker.preRuns ?? []);
368
- const postRunScripts = this.#getDockerRunScripts(docker.postRuns ?? []);
369
-
370
- const imageScript = docker.image
371
- ? this.#getDockerImageScript(docker.image, "oven/bun:1-slim")
372
- : "FROM oven/bun:1-slim";
373
- const command = docker.command ?? ["bun", "main.js"];
386
+ /** A declared Dockerfile string is verbatim, so a lib's steps are dropped rather than silently unapplied. */
387
+ static #resolveDocker(docker: DockerOption | undefined, libDocker: LibDockerConfig): DockerConfig {
388
+ if (typeof docker === "string") return docker;
389
+ return {
390
+ image: docker?.image ?? DEFAULT_DOCKER_IMAGE,
391
+ preRuns: dedupeDockerRuns([...libDocker.preRuns, ...(docker?.preRuns ?? [])]),
392
+ postRuns: dedupeDockerRuns([...libDocker.postRuns, ...(docker?.postRuns ?? [])]),
393
+ command: docker?.command ?? ["bun", "main.js"],
394
+ };
395
+ }
396
+ #makeDockerfile(): string {
397
+ if (typeof this.docker === "string") return this.docker;
398
+ const { image, preRuns, postRuns, command } = this.docker;
399
+ const preRunScripts = this.#getDockerRunScripts(preRuns);
400
+ const postRunScripts = this.#getDockerRunScripts(postRuns);
401
+ const imageScript = this.#getDockerImageScript(image, DEFAULT_DOCKER_IMAGE);
374
402
  // The image default matches what the build actually produced; a deployment narrows it further with its
375
403
  // own env, and can never widen it past the artifacts that are in the image.
376
404
  const webEnvLines = [
377
405
  ...(this.web.ssr ? [] : ["ENV AKAN_SSR=false"]),
378
406
  ...(this.web.csr ? [] : ["ENV AKAN_CSR=false"]),
379
407
  ].join("\n");
380
- const content = `${imageScript}
408
+ return `${imageScript}
381
409
  RUN apt-get update && apt-get upgrade -y && apt-get install -y --no-install-recommends ca-certificates tzdata && rm -rf /var/lib/apt/lists/*
382
410
  RUN ln -sf /usr/share/zoneinfo/Asia/Seoul /etc/localtime
383
411
  ARG TARGETARCH
@@ -400,13 +428,6 @@ ENV AKAN_PUBLIC_LOCALES=${this.i18n.locales.join(",")}
400
428
  ENV AKAN_PUBLIC_OPERATION_MODE=cloud
401
429
  ${webEnvLines}
402
430
  CMD [${command.map((c) => `"${c}"`).join(",")}]`;
403
- return {
404
- content,
405
- image: imageScript,
406
- preRuns: docker.preRuns ?? [],
407
- postRuns: docker.postRuns ?? [],
408
- command,
409
- };
410
431
  }
411
432
  static #importGeneration = 0;
412
433
  /**
@@ -434,23 +455,33 @@ CMD [${command.map((c) => `"${c}"`).join(",")}]`;
434
455
  ]);
435
456
  const resolved = typeof configImp === "function" ? configImp(app) : configImp;
436
457
  const { plugins, ...config } = (resolved ?? {}) as AppConfigDeclaration & { plugins?: AkanPlugin[] };
437
- const libExternalLibs = await AkanAppConfig.#collectLibExternalLibs(app, libs, bustImportCache);
438
- return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libExternalLibs);
458
+ const libContributions = await AkanAppConfig.#collectLibContributions(app, libs, bustImportCache);
459
+ return new AkanAppConfig(app, libs, rootPackageJson, config, baseDevEnv, plugins ?? [], libContributions);
439
460
  }
440
461
  //* Every workspace lib is read, not just this app's lib deps: narrowing the set needs the dependency
441
462
  //* scan, and the incremental page rebundle re-reads this config on every file change.
442
- static async #collectLibExternalLibs(app: App, libs: string[], bustImportCache: boolean): Promise<string[]> {
463
+ static async #collectLibContributions(
464
+ app: App,
465
+ libs: string[],
466
+ bustImportCache: boolean,
467
+ ): Promise<LibContributions> {
443
468
  const libConfigs = await Promise.all(
444
469
  libs.map(async (libName) =>
445
470
  LibExecutor.from(app, libName)
446
471
  .getConfig({ refresh: bustImportCache })
447
472
  .catch((error: unknown) => {
448
- app.logger.warn(`Skipped libs/${libName}/akan.config.ts externalLibs: ${String(error)}`);
473
+ app.logger.warn(`Skipped libs/${libName}/akan.config.ts contributions: ${String(error)}`);
449
474
  return null;
450
475
  }),
451
476
  ),
452
477
  );
453
- return libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []);
478
+ return {
479
+ externalLibs: libConfigs.flatMap((libConfig) => libConfig?.externalLibs ?? []),
480
+ docker: {
481
+ preRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.preRuns ?? []),
482
+ postRuns: libConfigs.flatMap((libConfig) => libConfig?.docker.postRuns ?? []),
483
+ },
484
+ };
454
485
  }
455
486
  #resolveProductionDependencyVersion(lib: string) {
456
487
  const rootVersion = this.rootPackageJson.dependencies?.[lib] ?? this.rootPackageJson.devDependencies?.[lib];
@@ -560,11 +591,13 @@ function mergeImageConfig(config: Partial<AkanImageConfig> = {}): AkanImageConfi
560
591
  export class AkanLibConfig implements LibConfigResult {
561
592
  lib: Lib;
562
593
  externalLibs: string[];
594
+ docker: LibDockerConfig;
563
595
  /** Live-only: plugins declared in this lib's `akan.config.ts` (never serialized). */
564
596
  plugins: AkanPlugin[];
565
597
  constructor(lib: Lib, config: DeepPartial<LibConfigResult>, plugins: AkanPlugin[] = []) {
566
598
  this.lib = lib;
567
599
  this.externalLibs = config?.externalLibs ?? [];
600
+ this.docker = { preRuns: config?.docker?.preRuns ?? [], postRuns: config?.docker?.postRuns ?? [] };
568
601
  this.plugins = plugins;
569
602
  }
570
603
  static async from(lib: Lib, { bustImportCache = false }: { bustImportCache?: boolean } = {}) {
@@ -19,10 +19,14 @@ export type {
19
19
  DatabaseMode,
20
20
  DeepPartial,
21
21
  DockerConfig,
22
+ DockerImageConfig,
23
+ DockerOption,
24
+ DockerRun,
22
25
  FileConventionScanResult,
23
26
  LibConfig,
24
27
  LibConfigInput,
25
28
  LibConfigResult,
29
+ LibDockerConfig,
26
30
  LibScanResult,
27
31
  MobileEnv,
28
32
  MobilePermission,
@@ -168,7 +168,7 @@ export class ApplicationBuildRunner {
168
168
  const akanConfig = await this.#app.getConfig();
169
169
  await Promise.all([
170
170
  this.#app.dist.writeJson("package.json", akanConfig.getProductionPackageJson()),
171
- this.#app.dist.writeFile(`${this.#app.dist.cwdPath}/Dockerfile`, akanConfig.docker.content),
171
+ this.#app.dist.writeFile(`${this.#app.dist.cwdPath}/Dockerfile`, akanConfig.dockerfile),
172
172
  ]);
173
173
  }
174
174
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akanjs/devkit",
3
- "version": "3.0.0-alpha.65",
3
+ "version": "3.0.0-alpha.66",
4
4
  "sourceType": "module",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -45,7 +45,7 @@
45
45
  "@langchain/openai": "^1.4.6",
46
46
  "@tailwindcss/node": "^4.3.0",
47
47
  "@trapezedev/project": "^7.1.4",
48
- "akanjs": "3.0.0-alpha.65",
48
+ "akanjs": "3.0.0-alpha.66",
49
49
  "chalk": "^5.6.2",
50
50
  "commander": "^14.0.3",
51
51
  "dayjs": "^1.11.20",
package/scanInfo.ts CHANGED
@@ -9,6 +9,7 @@ import type {
9
9
  PkgScanResult,
10
10
  ScanResult,
11
11
  } from "./akanConfig";
12
+ import { AkanAppConfig } from "./akanConfig";
12
13
  import { AppExecutor, LibExecutor, PkgExecutor, WorkspaceExecutor } from "./executors";
13
14
  import { isAllowedLibFacetRootFile, rootAllowedDirs, rootAllowedFiles } from "./workspaceLayout";
14
15
 
@@ -285,12 +286,11 @@ class ScanInfo {
285
286
  }),
286
287
  ]);
287
288
  const routes = exec.type === "lib" ? [] : await (exec as AppExecutor).getPageKeys();
288
- const scanResult: AppScanResult | LibScanResult = {
289
+ const common = {
289
290
  name: exec.name,
290
291
  type: exec.type,
291
292
  repoName: exec.workspace.repoName,
292
293
  serveDomain: WorkspaceExecutor.getBaseDevEnv(path.join(exec.workspace.workspaceRoot, ".env")).serveDomain,
293
- akanConfig,
294
294
  files,
295
295
  libDeps,
296
296
  pkgDeps,
@@ -298,6 +298,11 @@ class ScanInfo {
298
298
  devDependencies: npmDevDeps.filter((dep) => !isAkanFrameworkDependency(dep)),
299
299
  routes,
300
300
  };
301
+ //? `exec.type` and the resolved config class are correlated, which the union alone cannot express.
302
+ const scanResult: AppScanResult | LibScanResult =
303
+ akanConfig instanceof AkanAppConfig
304
+ ? ({ ...common, akanConfig } satisfies AppScanResult)
305
+ : ({ ...common, akanConfig } satisfies LibScanResult);
301
306
  return scanResult;
302
307
  }
303
308
 
@@ -462,7 +467,7 @@ export class LibInfo extends ScanInfo {
462
467
  LibInfo.libInfos.set(libName, await LibInfo.fromExecutor(libExecutor));
463
468
  }),
464
469
  );
465
- const libInfo = new LibInfo(exec, scanResult);
470
+ const libInfo = new LibInfo(exec, scanResult as LibScanResult);
466
471
  LibInfo.libInfos.set(exec.name, libInfo);
467
472
  LibInfo.#sortedLibIndices = null;
468
473
  return libInfo;