akanjs 2.0.0-beta.8 → 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.
@@ -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
@@ -6052,6 +6052,8 @@ class CssImportResolver {
6052
6052
 
6053
6053
  var SOURCE_EXTS3 = [".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs"];
6054
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[\\/]/;
6055
6057
 
6056
6058
  class CssCompiler {
6057
6059
  #logger = new Logger("CssCompiler");
@@ -6119,7 +6121,7 @@ class CssCompiler {
6119
6121
  const akanConfig2 = await this.#app.getConfig({ refresh });
6120
6122
  while (queue.length > 0) {
6121
6123
  const filePath = queue.shift();
6122
- if (!filePath || sourceFiles.has(filePath) || filePath.includes("node_modules"))
6124
+ if (!filePath || sourceFiles.has(filePath) || isIgnoredNodeModuleSource(filePath))
6123
6125
  continue;
6124
6126
  sourceFiles.add(filePath);
6125
6127
  let content;
@@ -6155,7 +6157,7 @@ class CssCompiler {
6155
6157
  if (NON_SOURCE_EXT_RE3.test(spec))
6156
6158
  continue;
6157
6159
  const resolved = await this.#resolveSourceImport(spec, importerDir, resolvePackage);
6158
- if (!resolved || sourceFiles.has(resolved) || resolved.includes("node_modules"))
6160
+ if (!resolved || sourceFiles.has(resolved) || isIgnoredNodeModuleSource(resolved))
6159
6161
  continue;
6160
6162
  queue.push(resolved);
6161
6163
  }
@@ -6243,7 +6245,7 @@ class CssCompiler {
6243
6245
  const files = new Set(sourcePaths);
6244
6246
  await Promise.all(dirs.map(async (dir) => {
6245
6247
  for await (const file of glob.scan({ cwd: dir, absolute: true })) {
6246
- if (file.includes("node_modules"))
6248
+ if (isIgnoredNodeModuleSource(file))
6247
6249
  continue;
6248
6250
  files.add(file);
6249
6251
  }
@@ -6290,6 +6292,9 @@ function resolveSourceWithRequire(id, fromBase) {
6290
6292
  function isSourceFile(filePath) {
6291
6293
  return SOURCE_EXTS3.includes(path23.extname(filePath));
6292
6294
  }
6295
+ function isIgnoredNodeModuleSource(filePath) {
6296
+ return NODE_MODULES_RE3.test(filePath) && !AKANJS_NODE_MODULE_RE3.test(filePath);
6297
+ }
6293
6298
  function getPageKeyBasePath(pageKey, basePaths) {
6294
6299
  const normalized = pageKey.split(path23.sep).join("/").replace(/^\.\//, "");
6295
6300
  const segments = normalized.split("/");
@@ -7907,21 +7912,65 @@ var getMobileTargets = async (app) => {
7907
7912
  const config = await app.getConfig();
7908
7913
  return Object.entries(config.mobile.targets).map(([name, target]) => ({ name, config: target }));
7909
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
+ };
7910
7943
  var resolveMobileTargets = async (app, selection) => {
7944
+ const config = await app.getConfig();
7911
7945
  const targets = await getMobileTargets(app);
7912
7946
  if (targets.length === 0)
7913
7947
  throw new Error(`No mobile targets configured for ${app.name}`);
7914
7948
  if (!selection) {
7915
- if (targets.length === 1)
7916
- return targets;
7917
- 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>.`);
7918
7953
  }
7919
- 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
+ }
7920
7964
  return targets;
7965
+ }
7921
7966
  const target = targets.find((candidate) => candidate.name === selection);
7922
- if (!target)
7923
- throw new Error(`Mobile target '${selection}' was not found. Available: ${targets.map((t) => t.name).join(", ")}`);
7924
- 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(", ")}`);
7925
7974
  };
7926
7975
  var resolveMobilePath = (target, pathname) => {
7927
7976
  const basePath2 = target.basePath?.replace(/^\/+|\/+$/g, "");
@@ -8118,7 +8167,8 @@ class CapacitorApp {
8118
8167
  async#writeCapacitorConfig() {
8119
8168
  await mkdir10(this.targetRoot, { recursive: true });
8120
8169
  const appInfoPath = path34.relative(this.targetRoot, path34.join(this.app.cwdPath, "akan.app.json")).split(path34.sep).join("/");
8121
- 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}`}";
8122
8172
  import appInfo from "${appInfoPath}";
8123
8173
 
8124
8174
  export default withBase((config) => config, appInfo, "${this.target.name}");
@@ -8568,7 +8618,7 @@ var formatCommandHelp = (command, key) => {
8568
8618
  const optName = `${flag}--${kebabName}`;
8569
8619
  const optDesc = opt.desc ?? "";
8570
8620
  const defaultVal = opt.default !== undefined ? chalk5.gray(` [default: ${String(opt.default)}]`) : "";
8571
- 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(", ")})`) : "";
8572
8622
  lines.push(` ${chalk5.green(optName)} ${chalk5.gray(optDesc)}${defaultVal}${choices}`);
8573
8623
  }
8574
8624
  lines.push("");
@@ -8588,7 +8638,7 @@ var handleOption = (programCommand, argMeta) => {
8588
8638
  ask
8589
8639
  } = argMeta.argsOption;
8590
8640
  const kebabName = camelToKebabCase2(argMeta.name);
8591
- 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;
8592
8642
  programCommand.option(`-${flag}, --${kebabName}${type === "boolean" ? " [boolean]" : ` <${kebabName}>`}`, `${desc}${ask ? ` (${ask})` : ""}${example ? ` (example: ${example})` : ""}${choices ? ` (choices: ${choices.map((choice) => choice.name).join(", ")})` : ""}`);
8593
8643
  return programCommand;
8594
8644
  };
@@ -8605,7 +8655,16 @@ var convertArgValue = (value, type) => {
8605
8655
  else
8606
8656
  return value === true || value === "true";
8607
8657
  };
8608
- 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) => {
8609
8668
  const {
8610
8669
  name,
8611
8670
  argsOption: { enum: enumChoices, default: defaultValue, type, desc, nullable, example, ask }
@@ -8614,13 +8673,13 @@ var getOptionValue = async (argMeta, opt) => {
8614
8673
  return convertArgValue(opt[argMeta.name], type ?? "string");
8615
8674
  else if (defaultValue !== undefined)
8616
8675
  return defaultValue;
8617
- else if (nullable)
8618
- return null;
8619
8676
  if (enumChoices) {
8620
- 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) ?? []);
8621
8678
  const choice = await select2({ message: ask ?? desc ?? `Select the ${name} value`, choices });
8622
8679
  return choice;
8623
- } else if (type === "boolean") {
8680
+ } else if (nullable)
8681
+ return null;
8682
+ else if (type === "boolean") {
8624
8683
  const message = ask ?? desc ?? `Do you want to set ${name}? ${desc ? ` (${desc})` : ""}: `;
8625
8684
  return await confirm({ message });
8626
8685
  } else {
@@ -8645,6 +8704,22 @@ var getArgumentValue = async (argMeta, value) => {
8645
8704
  const message = ask ? `${ask}: ` : desc ? `${desc}: ` : `Enter the ${name} value${example ? ` (example: ${example})` : ""}: `;
8646
8705
  return convertArgValue(await input2({ message }), type ?? "string");
8647
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
+ };
8648
8723
  var assertCurrentDirectoryIsWorkspaceRoot = async () => {
8649
8724
  const cwd = process.cwd();
8650
8725
  const [hasPackageJson, hasTsConfig, hasEnv] = await Promise.all([
@@ -8820,15 +8895,17 @@ It may cause unexpected behavior. Run \`akan update\` to update latest akanjs.`)
8820
8895
  if (targetMeta.targetOption.runsOnWorkspaceRoot)
8821
8896
  await assertCurrentDirectoryIsWorkspaceRoot();
8822
8897
  const workspace = WorkspaceExecutor.fromRoot();
8898
+ const commandContext = { values: {} };
8823
8899
  for (const argMeta of allArgMetas) {
8824
8900
  if (argMeta.type === "Option")
8825
- commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt);
8901
+ commandArgs[argMeta.idx] = await getOptionValue(argMeta, opt, commandContext);
8826
8902
  else if (argMeta.type === "Argument")
8827
8903
  commandArgs[argMeta.idx] = await getArgumentValue(argMeta, cmdArgs[argMeta.idx]);
8828
8904
  else
8829
8905
  commandArgs[argMeta.idx] = await getInternalArgumentValue(argMeta, cmdArgs[argMeta.idx], workspace);
8830
8906
  if (commandArgs[argMeta.idx] instanceof AppExecutor)
8831
8907
  process.env.AKAN_PUBLIC_APP_NAME = commandArgs[argMeta.idx].name;
8908
+ assignCommandContext(commandContext, argMeta, commandArgs[argMeta.idx]);
8832
8909
  if (opt.verbose)
8833
8910
  Executor.setVerbose(true);
8834
8911
  }
@@ -9850,14 +9927,22 @@ class ApplicationCommand extends command("application", [ApplicationScript], ({
9850
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) {
9851
9928
  await this.applicationScript.test(exec2, { write });
9852
9929
  }),
9853
- 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, {
9854
9935
  enum: ["local", "debug", "develop", "main"],
9855
9936
  desc: "backend environment",
9856
9937
  default: "debug"
9857
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) {
9858
9939
  await this.applicationScript.buildIos(app, { target: target2, env: asMobileEnv(env), write, regenerate });
9859
9940
  }),
9860
- 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, {
9861
9946
  enum: ["local", "debug", "develop", "main"],
9862
9947
  desc: "backend environment",
9863
9948
  default: "debug"
@@ -9979,21 +10064,25 @@ class PackageRunner extends runner("package") {
9979
10064
  const scanner = await TypeScriptDependencyScanner.from(pkg);
9980
10065
  const { npmDeps, npmDevDeps, missingDeps } = await scanner.getPackageBuildDependencies(pkg.name);
9981
10066
  const packageRuntimeDependencies = { akanjs: ["daisyui"] };
10067
+ const packageRuntimeDevDependencies = { akanjs: ["@biomejs/biome"] };
9982
10068
  const forcedRuntimeDeps = packageRuntimeDependencies[pkg.name] ?? [];
10069
+ const forcedRuntimeDevDeps = packageRuntimeDevDependencies[pkg.name] ?? [];
9983
10070
  const packageRuntimeDeps = [...new Set([...npmDeps, ...forcedRuntimeDeps])];
10071
+ const packageRuntimeDevDeps = [...new Set([...npmDevDeps, ...forcedRuntimeDevDeps])];
9984
10072
  const rootPackageJson = await pkg.workspace.getPackageJson();
9985
10073
  const rootDeps = { ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies };
9986
10074
  const missingForcedDeps = forcedRuntimeDeps.filter((dep) => !rootDeps[dep]);
9987
- const allMissingDeps = [...new Set([...missingDeps, ...missingForcedDeps])].sort();
10075
+ const missingForcedDevDeps = forcedRuntimeDevDeps.filter((dep) => !rootDeps[dep]);
10076
+ const allMissingDeps = [...new Set([...missingDeps, ...missingForcedDeps, ...missingForcedDevDeps])].sort();
9988
10077
  if (allMissingDeps.length > 0)
9989
10078
  throw new Error(`Missing dependency versions in root package.json: ${allMissingDeps.join(", ")}`);
9990
- await pkg.updatePackageJsonDependencies(packageRuntimeDeps, npmDevDeps);
10079
+ await pkg.updatePackageJsonDependencies(packageRuntimeDeps, packageRuntimeDevDeps);
9991
10080
  const hasBuildFile = await Bun.file(`${pkg.cwdPath}/build.ts`).exists();
9992
10081
  if (hasBuildFile) {
9993
10082
  await pkg.workspace.spawn(process.execPath, [`${pkg.cwdPath}/build.ts`], { env: process.env, stdio: "inherit" });
9994
10083
  } else {
9995
10084
  await $2`cp -r ${pkg.cwdPath}/. ${pkg.dist.cwdPath}`;
9996
- await Promise.all([pkg.generateDistPackageJson(packageRuntimeDeps, npmDevDeps), pkg.generateTsconfigJson()]);
10085
+ await Promise.all([pkg.generateDistPackageJson(packageRuntimeDeps, packageRuntimeDevDeps), pkg.generateTsconfigJson()]);
9997
10086
  }
9998
10087
  }
9999
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
 
@@ -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);