@nexical/cli 0.1.7 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (236) hide show
  1. package/.github/workflows/deploy.yml +3 -3
  2. package/README.md +317 -104
  3. package/dist/chunk-JYASTIIW.js +42 -0
  4. package/dist/chunk-JYASTIIW.js.map +1 -0
  5. package/dist/chunk-LZ3YQWAR.js +2204 -0
  6. package/dist/chunk-LZ3YQWAR.js.map +1 -0
  7. package/dist/chunk-OKXOCNXP.js +105 -0
  8. package/dist/chunk-OKXOCNXP.js.map +1 -0
  9. package/dist/chunk-OYFWMYPG.js +52 -0
  10. package/dist/chunk-OYFWMYPG.js.map +1 -0
  11. package/dist/chunk-WKERTCM6.js +74 -0
  12. package/dist/chunk-WKERTCM6.js.map +1 -0
  13. package/dist/index.js +32 -5
  14. package/dist/index.js.map +1 -1
  15. package/dist/src/commands/init.d.ts +11 -0
  16. package/dist/src/commands/init.js +88 -0
  17. package/dist/src/commands/init.js.map +1 -0
  18. package/dist/src/commands/module/add.d.ts +14 -0
  19. package/dist/src/commands/module/add.js +136 -0
  20. package/dist/src/commands/module/add.js.map +1 -0
  21. package/dist/src/commands/module/list.d.ts +10 -0
  22. package/dist/src/commands/module/list.js +73 -0
  23. package/dist/src/commands/module/list.js.map +1 -0
  24. package/dist/src/commands/module/remove.d.ts +12 -0
  25. package/dist/src/commands/module/remove.js +71 -0
  26. package/dist/src/commands/module/remove.js.map +1 -0
  27. package/dist/src/commands/module/update.d.ts +11 -0
  28. package/dist/src/commands/module/update.js +52 -0
  29. package/dist/src/commands/module/update.js.map +1 -0
  30. package/dist/src/commands/run.d.ts +11 -0
  31. package/dist/src/commands/run.js +93 -0
  32. package/dist/src/commands/run.js.map +1 -0
  33. package/dist/src/utils/discovery.d.ts +13 -0
  34. package/dist/src/utils/discovery.js +9 -0
  35. package/dist/src/utils/git.d.ts +16 -0
  36. package/dist/src/utils/git.js +29 -0
  37. package/dist/src/utils/git.js.map +1 -0
  38. package/dist/src/utils/url-resolver.d.ts +15 -0
  39. package/dist/src/utils/url-resolver.js +9 -0
  40. package/dist/src/utils/url-resolver.js.map +1 -0
  41. package/index.ts +29 -5
  42. package/package.json +33 -31
  43. package/src/commands/init.ts +85 -0
  44. package/src/commands/module/add.ts +169 -0
  45. package/src/commands/module/list.ts +69 -0
  46. package/src/commands/module/remove.ts +74 -0
  47. package/src/commands/module/update.ts +50 -0
  48. package/src/commands/run.ts +98 -0
  49. package/src/utils/discovery.ts +134 -0
  50. package/src/utils/git.ts +65 -0
  51. package/src/utils/url-resolver.ts +57 -0
  52. package/test/e2e/lifecycle.e2e.test.ts +152 -0
  53. package/test/integration/commands/init.integration.test.ts +82 -0
  54. package/test/integration/commands/module.integration.test.ts +144 -0
  55. package/test/integration/commands/run.integration.test.ts +90 -0
  56. package/test/integration/utils/command-loading.integration.test.ts +80 -0
  57. package/test/unit/commands/init.test.ts +153 -0
  58. package/test/unit/commands/module/add.test.ts +262 -0
  59. package/test/unit/commands/module/list.test.ts +115 -0
  60. package/test/unit/commands/module/remove.test.ts +89 -0
  61. package/test/unit/commands/module/update.test.ts +91 -0
  62. package/test/unit/commands/run.test.ts +252 -0
  63. package/test/unit/utils/command-discovery.test.ts +176 -0
  64. package/test/unit/utils/git.test.ts +152 -0
  65. package/test/unit/utils/integration-helpers.test.ts +72 -0
  66. package/test/unit/utils/url-resolver.test.ts +39 -0
  67. package/test/utils/integration-helpers.ts +66 -0
  68. package/vitest.e2e.config.ts +0 -1
  69. package/dist/chunk-JDRAVUKK.js +0 -48
  70. package/dist/chunk-JDRAVUKK.js.map +0 -1
  71. package/dist/src/commands/admin/create-user.d.ts +0 -15
  72. package/dist/src/commands/admin/create-user.js +0 -49
  73. package/dist/src/commands/admin/create-user.js.map +0 -1
  74. package/dist/src/commands/branch/create.d.ts +0 -19
  75. package/dist/src/commands/branch/create.js +0 -59
  76. package/dist/src/commands/branch/create.js.map +0 -1
  77. package/dist/src/commands/branch/delete.d.ts +0 -15
  78. package/dist/src/commands/branch/delete.js +0 -50
  79. package/dist/src/commands/branch/delete.js.map +0 -1
  80. package/dist/src/commands/branch/get.d.ts +0 -15
  81. package/dist/src/commands/branch/get.js +0 -53
  82. package/dist/src/commands/branch/get.js.map +0 -1
  83. package/dist/src/commands/branch/list.d.ts +0 -15
  84. package/dist/src/commands/branch/list.js +0 -51
  85. package/dist/src/commands/branch/list.js.map +0 -1
  86. package/dist/src/commands/job/get.d.ts +0 -15
  87. package/dist/src/commands/job/get.js +0 -62
  88. package/dist/src/commands/job/get.js.map +0 -1
  89. package/dist/src/commands/job/list.d.ts +0 -15
  90. package/dist/src/commands/job/list.js +0 -57
  91. package/dist/src/commands/job/list.js.map +0 -1
  92. package/dist/src/commands/job/logs.d.ts +0 -15
  93. package/dist/src/commands/job/logs.js +0 -67
  94. package/dist/src/commands/job/logs.js.map +0 -1
  95. package/dist/src/commands/job/trigger.d.ts +0 -19
  96. package/dist/src/commands/job/trigger.js +0 -74
  97. package/dist/src/commands/job/trigger.js.map +0 -1
  98. package/dist/src/commands/login.d.ts +0 -8
  99. package/dist/src/commands/login.js +0 -31
  100. package/dist/src/commands/login.js.map +0 -1
  101. package/dist/src/commands/project/create.d.ts +0 -24
  102. package/dist/src/commands/project/create.js +0 -63
  103. package/dist/src/commands/project/create.js.map +0 -1
  104. package/dist/src/commands/project/delete.d.ts +0 -20
  105. package/dist/src/commands/project/delete.js +0 -58
  106. package/dist/src/commands/project/delete.js.map +0 -1
  107. package/dist/src/commands/project/get.d.ts +0 -15
  108. package/dist/src/commands/project/get.js +0 -49
  109. package/dist/src/commands/project/get.js.map +0 -1
  110. package/dist/src/commands/project/list.d.ts +0 -15
  111. package/dist/src/commands/project/list.js +0 -45
  112. package/dist/src/commands/project/list.js.map +0 -1
  113. package/dist/src/commands/project/update.d.ts +0 -19
  114. package/dist/src/commands/project/update.js +0 -66
  115. package/dist/src/commands/project/update.js.map +0 -1
  116. package/dist/src/commands/team/create.d.ts +0 -19
  117. package/dist/src/commands/team/create.js +0 -45
  118. package/dist/src/commands/team/create.js.map +0 -1
  119. package/dist/src/commands/team/delete.d.ts +0 -20
  120. package/dist/src/commands/team/delete.js +0 -52
  121. package/dist/src/commands/team/delete.js.map +0 -1
  122. package/dist/src/commands/team/get.d.ts +0 -15
  123. package/dist/src/commands/team/get.js +0 -42
  124. package/dist/src/commands/team/get.js.map +0 -1
  125. package/dist/src/commands/team/list.d.ts +0 -8
  126. package/dist/src/commands/team/list.js +0 -30
  127. package/dist/src/commands/team/list.js.map +0 -1
  128. package/dist/src/commands/team/member/invite.d.ts +0 -20
  129. package/dist/src/commands/team/member/invite.js +0 -54
  130. package/dist/src/commands/team/member/invite.js.map +0 -1
  131. package/dist/src/commands/team/member/remove.d.ts +0 -15
  132. package/dist/src/commands/team/member/remove.js +0 -43
  133. package/dist/src/commands/team/member/remove.js.map +0 -1
  134. package/dist/src/commands/team/update.d.ts +0 -19
  135. package/dist/src/commands/team/update.js +0 -55
  136. package/dist/src/commands/team/update.js.map +0 -1
  137. package/dist/src/commands/token/generate.d.ts +0 -19
  138. package/dist/src/commands/token/generate.js +0 -48
  139. package/dist/src/commands/token/generate.js.map +0 -1
  140. package/dist/src/commands/token/list.d.ts +0 -8
  141. package/dist/src/commands/token/list.js +0 -31
  142. package/dist/src/commands/token/list.js.map +0 -1
  143. package/dist/src/commands/token/revoke.d.ts +0 -15
  144. package/dist/src/commands/token/revoke.js +0 -38
  145. package/dist/src/commands/token/revoke.js.map +0 -1
  146. package/dist/src/commands/whoami.d.ts +0 -8
  147. package/dist/src/commands/whoami.js +0 -26
  148. package/dist/src/commands/whoami.js.map +0 -1
  149. package/dist/src/utils/nexical-client.d.ts +0 -10
  150. package/dist/src/utils/nexical-client.js +0 -12
  151. package/src/commands/admin/create-user.ts +0 -46
  152. package/src/commands/branch/create.ts +0 -57
  153. package/src/commands/branch/delete.ts +0 -47
  154. package/src/commands/branch/get.ts +0 -50
  155. package/src/commands/branch/list.ts +0 -50
  156. package/src/commands/job/get.ts +0 -59
  157. package/src/commands/job/list.ts +0 -56
  158. package/src/commands/job/logs.ts +0 -67
  159. package/src/commands/job/trigger.ts +0 -73
  160. package/src/commands/login.ts +0 -31
  161. package/src/commands/project/create.ts +0 -61
  162. package/src/commands/project/delete.ts +0 -56
  163. package/src/commands/project/get.ts +0 -46
  164. package/src/commands/project/list.ts +0 -44
  165. package/src/commands/project/update.ts +0 -63
  166. package/src/commands/team/create.ts +0 -43
  167. package/src/commands/team/delete.ts +0 -50
  168. package/src/commands/team/get.ts +0 -39
  169. package/src/commands/team/list.ts +0 -26
  170. package/src/commands/team/member/invite.ts +0 -56
  171. package/src/commands/team/member/remove.ts +0 -40
  172. package/src/commands/team/update.ts +0 -53
  173. package/src/commands/token/generate.ts +0 -45
  174. package/src/commands/token/list.ts +0 -27
  175. package/src/commands/token/revoke.ts +0 -35
  176. package/src/commands/whoami.ts +0 -21
  177. package/src/utils/nexical-client.ts +0 -47
  178. package/test/e2e/auth.e2e.test.ts +0 -46
  179. package/test/e2e/job-workflow.e2e.test.ts +0 -33
  180. package/test/e2e/project-lifecycle.e2e.test.ts +0 -48
  181. package/test/e2e/setup.ts +0 -237
  182. package/test/e2e/utils.ts +0 -33
  183. package/test/integration/commands/admin/create-user.test.ts +0 -51
  184. package/test/integration/commands/branch/create.test.ts +0 -51
  185. package/test/integration/commands/branch/delete.test.ts +0 -43
  186. package/test/integration/commands/branch/get.test.ts +0 -49
  187. package/test/integration/commands/branch/list.test.ts +0 -47
  188. package/test/integration/commands/job/get.test.ts +0 -54
  189. package/test/integration/commands/job/list.test.ts +0 -47
  190. package/test/integration/commands/job/logs.test.ts +0 -47
  191. package/test/integration/commands/job/trigger.test.ts +0 -57
  192. package/test/integration/commands/login.test.ts +0 -62
  193. package/test/integration/commands/project/create.test.ts +0 -53
  194. package/test/integration/commands/project/delete.test.ts +0 -43
  195. package/test/integration/commands/project/get.test.ts +0 -51
  196. package/test/integration/commands/project/list.test.ts +0 -47
  197. package/test/integration/commands/project/update.test.ts +0 -53
  198. package/test/integration/commands/team/create.test.ts +0 -53
  199. package/test/integration/commands/team/delete.test.ts +0 -43
  200. package/test/integration/commands/team/get.test.ts +0 -50
  201. package/test/integration/commands/team/list.test.ts +0 -47
  202. package/test/integration/commands/team/member/invite.test.ts +0 -46
  203. package/test/integration/commands/team/member/remove.test.ts +0 -43
  204. package/test/integration/commands/team/update.test.ts +0 -50
  205. package/test/integration/commands/token/generate.test.ts +0 -51
  206. package/test/integration/commands/token/list.test.ts +0 -47
  207. package/test/integration/commands/token/revoke.test.ts +0 -43
  208. package/test/integration/commands/whoami.test.ts +0 -49
  209. package/test/unit/commands/admin/create-user.test.ts +0 -51
  210. package/test/unit/commands/branch/create.test.ts +0 -57
  211. package/test/unit/commands/branch/delete.test.ts +0 -49
  212. package/test/unit/commands/branch/get.test.ts +0 -67
  213. package/test/unit/commands/branch/list.test.ts +0 -62
  214. package/test/unit/commands/job/get.test.ts +0 -76
  215. package/test/unit/commands/job/list.test.ts +0 -62
  216. package/test/unit/commands/job/logs.test.ts +0 -60
  217. package/test/unit/commands/job/trigger.test.ts +0 -75
  218. package/test/unit/commands/login.test.ts +0 -64
  219. package/test/unit/commands/project/create.test.ts +0 -64
  220. package/test/unit/commands/project/delete.test.ts +0 -72
  221. package/test/unit/commands/project/get.test.ts +0 -73
  222. package/test/unit/commands/project/list.test.ts +0 -62
  223. package/test/unit/commands/project/update.test.ts +0 -58
  224. package/test/unit/commands/team/create.test.ts +0 -68
  225. package/test/unit/commands/team/delete.test.ts +0 -71
  226. package/test/unit/commands/team/get.test.ts +0 -70
  227. package/test/unit/commands/team/list.test.ts +0 -56
  228. package/test/unit/commands/team/member/invite.test.ts +0 -52
  229. package/test/unit/commands/team/member/remove.test.ts +0 -49
  230. package/test/unit/commands/team/update.test.ts +0 -63
  231. package/test/unit/commands/token/generate.test.ts +0 -65
  232. package/test/unit/commands/token/list.test.ts +0 -58
  233. package/test/unit/commands/token/revoke.test.ts +0 -49
  234. package/test/unit/commands/whoami.test.ts +0 -49
  235. package/test/unit/utils/nexical-client.test.ts +0 -113
  236. /package/dist/src/utils/{nexical-client.js.map → discovery.js.map} +0 -0
@@ -0,0 +1,11 @@
1
+ import { BaseCommand, CommandDefinition } from '@nexical/cli-core';
2
+
3
+ declare class RunCommand extends BaseCommand {
4
+ static usage: string;
5
+ static description: string;
6
+ static requiresProject: boolean;
7
+ static args: CommandDefinition;
8
+ run(options: any): Promise<void>;
9
+ }
10
+
11
+ export { RunCommand as default };
@@ -0,0 +1,93 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ require_lib
4
+ } from "../../chunk-LZ3YQWAR.js";
5
+ import {
6
+ __toESM,
7
+ init_esm_shims
8
+ } from "../../chunk-OYFWMYPG.js";
9
+
10
+ // src/commands/run.ts
11
+ init_esm_shims();
12
+ var import_fs_extra = __toESM(require_lib(), 1);
13
+ import { BaseCommand, logger } from "@nexical/cli-core";
14
+ import path from "path";
15
+ import { spawn } from "child_process";
16
+ import process from "process";
17
+ var RunCommand = class extends BaseCommand {
18
+ static usage = "run <script> [args...]";
19
+ static description = "Run a script inside the Nexical environment.";
20
+ static requiresProject = true;
21
+ static args = {
22
+ args: [
23
+ { name: "script", required: true, description: "The script to run (script-name OR module:script-name)" },
24
+ { name: "args...", required: false, description: "Arguments for the script" }
25
+ ]
26
+ };
27
+ async run(options) {
28
+ const projectRoot = this.projectRoot;
29
+ const script = options.script;
30
+ const scriptArgs = options.args || [];
31
+ if (!script) {
32
+ this.error("Please specify a script to run.");
33
+ return;
34
+ }
35
+ logger.debug("Run command context:", { script, args: scriptArgs, projectRoot });
36
+ let execPath = projectRoot;
37
+ let scriptName = script;
38
+ if (script.includes(":")) {
39
+ const [moduleName, name] = script.split(":");
40
+ execPath = path.resolve(projectRoot, "modules", moduleName);
41
+ scriptName = name;
42
+ logger.debug(`Resolving module script: ${moduleName}:${scriptName} at ${execPath}`);
43
+ } else {
44
+ logger.debug(`Resolving core script: ${scriptName} at ${execPath}`);
45
+ }
46
+ const pkgJsonPath = path.join(execPath, "package.json");
47
+ if (!await import_fs_extra.default.pathExists(pkgJsonPath)) {
48
+ this.error(`Failed to find package.json at ${execPath}`);
49
+ return;
50
+ }
51
+ try {
52
+ const pkg = await import_fs_extra.default.readJson(pkgJsonPath);
53
+ if (!pkg.scripts || !pkg.scripts[scriptName]) {
54
+ const type = script.includes(":") ? `module ${script.split(":")[0]}` : "Nexical core";
55
+ this.error(`Script "${scriptName}" does not exist in ${type}`);
56
+ return;
57
+ }
58
+ } catch (e) {
59
+ this.error(`Failed to read package.json at ${execPath}: ${e.message}`);
60
+ return;
61
+ }
62
+ const finalArgs = ["run", scriptName, "--", ...scriptArgs];
63
+ logger.debug(`Executing: npm ${finalArgs.join(" ")} in ${execPath}`);
64
+ const child = spawn("npm", finalArgs, {
65
+ cwd: execPath,
66
+ stdio: "inherit",
67
+ env: {
68
+ ...process.env,
69
+ FORCE_COLOR: "1"
70
+ }
71
+ });
72
+ const cleanup = () => {
73
+ child.kill();
74
+ process.exit();
75
+ };
76
+ process.on("SIGINT", cleanup);
77
+ process.on("SIGTERM", cleanup);
78
+ await new Promise((resolve) => {
79
+ child.on("close", (code) => {
80
+ process.off("SIGINT", cleanup);
81
+ process.off("SIGTERM", cleanup);
82
+ if (code !== 0) {
83
+ process.exit(code || 1);
84
+ }
85
+ resolve();
86
+ });
87
+ });
88
+ }
89
+ };
90
+ export {
91
+ RunCommand as default
92
+ };
93
+ //# sourceMappingURL=run.js.map
@@ -0,0 +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 { name: 'script', required: true, description: 'The script to run (script-name OR module:script-name)' },\n { name: 'args...', required: false, description: 'Arguments for the script' }\n ]\n };\n\n async run(options: any) {\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: any) {\n this.error(`Failed to read package.json at ${execPath}: ${e.message}`);\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,EAChD,OAAO,QAAQ;AAAA,EACf,OAAO,cAAc;AAAA,EACrB,OAAO,kBAAkB;AAAA,EAEzB,OAAO,OAA0B;AAAA,IAC7B,MAAM;AAAA,MACF,EAAE,MAAM,UAAU,UAAU,MAAM,aAAa,wDAAwD;AAAA,MACvG,EAAE,MAAM,WAAW,UAAU,OAAO,aAAa,2BAA2B;AAAA,IAChF;AAAA,EACJ;AAAA,EAEA,MAAM,IAAI,SAAc;AACpB,UAAM,cAAc,KAAK;AACzB,UAAM,SAAS,QAAQ;AACvB,UAAM,aAAa,QAAQ,QAAQ,CAAC;AAEpC,QAAI,CAAC,QAAQ;AACT,WAAK,MAAM,iCAAiC;AAC5C;AAAA,IACJ;AAEA,WAAO,MAAM,wBAAwB,EAAE,QAAQ,MAAM,YAAY,YAAY,CAAC;AAE9E,QAAI,WAAW;AACf,QAAI,aAAa;AAGjB,QAAI,OAAO,SAAS,GAAG,GAAG;AACtB,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,IACtF,OAAO;AACH,aAAO,MAAM,0BAA0B,UAAU,OAAO,QAAQ,EAAE;AAAA,IACtE;AAGA,UAAM,cAAc,KAAK,KAAK,UAAU,cAAc;AACtD,QAAI,CAAE,MAAM,gBAAAA,QAAG,WAAW,WAAW,GAAI;AACrC,WAAK,MAAM,kCAAkC,QAAQ,EAAE;AACvD;AAAA,IACJ;AAEA,QAAI;AACA,YAAM,MAAM,MAAM,gBAAAA,QAAG,SAAS,WAAW;AACzC,UAAI,CAAC,IAAI,WAAW,CAAC,IAAI,QAAQ,UAAU,GAAG;AAC1C,cAAM,OAAO,OAAO,SAAS,GAAG,IAAI,UAAU,OAAO,MAAM,GAAG,EAAE,CAAC,CAAC,KAAK;AACvE,aAAK,MAAM,WAAW,UAAU,uBAAuB,IAAI,EAAE;AAC7D;AAAA,MACJ;AAAA,IACJ,SAAS,GAAQ;AACb,WAAK,MAAM,kCAAkC,QAAQ,KAAK,EAAE,OAAO,EAAE;AACrE;AAAA,IACJ;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,MAClC,KAAK;AAAA,MACL,OAAO;AAAA,MACP,KAAK;AAAA,QACD,GAAG,QAAQ;AAAA,QACX,aAAa;AAAA,MACjB;AAAA,IACJ,CAAC;AAGD,UAAM,UAAU,MAAM;AAClB,YAAM,KAAK;AACX,cAAQ,KAAK;AAAA,IACjB;AAEA,YAAQ,GAAG,UAAU,OAAO;AAC5B,YAAQ,GAAG,WAAW,OAAO;AAE7B,UAAM,IAAI,QAAc,CAAC,YAAY;AACjC,YAAM,GAAG,SAAS,CAAC,SAAS;AAExB,gBAAQ,IAAI,UAAU,OAAO;AAC7B,gBAAQ,IAAI,WAAW,OAAO;AAE9B,YAAI,SAAS,GAAG;AACZ,kBAAQ,KAAK,QAAQ,CAAC;AAAA,QAC1B;AACA,gBAAQ;AAAA,MACZ,CAAC;AAAA,IACL,CAAC;AAAA,EACL;AACJ;","names":["fs"]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Discovers command directories to load into the CLI.
3
+ *
4
+ * Scans for:
5
+ * 1. Core commands (projectRoot/src/commands)
6
+ * 2. Module commands (projectRoot/src/modules/ * /src/commands)
7
+ *
8
+ * @param projectRoot - The root directory of the project
9
+ * @returns Array of absolute paths to command directories
10
+ */
11
+ declare function discoverCommandDirectories(projectRoot: string): string[];
12
+
13
+ export { discoverCommandDirectories };
@@ -0,0 +1,9 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ discoverCommandDirectories
4
+ } from "../../chunk-OKXOCNXP.js";
5
+ import "../../chunk-OYFWMYPG.js";
6
+ export {
7
+ discoverCommandDirectories
8
+ };
9
+ //# sourceMappingURL=discovery.js.map
@@ -0,0 +1,16 @@
1
+ declare function clone(url: string, destination: string, options?: {
2
+ recursive?: boolean;
3
+ depth?: number;
4
+ }): Promise<void>;
5
+ declare function getRemoteUrl(cwd: string, remote?: string): Promise<string>;
6
+ declare function updateSubmodules(cwd: string): Promise<void>;
7
+ declare function checkoutOrphan(branch: string, cwd: string): Promise<void>;
8
+ declare function addAll(cwd: string): Promise<void>;
9
+ declare function commit(message: string, cwd: string): Promise<void>;
10
+ declare function deleteBranch(branch: string, cwd: string): Promise<void>;
11
+ declare function renameBranch(branch: string, cwd: string): Promise<void>;
12
+ declare function removeRemote(remote: string, cwd: string): Promise<void>;
13
+ declare function renameRemote(oldName: string, newName: string, cwd: string): Promise<void>;
14
+ declare function branchExists(branch: string, cwd: string): Promise<boolean>;
15
+
16
+ export { addAll, branchExists, checkoutOrphan, clone, commit, deleteBranch, getRemoteUrl, removeRemote, renameBranch, renameRemote, updateSubmodules };
@@ -0,0 +1,29 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ addAll,
4
+ branchExists,
5
+ checkoutOrphan,
6
+ clone,
7
+ commit,
8
+ deleteBranch,
9
+ getRemoteUrl,
10
+ removeRemote,
11
+ renameBranch,
12
+ renameRemote,
13
+ updateSubmodules
14
+ } from "../../chunk-WKERTCM6.js";
15
+ import "../../chunk-OYFWMYPG.js";
16
+ export {
17
+ addAll,
18
+ branchExists,
19
+ checkoutOrphan,
20
+ clone,
21
+ commit,
22
+ deleteBranch,
23
+ getRemoteUrl,
24
+ removeRemote,
25
+ renameBranch,
26
+ renameRemote,
27
+ updateSubmodules
28
+ };
29
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Resolves a git URL from various shorthand formats.
3
+ *
4
+ * Supported formats:
5
+ * - gh@org/repo -> https://github.com/org/repo.git
6
+ * - gh@org/repo//path -> https://github.com/org/repo.git//path
7
+ * - https://github.com/org/repo -> https://github.com/org/repo.git
8
+ * - https://github.com/org/repo.git -> https://github.com/org/repo.git
9
+ *
10
+ * @param url The URL string to resolve
11
+ * @returns The fully qualified git URL with .git extension
12
+ */
13
+ declare function resolveGitUrl(url: string): string;
14
+
15
+ export { resolveGitUrl };
@@ -0,0 +1,9 @@
1
+ import { createRequire } from "module"; const require = createRequire(import.meta.url);
2
+ import {
3
+ resolveGitUrl
4
+ } from "../../chunk-JYASTIIW.js";
5
+ import "../../chunk-OYFWMYPG.js";
6
+ export {
7
+ resolveGitUrl
8
+ };
9
+ //# sourceMappingURL=url-resolver.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
package/index.ts CHANGED
@@ -1,14 +1,38 @@
1
1
  #!/usr/bin/env node
2
- import { CLI } from "@nexical/cli-core";
3
- import { fileURLToPath } from "node:url";
2
+ import { CLI, findProjectRoot } from '@nexical/cli-core';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { discoverCommandDirectories } from './src/utils/discovery.js';
4
5
  import pkg from './package.json';
5
6
  import path from 'node:path';
6
7
 
7
8
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
9
 
10
+ const commandName = 'nexical';
11
+ const projectRoot = await findProjectRoot(commandName, process.cwd()) || process.cwd();
12
+ const coreCommandsDir = path.resolve(__dirname, './src/commands');
13
+ const additionalCommands = discoverCommandDirectories(projectRoot);
14
+
15
+ // Filter out the source version of core commands if we are running from dist
16
+ const filteredAdditional = additionalCommands.filter(dir => {
17
+ if (dir === coreCommandsDir) return false;
18
+
19
+ // Handle the case where we are running from dist/ and it finds src/commands in projectRoot
20
+ if (coreCommandsDir.includes(path.join(path.sep, 'dist', 'src', 'commands'))) {
21
+ const srcVersion = coreCommandsDir.replace(
22
+ path.join(path.sep, 'dist', 'src', 'commands'),
23
+ path.join(path.sep, 'src', 'commands')
24
+ );
25
+ if (dir === srcVersion) return false;
26
+ }
27
+ return true;
28
+ });
29
+
9
30
  const app = new CLI({
10
- version: pkg.version,
11
- commandName: "nexical",
12
- searchDirectories: [path.resolve(__dirname, "./src/commands")],
31
+ version: pkg.version,
32
+ commandName: commandName,
33
+ searchDirectories: [...new Set([
34
+ coreCommandsDir,
35
+ ...filteredAdditional
36
+ ])]
13
37
  });
14
38
  app.start();
package/package.json CHANGED
@@ -1,32 +1,34 @@
1
1
  {
2
- "name": "@nexical/cli",
3
- "version": "0.1.7",
4
- "type": "module",
5
- "bin": {
6
- "nexical": "./dist/index.js"
7
- },
8
- "scripts": {
9
- "build": "tsup",
10
- "dev": "tsup --watch",
11
- "start": "node dist/index.js",
12
- "test": "npm run test:unit && npm run test:integration && npm run test:e2e",
13
- "test:unit": "vitest run --config vitest.config.ts --coverage",
14
- "test:integration": "vitest run --config vitest.integration.config.ts",
15
- "test:e2e": "npm run build && vitest run --config vitest.e2e.config.ts",
16
- "test:watch": "vitest"
17
- },
18
- "dependencies": {
19
- "@nexical/cli-core": "^0.1.12",
20
- "@nexical/sdk": "^0.4.2"
21
- },
22
- "devDependencies": {
23
- "@types/fs-extra": "^11.0.4",
24
- "@types/node": "^20.10.0",
25
- "@vitest/coverage-v8": "^4.0.15",
26
- "execa": "^9.6.1",
27
- "fs-extra": "^11.3.2",
28
- "tsup": "^8.0.1",
29
- "typescript": "^5.3.3",
30
- "vitest": "^4.0.15"
31
- }
32
- }
2
+ "name": "@nexical/cli",
3
+ "version": "0.10.0",
4
+ "type": "module",
5
+ "bin": {
6
+ "nexical": "./dist/index.js"
7
+ },
8
+ "scripts": {
9
+ "build": "tsup",
10
+ "dev": "tsup --watch",
11
+ "start": "node dist/index.js",
12
+ "test": "npm run test:unit && npm run test:integration && npm run test:e2e",
13
+ "test:unit": "vitest run --config vitest.config.ts --coverage",
14
+ "test:integration": "vitest run --config vitest.integration.config.ts",
15
+ "test:e2e": "npm run build && vitest run --config vitest.e2e.config.ts",
16
+ "test:watch": "vitest"
17
+ },
18
+ "dependencies": {
19
+ "@nexical/cli-core": "^0.1.12",
20
+ "yaml": "^2.3.4",
21
+ "fast-glob": "^3.3.3"
22
+ },
23
+ "devDependencies": {
24
+ "@types/fs-extra": "^11.0.4",
25
+ "@types/node": "^20.10.0",
26
+ "@vitest/coverage-v8": "^4.0.15",
27
+ "execa": "^9.6.1",
28
+ "fs-extra": "^11.3.2",
29
+ "tsup": "^8.0.1",
30
+ "tsx": "^4.21.0",
31
+ "typescript": "^5.3.3",
32
+ "vitest": "^4.0.15"
33
+ }
34
+ }
@@ -0,0 +1,85 @@
1
+ import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';
2
+ import * as git from '../utils/git.js';
3
+ import { resolveGitUrl } from '../utils/url-resolver.js';
4
+ import fs from 'fs-extra';
5
+ import path from 'path';
6
+
7
+ export default class InitCommand extends BaseCommand {
8
+ static usage = 'init';
9
+ static description = 'Initialize a new Nexical project.';
10
+ static requiresProject = false;
11
+
12
+ static args: CommandDefinition = {
13
+ args: [
14
+ { name: 'directory', required: true, description: 'Directory to initialize the project in' }
15
+ ],
16
+ options: [
17
+ {
18
+ name: '--repo <url>',
19
+ description: 'Starter repository URL (supports gh@owner/repo syntax)',
20
+ default: 'gh@nexical/app-core'
21
+ }
22
+ ]
23
+ };
24
+
25
+ async run(options: any) {
26
+ const directory = options.directory;
27
+ const targetPath = path.resolve(process.cwd(), directory);
28
+ let repoUrl = resolveGitUrl(options.repo);
29
+
30
+ logger.debug('Init options:', { directory, targetPath, repoUrl });
31
+
32
+ this.info(`Initializing project in: ${targetPath}`);
33
+ this.info(`Using starter repository: ${repoUrl}`);
34
+
35
+ if (await fs.pathExists(targetPath)) {
36
+ if ((await fs.readdir(targetPath)).length > 0) {
37
+ this.error(`Directory ${directory} is not empty.`);
38
+ process.exit(1);
39
+ }
40
+ } else {
41
+ await fs.mkdir(targetPath, { recursive: true });
42
+ }
43
+
44
+ try {
45
+ this.info('Cloning core repository...');
46
+ await git.clone(repoUrl, targetPath, { recursive: true });
47
+
48
+ this.info('Updating submodules...');
49
+ await git.updateSubmodules(targetPath);
50
+
51
+ this.info('Installing dependencies...');
52
+ await runCommand('npm install', targetPath);
53
+
54
+ this.info('Setting up upstream remote...');
55
+ await git.renameRemote('origin', 'upstream', targetPath);
56
+
57
+ // Ensure module directory
58
+ await fs.ensureDir(path.join(targetPath, 'modules'));
59
+
60
+ // Check for nexical.yaml, if not present create a default one
61
+ const configPath = path.join(targetPath, 'nexical.yaml');
62
+ if (!await fs.pathExists(configPath)) {
63
+ this.info('Creating default nexical.yaml...');
64
+ await fs.writeFile(configPath, 'name: ' + path.basename(targetPath) + '\nmodules: []\n');
65
+ }
66
+
67
+ // Create VERSION file
68
+ const versionPath = path.join(targetPath, 'VERSION');
69
+ // Check if version file exists, if not create it
70
+ if (!await fs.pathExists(versionPath)) {
71
+ this.info('Creating VERSION file with 0.1.0...');
72
+ await fs.writeFile(versionPath, '0.1.0');
73
+ }
74
+
75
+ await git.addAll(targetPath);
76
+ await git.commit('Initial site commit', targetPath);
77
+
78
+ this.success(`Project initialized successfully in ${directory}!`);
79
+
80
+ } catch (error: any) {
81
+ this.error(`Failed to initialize project: ${error.message}`);
82
+ process.exit(1);
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,169 @@
1
+ import { type CommandDefinition, BaseCommand, logger, runCommand } from '@nexical/cli-core';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import { clone, getRemoteUrl } from '../../utils/git.js';
5
+ import { resolveGitUrl } from '../../utils/url-resolver.js';
6
+ import YAML from 'yaml';
7
+
8
+ export default class ModuleAddCommand extends BaseCommand {
9
+ static usage = 'module add <url>';
10
+ static description = 'Add a module and its dependencies as git submodules.';
11
+ static requiresProject = true;
12
+
13
+ static args: CommandDefinition = {
14
+ args: [
15
+ { name: 'url', required: true, description: 'Git repository URL or gh@org/repo' }
16
+ ]
17
+ };
18
+
19
+ private visited = new Set<string>();
20
+
21
+ async run(options: any) {
22
+ const projectRoot = this.projectRoot as string;
23
+ let { url } = options;
24
+
25
+ if (!url) {
26
+ this.error('Please specify a repository URL.');
27
+ return;
28
+ }
29
+
30
+ try {
31
+ await this.installModule(url);
32
+
33
+ this.info('Syncing workspace dependencies...');
34
+ await runCommand('npm install', projectRoot);
35
+
36
+ this.success('All modules installed successfully.');
37
+ } catch (e: any) {
38
+ this.error(`Failed to add module: ${e.message}`);
39
+ }
40
+ }
41
+
42
+ private async installModule(url: string) {
43
+ const projectRoot = this.projectRoot as string;
44
+
45
+ // Resolve URL using utility
46
+ url = resolveGitUrl(url);
47
+
48
+ const [repoUrl, subPath] = url.split('.git//');
49
+ const cleanUrl = subPath ? repoUrl + '.git' : url;
50
+
51
+ if (this.visited.has(cleanUrl)) {
52
+ logger.debug(`Already visited ${cleanUrl}, skipping.`);
53
+ return;
54
+ }
55
+ this.visited.add(cleanUrl);
56
+
57
+ this.info(`Inspecting ${cleanUrl}...`);
58
+
59
+ // Stage 1: Inspect (Temp Clone)
60
+ const stagingDir = path.resolve(projectRoot!, '.nexical', 'cache', `staging-${Date.now()}-${Math.random().toString(36).substring(7)}`);
61
+ let moduleName = '';
62
+ let dependencies: string[] = [];
63
+
64
+ try {
65
+ await fs.ensureDir(stagingDir);
66
+
67
+ // Shallow clone to inspect
68
+ await clone(cleanUrl, stagingDir, { depth: 1 });
69
+
70
+ // Read module.yaml
71
+ const searchPath = subPath ? path.join(stagingDir, subPath) : stagingDir;
72
+ const moduleYamlPath = path.join(searchPath, 'module.yaml');
73
+ const moduleYmlPath = path.join(searchPath, 'module.yml');
74
+
75
+ let configPath = '';
76
+ if (await fs.pathExists(moduleYamlPath)) configPath = moduleYamlPath;
77
+ else if (await fs.pathExists(moduleYmlPath)) configPath = moduleYmlPath;
78
+ else {
79
+ throw new Error(`No module.yaml found in ${cleanUrl}${subPath ? '//' + subPath : ''}`);
80
+ }
81
+
82
+ const configContent = await fs.readFile(configPath, 'utf8');
83
+ const config = YAML.parse(configContent);
84
+
85
+ if (!config.name) {
86
+ throw new Error(`Module at ${url} is missing 'name' in module.yaml`);
87
+ }
88
+ moduleName = config.name;
89
+ dependencies = config.dependencies || [];
90
+
91
+ // Normalize dependencies to array if object (though spec says list of strings, defensiveness is good)
92
+ if (dependencies && !Array.isArray(dependencies)) {
93
+ dependencies = Object.keys(dependencies);
94
+ }
95
+
96
+ } catch (e: any) { // Catching as 'any' for error message access
97
+ throw e;
98
+ } finally {
99
+ // Cleanup staging always
100
+ await fs.remove(stagingDir);
101
+ }
102
+
103
+ // Stage 2: Conflict Detection
104
+ const targetDir = path.join(projectRoot!, 'modules', moduleName);
105
+ const relativeTargetDir = path.relative(projectRoot!, targetDir);
106
+
107
+ if (await fs.pathExists(targetDir)) {
108
+ // Check origin
109
+ const existingRemote = await getRemoteUrl(targetDir);
110
+ // We compare cleanUrl (the repo root).
111
+ // normalize both
112
+ const normExisting = existingRemote.replace(/\.git$/, '');
113
+ const normNew = cleanUrl.replace(/\.git$/, '');
114
+
115
+ if (normExisting !== normNew && existingRemote !== '') {
116
+ throw new Error(`Dependency Conflict! Module '${moduleName}' exists but remote '${existingRemote}' does not match '${cleanUrl}'.`);
117
+ }
118
+
119
+ this.info(`Module ${moduleName} already installed.`);
120
+ // Proceed to recurse, but skip add
121
+ } else {
122
+ // Stage 3: Submodule Add
123
+ this.info(`Installing ${moduleName} to ${relativeTargetDir}...`);
124
+ // We install the ROOT repo.
125
+ // IMPORTANT: If subPath exists, "Identity is Internal" means we name the folder `moduleName`.
126
+ // But the CONTENT will be the whole repo.
127
+ // If the user meant to only have the subdir, we can't do that with submodule add easily without manual git plumbing.
128
+ // Given instructions, I will proceed with submodule add of root repo to target dir.
129
+ await runCommand(`git submodule add ${cleanUrl} ${relativeTargetDir}`, projectRoot!);
130
+ }
131
+
132
+ // Update nexical.yaml
133
+ await this.addToConfig(moduleName);
134
+
135
+ // Stage 4: Recurse
136
+ if (dependencies.length > 0) {
137
+ this.info(`Resolving ${dependencies.length} dependencies for ${moduleName}...`);
138
+ for (const depUrl of dependencies) {
139
+ await this.installModule(depUrl);
140
+ }
141
+ }
142
+ }
143
+
144
+ private async addToConfig(moduleName: string) {
145
+ const projectRoot = this.projectRoot as string;
146
+ const configPath = path.join(projectRoot, 'nexical.yaml');
147
+
148
+ if (!await fs.pathExists(configPath)) {
149
+ // Not strictly required to exist for all operations, but good to have if we are tracking modules.
150
+ logger.warn('nexical.yaml not found, skipping module list update.');
151
+ return;
152
+ }
153
+
154
+ try {
155
+ const content = await fs.readFile(configPath, 'utf8');
156
+ let config = YAML.parse(content) || {};
157
+
158
+ if (!config.modules) config.modules = [];
159
+
160
+ if (!config.modules.includes(moduleName)) {
161
+ config.modules.push(moduleName);
162
+ await fs.writeFile(configPath, YAML.stringify(config));
163
+ logger.debug(`Added ${moduleName} to nexical.yaml modules list.`);
164
+ }
165
+ } catch (e: any) {
166
+ logger.warn(`Failed to update nexical.yaml: ${e.message}`);
167
+ }
168
+ }
169
+ }
@@ -0,0 +1,69 @@
1
+ import { BaseCommand, logger } from '@nexical/cli-core';
2
+ import fs from 'fs-extra';
3
+ import path from 'path';
4
+ import YAML from 'yaml';
5
+
6
+ export default class ModuleListCommand extends BaseCommand {
7
+ static usage = 'module list';
8
+ static description = 'List installed modules.';
9
+ static requiresProject = true;
10
+
11
+ async run() {
12
+ const projectRoot = this.projectRoot as string;
13
+ const modulesDir = path.resolve(projectRoot, 'modules');
14
+ logger.debug(`Scanning for modules in: ${modulesDir}`);
15
+
16
+ if (!(await fs.pathExists(modulesDir))) {
17
+ this.info('No modules installed (modules directory missing).');
18
+ return;
19
+ }
20
+
21
+ try {
22
+ const modules = await fs.readdir(modulesDir);
23
+ const validModules: { name: string; version: string; description: string }[] = [];
24
+
25
+ for (const moduleName of modules) {
26
+ const modulePath = path.join(modulesDir, moduleName);
27
+ if ((await fs.stat(modulePath)).isDirectory()) {
28
+ let version = 'unknown';
29
+ let description = '';
30
+
31
+ const pkgJsonPath = path.join(modulePath, 'package.json');
32
+ const moduleYamlPath = path.join(modulePath, 'module.yaml');
33
+ const moduleYmlPath = path.join(modulePath, 'module.yml');
34
+
35
+ let pkg: any = {};
36
+ let modConfig: any = {};
37
+
38
+ if (await fs.pathExists(pkgJsonPath)) {
39
+ try {
40
+ pkg = await fs.readJson(pkgJsonPath);
41
+ } catch (e) { /* ignore */ }
42
+ }
43
+
44
+ if (await fs.pathExists(moduleYamlPath) || await fs.pathExists(moduleYmlPath)) {
45
+ try {
46
+ const configPath = await fs.pathExists(moduleYamlPath) ? moduleYamlPath : moduleYmlPath;
47
+ const content = await fs.readFile(configPath, 'utf8');
48
+ modConfig = YAML.parse(content) || {};
49
+ } catch (e) { /* ignore */ }
50
+ }
51
+
52
+ version = pkg.version || 'unknown';
53
+ description = modConfig.description || pkg.description || '';
54
+ // Optionally use display name from module.yaml if present, but strictly list is usually dir name.
55
+ // Let's stick to dir name for "name" column, but description from module.yaml is good.
56
+ validModules.push({ name: moduleName, version, description });
57
+ }
58
+ }
59
+
60
+ if (validModules.length === 0) {
61
+ this.info('No modules installed.');
62
+ } else {
63
+ console.table(validModules);
64
+ }
65
+ } catch (error: any) {
66
+ this.error(`Failed to list modules: ${error.message}`);
67
+ }
68
+ }
69
+ }