@akanjs/devkit 2.1.1-rc.2 → 2.1.1

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.
package/executors.ts CHANGED
@@ -8,7 +8,12 @@ import {
8
8
  spawn,
9
9
  } from "node:child_process";
10
10
  import { readFileSync } from "node:fs";
11
- import { copyFile, mkdir, readdir as readDirEntries, stat } from "node:fs/promises";
11
+ import {
12
+ copyFile,
13
+ mkdir,
14
+ readdir as readDirEntries,
15
+ stat,
16
+ } from "node:fs/promises";
12
17
  import path from "node:path";
13
18
  import {
14
19
  capitalize,
@@ -21,7 +26,12 @@ import {
21
26
  import { $ } from "bun";
22
27
  import chalk from "chalk";
23
28
  import ts from "typescript";
24
- import { AkanAppConfig, AkanLibConfig, decreaseBuildNum, increaseBuildNum } from "./akanConfig";
29
+ import {
30
+ AkanAppConfig,
31
+ AkanLibConfig,
32
+ decreaseBuildNum,
33
+ increaseBuildNum,
34
+ } from "./akanConfig";
25
35
  import { FileSys } from "./fileSys";
26
36
  import { getDirname } from "./getDirname";
27
37
  import { Linter } from "./linter";
@@ -64,7 +74,8 @@ const staticTemplateFileExtensions = new Set([
64
74
  ".xml",
65
75
  ]);
66
76
 
67
- const formatCommandArg = (value: string) => (/^[\w@%+=:,./-]+$/.test(value) ? value : JSON.stringify(value));
77
+ const formatCommandArg = (value: string) =>
78
+ /^[\w@%+=:,./-]+$/.test(value) ? value : JSON.stringify(value);
68
79
 
69
80
  const formatCommandForDisplay = (command: string, args: string[] = []) =>
70
81
  [command, ...args].map(formatCommandArg).join(" ");
@@ -100,11 +111,21 @@ export class CommandExecutionError extends Error {
100
111
  cause,
101
112
  }: CommandExecutionErrorOptions) {
102
113
  const displayCommand = formatCommandForDisplay(command, args);
103
- const status = signal ? `signal: ${signal}` : `exit code: ${code ?? "unknown"}`;
114
+ const status = signal
115
+ ? `signal: ${signal}`
116
+ : `exit code: ${code ?? "unknown"}`;
104
117
  const output = (stderr || stdout).trim();
105
- super([`Command failed: ${displayCommand}`, `cwd: ${cwd}`, status, output ? `\n${output}` : ""].join("\n"), {
106
- cause,
107
- });
118
+ super(
119
+ [
120
+ `Command failed: ${displayCommand}`,
121
+ `cwd: ${cwd}`,
122
+ status,
123
+ output ? `\n${output}` : "",
124
+ ].join("\n"),
125
+ {
126
+ cause,
127
+ },
128
+ );
108
129
  this.name = "CommandExecutionError";
109
130
  this.command = command;
110
131
  this.args = args;
@@ -138,12 +159,17 @@ const parseEnvFile = (envPath: string): Record<string, string> => {
138
159
  for (const line of content.split(/\r?\n/)) {
139
160
  const trimmed = line.trim();
140
161
  if (!trimmed || trimmed.startsWith("#")) continue;
141
- const normalized = trimmed.startsWith("export ") ? trimmed.slice("export ".length).trim() : trimmed;
162
+ const normalized = trimmed.startsWith("export ")
163
+ ? trimmed.slice("export ".length).trim()
164
+ : trimmed;
142
165
  const separatorIndex = normalized.indexOf("=");
143
166
  if (separatorIndex <= 0) continue;
144
167
  const key = normalized.slice(0, separatorIndex).trim();
145
168
  let value = normalized.slice(separatorIndex + 1).trim();
146
- if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
169
+ if (
170
+ (value.startsWith('"') && value.endsWith('"')) ||
171
+ (value.startsWith("'") && value.endsWith("'"))
172
+ ) {
147
173
  value = value.slice(1, -1);
148
174
  }
149
175
  env[key] = value;
@@ -151,7 +177,13 @@ const parseEnvFile = (envPath: string): Record<string, string> => {
151
177
  return env;
152
178
  };
153
179
 
154
- const PAGE_ROUTE_EXPORTS = new Set(["default", "pageConfig", "head", "generateHead", "Loading"]);
180
+ const PAGE_ROUTE_EXPORTS = new Set([
181
+ "default",
182
+ "pageConfig",
183
+ "head",
184
+ "generateHead",
185
+ "Loading",
186
+ ]);
155
187
  const ROOT_LAYOUT_EXPORTS = new Set([
156
188
  "default",
157
189
  "head",
@@ -164,7 +196,12 @@ const ROOT_LAYOUT_EXPORTS = new Set([
164
196
  "gaTrackingId",
165
197
  "Loading",
166
198
  ]);
167
- const LAYOUT_ROUTE_EXPORTS = new Set(["default", "head", "generateHead", "Loading"]);
199
+ const LAYOUT_ROUTE_EXPORTS = new Set([
200
+ "default",
201
+ "head",
202
+ "generateHead",
203
+ "Loading",
204
+ ]);
168
205
 
169
206
  function validateRouteSourceExports(
170
207
  source: string,
@@ -172,23 +209,42 @@ function validateRouteSourceExports(
172
209
  kind: "page" | "layout",
173
210
  options: { rootLayout?: boolean } = {},
174
211
  ) {
175
- const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);
212
+ const sourceFile = ts.createSourceFile(
213
+ filePath,
214
+ source,
215
+ ts.ScriptTarget.Latest,
216
+ true,
217
+ ts.ScriptKind.TSX,
218
+ );
176
219
  const allowed =
177
- kind === "page" ? PAGE_ROUTE_EXPORTS : options.rootLayout ? ROOT_LAYOUT_EXPORTS : LAYOUT_ROUTE_EXPORTS;
220
+ kind === "page"
221
+ ? PAGE_ROUTE_EXPORTS
222
+ : options.rootLayout
223
+ ? ROOT_LAYOUT_EXPORTS
224
+ : LAYOUT_ROUTE_EXPORTS;
178
225
  const exported = new Set<string>();
179
226
  const assertExport = (name: string) => {
180
227
  if (!allowed.has(name)) {
181
- throw new Error(`[route-convention] unsupported export "${name}" in ${filePath}`);
228
+ throw new Error(
229
+ `[route-convention] unsupported export "${name}" in ${filePath}`,
230
+ );
182
231
  }
183
232
  exported.add(name);
184
233
  };
185
234
 
186
235
  for (const statement of sourceFile.statements) {
187
- if (ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement)) continue;
236
+ if (
237
+ ts.isInterfaceDeclaration(statement) ||
238
+ ts.isTypeAliasDeclaration(statement)
239
+ )
240
+ continue;
188
241
  if (ts.isExportDeclaration(statement)) {
189
242
  if (statement.isTypeOnly) continue;
190
243
  const clause = statement.exportClause;
191
- if (!clause) throw new Error(`[route-convention] export * is not allowed in route modules: ${filePath}`);
244
+ if (!clause)
245
+ throw new Error(
246
+ `[route-convention] export * is not allowed in route modules: ${filePath}`,
247
+ );
192
248
  if (ts.isNamedExports(clause)) {
193
249
  for (const element of clause.elements) {
194
250
  if (element.isTypeOnly) continue;
@@ -197,17 +253,22 @@ function validateRouteSourceExports(
197
253
  }
198
254
  continue;
199
255
  }
200
- const modifiers = ts.canHaveModifiers(statement) ? ts.getModifiers(statement) : undefined;
201
- const isExported = modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
256
+ const modifiers = ts.canHaveModifiers(statement)
257
+ ? ts.getModifiers(statement)
258
+ : undefined;
259
+ const isExported =
260
+ modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
202
261
  if (!isExported) continue;
203
- const isDefault = modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) ?? false;
262
+ const isDefault =
263
+ modifiers?.some((m) => m.kind === ts.SyntaxKind.DefaultKeyword) ?? false;
204
264
  if (isDefault) {
205
265
  assertExport("default");
206
266
  continue;
207
267
  }
208
268
  if (ts.isVariableStatement(statement)) {
209
269
  for (const declaration of statement.declarationList.declarations) {
210
- if (ts.isIdentifier(declaration.name)) assertExport(declaration.name.text);
270
+ if (ts.isIdentifier(declaration.name))
271
+ assertExport(declaration.name.text);
211
272
  }
212
273
  continue;
213
274
  }
@@ -217,7 +278,9 @@ function validateRouteSourceExports(
217
278
  }
218
279
  }
219
280
  if (exported.has("head") && exported.has("generateHead")) {
220
- throw new Error(`[route-convention] head and generateHead cannot both be exported in ${filePath}`);
281
+ throw new Error(
282
+ `[route-convention] head and generateHead cannot both be exported in ${filePath}`,
283
+ );
221
284
  }
222
285
  }
223
286
 
@@ -290,7 +353,11 @@ export class Executor {
290
353
  });
291
354
  }
292
355
 
293
- spawn(command: string, args: string[] = [], options: SpawnOptions = {}): Promise<string> {
356
+ spawn(
357
+ command: string,
358
+ args: string[] = [],
359
+ options: SpawnOptions = {},
360
+ ): Promise<string> {
294
361
  const cwd = options.cwd?.toString() ?? this.cwdPath;
295
362
  const proc = spawn(command, args, {
296
363
  cwd: this.cwdPath,
@@ -342,7 +409,11 @@ export class Executor {
342
409
  });
343
410
  });
344
411
  }
345
- spawnSync(command: string, args: string[] = [], options: SpawnOptions = {}): ChildProcess {
412
+ spawnSync(
413
+ command: string,
414
+ args: string[] = [],
415
+ options: SpawnOptions = {},
416
+ ): ChildProcess {
346
417
  const proc = spawn(command, args, {
347
418
  cwd: this.cwdPath,
348
419
  // stdio: "inherit",
@@ -423,7 +494,8 @@ export class Executor {
423
494
  }
424
495
  async mkdir(dirPath: string) {
425
496
  const writePath = this.getPath(dirPath);
426
- if (!(await FileSys.dirExists(writePath))) await mkdir(writePath, { recursive: true });
497
+ if (!(await FileSys.dirExists(writePath)))
498
+ await mkdir(writePath, { recursive: true });
427
499
  this.logger.verbose(`Make directory ${writePath}`);
428
500
  return this;
429
501
  }
@@ -436,16 +508,27 @@ export class Executor {
436
508
  return [];
437
509
  }
438
510
  }
439
- async getAllFiles(pattern = "**/*", { cwd }: { cwd?: string } = {}): Promise<string[]> {
511
+ async getAllFiles(
512
+ pattern = "**/*",
513
+ { cwd }: { cwd?: string } = {},
514
+ ): Promise<string[]> {
440
515
  const glob = new Bun.Glob(pattern);
441
- return Array.from(glob.scanSync({ cwd: cwd ?? this.cwdPath, onlyFiles: true }));
516
+ return Array.from(
517
+ glob.scanSync({ cwd: cwd ?? this.cwdPath, onlyFiles: true }),
518
+ );
442
519
  }
443
- async getFilesAndDirs(dirPath: string): Promise<{ files: string[]; dirs: string[] }> {
520
+ async getFilesAndDirs(
521
+ dirPath: string,
522
+ ): Promise<{ files: string[]; dirs: string[] }> {
444
523
  const fullDirPath = this.getPath(dirPath);
445
524
  const fileGlob = new Bun.Glob("*");
446
- const files = Array.from(fileGlob.scanSync({ cwd: fullDirPath, onlyFiles: true }));
525
+ const files = Array.from(
526
+ fileGlob.scanSync({ cwd: fullDirPath, onlyFiles: true }),
527
+ );
447
528
  const dirGlob = new Bun.Glob("*");
448
- const allEntries = Array.from(dirGlob.scanSync({ cwd: fullDirPath, onlyFiles: false }));
529
+ const allEntries = Array.from(
530
+ dirGlob.scanSync({ cwd: fullDirPath, onlyFiles: false }),
531
+ );
449
532
  const dirs = allEntries.filter((entry) => !files.includes(entry));
450
533
  return { files, dirs };
451
534
  }
@@ -473,7 +556,8 @@ export class Executor {
473
556
  const writePath = this.getPath(filePath);
474
557
  const dir = path.dirname(writePath);
475
558
  if (!(await FileSys.dirExists(dir))) await mkdir(dir, { recursive: true });
476
- let contentStr = typeof content === "string" ? content : JSON.stringify(content, null, 2);
559
+ let contentStr =
560
+ typeof content === "string" ? content : JSON.stringify(content, null, 2);
477
561
 
478
562
  if (await FileSys.fileExists(writePath)) {
479
563
  const currentContent = await FileSys.readText(writePath);
@@ -482,7 +566,8 @@ export class Executor {
482
566
  contentStr = currentContent;
483
567
  } else {
484
568
  await FileSys.writeText(writePath, contentStr);
485
- if (Logger.isVerbose()) this.logger.rawLog(chalk.yellow(`File Update: ${filePath}`));
569
+ if (Logger.isVerbose())
570
+ this.logger.rawLog(chalk.yellow(`File Update: ${filePath}`));
486
571
  }
487
572
  } else {
488
573
  await FileSys.writeText(writePath, contentStr);
@@ -494,7 +579,9 @@ export class Executor {
494
579
  await this.writeFile(filePath, content);
495
580
  }
496
581
  async getLocalFile(targetPath: string) {
497
- const filePath = path.isAbsolute(targetPath) ? targetPath : targetPath.replace(this.cwdPath, "");
582
+ const filePath = path.isAbsolute(targetPath)
583
+ ? targetPath
584
+ : targetPath.replace(this.cwdPath, "");
498
585
  const content = await this.readFile(filePath);
499
586
  return { filePath, content };
500
587
  }
@@ -511,7 +598,8 @@ export class Executor {
511
598
  const dest = this.getPath(destPath);
512
599
  if (!(await FileSys.exists(src))) return;
513
600
  const isDirectory = (await stat(src)).isDirectory();
514
- if (!(await FileSys.exists(dest)) && isDirectory) await mkdir(dest, { recursive: true });
601
+ if (!(await FileSys.exists(dest)) && isDirectory)
602
+ await mkdir(dest, { recursive: true });
515
603
  await $`cp -r ${src}${isDirectory ? "/." : ""} ${dest}`;
516
604
  }
517
605
  log(msg: string) {
@@ -526,12 +614,22 @@ export class Executor {
526
614
  this.logger.debug(msg);
527
615
  return this;
528
616
  }
529
- spinning(msg: string, { prefix = `${this.emoji}${this.name}`, indent = 0, enableSpin = !Executor.verbose } = {}) {
617
+ spinning(
618
+ msg: string,
619
+ {
620
+ prefix = `${this.emoji}${this.name}`,
621
+ indent = 0,
622
+ enableSpin = !Executor.verbose,
623
+ } = {},
624
+ ) {
530
625
  return new Spinner(msg, { prefix, indent, enableSpin }).start();
531
626
  }
532
627
 
533
628
  #tsconfig: TsConfigJson | null = null;
534
- async getTsConfig(pathname = "tsconfig.json", { refresh }: { refresh?: boolean } = {}): Promise<TsConfigJson> {
629
+ async getTsConfig(
630
+ pathname = "tsconfig.json",
631
+ { refresh }: { refresh?: boolean } = {},
632
+ ): Promise<TsConfigJson> {
535
633
  if (this.#tsconfig && !refresh) return this.#tsconfig;
536
634
  const tsconfig = (await this.readJson(pathname)) as TsConfigJson;
537
635
  if (tsconfig.extends) {
@@ -556,7 +654,11 @@ export class Executor {
556
654
  }
557
655
 
558
656
  #packageJson: PackageJson | null = null;
559
- async getPackageJson({ refresh }: { refresh?: boolean } = {}): Promise<PackageJson> {
657
+ async getPackageJson({
658
+ refresh,
659
+ }: {
660
+ refresh?: boolean;
661
+ } = {}): Promise<PackageJson> {
560
662
  if (this.#packageJson && !refresh) return this.#packageJson;
561
663
  const packageJson = (await this.readJson("package.json")) as PackageJson;
562
664
  this.#packageJson = packageJson;
@@ -603,39 +705,55 @@ export class Executor {
603
705
  };
604
706
  const result = await getContent.default(scanInfo ?? null, dict, options);
605
707
  if (result === null) return null;
606
- const filename = typeof result === "object" ? result.filename : path.basename(targetPath).replace(".js", ".ts");
708
+ const filename =
709
+ typeof result === "object"
710
+ ? result.filename
711
+ : path.basename(targetPath).replace(".js", ".ts");
607
712
  const content = typeof result === "object" ? result.content : result;
608
713
  const dirname = path.dirname(targetPath);
609
714
  const convertedTargetPath = Object.entries(dict).reduce(
610
- (path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value),
715
+ (path, [key, value]) =>
716
+ path.replace(new RegExp(`__${key}__`, "g"), value),
611
717
  `${dirname}/${filename}`,
612
718
  );
613
- this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
719
+ this.logger.verbose(
720
+ `Apply template ${templatePath} to ${convertedTargetPath}`,
721
+ );
614
722
  return this.writeFile(convertedTargetPath, content, { overwrite });
615
723
  } else if (targetPath.endsWith(".template")) {
616
724
  const content = await FileSys.readText(templatePath);
617
725
  const convertedTargetPath = Object.entries(dict).reduce(
618
- (path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value),
726
+ (path, [key, value]) =>
727
+ path.replace(new RegExp(`__${key}__`, "g"), value),
619
728
  targetPath.slice(0, -9),
620
729
  );
621
730
  const convertedContent = Object.entries(dict).reduce(
622
- (data, [key, value]) => data.replace(new RegExp(`<%= ${key} %>`, "g"), value),
731
+ (data, [key, value]) =>
732
+ data.replace(new RegExp(`<%= ${key} %>`, "g"), value),
623
733
  content,
624
734
  );
625
- this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
735
+ this.logger.verbose(
736
+ `Apply template ${templatePath} to ${convertedTargetPath}`,
737
+ );
626
738
  return this.writeFile(convertedTargetPath, convertedContent, {
627
739
  overwrite,
628
740
  });
629
- } else if (staticTemplateFileExtensions.has(path.extname(targetPath).toLowerCase())) {
741
+ } else if (
742
+ staticTemplateFileExtensions.has(path.extname(targetPath).toLowerCase())
743
+ ) {
630
744
  const convertedTargetPath = Object.entries(dict).reduce(
631
- (path, [key, value]) => path.replace(new RegExp(`__${key}__`, "g"), value),
745
+ (path, [key, value]) =>
746
+ path.replace(new RegExp(`__${key}__`, "g"), value),
632
747
  targetPath,
633
748
  );
634
749
  const writePath = this.getPath(convertedTargetPath);
635
750
  const dirname = path.dirname(writePath);
636
- if (!(await FileSys.dirExists(dirname))) await mkdir(dirname, { recursive: true });
751
+ if (!(await FileSys.dirExists(dirname)))
752
+ await mkdir(dirname, { recursive: true });
637
753
  await copyFile(templatePath, writePath);
638
- this.logger.verbose(`Apply template ${templatePath} to ${convertedTargetPath}`);
754
+ this.logger.verbose(
755
+ `Apply template ${templatePath} to ${convertedTargetPath}`,
756
+ );
639
757
  return { filePath: writePath, content: "" };
640
758
  } else return null;
641
759
  }
@@ -723,7 +841,10 @@ export class Executor {
723
841
  const dict = {
724
842
  ...(options.dict ?? {}),
725
843
  ...Object.fromEntries(
726
- Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]),
844
+ Object.entries(options.dict ?? {}).map(([key, value]) => [
845
+ capitalize(key),
846
+ capitalize(value),
847
+ ]),
727
848
  ),
728
849
  };
729
850
  return this._applyTemplate({ ...options, dict });
@@ -735,10 +856,69 @@ export class Executor {
735
856
  typeCheck(filePath: string) {
736
857
  const path = this.getPath(filePath);
737
858
  const typeChecker = this.getTypeChecker();
738
- const { fileDiagnostics, fileErrors, fileWarnings } = typeChecker.check(path);
859
+ const { fileDiagnostics, fileErrors, fileWarnings } =
860
+ typeChecker.check(path);
739
861
  const message = typeChecker.formatDiagnostics(fileDiagnostics);
740
862
  return { fileDiagnostics, fileErrors, fileWarnings, message };
741
863
  }
864
+ async typeCheckAsync(filePath: string) {
865
+ const path = this.getPath(filePath);
866
+ const entry = await this.#resolveTypecheckWorkerEntry();
867
+ const proc = Bun.spawn([process.execPath, entry], {
868
+ cwd: this.cwdPath,
869
+ env: {
870
+ ...process.env,
871
+ AKAN_TYPECHECK_CWD: this.cwdPath,
872
+ AKAN_TYPECHECK_FILE: path,
873
+ },
874
+ stdout: "pipe",
875
+ stderr: "pipe",
876
+ });
877
+ const [stdout, stderr, exitCode] = await Promise.all([
878
+ new Response(proc.stdout).text(),
879
+ new Response(proc.stderr).text(),
880
+ proc.exited,
881
+ ]);
882
+ if (exitCode !== 0)
883
+ throw new Error(
884
+ (stderr || stdout).trim() ||
885
+ `Typecheck failed with exit code ${exitCode}`,
886
+ );
887
+
888
+ const result = JSON.parse(stdout) as {
889
+ fileDiagnosticsCount: number;
890
+ fileErrorsCount: number;
891
+ fileWarningsCount: number;
892
+ message: string;
893
+ };
894
+ return {
895
+ fileDiagnostics: Array.from({ length: result.fileDiagnosticsCount }),
896
+ fileErrors: Array.from({ length: result.fileErrorsCount }),
897
+ fileWarnings: Array.from({ length: result.fileWarningsCount }),
898
+ message: result.message,
899
+ };
900
+ }
901
+ async #resolveTypecheckWorkerEntry() {
902
+ const dirname = getDirname(import.meta.url);
903
+ const candidates = [
904
+ path.join(
905
+ process.cwd(),
906
+ "pkgs/@akanjs/devkit/typecheck/typecheck.proc.ts",
907
+ ),
908
+ path.join(
909
+ process.cwd(),
910
+ "node_modules/@akanjs/devkit/typecheck/typecheck.proc.ts",
911
+ ),
912
+ path.join(dirname, "typecheck/typecheck.proc.ts"),
913
+ path.join(dirname, "typecheck.proc.js"),
914
+ path.join(dirname, "typecheck.proc.ts"),
915
+ ];
916
+ for (const candidate of candidates)
917
+ if (await Bun.file(candidate).exists()) return candidate;
918
+ throw new Error(
919
+ `[devkit] typecheck worker entry not found; looked in: ${candidates.join(", ")}`,
920
+ );
921
+ }
742
922
  getLinter() {
743
923
  this.linter ??= new Linter(this.cwdPath);
744
924
  return this.linter;
@@ -785,11 +965,16 @@ export class WorkspaceExecutor extends Executor {
785
965
  workspaceRoot?: string;
786
966
  repoName?: string;
787
967
  } = {}) {
788
- return WorkspaceExecutor.#execs.get(repoName) ?? new WorkspaceExecutor({ workspaceRoot, repoName });
968
+ return (
969
+ WorkspaceExecutor.#execs.get(repoName) ??
970
+ new WorkspaceExecutor({ workspaceRoot, repoName })
971
+ );
789
972
  }
790
973
  static getBaseDevEnv(envPath?: string) {
791
974
  // Bun auto-loads .env, so we use process.env directly
792
- const sourceEnv = envPath ? { ...process.env, ...parseEnvFile(envPath) } : process.env;
975
+ const sourceEnv = envPath
976
+ ? { ...process.env, ...parseEnvFile(envPath) }
977
+ : process.env;
793
978
 
794
979
  const appName = sourceEnv.AKAN_PUBLIC_APP_NAME;
795
980
  const workspaceRoot = sourceEnv.AKAN_WORKSPACE_ROOT;
@@ -811,7 +996,15 @@ export class WorkspaceExecutor extends Executor {
811
996
  | "local"
812
997
  | undefined;
813
998
  if (!env) throw new Error("AKAN_PUBLIC_ENV is not set");
814
- return { ...(appName ? { appName } : {}), workspaceRoot, repoName, serveDomain, env, portOffset, workspaceId };
999
+ return {
1000
+ ...(appName ? { appName } : {}),
1001
+ workspaceRoot,
1002
+ repoName,
1003
+ serveDomain,
1004
+ env,
1005
+ portOffset,
1006
+ workspaceId,
1007
+ };
815
1008
  }
816
1009
  getWorkspaceId<AllowEmpty extends boolean = false>({
817
1010
  allowEmpty,
@@ -819,7 +1012,8 @@ export class WorkspaceExecutor extends Executor {
819
1012
  allowEmpty?: AllowEmpty;
820
1013
  } = {}): AllowEmpty extends true ? string | undefined : string {
821
1014
  const { workspaceId } = WorkspaceExecutor.getBaseDevEnv();
822
- if (!workspaceId && !allowEmpty) throw new Error("Workspace ID is not found");
1015
+ if (!workspaceId && !allowEmpty)
1016
+ throw new Error("Workspace ID is not found");
823
1017
  return workspaceId as AllowEmpty extends true ? string | undefined : string;
824
1018
  }
825
1019
  async scan(): Promise<WorkspaceInfo> {
@@ -827,22 +1021,38 @@ export class WorkspaceExecutor extends Executor {
827
1021
  }
828
1022
  async getApps() {
829
1023
  if (!(await FileSys.dirExists(`${this.workspaceRoot}/apps`))) return [];
830
- return await this.#getDirHasFile(`${this.workspaceRoot}/apps`, "akan.config.ts");
1024
+ return await this.#getDirHasFile(
1025
+ `${this.workspaceRoot}/apps`,
1026
+ "akan.config.ts",
1027
+ );
831
1028
  }
832
1029
  async getLibs() {
833
1030
  if (!(await FileSys.dirExists(`${this.workspaceRoot}/libs`))) return [];
834
- return await this.#getDirHasFile(`${this.workspaceRoot}/libs`, "akan.config.ts");
1031
+ return await this.#getDirHasFile(
1032
+ `${this.workspaceRoot}/libs`,
1033
+ "akan.config.ts",
1034
+ );
835
1035
  }
836
1036
  async getSyss() {
837
- const [appNames, libNames] = await Promise.all([this.getApps(), this.getLibs()]);
1037
+ const [appNames, libNames] = await Promise.all([
1038
+ this.getApps(),
1039
+ this.getLibs(),
1040
+ ]);
838
1041
  return [appNames, libNames] as [string[], string[]];
839
1042
  }
840
1043
  async getPkgs() {
841
1044
  if (!(await FileSys.dirExists(`${this.workspaceRoot}/pkgs`))) return [];
842
- return await this.#getDirHasFile(`${this.workspaceRoot}/pkgs`, "package.json");
1045
+ return await this.#getDirHasFile(
1046
+ `${this.workspaceRoot}/pkgs`,
1047
+ "package.json",
1048
+ );
843
1049
  }
844
1050
  async getExecs() {
845
- const [appNames, libNames, pkgNames] = await Promise.all([this.getApps(), this.getLibs(), this.getPkgs()]);
1051
+ const [appNames, libNames, pkgNames] = await Promise.all([
1052
+ this.getApps(),
1053
+ this.getLibs(),
1054
+ this.getPkgs(),
1055
+ ]);
846
1056
  return [appNames, libNames, pkgNames] as [string[], string[], string[]];
847
1057
  }
848
1058
  async setPkgTsPaths(name: string) {
@@ -851,7 +1061,11 @@ export class WorkspaceExecutor extends Executor {
851
1061
  rootTsConfig.compilerOptions.paths[name] = [`./pkgs/${name}/index.ts`];
852
1062
  rootTsConfig.compilerOptions.paths[`${name}/*`] = [`./pkgs/${name}/*`];
853
1063
  if (rootTsConfig.references) {
854
- if (!rootTsConfig.references.some((ref) => ref.path === `./pkgs/${name}/tsconfig.json`))
1064
+ if (
1065
+ !rootTsConfig.references.some(
1066
+ (ref) => ref.path === `./pkgs/${name}/tsconfig.json`,
1067
+ )
1068
+ )
855
1069
  rootTsConfig.references.push({ path: `./pkgs/${name}/tsconfig.json` });
856
1070
  }
857
1071
  await this.writeJson("tsconfig.json", rootTsConfig);
@@ -859,11 +1073,14 @@ export class WorkspaceExecutor extends Executor {
859
1073
  }
860
1074
  async unsetPkgTsPaths(name: string) {
861
1075
  const rootTsConfig = (await this.readJson("tsconfig.json")) as TsConfigJson;
862
- const filteredKeys = Object.keys(rootTsConfig.compilerOptions.paths ?? {}).filter(
863
- (key) => key !== name && key !== `${name}/*`,
864
- );
1076
+ const filteredKeys = Object.keys(
1077
+ rootTsConfig.compilerOptions.paths ?? {},
1078
+ ).filter((key) => key !== name && key !== `${name}/*`);
865
1079
  rootTsConfig.compilerOptions.paths = Object.fromEntries(
866
- filteredKeys.map((key) => [key, rootTsConfig.compilerOptions.paths?.[key] ?? []]),
1080
+ filteredKeys.map((key) => [
1081
+ key,
1082
+ rootTsConfig.compilerOptions.paths?.[key] ?? [],
1083
+ ]),
867
1084
  );
868
1085
  if (rootTsConfig.references) {
869
1086
  rootTsConfig.references = rootTsConfig.references.filter(
@@ -875,7 +1092,12 @@ export class WorkspaceExecutor extends Executor {
875
1092
  }
876
1093
  async getDirInModule(basePath: string, name: string) {
877
1094
  const AVOID_DIRS = ["__lib", "__scalar", `_`, `_${name}`];
878
- const getDirs = async (dirname: string, maxDepth = 3, results: string[] = [], prefix = "") => {
1095
+ const getDirs = async (
1096
+ dirname: string,
1097
+ maxDepth = 3,
1098
+ results: string[] = [],
1099
+ prefix = "",
1100
+ ) => {
879
1101
  const dirs = await this.readdir(dirname);
880
1102
  await Promise.all(
881
1103
  dirs.map(async (dir) => {
@@ -883,7 +1105,8 @@ export class WorkspaceExecutor extends Executor {
883
1105
  const dirPath = path.join(dirname, dir);
884
1106
  if ((await stat(dirPath)).isDirectory()) {
885
1107
  results.push(`${prefix}${dir}`);
886
- if (maxDepth > 0) await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
1108
+ if (maxDepth > 0)
1109
+ await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
887
1110
  }
888
1111
  }),
889
1112
  );
@@ -891,23 +1114,34 @@ export class WorkspaceExecutor extends Executor {
891
1114
  };
892
1115
  return await getDirs(basePath);
893
1116
  }
894
- async commit(message: string, { init = false, add = true }: { init?: boolean; add?: boolean } = {}) {
1117
+ async commit(
1118
+ message: string,
1119
+ { init = false, add = true }: { init?: boolean; add?: boolean } = {},
1120
+ ) {
895
1121
  if (init) await this.exec(`git init --quiet`);
896
1122
  if (add) await this.exec(`git add .`);
897
1123
  await this.exec(`git commit --quiet -m "${message}"`);
898
1124
  }
899
1125
  async #getDirHasFile(basePath: string, targetFilename: string) {
900
1126
  const AVOID_DIRS = ["node_modules", "dist", "public", "webkit"];
901
- const getDirs = async (dirname: string, maxDepth = 3, results: string[] = [], prefix = "") => {
1127
+ const getDirs = async (
1128
+ dirname: string,
1129
+ maxDepth = 3,
1130
+ results: string[] = [],
1131
+ prefix = "",
1132
+ ) => {
902
1133
  const dirs = await this.readdir(dirname);
903
1134
  await Promise.all(
904
1135
  dirs.map(async (dir) => {
905
1136
  if (AVOID_DIRS.includes(dir)) return;
906
1137
  const dirPath = path.join(dirname, dir);
907
1138
  if ((await stat(dirPath)).isDirectory()) {
908
- const hasTargetFile = await FileSys.fileExists(path.join(dirPath, targetFilename));
1139
+ const hasTargetFile = await FileSys.fileExists(
1140
+ path.join(dirPath, targetFilename),
1141
+ );
909
1142
  if (hasTargetFile) results.push(`${prefix}${dir}`);
910
- if (maxDepth > 0) await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
1143
+ if (maxDepth > 0)
1144
+ await getDirs(dirPath, maxDepth - 1, results, `${prefix}${dir}/`);
911
1145
  }
912
1146
  }),
913
1147
  );
@@ -920,10 +1154,18 @@ export class WorkspaceExecutor extends Executor {
920
1154
  const [appNames, libNames] = await this.getSyss();
921
1155
  const scalarConstantExampleFiles = [
922
1156
  ...(
923
- await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getScalarConstantFiles()))
1157
+ await Promise.all(
1158
+ appNames.map((appName) =>
1159
+ AppExecutor.from(this, appName).getScalarConstantFiles(),
1160
+ ),
1161
+ )
924
1162
  ).flat(),
925
1163
  ...(
926
- await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getScalarConstantFiles()))
1164
+ await Promise.all(
1165
+ libNames.map((libName) =>
1166
+ LibExecutor.from(this, libName).getScalarConstantFiles(),
1167
+ ),
1168
+ )
927
1169
  ).flat(),
928
1170
  ];
929
1171
  return scalarConstantExampleFiles;
@@ -931,24 +1173,60 @@ export class WorkspaceExecutor extends Executor {
931
1173
  async getConstantFiles() {
932
1174
  const [appNames, libNames] = await this.getSyss();
933
1175
  const moduleConstantExampleFiles = [
934
- ...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getConstantFiles()))).flat(),
935
- ...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getConstantFiles()))).flat(),
1176
+ ...(
1177
+ await Promise.all(
1178
+ appNames.map((appName) =>
1179
+ AppExecutor.from(this, appName).getConstantFiles(),
1180
+ ),
1181
+ )
1182
+ ).flat(),
1183
+ ...(
1184
+ await Promise.all(
1185
+ libNames.map((libName) =>
1186
+ LibExecutor.from(this, libName).getConstantFiles(),
1187
+ ),
1188
+ )
1189
+ ).flat(),
936
1190
  ];
937
1191
  return moduleConstantExampleFiles;
938
1192
  }
939
1193
  async getDictionaryFiles() {
940
1194
  const [appNames, libNames] = await this.getSyss();
941
1195
  const moduleDictionaryExampleFiles = [
942
- ...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getDictionaryFiles()))).flat(),
943
- ...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getDictionaryFiles()))).flat(),
1196
+ ...(
1197
+ await Promise.all(
1198
+ appNames.map((appName) =>
1199
+ AppExecutor.from(this, appName).getDictionaryFiles(),
1200
+ ),
1201
+ )
1202
+ ).flat(),
1203
+ ...(
1204
+ await Promise.all(
1205
+ libNames.map((libName) =>
1206
+ LibExecutor.from(this, libName).getDictionaryFiles(),
1207
+ ),
1208
+ )
1209
+ ).flat(),
944
1210
  ];
945
1211
  return moduleDictionaryExampleFiles;
946
1212
  }
947
1213
  async getViewFiles() {
948
1214
  const [appNames, libNames] = await this.getSyss();
949
1215
  const viewExampleFiles = [
950
- ...(await Promise.all(appNames.map((appName) => AppExecutor.from(this, appName).getViewsSourceCode()))).flat(),
951
- ...(await Promise.all(libNames.map((libName) => LibExecutor.from(this, libName).getViewsSourceCode()))).flat(),
1216
+ ...(
1217
+ await Promise.all(
1218
+ appNames.map((appName) =>
1219
+ AppExecutor.from(this, appName).getViewsSourceCode(),
1220
+ ),
1221
+ )
1222
+ ).flat(),
1223
+ ...(
1224
+ await Promise.all(
1225
+ libNames.map((libName) =>
1226
+ LibExecutor.from(this, libName).getViewsSourceCode(),
1227
+ ),
1228
+ )
1229
+ ).flat(),
952
1230
  ];
953
1231
  return viewExampleFiles;
954
1232
  }
@@ -967,7 +1245,11 @@ export class SysExecutor extends Executor {
967
1245
  override name: string;
968
1246
  type: "app" | "lib";
969
1247
  override emoji: string;
970
- constructor({ workspace = WorkspaceExecutor.fromRoot(), name, type }: SysExecutorOptions) {
1248
+ constructor({
1249
+ workspace = WorkspaceExecutor.fromRoot(),
1250
+ name,
1251
+ type,
1252
+ }: SysExecutorOptions) {
971
1253
  super(name, `${workspace.workspaceRoot}/${type}s/${name}`);
972
1254
  this.workspace = workspace;
973
1255
  this.name = name;
@@ -984,7 +1266,8 @@ export class SysExecutor extends Executor {
984
1266
  return this.#akanConfig;
985
1267
  }
986
1268
  async getModules() {
987
- const path = this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
1269
+ const path =
1270
+ this.type === "app" ? `apps/${this.name}/lib` : `libs/${this.name}/lib`;
988
1271
  return await this.workspace.getDirInModule(path, this.name);
989
1272
  }
990
1273
 
@@ -996,17 +1279,26 @@ export class SysExecutor extends Executor {
996
1279
  allowEmpty,
997
1280
  }: {
998
1281
  allowEmpty?: AllowEmpty;
999
- } = {}): AllowEmpty extends true ? AppInfo | LibInfo | null : AppInfo | LibInfo {
1000
- if (!this.hasScanInfo() && !allowEmpty) throw new Error("Scan info is not available");
1001
- return this.#scanInfo as AllowEmpty extends true ? AppInfo | LibInfo | null : AppInfo | LibInfo;
1002
- }
1003
- #getScanTemplateTasks(scanInfo: AppInfo | LibInfo): (Promise<FileContent[]> | null)[] {
1282
+ } = {}): AllowEmpty extends true
1283
+ ? AppInfo | LibInfo | null
1284
+ : AppInfo | LibInfo {
1285
+ if (!this.hasScanInfo() && !allowEmpty)
1286
+ throw new Error("Scan info is not available");
1287
+ return this.#scanInfo as AllowEmpty extends true
1288
+ ? AppInfo | LibInfo | null
1289
+ : AppInfo | LibInfo;
1290
+ }
1291
+ #getScanTemplateTasks(
1292
+ scanInfo: AppInfo | LibInfo,
1293
+ ): (Promise<FileContent[]> | null)[] {
1004
1294
  return [
1005
1295
  this._applyTemplate({ basePath: "env", template: "env", scanInfo }),
1006
1296
  this._applyTemplate({ basePath: "lib", template: "lib", scanInfo }),
1007
1297
  this._applyTemplate({ basePath: ".", template: "server.ts", scanInfo }),
1008
1298
  this._applyTemplate({ basePath: ".", template: "client.ts", scanInfo }),
1009
- this.type === "lib" ? this._applyTemplate({ basePath: ".", template: "index.ts", scanInfo }) : null,
1299
+ this.type === "lib"
1300
+ ? this._applyTemplate({ basePath: ".", template: "index.ts", scanInfo })
1301
+ : null,
1010
1302
  ...scanFacetDirs.map((facet) =>
1011
1303
  this._applyTemplate({
1012
1304
  basePath: facet,
@@ -1067,7 +1359,11 @@ export class SysExecutor extends Executor {
1067
1359
  if (writeLib) {
1068
1360
  const libInfos = [...scanInfo.getLibInfos().values()];
1069
1361
  await this.#updateDependencies(scanInfo);
1070
- await Promise.all(libInfos.flatMap((libInfo) => libInfo.exec.#getScanTemplateTasks(libInfo)));
1362
+ await Promise.all(
1363
+ libInfos.flatMap((libInfo) =>
1364
+ libInfo.exec.#getScanTemplateTasks(libInfo),
1365
+ ),
1366
+ );
1071
1367
  }
1072
1368
  }
1073
1369
  this.#scanInfo = scanInfo;
@@ -1084,7 +1380,9 @@ export class SysExecutor extends Executor {
1084
1380
  ...libPackageJson,
1085
1381
  dependencies: {
1086
1382
  ...Object.fromEntries(
1087
- Object.entries(libPackageJson.dependencies ?? {}).filter(([dep]) => !devDependencySet.has(dep)),
1383
+ Object.entries(libPackageJson.dependencies ?? {}).filter(
1384
+ ([dep]) => !devDependencySet.has(dep),
1385
+ ),
1088
1386
  ),
1089
1387
  ...(Object.fromEntries(
1090
1388
  dependencies
@@ -1095,82 +1393,134 @@ export class SysExecutor extends Executor {
1095
1393
  },
1096
1394
  devDependencies: {
1097
1395
  ...Object.fromEntries(
1098
- Object.entries(libPackageJson.devDependencies ?? {}).filter(([dep]) => !dependencySet.has(dep)),
1396
+ Object.entries(libPackageJson.devDependencies ?? {}).filter(
1397
+ ([dep]) => !dependencySet.has(dep),
1398
+ ),
1099
1399
  ),
1100
1400
  ...(Object.fromEntries(
1101
1401
  devDependencies
1102
- .filter((dep) => rootPackageJson.dependencies?.[dep] || rootPackageJson.devDependencies?.[dep])
1402
+ .filter(
1403
+ (dep) =>
1404
+ rootPackageJson.dependencies?.[dep] ||
1405
+ rootPackageJson.devDependencies?.[dep],
1406
+ )
1103
1407
  .sort()
1104
- .map((dep) => [dep, rootPackageJson.devDependencies?.[dep] ?? rootPackageJson.dependencies?.[dep]]),
1408
+ .map((dep) => [
1409
+ dep,
1410
+ rootPackageJson.devDependencies?.[dep] ??
1411
+ rootPackageJson.dependencies?.[dep],
1412
+ ]),
1105
1413
  ) as Record<string, string>),
1106
1414
  },
1107
1415
  };
1108
1416
  await this.setPackageJson(libPkgJsonWithDeps);
1109
1417
  }
1110
1418
  override async getLocalFile(targetPath: string) {
1111
- const filePath = path.isAbsolute(targetPath) ? targetPath : `${this.type}s/${this.name}/${targetPath}`;
1419
+ const filePath = path.isAbsolute(targetPath)
1420
+ ? targetPath
1421
+ : `${this.type}s/${this.name}/${targetPath}`;
1112
1422
  const content = await this.workspace.readFile(filePath);
1113
1423
  return { filePath, content };
1114
1424
  }
1115
1425
 
1116
1426
  async getDatabaseModules() {
1117
1427
  const databaseModules = (await this.readdir("lib"))
1118
- .filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
1119
- .filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.constant.ts`).exists());
1428
+ .filter(
1429
+ (name) =>
1430
+ !name.startsWith("_") &&
1431
+ !name.startsWith("__") &&
1432
+ !name.endsWith(".ts"),
1433
+ )
1434
+ .filter((name) =>
1435
+ Bun.file(`${this.cwdPath}/lib/${name}/${name}.constant.ts`).exists(),
1436
+ );
1120
1437
  return databaseModules;
1121
1438
  }
1122
1439
 
1123
1440
  async getServiceModules() {
1124
1441
  const serviceModules = (await this.readdir("lib"))
1125
1442
  .filter((name) => name.startsWith("_") && !name.startsWith("__"))
1126
- .filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.service.ts`).exists());
1443
+ .filter((name) =>
1444
+ Bun.file(`${this.cwdPath}/lib/${name}/${name}.service.ts`).exists(),
1445
+ );
1127
1446
  return serviceModules;
1128
1447
  }
1129
1448
 
1130
1449
  async getScalarModules() {
1131
1450
  const scalarModules = (await this.readdir("lib/__scalar"))
1132
1451
  .filter((name) => !name.startsWith("_"))
1133
- .filter((name) => Bun.file(`${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`).exists());
1452
+ .filter((name) =>
1453
+ Bun.file(
1454
+ `${this.cwdPath}/lib/__scalar/${name}/${name}.constant.ts`,
1455
+ ).exists(),
1456
+ );
1134
1457
  return scalarModules;
1135
1458
  }
1136
1459
 
1137
1460
  async getViewComponents() {
1138
1461
  const viewComponents = (await this.readdir("lib"))
1139
- .filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
1140
- .filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.View.tsx`).exists());
1462
+ .filter(
1463
+ (name) =>
1464
+ !name.startsWith("_") &&
1465
+ !name.startsWith("__") &&
1466
+ !name.endsWith(".ts"),
1467
+ )
1468
+ .filter((name) =>
1469
+ Bun.file(`${this.cwdPath}/lib/${name}/${name}.View.tsx`).exists(),
1470
+ );
1141
1471
  return viewComponents;
1142
1472
  }
1143
1473
 
1144
1474
  async getUnitComponents() {
1145
1475
  const unitComponents = (await this.readdir("lib"))
1146
- .filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
1147
- .filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.Unit.tsx`).exists());
1476
+ .filter(
1477
+ (name) =>
1478
+ !name.startsWith("_") &&
1479
+ !name.startsWith("__") &&
1480
+ !name.endsWith(".ts"),
1481
+ )
1482
+ .filter((name) =>
1483
+ Bun.file(`${this.cwdPath}/lib/${name}/${name}.Unit.tsx`).exists(),
1484
+ );
1148
1485
  return unitComponents;
1149
1486
  }
1150
1487
  async getTemplateComponents() {
1151
1488
  const templateComponents = (await this.readdir("lib"))
1152
- .filter((name) => !name.startsWith("_") && !name.startsWith("__") && !name.endsWith(".ts"))
1153
- .filter((name) => Bun.file(`${this.cwdPath}/lib/${name}/${name}.Template.tsx`).exists());
1489
+ .filter(
1490
+ (name) =>
1491
+ !name.startsWith("_") &&
1492
+ !name.startsWith("__") &&
1493
+ !name.endsWith(".ts"),
1494
+ )
1495
+ .filter((name) =>
1496
+ Bun.file(`${this.cwdPath}/lib/${name}/${name}.Template.tsx`).exists(),
1497
+ );
1154
1498
  return templateComponents;
1155
1499
  }
1156
1500
 
1157
1501
  async getViewsSourceCode() {
1158
1502
  const viewComponents = await this.getViewComponents();
1159
1503
  return Promise.all(
1160
- viewComponents.map((viewComponent) => this.getLocalFile(`lib/${viewComponent}/${viewComponent}.View.tsx`)),
1504
+ viewComponents.map((viewComponent) =>
1505
+ this.getLocalFile(`lib/${viewComponent}/${viewComponent}.View.tsx`),
1506
+ ),
1161
1507
  );
1162
1508
  }
1163
1509
  async getUnitsSourceCode() {
1164
1510
  const unitComponents = await this.getUnitComponents();
1165
1511
  return Promise.all(
1166
- unitComponents.map((unitComponent) => this.getLocalFile(`lib/${unitComponent}/${unitComponent}.Unit.tsx`)),
1512
+ unitComponents.map((unitComponent) =>
1513
+ this.getLocalFile(`lib/${unitComponent}/${unitComponent}.Unit.tsx`),
1514
+ ),
1167
1515
  );
1168
1516
  }
1169
1517
  async getTemplatesSourceCode() {
1170
1518
  const templateComponents = await this.getTemplateComponents();
1171
1519
  return Promise.all(
1172
1520
  templateComponents.map((templateComponent) =>
1173
- this.getLocalFile(`lib/${templateComponent}/${templateComponent}.Template.tsx`),
1521
+ this.getLocalFile(
1522
+ `lib/${templateComponent}/${templateComponent}.Template.tsx`,
1523
+ ),
1174
1524
  ),
1175
1525
  );
1176
1526
  }
@@ -1179,7 +1529,9 @@ export class SysExecutor extends Executor {
1179
1529
  const scalarModules = await this.getScalarModules();
1180
1530
  return Promise.all(
1181
1531
  scalarModules.map((scalarModule) =>
1182
- this.getLocalFile(`lib/__scalar/${scalarModule}/${scalarModule}.constant.ts`),
1532
+ this.getLocalFile(
1533
+ `lib/__scalar/${scalarModule}/${scalarModule}.constant.ts`,
1534
+ ),
1183
1535
  ),
1184
1536
  );
1185
1537
  }
@@ -1187,13 +1539,19 @@ export class SysExecutor extends Executor {
1187
1539
  async getScalarDictionaryFiles() {
1188
1540
  const scalarModules = await this.getScalarModules();
1189
1541
  return Promise.all(
1190
- scalarModules.map((scalarModule) => this.getLocalFile(`lib/${scalarModule}/${scalarModule}.dictionary.ts`)),
1542
+ scalarModules.map((scalarModule) =>
1543
+ this.getLocalFile(`lib/${scalarModule}/${scalarModule}.dictionary.ts`),
1544
+ ),
1191
1545
  );
1192
1546
  }
1193
1547
 
1194
1548
  async getConstantFiles() {
1195
1549
  const modules = await this.getModules();
1196
- return Promise.all(modules.map((module) => this.getLocalFile(`lib/${module}/${module}.constant.ts`)));
1550
+ return Promise.all(
1551
+ modules.map((module) =>
1552
+ this.getLocalFile(`lib/${module}/${module}.constant.ts`),
1553
+ ),
1554
+ );
1197
1555
  }
1198
1556
  async getConstantFilesWithLibs() {
1199
1557
  const scanInfo =
@@ -1209,11 +1567,19 @@ export class SysExecutor extends Executor {
1209
1567
  ...(await LibExecutor.from(this, lib).getScalarConstantFiles()),
1210
1568
  ]),
1211
1569
  );
1212
- return [...sysContantFiles, ...sysScalarConstantFiles, ...libConstantFiles.flat()];
1570
+ return [
1571
+ ...sysContantFiles,
1572
+ ...sysScalarConstantFiles,
1573
+ ...libConstantFiles.flat(),
1574
+ ];
1213
1575
  }
1214
1576
  async getDictionaryFiles() {
1215
1577
  const modules = await this.getModules();
1216
- return Promise.all(modules.map((module) => this.getLocalFile(`lib/${module}/${module}.dictionary.ts`)));
1578
+ return Promise.all(
1579
+ modules.map((module) =>
1580
+ this.getLocalFile(`lib/${module}/${module}.dictionary.ts`),
1581
+ ),
1582
+ );
1217
1583
  }
1218
1584
  override async applyTemplate(options: {
1219
1585
  basePath: string;
@@ -1224,7 +1590,10 @@ export class SysExecutor extends Executor {
1224
1590
  const dict = {
1225
1591
  ...(options.dict ?? {}),
1226
1592
  ...Object.fromEntries(
1227
- Object.entries(options.dict ?? {}).map(([key, value]) => [capitalize(key), capitalize(value)]),
1593
+ Object.entries(options.dict ?? {}).map(([key, value]) => [
1594
+ capitalize(key),
1595
+ capitalize(value),
1596
+ ]),
1228
1597
  ),
1229
1598
  };
1230
1599
  const scanInfo = await this.scan();
@@ -1247,13 +1616,17 @@ export class AppExecutor extends SysExecutor {
1247
1616
  override emoji = execEmoji.app;
1248
1617
  constructor({ workspace, name }: AppExecutorOptions) {
1249
1618
  super({ workspace, name, type: "app" });
1250
- this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/apps/${name}`);
1619
+ this.dist = new Executor(
1620
+ `dist/${name}`,
1621
+ `${this.workspace.workspaceRoot}/dist/apps/${name}`,
1622
+ );
1251
1623
  }
1252
1624
  static #execs = new Map<string, AppExecutor>();
1253
1625
  static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
1254
1626
  const exec = AppExecutor.#execs.get(name);
1255
1627
  if (exec) return exec;
1256
- else if (executor instanceof WorkspaceExecutor) return new AppExecutor({ workspace: executor, name });
1628
+ else if (executor instanceof WorkspaceExecutor)
1629
+ return new AppExecutor({ workspace: executor, name });
1257
1630
  else return new AppExecutor({ workspace: executor.workspace, name });
1258
1631
  }
1259
1632
  getEnv() {
@@ -1263,7 +1636,9 @@ export class AppExecutor extends SysExecutor {
1263
1636
  const basePort = 8282;
1264
1637
  const portOffset = WorkspaceExecutor.getBaseDevEnv().portOffset;
1265
1638
  const PORT = basePort ? (basePort + portOffset).toString() : undefined;
1266
- const AKAN_PUBLIC_SERVER_PORT = portOffset ? (8282 + portOffset).toString() : undefined;
1639
+ const AKAN_PUBLIC_SERVER_PORT = portOffset
1640
+ ? (8282 + portOffset).toString()
1641
+ : undefined;
1267
1642
  return {
1268
1643
  ...process.env,
1269
1644
  AKAN_PUBLIC_APP_NAME: this.name,
@@ -1276,15 +1651,22 @@ export class AppExecutor extends SysExecutor {
1276
1651
  }
1277
1652
  async prepareCommand(type: "build" | "start") {
1278
1653
  const akanConfig = await this.getConfig();
1279
- const databaseMode = process.env.AKAN_DATABASE_MODE ?? akanConfig.defaultDatabaseMode ?? "single";
1654
+ const databaseMode =
1655
+ process.env.AKAN_DATABASE_MODE ??
1656
+ akanConfig.defaultDatabaseMode ??
1657
+ "single";
1280
1658
  const routeEnv = {
1281
1659
  AKAN_PUBLIC_BASE_PATHS: [...akanConfig.basePaths].join(","),
1282
1660
  AKAN_DATABASE_MODE: databaseMode,
1283
1661
  };
1284
1662
  Object.assign(process.env, routeEnv);
1285
1663
  if (type === "build") {
1286
- if (await this.exists(this.dist.cwdPath)) await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
1287
- await Promise.all([this.dist.mkdir("private"), this.dist.mkdir("public")]);
1664
+ if (await this.exists(this.dist.cwdPath))
1665
+ await this.dist.exec(`rm -rf ${this.dist.cwdPath}`);
1666
+ await Promise.all([
1667
+ this.dist.mkdir("private"),
1668
+ this.dist.mkdir("public"),
1669
+ ]);
1288
1670
  await Promise.all([
1289
1671
  this.cp("private", `${this.dist.cwdPath}/private`),
1290
1672
  this.cp("public", `${this.dist.cwdPath}/public`),
@@ -1322,7 +1704,11 @@ export class AppExecutor extends SysExecutor {
1322
1704
  }
1323
1705
 
1324
1706
  #pageKeys: string[] | null = null;
1325
- async getPageKeys({ refresh }: { refresh?: boolean } = {}): Promise<string[]> {
1707
+ async getPageKeys({
1708
+ refresh,
1709
+ }: {
1710
+ refresh?: boolean;
1711
+ } = {}): Promise<string[]> {
1326
1712
  if (this.#pageKeys && !refresh) return this.#pageKeys;
1327
1713
  const akanConfig = await this.getConfig();
1328
1714
  const glob = new Bun.Glob("**/*");
@@ -1350,10 +1736,18 @@ export class AppExecutor extends SysExecutor {
1350
1736
  });
1351
1737
  const parsed = parseRouteModuleKey(key);
1352
1738
  if (parsed.isInternalRootLayout) {
1353
- throw new Error(`[route-convention] __root_layout is reserved for Akan.js generated root layout: ${absPath}`);
1739
+ throw new Error(
1740
+ `[route-convention] __root_layout is reserved for Akan.js generated root layout: ${absPath}`,
1741
+ );
1354
1742
  }
1355
- const isRootLayout = parsed.kind === "layout" && parsed.moduleSegments.at(-1) === "_layout";
1356
- validateRouteSourceExports(await Bun.file(absPath).text(), absPath, parsed.kind, { rootLayout: isRootLayout });
1743
+ const isRootLayout =
1744
+ parsed.kind === "layout" && parsed.moduleSegments.at(-1) === "_layout";
1745
+ validateRouteSourceExports(
1746
+ await Bun.file(absPath).text(),
1747
+ absPath,
1748
+ parsed.kind,
1749
+ { rootLayout: isRootLayout },
1750
+ );
1357
1751
  pageKeys.push(key);
1358
1752
  }
1359
1753
  pageKeys.sort();
@@ -1369,27 +1763,56 @@ export class AppExecutor extends SysExecutor {
1369
1763
  const projectAssetsPath = `${this.cwdPath}/private`;
1370
1764
  const projectPublicLibPath = `${projectPublicPath}/libs`;
1371
1765
  const projectAssetsLibPath = `${projectAssetsPath}/libs`;
1372
- await Promise.all([this.removeDir(projectPublicLibPath), this.removeDir(projectAssetsLibPath)]);
1766
+ await Promise.all([
1767
+ this.removeDir(projectPublicLibPath),
1768
+ this.removeDir(projectAssetsLibPath),
1769
+ ]);
1373
1770
  const targetPublicDeps = [] as string[];
1374
1771
  for (const dep of libDeps) {
1375
- if (await this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/public`)) targetPublicDeps.push(dep);
1772
+ if (
1773
+ await this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/public`)
1774
+ )
1775
+ targetPublicDeps.push(dep);
1376
1776
  }
1377
1777
  const targetAssetsDeps = [] as string[];
1378
1778
  for (const dep of libDeps) {
1379
- if (await this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/private`)) targetAssetsDeps.push(dep);
1779
+ if (
1780
+ await this.exists(`${this.workspace.workspaceRoot}/libs/${dep}/private`)
1781
+ )
1782
+ targetAssetsDeps.push(dep);
1380
1783
  }
1381
- await Promise.all(targetPublicDeps.map((dep) => this.mkdir(`${projectPublicLibPath}/${dep}`)));
1382
- await Promise.all(targetAssetsDeps.map((dep) => this.mkdir(`${projectAssetsLibPath}/${dep}`)));
1784
+ await Promise.all(
1785
+ targetPublicDeps.map((dep) =>
1786
+ this.mkdir(`${projectPublicLibPath}/${dep}`),
1787
+ ),
1788
+ );
1789
+ await Promise.all(
1790
+ targetAssetsDeps.map((dep) =>
1791
+ this.mkdir(`${projectAssetsLibPath}/${dep}`),
1792
+ ),
1793
+ );
1383
1794
  await Promise.all([
1384
1795
  ...targetPublicDeps.map((dep) =>
1385
- this.cp(`${this.workspace.workspaceRoot}/libs/${dep}/public`, `${projectPublicLibPath}/${dep}`),
1796
+ this.cp(
1797
+ `${this.workspace.workspaceRoot}/libs/${dep}/public`,
1798
+ `${projectPublicLibPath}/${dep}`,
1799
+ ),
1386
1800
  ),
1387
1801
  ...targetAssetsDeps.map((dep) =>
1388
- this.cp(`${this.workspace.workspaceRoot}/libs/${dep}/private`, `${projectAssetsLibPath}/${dep}`),
1802
+ this.cp(
1803
+ `${this.workspace.workspaceRoot}/libs/${dep}/private`,
1804
+ `${projectAssetsLibPath}/${dep}`,
1805
+ ),
1389
1806
  ),
1390
1807
  ]);
1391
1808
  }
1392
- async scanSync({ refresh = false, write = true }: { refresh?: boolean; write?: boolean } = {}) {
1809
+ async scanSync({
1810
+ refresh = false,
1811
+ write = true,
1812
+ }: {
1813
+ refresh?: boolean;
1814
+ write?: boolean;
1815
+ } = {}) {
1393
1816
  const scanInfo = (await this.scan({
1394
1817
  refresh,
1395
1818
  write,
@@ -1414,13 +1837,17 @@ export class LibExecutor extends SysExecutor {
1414
1837
  override emoji = execEmoji.lib;
1415
1838
  constructor({ workspace, name }: LibExecutorOptions) {
1416
1839
  super({ workspace, name, type: "lib" });
1417
- this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/libs/${name}`);
1840
+ this.dist = new Executor(
1841
+ `dist/${name}`,
1842
+ `${this.workspace.workspaceRoot}/dist/libs/${name}`,
1843
+ );
1418
1844
  }
1419
1845
  static #execs = new Map<string, LibExecutor>();
1420
1846
  static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
1421
1847
  const exec = LibExecutor.#execs.get(name);
1422
1848
  if (exec) return exec;
1423
- else if (executor instanceof WorkspaceExecutor) return new LibExecutor({ workspace: executor, name });
1849
+ else if (executor instanceof WorkspaceExecutor)
1850
+ return new LibExecutor({ workspace: executor, name });
1424
1851
  else return new LibExecutor({ workspace: executor.workspace, name });
1425
1852
  }
1426
1853
 
@@ -1441,14 +1868,21 @@ export class PkgExecutor extends Executor {
1441
1868
  override name: string;
1442
1869
  dist: Executor;
1443
1870
  override emoji = execEmoji.pkg;
1444
- constructor({ workspace = WorkspaceExecutor.fromRoot(), name }: PkgExecutorOptions) {
1871
+ constructor({
1872
+ workspace = WorkspaceExecutor.fromRoot(),
1873
+ name,
1874
+ }: PkgExecutorOptions) {
1445
1875
  super(name, `${workspace.workspaceRoot}/pkgs/${name}`);
1446
1876
  this.workspace = workspace;
1447
1877
  this.name = name;
1448
- this.dist = new Executor(`dist/${name}`, `${this.workspace.workspaceRoot}/dist/pkgs/${name}`);
1878
+ this.dist = new Executor(
1879
+ `dist/${name}`,
1880
+ `${this.workspace.workspaceRoot}/dist/pkgs/${name}`,
1881
+ );
1449
1882
  }
1450
1883
  static from(executor: SysExecutor | WorkspaceExecutor, name: string) {
1451
- if (executor instanceof WorkspaceExecutor) return new PkgExecutor({ workspace: executor, name });
1884
+ if (executor instanceof WorkspaceExecutor)
1885
+ return new PkgExecutor({ workspace: executor, name });
1452
1886
  return new PkgExecutor({ workspace: executor.workspace, name });
1453
1887
  }
1454
1888
 
@@ -1460,7 +1894,10 @@ export class PkgExecutor extends Executor {
1460
1894
  this.#scanInfo = scanInfo;
1461
1895
  return scanInfo;
1462
1896
  }
1463
- async #getDependencyVersion(rootPackageJson: PackageJson, dep: string): Promise<string | undefined> {
1897
+ async #getDependencyVersion(
1898
+ rootPackageJson: PackageJson,
1899
+ dep: string,
1900
+ ): Promise<string | undefined> {
1464
1901
  const rootDeps = {
1465
1902
  ...rootPackageJson.dependencies,
1466
1903
  ...rootPackageJson.devDependencies,
@@ -1470,9 +1907,15 @@ export class PkgExecutor extends Executor {
1470
1907
 
1471
1908
  try {
1472
1909
  const packageJsonPath = `pkgs/${dep}/package.json`;
1473
- if (!(await Bun.file(path.join(this.workspace.workspaceRoot, packageJsonPath)).exists())) return undefined;
1910
+ if (
1911
+ !(await Bun.file(
1912
+ path.join(this.workspace.workspaceRoot, packageJsonPath),
1913
+ ).exists())
1914
+ )
1915
+ return undefined;
1474
1916
  const packageJson = await this.workspace.readJson(packageJsonPath);
1475
- if ((packageJson as PackageJson).name === dep) return (packageJson as PackageJson).version;
1917
+ if ((packageJson as PackageJson).name === dep)
1918
+ return (packageJson as PackageJson).version;
1476
1919
  } catch {
1477
1920
  return undefined;
1478
1921
  }
@@ -1483,7 +1926,9 @@ export class PkgExecutor extends Executor {
1483
1926
  devDependencies: string[] = [],
1484
1927
  ): Promise<Pick<PackageJson, "dependencies" | "devDependencies">> {
1485
1928
  const dependencyNames = [...new Set(dependencies)].sort();
1486
- const devDependencyNames = [...new Set(devDependencies)].filter((dep) => !dependencyNames.includes(dep)).sort();
1929
+ const devDependencyNames = [...new Set(devDependencies)]
1930
+ .filter((dep) => !dependencyNames.includes(dep))
1931
+ .sort();
1487
1932
  const dependencyVersions = new Map<string, string>();
1488
1933
  const missingDeps: string[] = [];
1489
1934
  for (const dep of [...dependencyNames, ...devDependencyNames]) {
@@ -1492,26 +1937,40 @@ export class PkgExecutor extends Executor {
1492
1937
  else missingDeps.push(dep);
1493
1938
  }
1494
1939
  if (missingDeps.length > 0)
1495
- throw new Error(`Missing dependency versions in root package.json: ${missingDeps.join(", ")}`);
1940
+ throw new Error(
1941
+ `Missing dependency versions in root package.json: ${missingDeps.join(", ")}`,
1942
+ );
1496
1943
 
1497
1944
  const toDependencyEntries = (names: string[]) =>
1498
1945
  names.map((dep) => {
1499
1946
  const version = dependencyVersions.get(dep);
1500
- if (!version) throw new Error(`Missing dependency versions in root package.json: ${dep}`);
1947
+ if (!version)
1948
+ throw new Error(
1949
+ `Missing dependency versions in root package.json: ${dep}`,
1950
+ );
1501
1951
  return [dep, version] as const;
1502
1952
  });
1503
1953
 
1504
1954
  return {
1505
1955
  dependencies: Object.fromEntries(toDependencyEntries(dependencyNames)),
1506
- devDependencies: Object.fromEntries(toDependencyEntries(devDependencyNames)),
1956
+ devDependencies: Object.fromEntries(
1957
+ toDependencyEntries(devDependencyNames),
1958
+ ),
1507
1959
  };
1508
1960
  }
1509
1961
  async updatePackageJsonDependencies(
1510
1962
  dependencies: string[] = [],
1511
1963
  devDependencies: string[] = [],
1512
1964
  ): Promise<PackageJson> {
1513
- const [rootPackageJson, pkgJson] = await Promise.all([this.workspace.getPackageJson(), this.getPackageJson()]);
1514
- const dependencyMaps = await this.#toDependencyMap(rootPackageJson, dependencies, devDependencies);
1965
+ const [rootPackageJson, pkgJson] = await Promise.all([
1966
+ this.workspace.getPackageJson(),
1967
+ this.getPackageJson(),
1968
+ ]);
1969
+ const dependencyMaps = await this.#toDependencyMap(
1970
+ rootPackageJson,
1971
+ dependencies,
1972
+ devDependencies,
1973
+ );
1515
1974
  const newPkgJson = {
1516
1975
  ...pkgJson,
1517
1976
  ...dependencyMaps,
@@ -1519,9 +1978,19 @@ export class PkgExecutor extends Executor {
1519
1978
  await this.writeJson("package.json", newPkgJson);
1520
1979
  return newPkgJson;
1521
1980
  }
1522
- async generateDistPackageJson(dependencies: string[] = [], devDependencies: string[] = []): Promise<PackageJson> {
1523
- const [rootPackageJson, pkgJson] = await Promise.all([this.workspace.getPackageJson(), this.getPackageJson()]);
1524
- const dependencyMaps = await this.#toDependencyMap(rootPackageJson, dependencies, devDependencies);
1981
+ async generateDistPackageJson(
1982
+ dependencies: string[] = [],
1983
+ devDependencies: string[] = [],
1984
+ ): Promise<PackageJson> {
1985
+ const [rootPackageJson, pkgJson] = await Promise.all([
1986
+ this.workspace.getPackageJson(),
1987
+ this.getPackageJson(),
1988
+ ]);
1989
+ const dependencyMaps = await this.#toDependencyMap(
1990
+ rootPackageJson,
1991
+ dependencies,
1992
+ devDependencies,
1993
+ );
1525
1994
  const distPkgJson: PackageJson = {
1526
1995
  ...pkgJson,
1527
1996
  type: "module",
@@ -1536,7 +2005,10 @@ export class PkgExecutor extends Executor {
1536
2005
  engines: { bun: ">=1.3.13" },
1537
2006
  ...dependencyMaps,
1538
2007
  };
1539
- await Promise.all([this.dist.writeJson("package.json", distPkgJson), this.writeJson("package.json", distPkgJson)]);
2008
+ await Promise.all([
2009
+ this.dist.writeJson("package.json", distPkgJson),
2010
+ this.writeJson("package.json", distPkgJson),
2011
+ ]);
1540
2012
  return distPkgJson;
1541
2013
  }
1542
2014
  async build(): Promise<void> {
@@ -1549,7 +2021,10 @@ export class PkgExecutor extends Executor {
1549
2021
  await this.cp(`${this.cwdPath}/dist`, this.dist.cwdPath);
1550
2022
  }
1551
2023
  async generateTsconfigJson(): Promise<TsConfigJson> {
1552
- const [rootTsconfig, pkgTsconfig] = await Promise.all([this.workspace.getTsConfig(), this.getTsConfig()]);
2024
+ const [rootTsconfig, pkgTsconfig] = await Promise.all([
2025
+ this.workspace.getTsConfig(),
2026
+ this.getTsConfig(),
2027
+ ]);
1553
2028
  const tsconfig: TsConfigJson = {
1554
2029
  ...rootTsconfig,
1555
2030
  ...pkgTsconfig,
@@ -1572,7 +2047,10 @@ export class ModuleExecutor extends Executor {
1572
2047
  sys: SysExecutor;
1573
2048
  override emoji = execEmoji.module;
1574
2049
  constructor({ sys, name }: ModuleExecutorOptions) {
1575
- super(name, `${sys.workspace.workspaceRoot}/${sys.type}s/${sys.name}/lib/${name}`);
2050
+ super(
2051
+ name,
2052
+ `${sys.workspace.workspaceRoot}/${sys.type}s/${sys.name}/lib/${name}`,
2053
+ );
1576
2054
  this.sys = sys;
1577
2055
  }
1578
2056
  static from(sysExecutor: SysExecutor, name: string) {