@nexical/cli 0.11.7 → 0.11.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 (46) hide show
  1. package/dist/{chunk-LZ3YQWAR.js → chunk-OUGA4CB4.js} +15 -11
  2. package/dist/chunk-OUGA4CB4.js.map +1 -0
  3. package/dist/index.js +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/commands/init.js +1 -1
  6. package/dist/src/commands/module/add.js +51 -20
  7. package/dist/src/commands/module/add.js.map +1 -1
  8. package/dist/src/commands/module/list.d.ts +1 -0
  9. package/dist/src/commands/module/list.js +55 -46
  10. package/dist/src/commands/module/list.js.map +1 -1
  11. package/dist/src/commands/module/remove.js +38 -13
  12. package/dist/src/commands/module/remove.js.map +1 -1
  13. package/dist/src/commands/module/update.js +16 -4
  14. package/dist/src/commands/module/update.js.map +1 -1
  15. package/dist/src/commands/run.js +19 -2
  16. package/dist/src/commands/run.js.map +1 -1
  17. package/dist/src/commands/setup.js +1 -1
  18. package/package.json +1 -1
  19. package/src/commands/module/add.ts +74 -31
  20. package/src/commands/module/list.ts +80 -57
  21. package/src/commands/module/remove.ts +50 -14
  22. package/src/commands/module/update.ts +19 -5
  23. package/src/commands/run.ts +21 -1
  24. package/test/e2e/lifecycle.e2e.test.ts +3 -2
  25. package/test/integration/commands/deploy.integration.test.ts +102 -0
  26. package/test/integration/commands/init.integration.test.ts +16 -1
  27. package/test/integration/commands/module.integration.test.ts +81 -55
  28. package/test/integration/commands/run.integration.test.ts +69 -74
  29. package/test/integration/commands/setup.integration.test.ts +53 -0
  30. package/test/unit/commands/deploy.test.ts +285 -0
  31. package/test/unit/commands/init.test.ts +15 -0
  32. package/test/unit/commands/module/add.test.ts +363 -254
  33. package/test/unit/commands/module/list.test.ts +100 -99
  34. package/test/unit/commands/module/remove.test.ts +143 -58
  35. package/test/unit/commands/module/update.test.ts +45 -62
  36. package/test/unit/commands/run.test.ts +16 -1
  37. package/test/unit/commands/setup.test.ts +25 -66
  38. package/test/unit/deploy/config-manager.test.ts +65 -0
  39. package/test/unit/deploy/providers/cloudflare.test.ts +210 -0
  40. package/test/unit/deploy/providers/github.test.ts +139 -0
  41. package/test/unit/deploy/providers/railway.test.ts +328 -0
  42. package/test/unit/deploy/registry.test.ts +227 -0
  43. package/test/unit/deploy/utils.test.ts +30 -0
  44. package/test/unit/utils/command-discovery.test.ts +145 -142
  45. package/test/unit/utils/git_utils.test.ts +49 -0
  46. package/dist/chunk-LZ3YQWAR.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../../chunk-LZ3YQWAR.js";
4
+ } from "../../../chunk-OUGA4CB4.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
@@ -10,7 +10,7 @@ import {
10
10
  // src/commands/module/list.ts
11
11
  init_esm_shims();
12
12
  var import_fs_extra = __toESM(require_lib(), 1);
13
- import { BaseCommand, logger } from "@nexical/cli-core";
13
+ import { BaseCommand } from "@nexical/cli-core";
14
14
  import path from "path";
15
15
  import YAML from "yaml";
16
16
  var ModuleListCommand = class extends BaseCommand {
@@ -19,56 +19,65 @@ var ModuleListCommand = class extends BaseCommand {
19
19
  static requiresProject = true;
20
20
  async run() {
21
21
  const projectRoot = this.projectRoot;
22
- const modulesDir = path.resolve(projectRoot, "modules");
23
- logger.debug(`Scanning for modules in: ${modulesDir}`);
24
- if (!await import_fs_extra.default.pathExists(modulesDir)) {
25
- this.info("No modules installed (modules directory missing).");
26
- return;
27
- }
28
- try {
29
- const modules = await import_fs_extra.default.readdir(modulesDir);
30
- const validModules = [];
31
- for (const moduleName of modules) {
32
- const modulePath = path.join(modulesDir, moduleName);
33
- if ((await import_fs_extra.default.stat(modulePath)).isDirectory()) {
34
- let version = "unknown";
35
- let description = "";
36
- const pkgJsonPath = path.join(modulePath, "package.json");
37
- const moduleYamlPath = path.join(modulePath, "module.yaml");
38
- const moduleYmlPath = path.join(modulePath, "module.yml");
39
- let pkg = {};
40
- let modConfig = {};
41
- if (await import_fs_extra.default.pathExists(pkgJsonPath)) {
42
- try {
43
- pkg = await import_fs_extra.default.readJson(pkgJsonPath);
44
- } catch {
45
- }
46
- }
47
- if (await import_fs_extra.default.pathExists(moduleYamlPath) || await import_fs_extra.default.pathExists(moduleYmlPath)) {
48
- try {
49
- const configPath = await import_fs_extra.default.pathExists(moduleYamlPath) ? moduleYamlPath : moduleYmlPath;
50
- const content = await import_fs_extra.default.readFile(configPath, "utf8");
51
- modConfig = YAML.parse(content) || {};
52
- } catch {
53
- }
22
+ const builtInLocations = [
23
+ { type: "backend", path: path.join(projectRoot, "apps/backend/modules") },
24
+ { type: "frontend", path: path.join(projectRoot, "apps/frontend/modules") },
25
+ // Check legacy `modules` folder just in case?
26
+ { type: "legacy", path: path.join(projectRoot, "modules") }
27
+ ];
28
+ const allModules = [];
29
+ for (const loc of builtInLocations) {
30
+ if (await import_fs_extra.default.pathExists(loc.path)) {
31
+ const modules = await import_fs_extra.default.readdir(loc.path);
32
+ for (const moduleName of modules) {
33
+ const modulePath = path.join(loc.path, moduleName);
34
+ if ((await import_fs_extra.default.stat(modulePath)).isDirectory()) {
35
+ const info = await this.getModuleInfo(
36
+ modulePath,
37
+ moduleName,
38
+ loc.type
39
+ );
40
+ allModules.push(info);
54
41
  }
55
- version = pkg.version || modConfig.version || "unknown";
56
- description = pkg.description || modConfig.description || "";
57
- validModules.push({ name: moduleName, version, description });
58
42
  }
59
43
  }
60
- if (validModules.length === 0) {
61
- this.info("No modules installed.");
62
- } else {
63
- console.table(validModules);
44
+ }
45
+ if (allModules.length === 0) {
46
+ this.info("No modules installed.");
47
+ } else {
48
+ allModules.sort((a, b) => {
49
+ if (a.type !== b.type) return a.type.localeCompare(b.type);
50
+ return a.name.localeCompare(b.name);
51
+ });
52
+ console.table(allModules);
53
+ }
54
+ }
55
+ async getModuleInfo(modulePath, dirName, type) {
56
+ let version = "unknown";
57
+ let description = "";
58
+ const pkgJsonPath = path.join(modulePath, "package.json");
59
+ const moduleYamlPath = path.join(modulePath, "module.yaml");
60
+ const moduleYmlPath = path.join(modulePath, "module.yml");
61
+ let pkg = {};
62
+ let modConfig = {};
63
+ if (await import_fs_extra.default.pathExists(pkgJsonPath)) {
64
+ try {
65
+ pkg = await import_fs_extra.default.readJson(pkgJsonPath) || {};
66
+ } catch {
64
67
  }
65
- } catch (error) {
66
- if (error instanceof Error) {
67
- this.error(`Failed to list modules: ${error.message}`);
68
- } else {
69
- this.error(`Failed to list modules: ${String(error)}`);
68
+ }
69
+ if (await import_fs_extra.default.pathExists(moduleYamlPath) || await import_fs_extra.default.pathExists(moduleYmlPath)) {
70
+ try {
71
+ const configPath = await import_fs_extra.default.pathExists(moduleYamlPath) ? moduleYamlPath : moduleYmlPath;
72
+ const content = await import_fs_extra.default.readFile(configPath, "utf8");
73
+ modConfig = YAML.parse(content) || {};
74
+ } catch {
70
75
  }
71
76
  }
77
+ version = pkg.version || modConfig.version || "unknown";
78
+ description = pkg.description || modConfig.description || "";
79
+ const name = modConfig.name || dirName;
80
+ return { name, version, description, type };
72
81
  }
73
82
  };
74
83
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/commands/module/list.ts"],"sourcesContent":["import { BaseCommand, logger } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport YAML from 'yaml';\n\nexport default class ModuleListCommand extends BaseCommand {\n static usage = 'module list';\n static description = 'List installed modules.';\n static requiresProject = true;\n\n async run() {\n const projectRoot = this.projectRoot as string;\n const modulesDir = path.resolve(projectRoot, 'modules');\n logger.debug(`Scanning for modules in: ${modulesDir}`);\n\n if (!(await fs.pathExists(modulesDir))) {\n this.info('No modules installed (modules directory missing).');\n return;\n }\n\n try {\n const modules = await fs.readdir(modulesDir);\n const validModules: { name: string; version: string; description: string }[] = [];\n\n for (const moduleName of modules) {\n const modulePath = path.join(modulesDir, moduleName);\n if ((await fs.stat(modulePath)).isDirectory()) {\n let version = 'unknown';\n let description = '';\n\n const pkgJsonPath = path.join(modulePath, 'package.json');\n const moduleYamlPath = path.join(modulePath, 'module.yaml');\n const moduleYmlPath = path.join(modulePath, 'module.yml');\n\n let pkg: Record<string, unknown> = {};\n let modConfig: Record<string, unknown> = {};\n\n if (await fs.pathExists(pkgJsonPath)) {\n try {\n pkg = await fs.readJson(pkgJsonPath);\n } catch {\n /* ignore */\n }\n }\n\n if ((await fs.pathExists(moduleYamlPath)) || (await fs.pathExists(moduleYmlPath))) {\n try {\n const configPath = (await fs.pathExists(moduleYamlPath))\n ? moduleYamlPath\n : moduleYmlPath;\n const content = await fs.readFile(configPath, 'utf8');\n modConfig = YAML.parse(content) || {};\n } catch {\n /* ignore */\n }\n }\n\n version = (pkg.version as string) || (modConfig.version as string) || 'unknown';\n description = (pkg.description as string) || (modConfig.description as string) || '';\n // Optionally use display name from module.yaml if present, but strictly list is usually dir name.\n // Let's stick to dir name for \"name\" column, but description from module.yaml is good.\n validModules.push({ name: moduleName, version, description });\n }\n }\n\n if (validModules.length === 0) {\n this.info('No modules installed.');\n } else {\n // eslint-disable-next-line no-console\n console.table(validModules);\n }\n } catch (error: unknown) {\n if (error instanceof Error) {\n this.error(`Failed to list modules: ${error.message}`);\n } else {\n this.error(`Failed to list modules: ${String(error)}`);\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,sBAAe;AADf,SAAS,aAAa,cAAc;AAEpC,OAAO,UAAU;AACjB,OAAO,UAAU;AAEjB,IAAqB,oBAArB,cAA+C,YAAY;AAAA,EACzD,OAAO,QAAQ;AAAA,EACf,OAAO,cAAc;AAAA,EACrB,OAAO,kBAAkB;AAAA,EAEzB,MAAM,MAAM;AACV,UAAM,cAAc,KAAK;AACzB,UAAM,aAAa,KAAK,QAAQ,aAAa,SAAS;AACtD,WAAO,MAAM,4BAA4B,UAAU,EAAE;AAErD,QAAI,CAAE,MAAM,gBAAAA,QAAG,WAAW,UAAU,GAAI;AACtC,WAAK,KAAK,mDAAmD;AAC7D;AAAA,IACF;AAEA,QAAI;AACF,YAAM,UAAU,MAAM,gBAAAA,QAAG,QAAQ,UAAU;AAC3C,YAAM,eAAyE,CAAC;AAEhF,iBAAW,cAAc,SAAS;AAChC,cAAM,aAAa,KAAK,KAAK,YAAY,UAAU;AACnD,aAAK,MAAM,gBAAAA,QAAG,KAAK,UAAU,GAAG,YAAY,GAAG;AAC7C,cAAI,UAAU;AACd,cAAI,cAAc;AAElB,gBAAM,cAAc,KAAK,KAAK,YAAY,cAAc;AACxD,gBAAM,iBAAiB,KAAK,KAAK,YAAY,aAAa;AAC1D,gBAAM,gBAAgB,KAAK,KAAK,YAAY,YAAY;AAExD,cAAI,MAA+B,CAAC;AACpC,cAAI,YAAqC,CAAC;AAE1C,cAAI,MAAM,gBAAAA,QAAG,WAAW,WAAW,GAAG;AACpC,gBAAI;AACF,oBAAM,MAAM,gBAAAA,QAAG,SAAS,WAAW;AAAA,YACrC,QAAQ;AAAA,YAER;AAAA,UACF;AAEA,cAAK,MAAM,gBAAAA,QAAG,WAAW,cAAc,KAAO,MAAM,gBAAAA,QAAG,WAAW,aAAa,GAAI;AACjF,gBAAI;AACF,oBAAM,aAAc,MAAM,gBAAAA,QAAG,WAAW,cAAc,IAClD,iBACA;AACJ,oBAAM,UAAU,MAAM,gBAAAA,QAAG,SAAS,YAAY,MAAM;AACpD,0BAAY,KAAK,MAAM,OAAO,KAAK,CAAC;AAAA,YACtC,QAAQ;AAAA,YAER;AAAA,UACF;AAEA,oBAAW,IAAI,WAAuB,UAAU,WAAsB;AACtE,wBAAe,IAAI,eAA2B,UAAU,eAA0B;AAGlF,uBAAa,KAAK,EAAE,MAAM,YAAY,SAAS,YAAY,CAAC;AAAA,QAC9D;AAAA,MACF;AAEA,UAAI,aAAa,WAAW,GAAG;AAC7B,aAAK,KAAK,uBAAuB;AAAA,MACnC,OAAO;AAEL,gBAAQ,MAAM,YAAY;AAAA,MAC5B;AAAA,IACF,SAAS,OAAgB;AACvB,UAAI,iBAAiB,OAAO;AAC1B,aAAK,MAAM,2BAA2B,MAAM,OAAO,EAAE;AAAA,MACvD,OAAO;AACL,aAAK,MAAM,2BAA2B,OAAO,KAAK,CAAC,EAAE;AAAA,MACvD;AAAA,IACF;AAAA,EACF;AACF;","names":["fs"]}
1
+ {"version":3,"sources":["../../../../src/commands/module/list.ts"],"sourcesContent":["import { BaseCommand } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport YAML from 'yaml';\n\ninterface ModuleInfo {\n name: string;\n version: string;\n description: string;\n type: 'backend' | 'frontend' | 'legacy';\n}\n\nexport default class ModuleListCommand extends BaseCommand {\n static usage = 'module list';\n static description = 'List installed modules.';\n static requiresProject = true;\n\n async run() {\n const projectRoot = this.projectRoot as string;\n\n // Define locations to scan\n const builtInLocations = [\n { type: 'backend', path: path.join(projectRoot, 'apps/backend/modules') },\n { type: 'frontend', path: path.join(projectRoot, 'apps/frontend/modules') },\n // Check legacy `modules` folder just in case?\n { type: 'legacy', path: path.join(projectRoot, 'modules') },\n ];\n\n const allModules: ModuleInfo[] = [];\n\n for (const loc of builtInLocations) {\n if (await fs.pathExists(loc.path)) {\n const modules = await fs.readdir(loc.path);\n\n for (const moduleName of modules) {\n const modulePath = path.join(loc.path, moduleName);\n if ((await fs.stat(modulePath)).isDirectory()) {\n const info = await this.getModuleInfo(\n modulePath,\n moduleName,\n loc.type as 'backend' | 'frontend' | 'legacy',\n );\n allModules.push(info);\n }\n }\n }\n }\n\n if (allModules.length === 0) {\n this.info('No modules installed.');\n } else {\n // Sort by type then name\n allModules.sort((a, b) => {\n if (a.type !== b.type) return a.type.localeCompare(b.type);\n return a.name.localeCompare(b.name);\n });\n // eslint-disable-next-line no-console\n console.table(allModules);\n }\n }\n\n private async getModuleInfo(\n modulePath: string,\n dirName: string,\n type: 'backend' | 'frontend' | 'legacy',\n ): Promise<ModuleInfo> {\n let version = 'unknown';\n let description = '';\n\n const pkgJsonPath = path.join(modulePath, 'package.json');\n const moduleYamlPath = path.join(modulePath, 'module.yaml');\n const moduleYmlPath = path.join(modulePath, 'module.yml');\n\n let pkg: Record<string, unknown> = {};\n let modConfig: Record<string, unknown> = {};\n\n if (await fs.pathExists(pkgJsonPath)) {\n try {\n pkg = (await fs.readJson(pkgJsonPath)) || {};\n } catch {\n /* ignore */\n }\n }\n\n if ((await fs.pathExists(moduleYamlPath)) || (await fs.pathExists(moduleYmlPath))) {\n try {\n const configPath = (await fs.pathExists(moduleYamlPath)) ? moduleYamlPath : moduleYmlPath;\n const content = await fs.readFile(configPath, 'utf8');\n modConfig = YAML.parse(content) || {};\n } catch {\n /* ignore */\n }\n }\n\n version = (pkg.version as string) || (modConfig.version as string) || 'unknown';\n description = (pkg.description as string) || (modConfig.description as string) || '';\n\n // Use config name if available, else dirName\n const name = (modConfig.name as string) || dirName;\n\n return { name, version, description, type };\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,sBAAe;AADf,SAAS,mBAAmB;AAE5B,OAAO,UAAU;AACjB,OAAO,UAAU;AASjB,IAAqB,oBAArB,cAA+C,YAAY;AAAA,EACzD,OAAO,QAAQ;AAAA,EACf,OAAO,cAAc;AAAA,EACrB,OAAO,kBAAkB;AAAA,EAEzB,MAAM,MAAM;AACV,UAAM,cAAc,KAAK;AAGzB,UAAM,mBAAmB;AAAA,MACvB,EAAE,MAAM,WAAW,MAAM,KAAK,KAAK,aAAa,sBAAsB,EAAE;AAAA,MACxE,EAAE,MAAM,YAAY,MAAM,KAAK,KAAK,aAAa,uBAAuB,EAAE;AAAA;AAAA,MAE1E,EAAE,MAAM,UAAU,MAAM,KAAK,KAAK,aAAa,SAAS,EAAE;AAAA,IAC5D;AAEA,UAAM,aAA2B,CAAC;AAElC,eAAW,OAAO,kBAAkB;AAClC,UAAI,MAAM,gBAAAA,QAAG,WAAW,IAAI,IAAI,GAAG;AACjC,cAAM,UAAU,MAAM,gBAAAA,QAAG,QAAQ,IAAI,IAAI;AAEzC,mBAAW,cAAc,SAAS;AAChC,gBAAM,aAAa,KAAK,KAAK,IAAI,MAAM,UAAU;AACjD,eAAK,MAAM,gBAAAA,QAAG,KAAK,UAAU,GAAG,YAAY,GAAG;AAC7C,kBAAM,OAAO,MAAM,KAAK;AAAA,cACtB;AAAA,cACA;AAAA,cACA,IAAI;AAAA,YACN;AACA,uBAAW,KAAK,IAAI;AAAA,UACtB;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,QAAI,WAAW,WAAW,GAAG;AAC3B,WAAK,KAAK,uBAAuB;AAAA,IACnC,OAAO;AAEL,iBAAW,KAAK,CAAC,GAAG,MAAM;AACxB,YAAI,EAAE,SAAS,EAAE,KAAM,QAAO,EAAE,KAAK,cAAc,EAAE,IAAI;AACzD,eAAO,EAAE,KAAK,cAAc,EAAE,IAAI;AAAA,MACpC,CAAC;AAED,cAAQ,MAAM,UAAU;AAAA,IAC1B;AAAA,EACF;AAAA,EAEA,MAAc,cACZ,YACA,SACA,MACqB;AACrB,QAAI,UAAU;AACd,QAAI,cAAc;AAElB,UAAM,cAAc,KAAK,KAAK,YAAY,cAAc;AACxD,UAAM,iBAAiB,KAAK,KAAK,YAAY,aAAa;AAC1D,UAAM,gBAAgB,KAAK,KAAK,YAAY,YAAY;AAExD,QAAI,MAA+B,CAAC;AACpC,QAAI,YAAqC,CAAC;AAE1C,QAAI,MAAM,gBAAAA,QAAG,WAAW,WAAW,GAAG;AACpC,UAAI;AACF,cAAO,MAAM,gBAAAA,QAAG,SAAS,WAAW,KAAM,CAAC;AAAA,MAC7C,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,QAAK,MAAM,gBAAAA,QAAG,WAAW,cAAc,KAAO,MAAM,gBAAAA,QAAG,WAAW,aAAa,GAAI;AACjF,UAAI;AACF,cAAM,aAAc,MAAM,gBAAAA,QAAG,WAAW,cAAc,IAAK,iBAAiB;AAC5E,cAAM,UAAU,MAAM,gBAAAA,QAAG,SAAS,YAAY,MAAM;AACpD,oBAAY,KAAK,MAAM,OAAO,KAAK,CAAC;AAAA,MACtC,QAAQ;AAAA,MAER;AAAA,IACF;AAEA,cAAW,IAAI,WAAuB,UAAU,WAAsB;AACtE,kBAAe,IAAI,eAA2B,UAAU,eAA0B;AAGlF,UAAM,OAAQ,UAAU,QAAmB;AAE3C,WAAO,EAAE,MAAM,SAAS,aAAa,KAAK;AAAA,EAC5C;AACF;","names":["fs"]}
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../../chunk-LZ3YQWAR.js";
4
+ } from "../../../chunk-OUGA4CB4.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
@@ -23,21 +23,31 @@ var ModuleRemoveCommand = class extends BaseCommand {
23
23
  async run(options) {
24
24
  const projectRoot = this.projectRoot;
25
25
  const { name } = options;
26
- const relativePath = `modules/${name}`;
27
- const fullPath = path.resolve(projectRoot, relativePath);
28
- logger.debug("Removing module at:", fullPath);
29
- if (!await import_fs_extra.default.pathExists(fullPath)) {
30
- this.error(`Module ${name} not found at ${relativePath}.`);
26
+ const locations = [
27
+ { type: "backend", path: `apps/backend/modules/${name}` },
28
+ { type: "frontend", path: `apps/frontend/modules/${name}` },
29
+ { type: "legacy", path: `modules/${name}` }
30
+ ];
31
+ let targetLoc = null;
32
+ let fullPath = "";
33
+ for (const loc of locations) {
34
+ const absPath = path.resolve(projectRoot, loc.path);
35
+ if (await import_fs_extra.default.pathExists(absPath)) {
36
+ targetLoc = loc;
37
+ fullPath = absPath;
38
+ break;
39
+ }
40
+ }
41
+ if (!targetLoc) {
42
+ this.error(`Module ${name} not found in any standard location.`);
31
43
  return;
32
44
  }
33
- this.info(`Removing module ${name}...`);
45
+ const relativePath = targetLoc.path;
46
+ logger.debug("Removing module at:", fullPath);
47
+ this.info(`Removing module ${name} (${targetLoc.type})...`);
34
48
  try {
35
49
  await runCommand(`git submodule deinit -f ${relativePath}`, projectRoot);
36
50
  await runCommand(`git rm -f ${relativePath}`, projectRoot);
37
- const gitModulesDir = path.resolve(projectRoot, ".git", "modules", "modules", name);
38
- if (await import_fs_extra.default.pathExists(gitModulesDir)) {
39
- await import_fs_extra.default.remove(gitModulesDir);
40
- }
41
51
  this.info("Syncing workspace dependencies...");
42
52
  await runCommand("npm install", projectRoot);
43
53
  await this.removeFromConfig(name);
@@ -57,8 +67,23 @@ var ModuleRemoveCommand = class extends BaseCommand {
57
67
  try {
58
68
  const content = await import_fs_extra.default.readFile(configPath, "utf8");
59
69
  const config = YAML.parse(content) || {};
60
- if (config.modules && config.modules.includes(moduleName)) {
61
- config.modules = config.modules.filter((m) => m !== moduleName);
70
+ let changed = false;
71
+ if (config.modules) {
72
+ if (!Array.isArray(config.modules)) {
73
+ for (const key of Object.keys(config.modules)) {
74
+ if (Array.isArray(config.modules[key]) && config.modules[key].includes(moduleName)) {
75
+ config.modules[key] = config.modules[key].filter((m) => m !== moduleName);
76
+ changed = true;
77
+ }
78
+ }
79
+ } else {
80
+ if (config.modules.includes(moduleName)) {
81
+ config.modules = config.modules.filter((m) => m !== moduleName);
82
+ changed = true;
83
+ }
84
+ }
85
+ }
86
+ if (changed) {
62
87
  await import_fs_extra.default.writeFile(configPath, YAML.stringify(config));
63
88
  logger.debug(`Removed ${moduleName} from nexical.yaml modules list.`);
64
89
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/commands/module/remove.ts"],"sourcesContent":["import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport YAML from 'yaml';\n\nexport default class ModuleRemoveCommand extends BaseCommand {\n static usage = 'module remove <name>';\n static description = 'Remove an installed module.';\n static requiresProject = true;\n\n static args: CommandDefinition = {\n args: [{ name: 'name', required: true, description: 'Name of the module to remove' }],\n };\n\n async run(options: { name: string }) {\n const projectRoot = this.projectRoot as string;\n const { name } = options;\n\n const relativePath = `modules/${name}`;\n const fullPath = path.resolve(projectRoot, relativePath);\n\n logger.debug('Removing module at:', fullPath);\n\n if (!(await fs.pathExists(fullPath))) {\n this.error(`Module ${name} not found at ${relativePath}.`);\n return;\n }\n\n this.info(`Removing module ${name}...`);\n\n try {\n await runCommand(`git submodule deinit -f ${relativePath}`, projectRoot);\n await runCommand(`git rm -f ${relativePath}`, projectRoot);\n\n // Clean up .git/modules\n const gitModulesDir = path.resolve(projectRoot, '.git', 'modules', 'modules', name);\n if (await fs.pathExists(gitModulesDir)) {\n await fs.remove(gitModulesDir);\n }\n\n this.info('Syncing workspace dependencies...');\n await runCommand('npm install', projectRoot);\n\n await this.removeFromConfig(name);\n\n this.success(`Module ${name} removed successfully.`);\n } catch (e: unknown) {\n if (e instanceof Error) {\n this.error(`Failed to remove module: ${e.message}`);\n } else {\n this.error(`Failed to remove module: ${String(e)}`);\n }\n }\n }\n\n private async removeFromConfig(moduleName: string) {\n const projectRoot = this.projectRoot as string;\n const configPath = path.join(projectRoot, 'nexical.yaml');\n\n if (!(await fs.pathExists(configPath))) return;\n\n try {\n const content = await fs.readFile(configPath, 'utf8');\n const config = YAML.parse(content) || {};\n\n if (config.modules && config.modules.includes(moduleName)) {\n config.modules = config.modules.filter((m: string) => m !== moduleName);\n await fs.writeFile(configPath, YAML.stringify(config));\n logger.debug(`Removed ${moduleName} from nexical.yaml modules list.`);\n }\n } catch (e: unknown) {\n if (e instanceof Error) {\n logger.warn(`Failed to update nexical.yaml: ${e.message}`);\n } else {\n logger.warn(`Failed to update nexical.yaml: ${String(e)}`);\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,sBAAe;AADf,SAAiC,aAAa,QAAQ,kBAAkB;AAExE,OAAO,UAAU;AACjB,OAAO,UAAU;AAEjB,IAAqB,sBAArB,cAAiD,YAAY;AAAA,EAC3D,OAAO,QAAQ;AAAA,EACf,OAAO,cAAc;AAAA,EACrB,OAAO,kBAAkB;AAAA,EAEzB,OAAO,OAA0B;AAAA,IAC/B,MAAM,CAAC,EAAE,MAAM,QAAQ,UAAU,MAAM,aAAa,+BAA+B,CAAC;AAAA,EACtF;AAAA,EAEA,MAAM,IAAI,SAA2B;AACnC,UAAM,cAAc,KAAK;AACzB,UAAM,EAAE,KAAK,IAAI;AAEjB,UAAM,eAAe,WAAW,IAAI;AACpC,UAAM,WAAW,KAAK,QAAQ,aAAa,YAAY;AAEvD,WAAO,MAAM,uBAAuB,QAAQ;AAE5C,QAAI,CAAE,MAAM,gBAAAA,QAAG,WAAW,QAAQ,GAAI;AACpC,WAAK,MAAM,UAAU,IAAI,iBAAiB,YAAY,GAAG;AACzD;AAAA,IACF;AAEA,SAAK,KAAK,mBAAmB,IAAI,KAAK;AAEtC,QAAI;AACF,YAAM,WAAW,2BAA2B,YAAY,IAAI,WAAW;AACvE,YAAM,WAAW,aAAa,YAAY,IAAI,WAAW;AAGzD,YAAM,gBAAgB,KAAK,QAAQ,aAAa,QAAQ,WAAW,WAAW,IAAI;AAClF,UAAI,MAAM,gBAAAA,QAAG,WAAW,aAAa,GAAG;AACtC,cAAM,gBAAAA,QAAG,OAAO,aAAa;AAAA,MAC/B;AAEA,WAAK,KAAK,mCAAmC;AAC7C,YAAM,WAAW,eAAe,WAAW;AAE3C,YAAM,KAAK,iBAAiB,IAAI;AAEhC,WAAK,QAAQ,UAAU,IAAI,wBAAwB;AAAA,IACrD,SAAS,GAAY;AACnB,UAAI,aAAa,OAAO;AACtB,aAAK,MAAM,4BAA4B,EAAE,OAAO,EAAE;AAAA,MACpD,OAAO;AACL,aAAK,MAAM,4BAA4B,OAAO,CAAC,CAAC,EAAE;AAAA,MACpD;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,iBAAiB,YAAoB;AACjD,UAAM,cAAc,KAAK;AACzB,UAAM,aAAa,KAAK,KAAK,aAAa,cAAc;AAExD,QAAI,CAAE,MAAM,gBAAAA,QAAG,WAAW,UAAU,EAAI;AAExC,QAAI;AACF,YAAM,UAAU,MAAM,gBAAAA,QAAG,SAAS,YAAY,MAAM;AACpD,YAAM,SAAS,KAAK,MAAM,OAAO,KAAK,CAAC;AAEvC,UAAI,OAAO,WAAW,OAAO,QAAQ,SAAS,UAAU,GAAG;AACzD,eAAO,UAAU,OAAO,QAAQ,OAAO,CAAC,MAAc,MAAM,UAAU;AACtE,cAAM,gBAAAA,QAAG,UAAU,YAAY,KAAK,UAAU,MAAM,CAAC;AACrD,eAAO,MAAM,WAAW,UAAU,kCAAkC;AAAA,MACtE;AAAA,IACF,SAAS,GAAY;AACnB,UAAI,aAAa,OAAO;AACtB,eAAO,KAAK,kCAAkC,EAAE,OAAO,EAAE;AAAA,MAC3D,OAAO;AACL,eAAO,KAAK,kCAAkC,OAAO,CAAC,CAAC,EAAE;AAAA,MAC3D;AAAA,IACF;AAAA,EACF;AACF;","names":["fs"]}
1
+ {"version":3,"sources":["../../../../src/commands/module/remove.ts"],"sourcesContent":["import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport YAML from 'yaml';\n\nexport default class ModuleRemoveCommand extends BaseCommand {\n static usage = 'module remove <name>';\n static description = 'Remove an installed module.';\n static requiresProject = true;\n\n static args: CommandDefinition = {\n args: [{ name: 'name', required: true, description: 'Name of the module to remove' }],\n };\n\n async run(options: { name: string }) {\n const projectRoot = this.projectRoot as string;\n const { name } = options;\n\n // Check locations\n const locations = [\n { type: 'backend', path: `apps/backend/modules/${name}` },\n { type: 'frontend', path: `apps/frontend/modules/${name}` },\n { type: 'legacy', path: `modules/${name}` },\n ];\n\n let targetLoc: { type: string; path: string } | null = null;\n let fullPath = '';\n\n for (const loc of locations) {\n const absPath = path.resolve(projectRoot, loc.path);\n if (await fs.pathExists(absPath)) {\n targetLoc = loc;\n fullPath = absPath;\n break;\n }\n }\n\n if (!targetLoc) {\n this.error(`Module ${name} not found in any standard location.`);\n return;\n }\n\n const relativePath = targetLoc.path;\n\n logger.debug('Removing module at:', fullPath);\n this.info(`Removing module ${name} (${targetLoc.type})...`);\n\n try {\n await runCommand(`git submodule deinit -f ${relativePath}`, projectRoot);\n await runCommand(`git rm -f ${relativePath}`, projectRoot);\n\n // Clean up .git/modules if needed (git rm often handles this but sometimes leaves stale dirs in .git/modules)\n // The path in .git/modules depends on how it was added.\n // Usually .git/modules/apps/backend/modules/name\n // We'll leave strict git cleanup to git, manually removing can be risky if path structure varies.\n // But we can check for the directory itself just in case.\n\n this.info('Syncing workspace dependencies...');\n await runCommand('npm install', projectRoot);\n\n await this.removeFromConfig(name);\n\n this.success(`Module ${name} removed successfully.`);\n } catch (e: unknown) {\n if (e instanceof Error) {\n this.error(`Failed to remove module: ${e.message}`);\n } else {\n this.error(`Failed to remove module: ${String(e)}`);\n }\n }\n }\n\n private async removeFromConfig(moduleName: string) {\n const projectRoot = this.projectRoot as string;\n const configPath = path.join(projectRoot, 'nexical.yaml');\n\n if (!(await fs.pathExists(configPath))) return;\n\n try {\n const content = await fs.readFile(configPath, 'utf8');\n const config = YAML.parse(content) || {};\n\n let changed = false;\n\n if (config.modules) {\n // Check if object\n if (!Array.isArray(config.modules)) {\n for (const key of Object.keys(config.modules)) {\n if (Array.isArray(config.modules[key]) && config.modules[key].includes(moduleName)) {\n config.modules[key] = config.modules[key].filter((m: string) => m !== moduleName);\n changed = true;\n }\n }\n } else {\n // Legacy array\n if (config.modules.includes(moduleName)) {\n config.modules = config.modules.filter((m: string) => m !== moduleName);\n changed = true;\n }\n }\n }\n\n if (changed) {\n await fs.writeFile(configPath, YAML.stringify(config));\n logger.debug(`Removed ${moduleName} from nexical.yaml modules list.`);\n }\n } catch (e: unknown) {\n if (e instanceof Error) {\n logger.warn(`Failed to update nexical.yaml: ${e.message}`);\n } else {\n logger.warn(`Failed to update nexical.yaml: ${String(e)}`);\n }\n }\n }\n}\n"],"mappings":"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","names":["fs"]}
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../../chunk-LZ3YQWAR.js";
4
+ } from "../../../chunk-OUGA4CB4.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
@@ -26,12 +26,24 @@ var ModuleUpdateCommand = class extends BaseCommand {
26
26
  logger.debug("Update context:", { name, projectRoot });
27
27
  try {
28
28
  if (name) {
29
- const relativePath = `modules/${name}`;
30
- const fullPath = path.resolve(projectRoot, relativePath);
31
- if (!await import_fs_extra.default.pathExists(fullPath)) {
29
+ const locations = [
30
+ { type: "backend", path: `apps/backend/modules/${name}` },
31
+ { type: "frontend", path: `apps/frontend/modules/${name}` },
32
+ { type: "legacy", path: `modules/${name}` }
33
+ ];
34
+ let targetLoc = null;
35
+ for (const loc of locations) {
36
+ const absPath = path.resolve(projectRoot, loc.path);
37
+ if (await import_fs_extra.default.pathExists(absPath)) {
38
+ targetLoc = loc;
39
+ break;
40
+ }
41
+ }
42
+ if (!targetLoc) {
32
43
  this.error(`Module ${name} not found.`);
33
44
  return;
34
45
  }
46
+ const relativePath = targetLoc.path;
35
47
  await runCommand(`git submodule update --remote --merge ${relativePath}`, projectRoot);
36
48
  } else {
37
49
  await runCommand("git submodule update --remote --merge", projectRoot);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/commands/module/update.ts"],"sourcesContent":["import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\n\nexport default class ModuleUpdateCommand extends BaseCommand {\n static usage = 'module update [name]';\n static description = 'Update a specific module or all modules.';\n static requiresProject = true;\n\n static args: CommandDefinition = {\n args: [{ name: 'name', required: false, description: 'Name of the module to update' }],\n };\n\n async run(options: { name?: string }) {\n const projectRoot = this.projectRoot as string;\n const { name } = options;\n\n this.info(name ? `Updating module ${name}...` : 'Updating all modules...');\n logger.debug('Update context:', { name, projectRoot: projectRoot });\n\n try {\n if (name) {\n const relativePath = `modules/${name}`;\n const fullPath = path.resolve(projectRoot, relativePath);\n\n if (!(await fs.pathExists(fullPath))) {\n this.error(`Module ${name} not found.`);\n return;\n }\n\n // Update specific module\n // We enter the directory and pull? Or generic submodule update?\n // Generic submodule update --remote src/modules/name\n await runCommand(`git submodule update --remote --merge ${relativePath}`, projectRoot);\n } else {\n // Update all\n await runCommand('git submodule update --remote --merge', projectRoot);\n }\n\n this.info('Syncing workspace dependencies...');\n await runCommand('npm install', projectRoot);\n\n this.success('Modules updated successfully.');\n } catch (e: unknown) {\n if (e instanceof Error) {\n this.error(`Failed to update modules: ${e.message}`);\n } else {\n this.error(`Failed to update modules: ${String(e)}`);\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,sBAAe;AADf,SAAiC,aAAa,QAAQ,kBAAkB;AAExE,OAAO,UAAU;AAEjB,IAAqB,sBAArB,cAAiD,YAAY;AAAA,EAC3D,OAAO,QAAQ;AAAA,EACf,OAAO,cAAc;AAAA,EACrB,OAAO,kBAAkB;AAAA,EAEzB,OAAO,OAA0B;AAAA,IAC/B,MAAM,CAAC,EAAE,MAAM,QAAQ,UAAU,OAAO,aAAa,+BAA+B,CAAC;AAAA,EACvF;AAAA,EAEA,MAAM,IAAI,SAA4B;AACpC,UAAM,cAAc,KAAK;AACzB,UAAM,EAAE,KAAK,IAAI;AAEjB,SAAK,KAAK,OAAO,mBAAmB,IAAI,QAAQ,yBAAyB;AACzE,WAAO,MAAM,mBAAmB,EAAE,MAAM,YAAyB,CAAC;AAElE,QAAI;AACF,UAAI,MAAM;AACR,cAAM,eAAe,WAAW,IAAI;AACpC,cAAM,WAAW,KAAK,QAAQ,aAAa,YAAY;AAEvD,YAAI,CAAE,MAAM,gBAAAA,QAAG,WAAW,QAAQ,GAAI;AACpC,eAAK,MAAM,UAAU,IAAI,aAAa;AACtC;AAAA,QACF;AAKA,cAAM,WAAW,yCAAyC,YAAY,IAAI,WAAW;AAAA,MACvF,OAAO;AAEL,cAAM,WAAW,yCAAyC,WAAW;AAAA,MACvE;AAEA,WAAK,KAAK,mCAAmC;AAC7C,YAAM,WAAW,eAAe,WAAW;AAE3C,WAAK,QAAQ,+BAA+B;AAAA,IAC9C,SAAS,GAAY;AACnB,UAAI,aAAa,OAAO;AACtB,aAAK,MAAM,6BAA6B,EAAE,OAAO,EAAE;AAAA,MACrD,OAAO;AACL,aAAK,MAAM,6BAA6B,OAAO,CAAC,CAAC,EAAE;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AACF;","names":["fs"]}
1
+ {"version":3,"sources":["../../../../src/commands/module/update.ts"],"sourcesContent":["import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\n\nexport default class ModuleUpdateCommand extends BaseCommand {\n static usage = 'module update [name]';\n static description = 'Update a specific module or all modules.';\n static requiresProject = true;\n\n static args: CommandDefinition = {\n args: [{ name: 'name', required: false, description: 'Name of the module to update' }],\n };\n\n async run(options: { name?: string }) {\n const projectRoot = this.projectRoot as string;\n const { name } = options;\n\n this.info(name ? `Updating module ${name}...` : 'Updating all modules...');\n logger.debug('Update context:', { name, projectRoot: projectRoot });\n\n try {\n if (name) {\n // Check locations\n const locations = [\n { type: 'backend', path: `apps/backend/modules/${name}` },\n { type: 'frontend', path: `apps/frontend/modules/${name}` },\n { type: 'legacy', path: `modules/${name}` },\n ];\n\n let targetLoc: { type: string; path: string } | null = null;\n\n for (const loc of locations) {\n const absPath = path.resolve(projectRoot, loc.path);\n if (await fs.pathExists(absPath)) {\n targetLoc = loc;\n break;\n }\n }\n\n if (!targetLoc) {\n this.error(`Module ${name} not found.`);\n return;\n }\n\n const relativePath = targetLoc.path;\n\n // Update specific module\n await runCommand(`git submodule update --remote --merge ${relativePath}`, projectRoot);\n } else {\n // Update all\n await runCommand('git submodule update --remote --merge', projectRoot);\n }\n\n this.info('Syncing workspace dependencies...');\n await runCommand('npm install', projectRoot);\n\n this.success('Modules updated successfully.');\n } catch (e: unknown) {\n if (e instanceof Error) {\n this.error(`Failed to update modules: ${e.message}`);\n } else {\n this.error(`Failed to update modules: ${String(e)}`);\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,sBAAe;AADf,SAAiC,aAAa,QAAQ,kBAAkB;AAExE,OAAO,UAAU;AAEjB,IAAqB,sBAArB,cAAiD,YAAY;AAAA,EAC3D,OAAO,QAAQ;AAAA,EACf,OAAO,cAAc;AAAA,EACrB,OAAO,kBAAkB;AAAA,EAEzB,OAAO,OAA0B;AAAA,IAC/B,MAAM,CAAC,EAAE,MAAM,QAAQ,UAAU,OAAO,aAAa,+BAA+B,CAAC;AAAA,EACvF;AAAA,EAEA,MAAM,IAAI,SAA4B;AACpC,UAAM,cAAc,KAAK;AACzB,UAAM,EAAE,KAAK,IAAI;AAEjB,SAAK,KAAK,OAAO,mBAAmB,IAAI,QAAQ,yBAAyB;AACzE,WAAO,MAAM,mBAAmB,EAAE,MAAM,YAAyB,CAAC;AAElE,QAAI;AACF,UAAI,MAAM;AAER,cAAM,YAAY;AAAA,UAChB,EAAE,MAAM,WAAW,MAAM,wBAAwB,IAAI,GAAG;AAAA,UACxD,EAAE,MAAM,YAAY,MAAM,yBAAyB,IAAI,GAAG;AAAA,UAC1D,EAAE,MAAM,UAAU,MAAM,WAAW,IAAI,GAAG;AAAA,QAC5C;AAEA,YAAI,YAAmD;AAEvD,mBAAW,OAAO,WAAW;AAC3B,gBAAM,UAAU,KAAK,QAAQ,aAAa,IAAI,IAAI;AAClD,cAAI,MAAM,gBAAAA,QAAG,WAAW,OAAO,GAAG;AAChC,wBAAY;AACZ;AAAA,UACF;AAAA,QACF;AAEA,YAAI,CAAC,WAAW;AACd,eAAK,MAAM,UAAU,IAAI,aAAa;AACtC;AAAA,QACF;AAEA,cAAM,eAAe,UAAU;AAG/B,cAAM,WAAW,yCAAyC,YAAY,IAAI,WAAW;AAAA,MACvF,OAAO;AAEL,cAAM,WAAW,yCAAyC,WAAW;AAAA,MACvE;AAEA,WAAK,KAAK,mCAAmC;AAC7C,YAAM,WAAW,eAAe,WAAW;AAE3C,WAAK,QAAQ,+BAA+B;AAAA,IAC9C,SAAS,GAAY;AACnB,UAAI,aAAa,OAAO;AACtB,aAAK,MAAM,6BAA6B,EAAE,OAAO,EAAE;AAAA,MACrD,OAAO;AACL,aAAK,MAAM,6BAA6B,OAAO,CAAC,CAAC,EAAE;AAAA,MACrD;AAAA,IACF;AAAA,EACF;AACF;","names":["fs"]}
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../chunk-LZ3YQWAR.js";
4
+ } from "../../chunk-OUGA4CB4.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
@@ -41,8 +41,25 @@ var RunCommand = class extends BaseCommand {
41
41
  let scriptName = script;
42
42
  if (script.includes(":")) {
43
43
  const [moduleName, name] = script.split(":");
44
- execPath = path.resolve(projectRoot, "modules", moduleName);
45
44
  scriptName = name;
45
+ const locations = [
46
+ { type: "backend", path: `apps/backend/modules/${moduleName}` },
47
+ { type: "frontend", path: `apps/frontend/modules/${moduleName}` },
48
+ { type: "legacy", path: `modules/${moduleName}` }
49
+ ];
50
+ let found = false;
51
+ for (const loc of locations) {
52
+ const absPath = path.resolve(projectRoot, loc.path);
53
+ if (await import_fs_extra.default.pathExists(absPath)) {
54
+ execPath = absPath;
55
+ found = true;
56
+ break;
57
+ }
58
+ }
59
+ if (!found) {
60
+ this.error(`Module ${moduleName} not found.`);
61
+ return;
62
+ }
46
63
  logger.debug(`Resolving module script: ${moduleName}:${scriptName} at ${execPath}`);
47
64
  } else {
48
65
  logger.debug(`Resolving core script: ${scriptName} at ${execPath}`);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/commands/run.ts"],"sourcesContent":["import { type CommandDefinition, BaseCommand, logger } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport { spawn } from 'child_process';\nimport process from 'node:process';\n\nexport default class RunCommand extends BaseCommand {\n static usage = 'run <script> [args...]';\n static description = 'Run a script inside the Nexical environment.';\n static requiresProject = true;\n\n static args: CommandDefinition = {\n args: [\n {\n name: 'script',\n required: true,\n description: 'The script to run (script-name OR module:script-name)',\n },\n { name: 'args...', required: false, description: 'Arguments for the script' },\n ],\n };\n\n async run(options: { script: string; args?: string[] }) {\n const projectRoot = this.projectRoot as string;\n const script = options.script;\n const scriptArgs = options.args || [];\n\n if (!script) {\n this.error('Please specify a script to run.');\n return;\n }\n\n logger.debug('Run command context:', { script, args: scriptArgs, projectRoot });\n\n let execPath = projectRoot;\n let scriptName = script;\n\n // Handle module:script syntax\n if (script.includes(':')) {\n const [moduleName, name] = script.split(':');\n execPath = path.resolve(projectRoot, 'modules', moduleName);\n scriptName = name;\n\n logger.debug(`Resolving module script: ${moduleName}:${scriptName} at ${execPath}`);\n } else {\n logger.debug(`Resolving core script: ${scriptName} at ${execPath}`);\n }\n\n // Validate script existence\n const pkgJsonPath = path.join(execPath, 'package.json');\n if (!(await fs.pathExists(pkgJsonPath))) {\n this.error(`Failed to find package.json at ${execPath}`);\n return;\n }\n\n try {\n const pkg = await fs.readJson(pkgJsonPath);\n if (!pkg.scripts || !pkg.scripts[scriptName]) {\n const type = script.includes(':') ? `module ${script.split(':')[0]}` : 'Nexical core';\n this.error(`Script \"${scriptName}\" does not exist in ${type}`);\n return;\n }\n } catch (e: unknown) {\n if (e instanceof Error) {\n this.error(`Failed to read package.json at ${execPath}: ${e.message}`);\n } else {\n this.error(`Failed to read package.json at ${execPath}: ${String(e)}`);\n }\n return;\n }\n\n const finalArgs = ['run', scriptName, '--', ...scriptArgs];\n logger.debug(`Executing: npm ${finalArgs.join(' ')} in ${execPath}`);\n\n const child = spawn('npm', finalArgs, {\n cwd: execPath,\n stdio: 'inherit',\n env: {\n ...process.env,\n FORCE_COLOR: '1',\n },\n });\n\n // Handle process termination to kill child\n const cleanup = () => {\n child.kill();\n process.exit();\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n\n await new Promise<void>((resolve) => {\n child.on('close', (code) => {\n // Remove listeners to prevent memory leaks if this command is run multiple times in-process (e.g. tests)\n process.off('SIGINT', cleanup);\n process.off('SIGTERM', cleanup);\n\n if (code !== 0) {\n process.exit(code || 1);\n }\n resolve();\n });\n });\n }\n}\n"],"mappings":";;;;;;;;;;AAAA;AACA,sBAAe;AADf,SAAiC,aAAa,cAAc;AAE5D,OAAO,UAAU;AACjB,SAAS,aAAa;AACtB,OAAO,aAAa;AAEpB,IAAqB,aAArB,cAAwC,YAAY;AAAA,EAClD,OAAO,QAAQ;AAAA,EACf,OAAO,cAAc;AAAA,EACrB,OAAO,kBAAkB;AAAA,EAEzB,OAAO,OAA0B;AAAA,IAC/B,MAAM;AAAA,MACJ;AAAA,QACE,MAAM;AAAA,QACN,UAAU;AAAA,QACV,aAAa;AAAA,MACf;AAAA,MACA,EAAE,MAAM,WAAW,UAAU,OAAO,aAAa,2BAA2B;AAAA,IAC9E;AAAA,EACF;AAAA,EAEA,MAAM,IAAI,SAA8C;AACtD,UAAM,cAAc,KAAK;AACzB,UAAM,SAAS,QAAQ;AACvB,UAAM,aAAa,QAAQ,QAAQ,CAAC;AAEpC,QAAI,CAAC,QAAQ;AACX,WAAK,MAAM,iCAAiC;AAC5C;AAAA,IACF;AAEA,WAAO,MAAM,wBAAwB,EAAE,QAAQ,MAAM,YAAY,YAAY,CAAC;AAE9E,QAAI,WAAW;AACf,QAAI,aAAa;AAGjB,QAAI,OAAO,SAAS,GAAG,GAAG;AACxB,YAAM,CAAC,YAAY,IAAI,IAAI,OAAO,MAAM,GAAG;AAC3C,iBAAW,KAAK,QAAQ,aAAa,WAAW,UAAU;AAC1D,mBAAa;AAEb,aAAO,MAAM,4BAA4B,UAAU,IAAI,UAAU,OAAO,QAAQ,EAAE;AAAA,IACpF,OAAO;AACL,aAAO,MAAM,0BAA0B,UAAU,OAAO,QAAQ,EAAE;AAAA,IACpE;AAGA,UAAM,cAAc,KAAK,KAAK,UAAU,cAAc;AACtD,QAAI,CAAE,MAAM,gBAAAA,QAAG,WAAW,WAAW,GAAI;AACvC,WAAK,MAAM,kCAAkC,QAAQ,EAAE;AACvD;AAAA,IACF;AAEA,QAAI;AACF,YAAM,MAAM,MAAM,gBAAAA,QAAG,SAAS,WAAW;AACzC,UAAI,CAAC,IAAI,WAAW,CAAC,IAAI,QAAQ,UAAU,GAAG;AAC5C,cAAM,OAAO,OAAO,SAAS,GAAG,IAAI,UAAU,OAAO,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK;AACvE,aAAK,MAAM,WAAW,UAAU,uBAAuB,IAAI,EAAE;AAC7D;AAAA,MACF;AAAA,IACF,SAAS,GAAY;AACnB,UAAI,aAAa,OAAO;AACtB,aAAK,MAAM,kCAAkC,QAAQ,KAAK,EAAE,OAAO,EAAE;AAAA,MACvE,OAAO;AACL,aAAK,MAAM,kCAAkC,QAAQ,KAAK,OAAO,CAAC,CAAC,EAAE;AAAA,MACvE;AACA;AAAA,IACF;AAEA,UAAM,YAAY,CAAC,OAAO,YAAY,MAAM,GAAG,UAAU;AACzD,WAAO,MAAM,kBAAkB,UAAU,KAAK,GAAG,CAAC,OAAO,QAAQ,EAAE;AAEnE,UAAM,QAAQ,MAAM,OAAO,WAAW;AAAA,MACpC,KAAK;AAAA,MACL,OAAO;AAAA,MACP,KAAK;AAAA,QACH,GAAG,QAAQ;AAAA,QACX,aAAa;AAAA,MACf;AAAA,IACF,CAAC;AAGD,UAAM,UAAU,MAAM;AACpB,YAAM,KAAK;AACX,cAAQ,KAAK;AAAA,IACf;AAEA,YAAQ,GAAG,UAAU,OAAO;AAC5B,YAAQ,GAAG,WAAW,OAAO;AAE7B,UAAM,IAAI,QAAc,CAAC,YAAY;AACnC,YAAM,GAAG,SAAS,CAAC,SAAS;AAE1B,gBAAQ,IAAI,UAAU,OAAO;AAC7B,gBAAQ,IAAI,WAAW,OAAO;AAE9B,YAAI,SAAS,GAAG;AACd,kBAAQ,KAAK,QAAQ,CAAC;AAAA,QACxB;AACA,gBAAQ;AAAA,MACV,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AACF;","names":["fs"]}
1
+ {"version":3,"sources":["../../../src/commands/run.ts"],"sourcesContent":["import { type CommandDefinition, BaseCommand, logger } from '@nexical/cli-core';\nimport fs from 'fs-extra';\nimport path from 'path';\nimport { spawn } from 'child_process';\nimport process from 'node:process';\n\nexport default class RunCommand extends BaseCommand {\n static usage = 'run <script> [args...]';\n static description = 'Run a script inside the Nexical environment.';\n static requiresProject = true;\n\n static args: CommandDefinition = {\n args: [\n {\n name: 'script',\n required: true,\n description: 'The script to run (script-name OR module:script-name)',\n },\n { name: 'args...', required: false, description: 'Arguments for the script' },\n ],\n };\n\n async run(options: { script: string; args?: string[] }) {\n const projectRoot = this.projectRoot as string;\n const script = options.script;\n const scriptArgs = options.args || [];\n\n if (!script) {\n this.error('Please specify a script to run.');\n return;\n }\n\n logger.debug('Run command context:', { script, args: scriptArgs, projectRoot });\n\n let execPath = projectRoot;\n let scriptName = script;\n\n // Handle module:script syntax\n if (script.includes(':')) {\n const [moduleName, name] = script.split(':');\n scriptName = name;\n\n const locations = [\n { type: 'backend', path: `apps/backend/modules/${moduleName}` },\n { type: 'frontend', path: `apps/frontend/modules/${moduleName}` },\n { type: 'legacy', path: `modules/${moduleName}` },\n ];\n\n let found = false;\n for (const loc of locations) {\n const absPath = path.resolve(projectRoot, loc.path);\n if (await fs.pathExists(absPath)) {\n execPath = absPath;\n found = true;\n break;\n }\n }\n\n if (!found) {\n this.error(`Module ${moduleName} not found.`);\n return;\n }\n\n logger.debug(`Resolving module script: ${moduleName}:${scriptName} at ${execPath}`);\n } else {\n logger.debug(`Resolving core script: ${scriptName} at ${execPath}`);\n }\n\n // Validate script existence\n const pkgJsonPath = path.join(execPath, 'package.json');\n if (!(await fs.pathExists(pkgJsonPath))) {\n this.error(`Failed to find package.json at ${execPath}`);\n return;\n }\n\n try {\n const pkg = await fs.readJson(pkgJsonPath);\n if (!pkg.scripts || !pkg.scripts[scriptName]) {\n const type = script.includes(':') ? `module ${script.split(':')[0]}` : 'Nexical core';\n this.error(`Script \"${scriptName}\" does not exist in ${type}`);\n return;\n }\n } catch (e: unknown) {\n if (e instanceof Error) {\n this.error(`Failed to read package.json at ${execPath}: ${e.message}`);\n } else {\n this.error(`Failed to read package.json at ${execPath}: ${String(e)}`);\n }\n return;\n }\n\n const finalArgs = ['run', scriptName, '--', ...scriptArgs];\n logger.debug(`Executing: npm ${finalArgs.join(' ')} in ${execPath}`);\n\n const child = spawn('npm', finalArgs, {\n cwd: execPath,\n stdio: 'inherit',\n env: {\n ...process.env,\n FORCE_COLOR: '1',\n },\n });\n\n // Handle process termination to kill child\n const cleanup = () => {\n child.kill();\n process.exit();\n };\n\n process.on('SIGINT', cleanup);\n process.on('SIGTERM', cleanup);\n\n await new Promise<void>((resolve) => {\n child.on('close', (code) => {\n // Remove listeners to prevent memory leaks if this command is run multiple times in-process (e.g. tests)\n process.off('SIGINT', cleanup);\n process.off('SIGTERM', cleanup);\n\n if (code !== 0) {\n process.exit(code || 1);\n }\n resolve();\n });\n });\n }\n}\n"],"mappings":"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","names":["fs"]}
@@ -1,7 +1,7 @@
1
1
  import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
2
  import {
3
3
  require_lib
4
- } from "../../chunk-LZ3YQWAR.js";
4
+ } from "../../chunk-OUGA4CB4.js";
5
5
  import {
6
6
  __toESM,
7
7
  init_esm_shims
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nexical/cli",
3
- "version": "0.11.7",
3
+ "version": "0.11.9",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "nexical": "./dist/index.js"
@@ -66,6 +66,7 @@ export default class ModuleAddCommand extends BaseCommand {
66
66
  `staging-${Date.now()}-${Math.random().toString(36).substring(7)}`,
67
67
  );
68
68
  let moduleName = '';
69
+ let moduleType: 'backend' | 'frontend' = 'backend'; // Default to backend if uncertain, but we should detect.
69
70
  let dependencies: string[] = [];
70
71
 
71
72
  try {
@@ -74,28 +75,68 @@ export default class ModuleAddCommand extends BaseCommand {
74
75
  // Shallow clone to inspect
75
76
  await clone(cleanUrl, stagingDir, { depth: 1 });
76
77
 
77
- // Read module.yaml
78
+ // Search path handling
78
79
  const searchPath = subPath ? path.join(stagingDir, subPath) : stagingDir;
80
+
81
+ // 1. Detect Module Name & Dependencies
79
82
  const moduleYamlPath = path.join(searchPath, 'module.yaml');
80
83
  const moduleYmlPath = path.join(searchPath, 'module.yml');
84
+ const pkgJsonPath = path.join(searchPath, 'package.json');
81
85
 
82
86
  let configPath = '';
83
87
  if (await fs.pathExists(moduleYamlPath)) configPath = moduleYamlPath;
84
88
  else if (await fs.pathExists(moduleYmlPath)) configPath = moduleYmlPath;
85
- else {
86
- throw new Error(`No module.yaml found in ${cleanUrl}${subPath ? '//' + subPath : ''}`);
89
+
90
+ // Try to get name from module.yaml/yml
91
+ if (configPath) {
92
+ const configContent = await fs.readFile(configPath, 'utf8');
93
+ const config = YAML.parse(configContent);
94
+ if (config.name) moduleName = config.name;
95
+ dependencies = config.dependencies || [];
87
96
  }
88
97
 
89
- const configContent = await fs.readFile(configPath, 'utf8');
90
- const config = YAML.parse(configContent);
98
+ // If no name yet, try package.json
99
+ if (!moduleName && (await fs.pathExists(pkgJsonPath))) {
100
+ try {
101
+ const pkg = await fs.readJson(pkgJsonPath);
102
+ if (pkg.name) {
103
+ // Handle scoped packages @modules/name -> name
104
+ moduleName = pkg.name.startsWith('@modules/') ? pkg.name.split('/')[1] : pkg.name;
105
+ }
106
+ } catch {
107
+ /* ignore */
108
+ }
109
+ }
91
110
 
92
- if (!config.name) {
93
- throw new Error(`Module at ${url} is missing 'name' in module.yaml`);
111
+ // Fallback to git repo name if still no name
112
+ if (!moduleName) {
113
+ moduleName = path.basename(cleanUrl, '.git');
94
114
  }
95
- moduleName = config.name;
96
- dependencies = config.dependencies || [];
97
115
 
98
- // Normalize dependencies to array if object (though spec says list of strings, defensiveness is good)
116
+ // 2. Detect Module Type
117
+ // Frontend indicators: ui.yaml, or specifically typed in module.config.mjs (harder to parse statically), or package.json dependencies like 'react'/'astro' (maybe too broad).
118
+ // Backend indicators: models.yaml, api.yaml, access.yaml.
119
+
120
+ const hasUiYaml = await fs.pathExists(path.join(searchPath, 'ui.yaml'));
121
+ const hasModelsYaml = await fs.pathExists(path.join(searchPath, 'models.yaml'));
122
+ const hasApiYaml = await fs.pathExists(path.join(searchPath, 'api.yaml'));
123
+
124
+ if (hasUiYaml) {
125
+ moduleType = 'frontend';
126
+ } else if (hasModelsYaml || hasApiYaml) {
127
+ moduleType = 'backend';
128
+ } else {
129
+ // Fallback: Check checking package.json for "auth-astro" which is common in both, but maybe "react" or "vue" for frontend?
130
+ // Let's assume Backend default if ambiguous for now, or check for specific folder structure?
131
+ // Let's look for `src/components` vs `src/services`.
132
+ if (await fs.pathExists(path.join(searchPath, 'src', 'components'))) {
133
+ moduleType = 'frontend';
134
+ } else {
135
+ moduleType = 'backend';
136
+ }
137
+ }
138
+
139
+ // Normalize dependencies
99
140
  if (dependencies && !Array.isArray(dependencies)) {
100
141
  dependencies = Object.keys(dependencies);
101
142
  }
@@ -104,39 +145,34 @@ export default class ModuleAddCommand extends BaseCommand {
104
145
  await fs.remove(stagingDir);
105
146
  }
106
147
 
107
- // Stage 2: Conflict Detection
108
- const targetDir = path.join(projectRoot!, 'modules', moduleName);
109
- const relativeTargetDir = path.relative(projectRoot!, targetDir);
148
+ // Stage 2: Conflict Detection & Path Resolution
149
+ const modulesBaseDir =
150
+ moduleType === 'frontend' ? 'apps/frontend/modules' : 'apps/backend/modules';
151
+ const relativeTargetDir = path.join(modulesBaseDir, moduleName);
152
+ const targetDir = path.join(projectRoot!, relativeTargetDir);
110
153
 
111
154
  if (await fs.pathExists(targetDir)) {
112
155
  // Check origin
113
156
  const existingRemote = await getRemoteUrl(targetDir);
114
- // We compare cleanUrl (the repo root).
115
- // normalize both
116
157
  const normExisting = existingRemote.replace(/\.git$/, '');
117
158
  const normNew = cleanUrl.replace(/\.git$/, '');
118
159
 
119
160
  if (normExisting !== normNew && existingRemote !== '') {
120
161
  throw new Error(
121
- `Dependency Conflict! Module '${moduleName}' exists but remote '${existingRemote}' does not match '${cleanUrl}'.`,
162
+ `Dependency Conflict! Module '${moduleName}' exists in ${moduleType} but remote '${existingRemote}' does not match '${cleanUrl}'.`,
122
163
  );
123
164
  }
124
165
 
125
- this.info(`Module ${moduleName} already installed.`);
126
- // Proceed to recurse, but skip add
166
+ this.info(`Module ${moduleName} already installed in ${moduleType}.`);
127
167
  } else {
128
168
  // Stage 3: Submodule Add
129
- this.info(`Installing ${moduleName} to ${relativeTargetDir}...`);
130
- // We install the ROOT repo.
131
- // IMPORTANT: If subPath exists, "Identity is Internal" means we name the folder `moduleName`.
132
- // But the CONTENT will be the whole repo.
133
- // If the user meant to only have the subdir, we can't do that with submodule add easily without manual git plumbing.
134
- // Given instructions, I will proceed with submodule add of root repo to target dir.
169
+ this.info(`Installing ${moduleName} (${moduleType}) to ${relativeTargetDir}...`);
170
+ await fs.ensureDir(path.dirname(targetDir)); // Ensure apps/backend/modules exists
135
171
  await runCommand(`git submodule add ${cleanUrl} ${relativeTargetDir}`, projectRoot!);
136
172
  }
137
173
 
138
174
  // Update nexical.yaml
139
- await this.addToConfig(moduleName);
175
+ await this.addToConfig(moduleName, moduleType);
140
176
 
141
177
  // Stage 4: Recurse
142
178
  if (dependencies.length > 0) {
@@ -147,12 +183,11 @@ export default class ModuleAddCommand extends BaseCommand {
147
183
  }
148
184
  }
149
185
 
150
- private async addToConfig(moduleName: string) {
186
+ private async addToConfig(moduleName: string, type: 'backend' | 'frontend') {
151
187
  const projectRoot = this.projectRoot as string;
152
188
  const configPath = path.join(projectRoot, 'nexical.yaml');
153
189
 
154
190
  if (!(await fs.pathExists(configPath))) {
155
- // Not strictly required to exist for all operations, but good to have if we are tracking modules.
156
191
  logger.warn('nexical.yaml not found, skipping module list update.');
157
192
  return;
158
193
  }
@@ -161,12 +196,20 @@ export default class ModuleAddCommand extends BaseCommand {
161
196
  const content = await fs.readFile(configPath, 'utf8');
162
197
  const config = YAML.parse(content) || {};
163
198
 
164
- if (!config.modules) config.modules = [];
199
+ if (!config.modules) config.modules = {};
200
+
201
+ // Migration: If modules is array, convert to object
202
+ if (Array.isArray(config.modules)) {
203
+ const oldModules = config.modules;
204
+ config.modules = { backend: oldModules, frontend: [] }; // Assume old were backend? Or just move them to backend for safety.
205
+ }
206
+
207
+ if (!config.modules[type]) config.modules[type] = [];
165
208
 
166
- if (!config.modules.includes(moduleName)) {
167
- config.modules.push(moduleName);
209
+ if (!config.modules[type].includes(moduleName)) {
210
+ config.modules[type].push(moduleName);
168
211
  await fs.writeFile(configPath, YAML.stringify(config));
169
- logger.debug(`Added ${moduleName} to nexical.yaml modules list.`);
212
+ logger.debug(`Added ${moduleName} to nexical.yaml modules.${type} list.`);
170
213
  }
171
214
  } catch (e: unknown) {
172
215
  if (e instanceof Error) {