@akanjs/devkit 2.1.1 → 2.1.2-rc.0

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