akanjs 2.0.0-beta.7 → 2.0.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/cli/application/application.command.ts +11 -3
  2. package/cli/index.js +117 -24
  3. package/cli/package/package.runner.ts +7 -3
  4. package/cli/templates/app/page/_layout.tsx +0 -1
  5. package/cli/templates/workspaceRoot/.gitignore.template +1 -11
  6. package/client/csrTypes.ts +1 -1
  7. package/devkit/capacitor.base.config.ts +1 -1
  8. package/devkit/capacitorApp.ts +5 -1
  9. package/devkit/commandDecorators/argMeta.ts +28 -14
  10. package/devkit/commandDecorators/command.ts +41 -15
  11. package/devkit/commandDecorators/commandBuilder.ts +78 -42
  12. package/devkit/commandDecorators/helpFormatter.ts +7 -4
  13. package/devkit/frontendBuild/cssCompiler.ts +9 -3
  14. package/devkit/incrementalBuilder/incrementalBuilder.proc.ts +2 -1
  15. package/devkit/mobile/mobileTarget.ts +48 -8
  16. package/devkit/src/capacitorApp.ts +277 -0
  17. package/devkit/transforms/barrelImportsPlugin.ts +6 -0
  18. package/package.json +2 -1
  19. package/server/hmr/clientScript.ts +8 -5
  20. package/ui/Portal.tsx +2 -0
  21. package/ui/System/CSR.tsx +6 -5
  22. package/ui/System/SSR.tsx +2 -2
  23. package/webkit/bootCsr.tsx +8 -5
  24. package/cli/templates/app/common/commonLogic.ts +0 -12
  25. package/cli/templates/app/common/index.ts +0 -10
  26. package/cli/templates/app/srvkit/backendLogic.ts +0 -12
  27. package/cli/templates/app/srvkit/index.ts +0 -10
  28. package/cli/templates/app/ui/UiComponent.ts +0 -16
  29. package/cli/templates/app/ui/index.ts +0 -10
  30. package/cli/templates/app/webkit/frontendLogic.ts +0 -12
  31. package/cli/templates/app/webkit/index.ts +0 -10
  32. package/cli/templates/module/index.tsx +0 -44
  33. /package/cli/templates/app/public/{favicon.ico → favicon.ico.template} +0 -0
  34. /package/cli/templates/app/public/{logo.png → logo.png.template} +0 -0
@@ -1,5 +1,5 @@
1
1
  import { select } from "@inquirer/prompts";
2
- import { App, command, Exec, Sys, Workspace } from "akanjs/devkit";
2
+ import { App, command, Exec, getMobileTargetChoices, Sys, Workspace } from "akanjs/devkit";
3
3
 
4
4
  import { ApplicationScript } from "./application.script";
5
5
 
@@ -53,7 +53,11 @@ export class ApplicationCommand extends command("application", [ApplicationScrip
53
53
  }),
54
54
  buildIos: target({ short: true, desc: "Build iOS app with Capacitor" })
55
55
  .with(App)
56
- .option("target", String, { desc: "mobile target name or all" })
56
+ .option("target", String, {
57
+ desc: "mobile target name or all",
58
+ ask: "Select mobile target",
59
+ enum: async ({ app }) => await getMobileTargetChoices(app),
60
+ })
57
61
  .option("env", String, {
58
62
  enum: ["local", "debug", "develop", "main"],
59
63
  desc: "backend environment",
@@ -66,7 +70,11 @@ export class ApplicationCommand extends command("application", [ApplicationScrip
66
70
  }),
67
71
  buildAndroid: target({ short: true, desc: "Build Android app with Capacitor" })
68
72
  .with(App)
69
- .option("target", String, { desc: "mobile target name or all" })
73
+ .option("target", String, {
74
+ desc: "mobile target name or all",
75
+ ask: "Select mobile target",
76
+ enum: async ({ app }) => await getMobileTargetChoices(app),
77
+ })
70
78
  .option("env", String, {
71
79
  enum: ["local", "debug", "develop", "main"],
72
80
  desc: "backend environment",
package/cli/index.js CHANGED
@@ -4816,6 +4816,9 @@ var rewriteSingleStatement = (stmt, map) => {
4816
4816
  return null;
4817
4817
  const lines = [];
4818
4818
  const tail = ";";
4819
+ if (shouldPreserveBarrelSideEffects(stmt.specifier)) {
4820
+ lines.push(`import "${stmt.specifier}"${tail}`);
4821
+ }
4819
4822
  if (clause.defaultImport || remaining.length > 0) {
4820
4823
  const parts = [];
4821
4824
  if (clause.defaultImport)
@@ -4831,6 +4834,7 @@ var rewriteSingleStatement = (stmt, map) => {
4831
4834
  return lines.join(`
4832
4835
  `);
4833
4836
  };
4837
+ var shouldPreserveBarrelSideEffects = (specifier) => /^@(apps|libs)\/[^/]+\/client$/.test(specifier);
4834
4838
  var serializeNamedItem = (item) => {
4835
4839
  const prefix = item.isType ? "type " : "";
4836
4840
  if (item.imported === item.local)
@@ -6048,6 +6052,8 @@ class CssImportResolver {
6048
6052
 
6049
6053
  var SOURCE_EXTS3 = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
6050
6054
  var NON_SOURCE_EXT_RE3 = /\.(json|svg|png|jpe?g|webp|gif|avif|ico|woff2?|ttf|otf|mp3|mp4|wav)$/i;
6055
+ var NODE_MODULES_RE3 = /[\\/]node_modules[\\/]/;
6056
+ var AKANJS_NODE_MODULE_RE3 = /[\\/]node_modules[\\/]akanjs[\\/]/;
6051
6057
 
6052
6058
  class CssCompiler {
6053
6059
  #logger = new Logger("CssCompiler");
@@ -6115,7 +6121,7 @@ class CssCompiler {
6115
6121
  const akanConfig2 = await this.#app.getConfig({ refresh });
6116
6122
  while (queue.length > 0) {
6117
6123
  const filePath = queue.shift();
6118
- if (!filePath || sourceFiles.has(filePath) || filePath.includes("node_modules"))
6124
+ if (!filePath || sourceFiles.has(filePath) || isIgnoredNodeModuleSource(filePath))
6119
6125
  continue;
6120
6126
  sourceFiles.add(filePath);
6121
6127
  let content;
@@ -6151,7 +6157,7 @@ class CssCompiler {
6151
6157
  if (NON_SOURCE_EXT_RE3.test(spec))
6152
6158
  continue;
6153
6159
  const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
6154
- if (!resolved || sourceFiles.has(resolved) || resolved.includes("node_modules"))
6160
+ if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
6155
6161
  continue;
6156
6162
  queue.push(resolved);
6157
6163
  }
@@ -6239,7 +6245,7 @@ class CssCompiler {
6239
6245
  const files = new Set(sourcePaths);
6240
6246
  await Promise.all(dirs.map(async (dir) => {
6241
6247
  for await (const file of glob.scan({ cwd: dir, absolute: true })) {
6242
- if (file.includes("node_modules"))
6248
+ if (isIgnoredNodeModuleSource(file))
6243
6249
  continue;
6244
6250
  files.add(file);
6245
6251
  }
@@ -6286,6 +6292,9 @@ function resolveSourceWithRequire(id, fromBase) {
6286
6292
  function isSourceFile(filePath) {
6287
6293
  return SOURCE_EXTS3.includes(path23.extname(filePath));
6288
6294
  }
6295
+ function isIgnoredNodeModuleSource(filePath) {
6296
+ return NODE_MODULES_RE3.test(filePath) && !AKANJS_NODE_MODULE_RE3.test(filePath);
6297
+ }
6289
6298
  function getPageKeyBasePath(pageKey, basePaths) {
6290
6299
  const normalized = pageKey.split(path23.sep).join("/").replace(/^\.\//, "");
6291
6300
  const segments = normalized.split("/");
@@ -7903,21 +7912,65 @@ var getMobileTargets = async (app) => {
7903
7912
  const config = await app.getConfig();
7904
7913
  return Object.entries(config.mobile.targets).map(([name, target]) => ({ name, config: target }));
7905
7914
  };
7915
+ var getMobileTargetChoices = async (app) => {
7916
+ const config = await app.getConfig();
7917
+ const targetNames = Object.keys(config.mobile.targets);
7918
+ if (targetNames.length > 1)
7919
+ return targetNames;
7920
+ const basePaths = [...config.basePaths];
7921
+ if (basePaths.length > 1)
7922
+ return basePaths;
7923
+ if (targetNames.length > 0)
7924
+ return targetNames;
7925
+ return basePaths;
7926
+ };
7927
+ var resolveMobileTargetByBasePath = (targets, basePath2) => {
7928
+ const normalizedBasePath = basePath2.replace(/^\/+|\/+$/g, "");
7929
+ const byBasePath = targets.find((target) => target.config.basePath?.replace(/^\/+|\/+$/g, "") === normalizedBasePath);
7930
+ if (byBasePath)
7931
+ return byBasePath;
7932
+ const [template] = targets;
7933
+ if (!template)
7934
+ return;
7935
+ return {
7936
+ name: template.name,
7937
+ config: {
7938
+ ...template.config,
7939
+ basePath: normalizedBasePath
7940
+ }
7941
+ };
7942
+ };
7906
7943
  var resolveMobileTargets = async (app, selection) => {
7944
+ const config = await app.getConfig();
7907
7945
  const targets = await getMobileTargets(app);
7908
7946
  if (targets.length === 0)
7909
7947
  throw new Error(`No mobile targets configured for ${app.name}`);
7910
7948
  if (!selection) {
7911
- if (targets.length === 1)
7912
- return targets;
7913
- throw new Error(`Multiple mobile targets found for ${app.name}. Pass --target <${targets.map((t) => t.name).join("|")}|all>.`);
7949
+ const choices2 = await getMobileTargetChoices(app);
7950
+ if (choices2.length === 1)
7951
+ return resolveMobileTargets(app, choices2[0]);
7952
+ throw new Error(`Multiple mobile targets found for ${app.name}. Pass --target <${choices2.join("|")}|all>.`);
7914
7953
  }
7915
- if (selection === "all")
7954
+ if (selection === "all") {
7955
+ if (Object.keys(config.mobile.targets).length > 1)
7956
+ return targets;
7957
+ const basePaths = [...config.basePaths];
7958
+ if (basePaths.length > 1) {
7959
+ return basePaths.flatMap((basePath2) => {
7960
+ const resolved = resolveMobileTargetByBasePath(targets, basePath2);
7961
+ return resolved ? [resolved] : [];
7962
+ });
7963
+ }
7916
7964
  return targets;
7965
+ }
7917
7966
  const target = targets.find((candidate) => candidate.name === selection);
7918
- if (!target)
7919
- throw new Error(`Mobile target '${selection}' was not found. Available: ${targets.map((t) => t.name).join(", ")}`);
7920
- return [target];
7967
+ if (target)
7968
+ return [target];
7969
+ const basePathTarget = resolveMobileTargetByBasePath(targets, selection);
7970
+ if (basePathTarget && config.basePaths.has(selection.replace(/^\/+|\/+$/g, "")))
7971
+ return [basePathTarget];
7972
+ const choices = await getMobileTargetChoices(app);
7973
+ throw new Error(`Mobile target '${selection}' was not found. Available: ${choices.join(", ")}`);
7921
7974
  };
7922
7975
  var resolveMobilePath = (target, pathname) => {
7923
7976
  const basePath2 = target.basePath?.replace(/^\/+|\/+$/g, "");
@@ -8114,7 +8167,8 @@ class CapacitorApp {
8114
8167
  async#writeCapacitorConfig() {
8115
8168
  await mkdir10(this.targetRoot, { recursive: true });
8116
8169
  const appInfoPath = path34.relative(this.targetRoot, path34.join(this.app.cwdPath, "akan.app.json")).split(path34.sep).join("/");
8117
- const content = `import { withBase } from "akanjs/devkit/capacitor.base.config";
8170
+ const baseConfigPath = path34.relative(this.targetRoot, path34.join(this.app.workspace.cwdPath, "pkgs/akanjs/devkit/capacitor.base.config")).split(path34.sep).join("/");
8171
+ const content = `import { withBase } from "${baseConfigPath.startsWith(".") ? baseConfigPath : `./${baseConfigPath}`}";
8118
8172
  import appInfo from "${appInfoPath}";
8119
8173
 
8120
8174
  export default withBase((config) => config, appInfo, "${this.target.name}");
@@ -8564,7 +8618,7 @@ var formatCommandHelp = (command, key) => {
8564
8618
  const optName = `${flag}--${kebabName}`;
8565
8619
  const optDesc = opt.desc ?? "";
8566
8620
  const defaultVal = opt.default !== undefined ? chalk5.gray(` [default: ${String(opt.default)}]`) : "";
8567
- const choices = opt.enum ? chalk5.gray(` (${opt.enum.map(formatChoice).join(", ")})`) : "";
8621
+ const choices = opt.enum ? chalk5.gray(typeof opt.enum === "function" ? " ([dynamic choices])" : ` (${opt.enum.map(formatChoice).join(", ")})`) : "";
8568
8622
  lines.push(` ${chalk5.green(optName)} ${chalk5.gray(optDesc)}${defaultVal}${choices}`);
8569
8623
  }
8570
8624
  lines.push("");
@@ -8584,7 +8638,7 @@ var handleOption = (programCommand, argMeta) => {
8584
8638
  ask
8585
8639
  } = argMeta.argsOption;
8586
8640
  const kebabName = camelToKebabCase2(argMeta.name);
8587
- const choices = enumChoices?.map((choice) => typeof choice === "object" ? { value: choice.value, name: choice.label } : { value: choice, name: choice.toString() });
8641
+ const choices = enumChoices && typeof enumChoices !== "function" ? normalizeEnumChoices(enumChoices) : null;
8588
8642
  programCommand.option(`-${flag}, --${kebabName}${type === "boolean" ? " [boolean]" : ` <${kebabName}>`}`, `${desc}${ask ? ` (${ask})` : ""}${example ? ` (example: ${example})` : ""}${choices ? ` (choices: ${choices.map((choice) => choice.name).join(", ")})` : ""}`);
8589
8643
  return programCommand;
8590
8644
  };
@@ -8601,7 +8655,16 @@ var convertArgValue = (value, type) => {
8601
8655
  else
8602
8656
  return value === true || value === "true";
8603
8657
  };
8604
- var getOptionValue = async (argMeta, opt) => {
8658
+ var normalizeEnumChoices = (enumChoices) => enumChoices.map((choice) => typeof choice === "object" ? { value: choice.value, name: choice.label } : { value: choice, name: choice.toString() });
8659
+ var resolveEnumChoices = async (argMeta, context) => {
8660
+ const enumChoices = argMeta.argsOption.enum;
8661
+ if (!enumChoices)
8662
+ return null;
8663
+ if (typeof enumChoices === "function")
8664
+ return await enumChoices(context);
8665
+ return enumChoices;
8666
+ };
8667
+ var getOptionValue = async (argMeta, opt, context) => {
8605
8668
  const {
8606
8669
  name,
8607
8670
  argsOption: { enum: enumChoices, default: defaultValue, type, desc, nullable, example, ask }
@@ -8610,13 +8673,13 @@ var getOptionValue = async (argMeta, opt) => {
8610
8673
  return convertArgValue(opt[argMeta.name], type ?? "string");
8611
8674
  else if (defaultValue !== undefined)
8612
8675
  return defaultValue;
8613
- else if (nullable)
8614
- return null;
8615
8676
  if (enumChoices) {
8616
- const choices = enumChoices.map((choice2) => typeof choice2 === "object" ? { value: choice2.value, name: choice2.label } : { value: choice2, name: choice2.toString() });
8677
+ const choices = normalizeEnumChoices(await resolveEnumChoices(argMeta, context) ?? []);
8617
8678
  const choice = await select2({ message: ask ?? desc ?? `Select the ${name} value`, choices });
8618
8679
  return choice;
8619
- } else if (type === "boolean") {
8680
+ } else if (nullable)
8681
+ return null;
8682
+ else if (type === "boolean") {
8620
8683
  const message = ask ?? desc ?? `Do you want to set ${name}? ${desc ? ` (${desc})` : ""}: `;
8621
8684
  return await confirm({ message });
8622
8685
  } else {
@@ -8641,6 +8704,22 @@ var getArgumentValue = async (argMeta, value) => {
8641
8704
  const message = ask ? `${ask}: ` : desc ? `${desc}: ` : `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
8642
8705
  return convertArgValue(await input2({ message }), type ?? "string");
8643
8706
  };
8707
+ var assignCommandContext = (context, argMeta, value) => {
8708
+ if (value instanceof AppExecutor)
8709
+ context.app = value;
8710
+ else if (value instanceof LibExecutor)
8711
+ context.lib = value;
8712
+ else if (value instanceof PkgExecutor)
8713
+ context.pkg = value;
8714
+ else if (value instanceof ModuleExecutor)
8715
+ context.module = value;
8716
+ else if (value instanceof Executor)
8717
+ context.exec = value;
8718
+ if (argMeta.type === "Argument" || argMeta.type === "Option")
8719
+ context.values[argMeta.name] = value;
8720
+ else
8721
+ context.values[argMeta.type.toLowerCase()] = value;
8722
+ };
8644
8723
  var assertCurrentDirectoryIsWorkspaceRoot = async () => {
8645
8724
  const cwd = process.cwd();
8646
8725
  const [hasPackageJson, hasTsConfig, hasEnv] = await Promise.all([
@@ -8816,15 +8895,17 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
8816
8895
  if (targetMeta.targetOption.runsOnWorkspaceRoot)
8817
8896
  await assertCurrentDirectoryIsWorkspaceRoot();
8818
8897
  const workspace = WorkspaceExecutor.fromRoot();
8898
+ const commandContext = { values: {} };
8819
8899
  for (const argMeta of allArgMetas) {
8820
8900
  if (argMeta.type === "Option")
8821
- commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt);
8901
+ commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt, commandContext);
8822
8902
  else if (argMeta.type === "Argument")
8823
8903
  commandArgs[argMeta.idx] = await getArgumentValue(argMeta, cmdArgs[argMeta.idx]);
8824
8904
  else
8825
8905
  commandArgs[argMeta.idx] = await getInternalArgumentValue(argMeta, cmdArgs[argMeta.idx], workspace);
8826
8906
  if (commandArgs[argMeta.idx] instanceof AppExecutor)
8827
8907
  process.env.AKAN_PUBLIC_APP_NAME = commandArgs[argMeta.idx].name;
8908
+ assignCommandContext(commandContext, argMeta, commandArgs[argMeta.idx]);
8828
8909
  if (opt.verbose)
8829
8910
  Executor.setVerbose(true);
8830
8911
  }
@@ -9846,14 +9927,22 @@ class ApplicationCommand extends command("application", [ApplicationScript], ({
9846
9927
  test: target({ desc: "Prepare and test an app, library, or package" }).with(Exec).option("write", Boolean, { desc: "write code generation", default: true }).exec(async function(exec2, write) {
9847
9928
  await this.applicationScript.test(exec2, { write });
9848
9929
  }),
9849
- buildIos: target({ short: true, desc: "Build iOS app with Capacitor" }).with(App).option("target", String, { desc: "mobile target name or all" }).option("env", String, {
9930
+ buildIos: target({ short: true, desc: "Build iOS app with Capacitor" }).with(App).option("target", String, {
9931
+ desc: "mobile target name or all",
9932
+ ask: "Select mobile target",
9933
+ enum: async ({ app }) => await getMobileTargetChoices(app)
9934
+ }).option("env", String, {
9850
9935
  enum: ["local", "debug", "develop", "main"],
9851
9936
  desc: "backend environment",
9852
9937
  default: "debug"
9853
9938
  }).option("write", Boolean, { desc: "write code generation", default: true }).option("regenerate", Boolean, { flag: "g", desc: "delete and regenerate native project", default: false }).exec(async function(app, target2, env, write, regenerate) {
9854
9939
  await this.applicationScript.buildIos(app, { target: target2, env: asMobileEnv(env), write, regenerate });
9855
9940
  }),
9856
- buildAndroid: target({ short: true, desc: "Build Android app with Capacitor" }).with(App).option("target", String, { desc: "mobile target name or all" }).option("env", String, {
9941
+ buildAndroid: target({ short: true, desc: "Build Android app with Capacitor" }).with(App).option("target", String, {
9942
+ desc: "mobile target name or all",
9943
+ ask: "Select mobile target",
9944
+ enum: async ({ app }) => await getMobileTargetChoices(app)
9945
+ }).option("env", String, {
9857
9946
  enum: ["local", "debug", "develop", "main"],
9858
9947
  desc: "backend environment",
9859
9948
  default: "debug"
@@ -9975,21 +10064,25 @@ class PackageRunner extends runner("package") {
9975
10064
  const scanner = await TypeScriptDependencyScanner.from(pkg);
9976
10065
  const { npmDeps, npmDevDeps, missingDeps } = await scanner.getPackageBuildDependencies(pkg.name);
9977
10066
  const packageRuntimeDependencies = { akanjs: ["daisyui"] };
10067
+ const packageRuntimeDevDependencies = { akanjs: ["@biomejs/biome"] };
9978
10068
  const forcedRuntimeDeps = packageRuntimeDependencies[pkg.name] ?? [];
10069
+ const forcedRuntimeDevDeps = packageRuntimeDevDependencies[pkg.name] ?? [];
9979
10070
  const packageRuntimeDeps = [...new Set([...npmDeps, ...forcedRuntimeDeps])];
10071
+ const packageRuntimeDevDeps = [...new Set([...npmDevDeps, ...forcedRuntimeDevDeps])];
9980
10072
  const rootPackageJson = await pkg.workspace.getPackageJson();
9981
10073
  const rootDeps = { ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies };
9982
10074
  const missingForcedDeps = forcedRuntimeDeps.filter((dep) => !rootDeps[dep]);
9983
- const allMissingDeps = [...new Set([...missingDeps, ...missingForcedDeps])].sort();
10075
+ const missingForcedDevDeps = forcedRuntimeDevDeps.filter((dep) => !rootDeps[dep]);
10076
+ const allMissingDeps = [...new Set([...missingDeps, ...missingForcedDeps, ...missingForcedDevDeps])].sort();
9984
10077
  if (allMissingDeps.length > 0)
9985
10078
  throw new Error(`Missing dependency versions in root package.json: ${allMissingDeps.join(", ")}`);
9986
- await pkg.updatePackageJsonDependencies(packageRuntimeDeps, npmDevDeps);
10079
+ await pkg.updatePackageJsonDependencies(packageRuntimeDeps, packageRuntimeDevDeps);
9987
10080
  const hasBuildFile = await Bun.file(`${pkg.cwdPath}/build.ts`).exists();
9988
10081
  if (hasBuildFile) {
9989
10082
  await pkg.workspace.spawn(process.execPath, [`${pkg.cwdPath}/build.ts`], { env: process.env, stdio: "inherit" });
9990
10083
  } else {
9991
10084
  await $2`cp -r ${pkg.cwdPath}/. ${pkg.dist.cwdPath}`;
9992
- await Promise.all([pkg.generateDistPackageJson(packageRuntimeDeps, npmDevDeps), pkg.generateTsconfigJson()]);
10085
+ await Promise.all([pkg.generateDistPackageJson(packageRuntimeDeps, packageRuntimeDevDeps), pkg.generateTsconfigJson()]);
9993
10086
  }
9994
10087
  }
9995
10088
  async updateWorskpaceRootPackageJson(workspace, rootPackageJson) {
@@ -39,23 +39,27 @@ export class PackageRunner extends runner("package") {
39
39
  const scanner = await TypeScriptDependencyScanner.from(pkg);
40
40
  const { npmDeps, npmDevDeps, missingDeps } = await scanner.getPackageBuildDependencies(pkg.name);
41
41
  const packageRuntimeDependencies: Record<string, string[]> = { akanjs: ["daisyui"] };
42
+ const packageRuntimeDevDependencies: Record<string, string[]> = { akanjs: ["@biomejs/biome"] };
42
43
  const forcedRuntimeDeps = packageRuntimeDependencies[pkg.name] ?? [];
44
+ const forcedRuntimeDevDeps = packageRuntimeDevDependencies[pkg.name] ?? [];
43
45
  const packageRuntimeDeps = [...new Set([...npmDeps, ...forcedRuntimeDeps])];
46
+ const packageRuntimeDevDeps = [...new Set([...npmDevDeps, ...forcedRuntimeDevDeps])];
44
47
  const rootPackageJson = await pkg.workspace.getPackageJson();
45
48
  const rootDeps = { ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies };
46
49
  const missingForcedDeps = forcedRuntimeDeps.filter((dep) => !rootDeps[dep]);
47
- const allMissingDeps = [...new Set([...missingDeps, ...missingForcedDeps])].sort();
50
+ const missingForcedDevDeps = forcedRuntimeDevDeps.filter((dep) => !rootDeps[dep]);
51
+ const allMissingDeps = [...new Set([...missingDeps, ...missingForcedDeps, ...missingForcedDevDeps])].sort();
48
52
  if (allMissingDeps.length > 0)
49
53
  throw new Error(`Missing dependency versions in root package.json: ${allMissingDeps.join(", ")}`);
50
54
 
51
- await pkg.updatePackageJsonDependencies(packageRuntimeDeps, npmDevDeps);
55
+ await pkg.updatePackageJsonDependencies(packageRuntimeDeps, packageRuntimeDevDeps);
52
56
 
53
57
  const hasBuildFile = await Bun.file(`${pkg.cwdPath}/build.ts`).exists();
54
58
  if (hasBuildFile) {
55
59
  await pkg.workspace.spawn(process.execPath, [`${pkg.cwdPath}/build.ts`], { env: process.env, stdio: "inherit" });
56
60
  } else {
57
61
  await $`cp -r ${pkg.cwdPath}/. ${pkg.dist.cwdPath}`;
58
- await Promise.all([pkg.generateDistPackageJson(packageRuntimeDeps, npmDevDeps), pkg.generateTsconfigJson()]);
62
+ await Promise.all([pkg.generateDistPackageJson(packageRuntimeDeps, packageRuntimeDevDeps), pkg.generateTsconfigJson()]);
59
63
  }
60
64
  }
61
65
 
@@ -10,7 +10,6 @@ export default function getContent(scanInfo: AppInfo | LibInfo | null, dict: Dic
10
10
  content: `
11
11
  import "./styles.css";
12
12
  import type { LayoutProps } from "akanjs/client";
13
- import { fetch } from "@${dict.appName}/client";
14
13
  ${isUsingShared ? "import { Auth } from '@shared/ui';" : ""}
15
14
 
16
15
  export const head = (
@@ -25,10 +25,6 @@ apps/*/scripts
25
25
  **/*secrets.yaml
26
26
  **/kubeconfig.yaml
27
27
  **/secrets.*
28
- **/.idea
29
- apps/**/src/schema.gql
30
- .next
31
- .vite
32
28
  dump
33
29
  local
34
30
 
@@ -50,7 +46,6 @@ DerivedData
50
46
  *.ipa
51
47
  *.xcuserstate
52
48
 
53
- # **/android
54
49
  build/
55
50
  .gradle
56
51
  local.properties
@@ -122,9 +117,4 @@ libs/*/common/index.ts
122
117
  libs/*/client.ts
123
118
  libs/*/server.ts
124
119
  libs/*/index.ts
125
- # **/capacitor.config.ts
126
- # **/postcss.config.js
127
- # **/playwright.config.ts
128
- # **/next-env.d.ts
129
- # **/tsconfig.json
130
- # **/tsconfig.spec.json
120
+ **/.akan
@@ -111,7 +111,7 @@ export interface LayoutModule {
111
111
  }
112
112
  export type RouteModule = PageModule | LayoutModule;
113
113
  export interface Route {
114
- csrConfig?: PageConfig;
114
+ PageConfig?: PageConfig;
115
115
  path: string;
116
116
  renderPage?: RouteRender;
117
117
  renderLayout?: RouteRender;
@@ -53,7 +53,7 @@ export const withBase = (
53
53
  androidScheme: "http",
54
54
  url: localCsrUrl(ip, target),
55
55
  cleartext: true,
56
- allowNavigation: [`http://${ip}:8282/*`],
56
+ allowNavigation: [ip, "localhost"],
57
57
  }
58
58
  : {
59
59
  allowNavigation: ["*"],
@@ -201,7 +201,11 @@ export class CapacitorApp {
201
201
  .relative(this.targetRoot, path.join(this.app.cwdPath, "akan.app.json"))
202
202
  .split(path.sep)
203
203
  .join("/");
204
- const content = `import { withBase } from "akanjs/devkit/capacitor.base.config";
204
+ const baseConfigPath = path
205
+ .relative(this.targetRoot, path.join(this.app.workspace.cwdPath, "pkgs/akanjs/devkit/capacitor.base.config"))
206
+ .split(path.sep)
207
+ .join("/");
208
+ const content = `import { withBase } from "${baseConfigPath.startsWith(".") ? baseConfigPath : `./${baseConfigPath}`}";
205
209
  import appInfo from "${appInfoPath}";
206
210
 
207
211
  export default withBase((config) => config, appInfo, "${this.target.name}");
@@ -18,19 +18,33 @@ export type InternalArgType = (typeof internalArgTypes)[number];
18
18
  export type PrimitiveArgType = StringConstructor | NumberConstructor | BooleanConstructor;
19
19
  export type NormalizedPrimitiveArgType = "string" | "number" | "boolean";
20
20
 
21
- export interface ArgsOption {
21
+ export type CommandContext = {
22
+ values: Record<string, unknown>;
23
+ app?: AppExecutor;
24
+ lib?: LibExecutor;
25
+ sys?: SysExecutor;
26
+ pkg?: PkgExecutor;
27
+ module?: ModuleExecutor;
28
+ exec?: Executor;
29
+ };
30
+
31
+ export type EnumChoice = string | number | { label: string; value: string | number | boolean };
32
+ export type EnumChoices = readonly EnumChoice[];
33
+ export type DynamicEnum<Context> = (context: Context) => EnumChoices | Promise<EnumChoices>;
34
+
35
+ export interface ArgsOption<Context = CommandContext> {
22
36
  type?: "string" | "number" | "boolean";
23
37
  flag?: string;
24
38
  desc?: string;
25
39
  default?: string | number | boolean;
26
40
  nullable?: boolean;
27
41
  example?: string | number | boolean;
28
- enum?: (string | number)[] | readonly (string | number)[] | { label: string; value: string | number | boolean }[];
42
+ enum?: EnumChoices | DynamicEnum<Context>;
29
43
  ask?: string;
30
44
  }
31
- export interface ArgMeta {
45
+ export interface ArgMeta<Context = CommandContext> {
32
46
  name: string;
33
- argsOption: ArgsOption;
47
+ argsOption: ArgsOption<Context>;
34
48
  key: string;
35
49
  idx: number;
36
50
  type: ArgType;
@@ -52,12 +66,12 @@ export const getArgMetas = (
52
66
  return [allArgMetas, argMetas, internalArgMetas];
53
67
  };
54
68
 
55
- export interface InternalArgToken<T = unknown> {
56
- type: InternalArgType;
69
+ export interface InternalArgToken<T = unknown, Type extends InternalArgType = InternalArgType> {
70
+ type: Type;
57
71
  _value: T;
58
72
  }
59
73
 
60
- const createInternalArgToken = <T>(type: InternalArgType) => ({ type }) as InternalArgToken<T>;
74
+ const createInternalArgToken = <T, Type extends InternalArgType>(type: Type) => ({ type }) as InternalArgToken<T, Type>;
61
75
 
62
76
  export const normalizePrimitiveArgType = (type: PrimitiveArgType): NormalizedPrimitiveArgType => {
63
77
  if (type === String) return "string";
@@ -66,23 +80,23 @@ export const normalizePrimitiveArgType = (type: PrimitiveArgType): NormalizedPri
66
80
  throw new Error(`Invalid primitive argument type: ${type}`);
67
81
  };
68
82
 
69
- export const App = createInternalArgToken<AppExecutor>("App");
83
+ export const App = createInternalArgToken<AppExecutor, "App">("App");
70
84
  export type App = AppExecutor;
71
85
 
72
- export const Lib = createInternalArgToken<LibExecutor>("Lib");
86
+ export const Lib = createInternalArgToken<LibExecutor, "Lib">("Lib");
73
87
  export type Lib = LibExecutor;
74
88
 
75
- export const Sys = createInternalArgToken<SysExecutor>("Sys");
89
+ export const Sys = createInternalArgToken<SysExecutor, "Sys">("Sys");
76
90
  export type Sys = SysExecutor;
77
91
 
78
- export const Exec = createInternalArgToken<Executor>("Exec");
92
+ export const Exec = createInternalArgToken<Executor, "Exec">("Exec");
79
93
  export type Exec = Executor;
80
94
 
81
- export const Pkg = createInternalArgToken<PkgExecutor>("Pkg");
95
+ export const Pkg = createInternalArgToken<PkgExecutor, "Pkg">("Pkg");
82
96
  export type Pkg = PkgExecutor;
83
97
 
84
- export const Module = createInternalArgToken<ModuleExecutor>("Module");
98
+ export const Module = createInternalArgToken<ModuleExecutor, "Module">("Module");
85
99
  export type Module = ModuleExecutor;
86
100
 
87
- export const Workspace = createInternalArgToken<WorkspaceExecutor>("Workspace");
101
+ export const Workspace = createInternalArgToken<WorkspaceExecutor, "Workspace">("Workspace");
88
102
  export type Workspace = WorkspaceExecutor;
@@ -5,7 +5,14 @@ import { type Command, program } from "commander";
5
5
 
6
6
  import { FileSys, getDirname, type PackageJson } from "..";
7
7
  import { AppExecutor, Executor, LibExecutor, ModuleExecutor, PkgExecutor, WorkspaceExecutor } from "../executors";
8
- import { type ArgMeta, getArgMetas, type InternalArgMeta } from "./argMeta";
8
+ import {
9
+ type ArgMeta,
10
+ type CommandContext,
11
+ type EnumChoice,
12
+ type EnumChoices,
13
+ getArgMetas,
14
+ type InternalArgMeta,
15
+ } from "./argMeta";
9
16
  import { CommandContainer } from "./dependencyBuilder";
10
17
  import { formatCommandHelp, formatHelp } from "./helpFormatter";
11
18
  import { type CommandCls, getTargetMetas } from "./targetMeta";
@@ -22,11 +29,7 @@ const handleOption = (programCommand: Command, argMeta: ArgMeta) => {
22
29
  ask,
23
30
  } = argMeta.argsOption;
24
31
  const kebabName = camelToKebabCase(argMeta.name);
25
- const choices = enumChoices?.map((choice: string | number | { label: string; value: string | number | boolean }) =>
26
- typeof choice === "object"
27
- ? { value: choice.value, name: choice.label }
28
- : { value: choice, name: choice.toString() },
29
- );
32
+ const choices = enumChoices && typeof enumChoices !== "function" ? normalizeEnumChoices(enumChoices) : null;
30
33
  programCommand.option(
31
34
  `-${flag}, --${kebabName}${type === "boolean" ? " [boolean]" : ` <${kebabName}>`}`,
32
35
  `${desc}${ask ? ` (${ask})` : ""}${example ? ` (example: ${example})` : ""}${choices ? ` (choices: ${choices.map((choice) => choice.name).join(", ")})` : ""}`,
@@ -48,24 +51,34 @@ const convertArgValue = (value: string | boolean, type: "string" | "number" | "b
48
51
  else return value === true || value === "true";
49
52
  };
50
53
 
51
- const getOptionValue = async (argMeta: ArgMeta, opt: Record<string, unknown>) => {
54
+ const normalizeEnumChoices = (enumChoices: EnumChoices) =>
55
+ enumChoices.map((choice: EnumChoice) =>
56
+ typeof choice === "object"
57
+ ? { value: choice.value, name: choice.label }
58
+ : { value: choice, name: choice.toString() },
59
+ );
60
+
61
+ const resolveEnumChoices = async (argMeta: ArgMeta, context: CommandContext) => {
62
+ const enumChoices = argMeta.argsOption.enum;
63
+ if (!enumChoices) return null;
64
+ if (typeof enumChoices === "function") return await enumChoices(context);
65
+ return enumChoices;
66
+ };
67
+
68
+ const getOptionValue = async (argMeta: ArgMeta, opt: Record<string, unknown>, context: CommandContext) => {
52
69
  const {
53
70
  name,
54
71
  argsOption: { enum: enumChoices, default: defaultValue, type, desc, nullable, example, ask },
55
72
  } = argMeta;
56
73
  if (opt[argMeta.name] !== undefined) return convertArgValue(opt[argMeta.name] as string, type ?? "string");
57
74
  else if (defaultValue !== undefined) return defaultValue;
58
- else if (nullable) return null;
59
75
 
60
76
  if (enumChoices) {
61
- const choices = enumChoices.map((choice: string | number | { label: string; value: string | number | boolean }) =>
62
- typeof choice === "object"
63
- ? { value: choice.value, name: choice.label }
64
- : { value: choice, name: choice.toString() },
65
- );
77
+ const choices = normalizeEnumChoices((await resolveEnumChoices(argMeta, context)) ?? []);
66
78
  const choice = await select({ message: ask ?? desc ?? `Select the ${name} value`, choices });
67
79
  return choice;
68
- } else if (type === "boolean") {
80
+ } else if (nullable) return null;
81
+ else if (type === "boolean") {
69
82
  const message = ask ?? desc ?? `Do you want to set ${name}? ${desc ? ` (${desc})` : ""}: `;
70
83
  return await confirm({ message });
71
84
  } else {
@@ -96,6 +109,16 @@ const getArgumentValue = async (argMeta: ArgMeta, value: string | undefined) =>
96
109
  return convertArgValue(await input({ message }), type ?? "string");
97
110
  };
98
111
 
112
+ const assignCommandContext = (context: CommandContext, argMeta: ArgMeta | InternalArgMeta, value: unknown) => {
113
+ if (value instanceof AppExecutor) context.app = value;
114
+ else if (value instanceof LibExecutor) context.lib = value;
115
+ else if (value instanceof PkgExecutor) context.pkg = value;
116
+ else if (value instanceof ModuleExecutor) context.module = value;
117
+ else if (value instanceof Executor) context.exec = value;
118
+ if (argMeta.type === "Argument" || argMeta.type === "Option") context.values[argMeta.name] = value;
119
+ else context.values[argMeta.type.toLowerCase()] = value;
120
+ };
121
+
99
122
  const assertCurrentDirectoryIsWorkspaceRoot = async () => {
100
123
  const cwd = process.cwd();
101
124
  const [hasPackageJson, hasTsConfig, hasEnv] = await Promise.all([
@@ -281,8 +304,10 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
281
304
  const commandArgs = [] as unknown[];
282
305
  if (targetMeta.targetOption.runsOnWorkspaceRoot) await assertCurrentDirectoryIsWorkspaceRoot();
283
306
  const workspace = WorkspaceExecutor.fromRoot();
307
+ const commandContext: CommandContext = { values: {} };
284
308
  for (const argMeta of allArgMetas) {
285
- if (argMeta.type === "Option") commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt);
309
+ if (argMeta.type === "Option")
310
+ commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt, commandContext);
286
311
  else if (argMeta.type === "Argument")
287
312
  commandArgs[argMeta.idx] = await getArgumentValue(argMeta, cmdArgs[argMeta.idx] as string);
288
313
  else
@@ -294,6 +319,7 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`,
294
319
 
295
320
  if (commandArgs[argMeta.idx] instanceof AppExecutor)
296
321
  process.env.AKAN_PUBLIC_APP_NAME = (commandArgs[argMeta.idx] as AppExecutor).name;
322
+ assignCommandContext(commandContext, argMeta, commandArgs[argMeta.idx]);
297
323
  if ((opt as { verbose?: boolean }).verbose) Executor.setVerbose(true);
298
324
  }
299
325
  const cmd = CommandContainer.get(command);