akanjs 2.0.0-beta.1 → 2.0.0-beta.10

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 (51) hide show
  1. package/cli/application/application.command.ts +11 -3
  2. package/cli/guidelines/databaseModule/databaseModule.instruction.md +1 -1
  3. package/cli/guidelines/modelConstant/modelConstant.instruction.md +5 -5
  4. package/cli/guidelines/modelDocument/modelDocument.instruction.md +34 -61
  5. package/cli/guidelines/modelService/modelService.instruction.md +1 -1
  6. package/cli/index.js +157 -58
  7. package/cli/package/package.runner.ts +24 -7
  8. package/cli/package/package.script.ts +2 -2
  9. package/cli/templates/app/page/_index.tsx +200 -76
  10. package/cli/templates/app/page/_layout.tsx +0 -1
  11. package/cli/templates/module/__Model__.Zone.tsx +1 -1
  12. package/cli/templates/module/__model__.document.ts +1 -1
  13. package/cli/templates/workspaceRoot/.gitignore.template +1 -11
  14. package/cli/templates/workspaceRoot/biome.json.template +16 -0
  15. package/cli/workspace/workspace.command.ts +2 -6
  16. package/cli/workspace/workspace.runner.ts +1 -7
  17. package/cli/workspace/workspace.script.ts +14 -8
  18. package/client/csrTypes.ts +1 -1
  19. package/constant/fieldInfo.ts +1 -1
  20. package/devkit/capacitor.base.config.ts +1 -1
  21. package/devkit/capacitorApp.ts +5 -1
  22. package/devkit/commandDecorators/argMeta.ts +28 -14
  23. package/devkit/commandDecorators/command.ts +41 -15
  24. package/devkit/commandDecorators/commandBuilder.ts +78 -42
  25. package/devkit/commandDecorators/helpFormatter.ts +7 -4
  26. package/devkit/frontendBuild/cssCompiler.ts +9 -3
  27. package/devkit/incrementalBuilder/incrementalBuilder.proc.ts +2 -1
  28. package/devkit/lint/no-deep-internal-import.grit +25 -0
  29. package/devkit/mobile/mobileTarget.ts +48 -8
  30. package/devkit/src/capacitorApp.ts +277 -0
  31. package/devkit/transforms/barrelImportsPlugin.ts +6 -0
  32. package/package.json +3 -1
  33. package/server/hmr/clientScript.ts +8 -5
  34. package/server/resolver/resolver.contract.fixture.ts +1 -1
  35. package/ui/Field.tsx +0 -1
  36. package/ui/Portal.tsx +2 -0
  37. package/ui/System/CSR.tsx +6 -5
  38. package/ui/System/SSR.tsx +1 -1
  39. package/ui/System/SelectLanguage.tsx +1 -1
  40. package/webkit/bootCsr.tsx +8 -5
  41. package/cli/templates/app/common/commonLogic.ts +0 -12
  42. package/cli/templates/app/common/index.ts +0 -10
  43. package/cli/templates/app/srvkit/backendLogic.ts +0 -12
  44. package/cli/templates/app/srvkit/index.ts +0 -10
  45. package/cli/templates/app/ui/UiComponent.ts +0 -16
  46. package/cli/templates/app/ui/index.ts +0 -10
  47. package/cli/templates/app/webkit/frontendLogic.ts +0 -12
  48. package/cli/templates/app/webkit/index.ts +0 -10
  49. package/cli/templates/module/index.tsx +0 -44
  50. /package/cli/templates/app/public/{favicon.ico → favicon.ico.template} +0 -0
  51. /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",
@@ -94,7 +94,7 @@ Create the MongoDB schema with document methods and middleware:
94
94
 
95
95
  ```typescript
96
96
  import { dayjs } from "akanjs/base";
97
- import { beyond, by, Database, into, Loader, type SchemaOf } from "akanjs/document";
97
+ import { by, Database, into, Loader, type SchemaOf } from "akanjs/document";
98
98
  import { hashPassword } from "@libs/shared/server";
99
99
 
100
100
  import * as cnst from "../cnst";
@@ -80,7 +80,7 @@ export class Drone extends Full(DroneObject, LightDrone) {
80
80
  // 7. Insight Model
81
81
 
82
82
  export class DroneInsight {
83
- @Field.Prop(() => Int, { default: 0, accumulate: { $sum: 1 } })
83
+ @Field.Prop(() => Int, { default: 0, accumulate: {} })
84
84
  count: number;
85
85
  }
86
86
 
@@ -223,7 +223,7 @@ Insight model defines statistical fields:
223
223
 
224
224
  ```typescript
225
225
  export class DroneInsight {
226
- @Field.Prop(() => Int, { default: 0, accumulate: { $sum: 1 } })
226
+ @Field.Prop(() => Int, { default: 0, accumulate: {} })
227
227
  count: number;
228
228
  }
229
229
  ```
@@ -314,8 +314,8 @@ const drones = await this.listByName("myDrone", { sort: "alphabetical" });
314
314
  | `enum` | `any[]` | - | Enum values restriction | `@Field.Prop(()=> String, { enum: ["active","inactive"] }) status;` |
315
315
  | `minlength` | `number` | - | Minimum string length | `@Field.Prop(()=> String, { minlength: 2 }) name: string;` |
316
316
  | `maxlength` | `number` | - | Maximum string length | `@Field.Prop(()=> String, { maxlength: 30 }) title: string;` |
317
- | `query` | `object` | - | Query value for Summary fields | `@Field.Prop(() => Int, { query: { status: { $ne: 'inactive' } })` |
318
- | `accumulate` | `object` | - | Aggregation value for Insight fields | `@Field.Prop(() => Int, { accumulate: { $sum: 1 } }) count: number;` |
317
+ | `query` | `object` | - | Query value for Summary fields | `@Field.Prop(() => Int, { query: { status: { ne: "inactive" } } })` |
318
+ | `accumulate` | `object` | - | Document query filter for Insight count fields | `@Field.Prop(() => Int, { accumulate: { status: "active" } }) activeCount: number;` |
319
319
  | `example` | `any` | - | Example value for API docs | `@Field.Prop(()=> String, { example: "contact@akanjs.com" }) email;` |
320
320
  | `of` | `any` | - | Value type for Map fields | `@Field.Prop(()=> Map, { of: Date }) readAts: Map<string, Dayjs>;` |
321
321
  | `validate` | `(value) => boolean` | - | Custom validation function | `@Field.Prop(()=> String, { validate: (v)=> v.includes("@") }) email;` |
@@ -477,7 +477,7 @@ export class Drone extends Full(DroneObject, LightDrone) {
477
477
  // Insight Model
478
478
 
479
479
  export class DroneInsight {
480
- @Field.Prop(() => Int, { accumulate: { $sum: 1 } })
480
+ @Field.Prop(() => Int, { accumulate: {} })
481
481
  count: number;
482
482
  }
483
483
 
@@ -118,26 +118,14 @@ export class OrderModel extends into(Order, cnst.orderCnst) {
118
118
  .save();
119
119
  }
120
120
 
121
- // Aggregation example
121
+ // Insight-style count example
122
122
  async getOrderStatsByUser(userId: string) {
123
- const result = await this.Order.aggregate([
124
- { $match: { user: userId } },
125
- {
126
- $group: {
127
- _id: "$status",
128
- count: { $sum: 1 },
129
- totalAmount: { $sum: "$totalAmount" },
130
- },
131
- },
132
- ]);
133
-
134
- return result.reduce((acc, item) => {
135
- acc[item._id] = {
136
- count: item.count,
137
- totalAmount: item.totalAmount,
138
- };
139
- return acc;
140
- }, {});
123
+ const baseQuery = { user: userId };
124
+
125
+ return {
126
+ active: await this.count({ ...baseQuery, status: "active" }),
127
+ refunded: await this.count({ ...baseQuery, status: "refunded" }),
128
+ };
141
129
  }
142
130
  }
143
131
  ```
@@ -191,37 +179,30 @@ Indexes are crucial for query performance. Define them in the Middleware's `onSc
191
179
  - **Unique index** - Ensure field values are unique across the collection
192
180
  - **Sparse index** - Only include documents that have the indexed field
193
181
 
194
- ## How to Use MongoDB Model
182
+ ## How to Query Documents
195
183
 
196
- The Akan.js framework provides direct access to the Mongoose model, allowing you to perform standard MongoDB operations:
184
+ The Akan.js framework uses document query objects for common filtering and counting:
197
185
 
198
186
  ```typescript
199
187
  // Find operations
200
- const product = await productModel.Product.findById(id);
201
- const activeProducts = await productModel.Product.find({ status: "active" });
188
+ const product = await productModel.pickById(id);
189
+ const activeProducts = await productModel.list({ status: "active" });
202
190
 
203
191
  // Update operations
204
- await productModel.Product.updateMany({ category: "electronics" }, { $set: { onSale: true } });
192
+ await productModel.updateManyByQuery({ category: "electronics" }, { set: { onSale: true } });
205
193
 
206
- // Aggregation
207
- const stats = await productModel.Product.aggregate([
208
- { $match: { status: "active" } },
209
- {
210
- $group: {
211
- _id: "$category",
212
- count: { $sum: 1 },
213
- avgPrice: { $avg: "$price" },
214
- },
215
- },
216
- ]);
194
+ // Insight counts
195
+ const activeCount = await productModel.count({ status: "active" });
196
+ const categoryCount = await productModel.count({ category: "electronics" });
217
197
 
218
198
  // Advanced querying
219
- const products = await productModel.Product.find({
220
- price: { $gte: 100, $lte: 500 },
221
- category: { $in: ["electronics", "gadgets"] },
222
- })
223
- .sort({ rating: -1 })
224
- .limit(10);
199
+ const products = await productModel.list(
200
+ {
201
+ price: { gte: 100, lte: 500 },
202
+ category: { oneOf: ["electronics", "gadgets"] },
203
+ },
204
+ { sort: { rating: -1 }, limit: 10 },
205
+ );
225
206
  ```
226
207
 
227
208
  ## How to Use DataLoader
@@ -422,16 +403,17 @@ export class ProductModel extends into(Product, cnst.productCnst) {
422
403
  tagProductsLoader: Loader<string, Product[]>;
423
404
 
424
405
  async findPopularProducts(limit = 10) {
425
- return this.Product.find({
406
+ return this.list(
407
+ {
426
408
  status: "active",
427
- popularity: { $gte: 4 },
428
- })
429
- .sort({ popularity: -1 })
430
- .limit(limit);
409
+ popularity: { gte: 4 },
410
+ },
411
+ { sort: { popularity: -1 }, limit },
412
+ );
431
413
  }
432
414
 
433
415
  async updatePrices(categoryId: string, increasePercentage: number) {
434
- const products = await this.Product.find({ category: categoryId });
416
+ const products = await this.list({ category: categoryId });
435
417
 
436
418
  const updates = products.map((product) => {
437
419
  const newPrice = product.price * (1 + increasePercentage / 100);
@@ -441,20 +423,11 @@ export class ProductModel extends into(Product, cnst.productCnst) {
441
423
  return Promise.all(updates);
442
424
  }
443
425
 
444
- async getCategoryStats() {
445
- const result = await this.Product.aggregate([
446
- { $match: { status: "active" } },
447
- {
448
- $group: {
449
- _id: "$category",
450
- count: { $sum: 1 },
451
- avgPrice: { $avg: "$price" },
452
- minPrice: { $min: "$price" },
453
- maxPrice: { $max: "$price" },
454
- },
455
- },
456
- { $sort: { count: -1 } },
457
- ]);
426
+ async getCategoryStats(categoryId: string) {
427
+ const result = {
428
+ activeCount: await this.count({ status: "active", category: categoryId }),
429
+ popularCount: await this.count({ status: "active", category: categoryId, popularity: { gte: 4 } }),
430
+ };
458
431
 
459
432
  // Cache the results
460
433
  await this.ProductCache.set("stats", "categories", JSON.stringify(result), { expireAt: dayjs().add(1, "hour") });
@@ -644,7 +644,7 @@ export class OrderService extends DbService(db.orderDb) {
644
644
  // Return stock to inventory
645
645
  for (const item of order.items) {
646
646
  await this.productService.updateProduct(item.product, {
647
- $inc: { stock: item.quantity },
647
+ inc: { stock: item.quantity },
648
648
  });
649
649
  }
650
650
 
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"
@@ -9946,13 +10035,16 @@ class ApplicationCommand extends command("application", [ApplicationScript], ({
9946
10035
  })) {
9947
10036
  }
9948
10037
 
10038
+ import path36 from "path";
9949
10039
  var {$: $2 } = globalThis.Bun;
9950
10040
 
9951
10041
  class PackageRunner extends runner("package") {
9952
- async version(workspace) {
9953
- const pkgJson = await FileSys.readJson("package.json");
10042
+ async version(workspace, { log = true } = {}) {
10043
+ const pkgJson = await FileSys.readJson(process.env.USE_AKANJS_PKGS === "true" ? `${workspace.workspaceRoot}/pkgs/akanjs/package.json` : `${path36.dirname(Bun.main)}/../package.json`);
9954
10044
  const version = pkgJson.version;
9955
- Logger.rawLog(`${pkgJson.name}@${version}`);
10045
+ if (log)
10046
+ Logger.rawLog(`${pkgJson.name}@${version}`);
10047
+ return version;
9956
10048
  }
9957
10049
  async createPackage(workspace, pkgName) {
9958
10050
  await workspace.applyTemplate({ basePath: `pkgs/${pkgName}`, template: "pkgRoot", dict: { pkgName } });
@@ -9971,15 +10063,26 @@ class PackageRunner extends runner("package") {
9971
10063
  await pkg.dist.mkdir(pkg.dist.cwdPath);
9972
10064
  const scanner = await TypeScriptDependencyScanner.from(pkg);
9973
10065
  const { npmDeps, npmDevDeps, missingDeps } = await scanner.getPackageBuildDependencies(pkg.name);
9974
- if (missingDeps.length > 0)
9975
- throw new Error(`Missing dependency versions in root package.json: ${missingDeps.join(", ")}`);
9976
- await pkg.updatePackageJsonDependencies(npmDeps, npmDevDeps);
10066
+ const packageRuntimeDependencies = { akanjs: ["daisyui"] };
10067
+ const packageRuntimeDevDependencies = { akanjs: ["@biomejs/biome"] };
10068
+ const forcedRuntimeDeps = packageRuntimeDependencies[pkg.name] ?? [];
10069
+ const forcedRuntimeDevDeps = packageRuntimeDevDependencies[pkg.name] ?? [];
10070
+ const packageRuntimeDeps = [...new Set([...npmDeps, ...forcedRuntimeDeps])];
10071
+ const packageRuntimeDevDeps = [...new Set([...npmDevDeps, ...forcedRuntimeDevDeps])];
10072
+ const rootPackageJson = await pkg.workspace.getPackageJson();
10073
+ const rootDeps = { ...rootPackageJson.dependencies, ...rootPackageJson.devDependencies };
10074
+ const missingForcedDeps = forcedRuntimeDeps.filter((dep) => !rootDeps[dep]);
10075
+ const missingForcedDevDeps = forcedRuntimeDevDeps.filter((dep) => !rootDeps[dep]);
10076
+ const allMissingDeps = [...new Set([...missingDeps, ...missingForcedDeps, ...missingForcedDevDeps])].sort();
10077
+ if (allMissingDeps.length > 0)
10078
+ throw new Error(`Missing dependency versions in root package.json: ${allMissingDeps.join(", ")}`);
10079
+ await pkg.updatePackageJsonDependencies(packageRuntimeDeps, packageRuntimeDevDeps);
9977
10080
  const hasBuildFile = await Bun.file(`${pkg.cwdPath}/build.ts`).exists();
9978
10081
  if (hasBuildFile) {
9979
10082
  await pkg.workspace.spawn(process.execPath, [`${pkg.cwdPath}/build.ts`], { env: process.env, stdio: "inherit" });
9980
10083
  } else {
9981
10084
  await $2`cp -r ${pkg.cwdPath}/. ${pkg.dist.cwdPath}`;
9982
- await Promise.all([pkg.generateDistPackageJson(npmDeps, npmDevDeps), pkg.generateTsconfigJson()]);
10085
+ await Promise.all([pkg.generateDistPackageJson(packageRuntimeDeps, packageRuntimeDevDeps), pkg.generateTsconfigJson()]);
9983
10086
  }
9984
10087
  }
9985
10088
  async updateWorskpaceRootPackageJson(workspace, rootPackageJson) {
@@ -9996,8 +10099,8 @@ class PackageRunner extends runner("package") {
9996
10099
  }
9997
10100
 
9998
10101
  class PackageScript extends script("package", [PackageRunner]) {
9999
- async version(workspace) {
10000
- await this.packageRunner.version(workspace);
10102
+ async version(workspace, { log = true } = {}) {
10103
+ return await this.packageRunner.version(workspace, { log });
10001
10104
  }
10002
10105
  async createPackage(workspace, pkgName) {
10003
10106
  const spinner2 = workspace.spinning(`Creating package in pkgs/${pkgName}...`);
@@ -10256,14 +10359,14 @@ class GuidelinePrompt extends Prompter {
10256
10359
  async#getScanFilePaths(matchPattern, { avoidDirs = ["node_modules", ".next"], filterText } = {}) {
10257
10360
  const glob = new Bun.Glob(matchPattern);
10258
10361
  const paths = [];
10259
- for await (const path36 of glob.scan({ cwd: this.workspace.workspaceRoot, absolute: true })) {
10260
- if (avoidDirs.some((dir) => path36.includes(dir)))
10362
+ for await (const path37 of glob.scan({ cwd: this.workspace.workspaceRoot, absolute: true })) {
10363
+ if (avoidDirs.some((dir) => path37.includes(dir)))
10261
10364
  continue;
10262
- const fileContent = await FileSys.readText(path36);
10365
+ const fileContent = await FileSys.readText(path37);
10263
10366
  const textFilter = filterText ? new RegExp(filterText) : null;
10264
10367
  if (filterText && !textFilter?.test(fileContent))
10265
10368
  continue;
10266
- paths.push(path36);
10369
+ paths.push(path37);
10267
10370
  }
10268
10371
  return paths;
10269
10372
  }
@@ -11249,18 +11352,13 @@ class ScalarCommand extends command("scalar", [ScalarScript], ({ public: target
11249
11352
  })) {
11250
11353
  }
11251
11354
 
11252
- import path37 from "path";
11253
-
11254
- import path36 from "path";
11255
- import latestVersion2 from "latest-version";
11355
+ import path38 from "path";
11256
11356
 
11357
+ import path37 from "path";
11257
11358
  class WorkspaceRunner extends runner("workspace") {
11258
- async#resolveAkanVersion(tag) {
11259
- return /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/.test(tag) ? tag : await latestVersion2("akanjs", { version: tag });
11260
- }
11261
- async createWorkspace(repoName, appName, { dirname: dirname3 = ".", tag = "latest", init = true }) {
11359
+ async createWorkspace(repoName, appName, { dirname: dirname3 = ".", init = true, akanVersion }) {
11262
11360
  const cwdPath = process.cwd();
11263
- const workspaceRoot = path36.join(cwdPath, dirname3, repoName);
11361
+ const workspaceRoot = path37.join(cwdPath, dirname3, repoName);
11264
11362
  const workspace = WorkspaceExecutor.fromRoot({ workspaceRoot, repoName });
11265
11363
  const templateSpinner = workspace.spinning(`Creating workspace template files in ${dirname3}/${repoName}...`);
11266
11364
  await workspace.applyTemplate({
@@ -11270,7 +11368,6 @@ class WorkspaceRunner extends runner("workspace") {
11270
11368
  });
11271
11369
  templateSpinner.succeed(`Workspace files created in ${dirname3}/${repoName}`);
11272
11370
  const rootPackageJson = await workspace.getPackageJson();
11273
- const akanVersion = await this.#resolveAkanVersion(tag);
11274
11371
  const packageJson = {
11275
11372
  ...rootPackageJson,
11276
11373
  dependencies: {
@@ -11299,14 +11396,19 @@ class WorkspaceRunner extends runner("workspace") {
11299
11396
  }
11300
11397
  }
11301
11398
 
11302
- class WorkspaceScript extends script("workspace", [WorkspaceRunner, ApplicationScript, LibraryScript]) {
11303
- async createWorkspace(repoName, appName, {
11304
- dirname: dirname3 = ".",
11305
- installLibs = false,
11306
- tag = "latest",
11307
- init = true
11308
- }) {
11309
- const workspace = await this.workspaceRunner.createWorkspace(repoName, appName, { dirname: dirname3, tag, init });
11399
+ class WorkspaceScript extends script("workspace", [
11400
+ WorkspaceRunner,
11401
+ ApplicationScript,
11402
+ LibraryScript,
11403
+ PackageScript
11404
+ ]) {
11405
+ async createWorkspace(repoName, appName, { dirname: dirname3 = ".", installLibs = false, init = true }) {
11406
+ const akanVersion = await this.packageScript.version({ log: false });
11407
+ const workspace = await this.workspaceRunner.createWorkspace(repoName, appName, {
11408
+ dirname: dirname3,
11409
+ init,
11410
+ akanVersion
11411
+ });
11310
11412
  if (installLibs) {
11311
11413
  await this.libraryScript.installLibrary(workspace, "util");
11312
11414
  await this.libraryScript.installLibrary(workspace, "shared");
@@ -11319,7 +11421,7 @@ class WorkspaceScript extends script("workspace", [WorkspaceRunner, ApplicationS
11319
11421
  } catch (_) {
11320
11422
  gitSpinner.fail("Git repository initialization failed. It's not fatal, you can commit manually");
11321
11423
  }
11322
- const workspacePath = path37.join(dirname3, repoName);
11424
+ const workspacePath = path38.join(dirname3, repoName);
11323
11425
  Logger.rawLog(`
11324
11426
  \uD83C\uDF89 Welcome aboard! Workspace created in ${dirname3}/${repoName}`);
11325
11427
  Logger.rawLog(`\uD83D\uDE80 Run \`cd ${workspacePath} && akan start ${appName}\` to start the development server.`);
@@ -11374,15 +11476,12 @@ class WorkspaceCommand extends command("workspace", [WorkspaceScript], ({ public
11374
11476
  value: true
11375
11477
  }
11376
11478
  ]
11377
- }).option("tag", String, {
11378
- desc: "tag of the update",
11379
- default: "latest"
11380
11479
  }).option("init", Boolean, {
11381
11480
  desc: "Do you want to initialize the workspace? (Recommended)",
11382
11481
  default: true
11383
- }).exec(async function(workspaceName, app, dir, libs, tag, init) {
11482
+ }).exec(async function(workspaceName, app, dir, libs, init) {
11384
11483
  const appName = app || "app";
11385
- await this.workspaceScript.createWorkspace(workspaceName.toLowerCase().replace(/ /g, "-"), appName.toLowerCase().replace(/ /g, "-"), { dirname: dir, installLibs: libs, tag, init });
11484
+ await this.workspaceScript.createWorkspace(workspaceName.toLowerCase().replace(/ /g, "-"), appName.toLowerCase().replace(/ /g, "-"), { dirname: dir, installLibs: libs, init });
11386
11485
  }),
11387
11486
  lint: target({ desc: "Lint and fix code in a specific app/lib/pkg" }).with(Exec).option("fix", Boolean, { default: true }).with(Workspace).exec(async function(exec2, fix, workspace) {
11388
11487
  await this.workspaceScript.lint(exec2, workspace, { fix });