@xeno-js/cli 0.1.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 (172) hide show
  1. package/.github/.copilot-instruction.md +73 -0
  2. package/.github/agents/git-operator.agent.md +86 -0
  3. package/.github/agents/unit-tester.agent.md +68 -0
  4. package/.husky/commit-msg +1 -0
  5. package/.husky/pre-commit +1 -0
  6. package/.husky/pre-push +1 -0
  7. package/.prettierignore +7 -0
  8. package/.prettierrc.json +37 -0
  9. package/LICENSE +15 -0
  10. package/README.md +236 -0
  11. package/commitlint.config.cjs +39 -0
  12. package/dist/domain/contracts/icommand-cli.contracts.js +2 -0
  13. package/dist/domain/contracts/icommand-runner.contracts.js +2 -0
  14. package/dist/domain/contracts/idispatch.contracts.js +2 -0
  15. package/dist/domain/contracts/ifile-service.contracts.js +2 -0
  16. package/dist/domain/contracts/igenerator.contracts.js +2 -0
  17. package/dist/domain/contracts/index.js +2 -0
  18. package/dist/domain/contracts/iscaffold-strategy.contracts.js +2 -0
  19. package/dist/domain/index.js +2 -0
  20. package/dist/generator.contracts.js +13 -0
  21. package/dist/index.js +31 -0
  22. package/dist/infrastructure/bootstrap/bootstrapper.js +32 -0
  23. package/dist/infrastructure/bootstrap/index.js +17 -0
  24. package/dist/infrastructure/builder/builder.js +16 -0
  25. package/dist/infrastructure/builder/builder.test.js +30 -0
  26. package/dist/infrastructure/builder/index.js +17 -0
  27. package/dist/infrastructure/command_launcher/command.utils.js +30 -0
  28. package/dist/infrastructure/command_launcher/index.js +2 -0
  29. package/dist/infrastructure/commands/command-runner.service.js +36 -0
  30. package/dist/infrastructure/commands/generate.command.js +48 -0
  31. package/dist/infrastructure/commands/help.command.js +44 -0
  32. package/dist/infrastructure/commands/index.js +20 -0
  33. package/dist/infrastructure/commands/new-project.command.js +43 -0
  34. package/dist/infrastructure/dispatcher/dispatcher.js +39 -0
  35. package/dist/infrastructure/dispatcher/index.js +17 -0
  36. package/dist/infrastructure/file/file.service.js +13 -0
  37. package/dist/infrastructure/file/index.js +17 -0
  38. package/dist/infrastructure/generators/bootstrap.generator.js +118 -0
  39. package/dist/infrastructure/generators/core/bootstrap.generator.js +118 -0
  40. package/dist/infrastructure/generators/core/command/cqrs.generator.js +49 -0
  41. package/dist/infrastructure/generators/core/command/generate-command.generator.js +163 -0
  42. package/dist/infrastructure/generators/core/command/generate-query.generator.js +131 -0
  43. package/dist/infrastructure/generators/core/drizzle-sql-lite.generator.js +78 -0
  44. package/dist/infrastructure/generators/core/drizzle.generator.js +85 -0
  45. package/dist/infrastructure/generators/core/env.generator.js +69 -0
  46. package/dist/infrastructure/generators/core/index.js +25 -0
  47. package/dist/infrastructure/generators/core/main.generator.js +45 -0
  48. package/dist/infrastructure/generators/core/packagejson.generator.js +78 -0
  49. package/dist/infrastructure/generators/core/readme.generator.js +431 -0
  50. package/dist/infrastructure/generators/core/registry.generator.js +46 -0
  51. package/dist/infrastructure/generators/core/tsconfig.generator.js +41 -0
  52. package/dist/infrastructure/generators/core-packagejson.generator.js +78 -0
  53. package/dist/infrastructure/generators/drizzle-sql-lite.generator.js +78 -0
  54. package/dist/infrastructure/generators/drizzle.generator.js +85 -0
  55. package/dist/infrastructure/generators/env-vue.generator.js +40 -0
  56. package/dist/infrastructure/generators/env.generator.js +71 -0
  57. package/dist/infrastructure/generators/gitignore.generator.js +62 -0
  58. package/dist/infrastructure/generators/index.js +26 -0
  59. package/dist/infrastructure/generators/main.generator.js +45 -0
  60. package/dist/infrastructure/generators/readme.generator.js +431 -0
  61. package/dist/infrastructure/generators/registry.generator.js +46 -0
  62. package/dist/infrastructure/generators/tsconfig-vue.generator.js +39 -0
  63. package/dist/infrastructure/generators/tsconfig.generator.js +41 -0
  64. package/dist/infrastructure/generators/vite-config.generator.js +39 -0
  65. package/dist/infrastructure/generators/vue/app-vue.generator.js +26 -0
  66. package/dist/infrastructure/generators/vue/bootstrap-ts.generator.js +59 -0
  67. package/dist/infrastructure/generators/vue/command_query/cqrs.generator.js +36 -0
  68. package/dist/infrastructure/generators/vue/command_query/generate-command-vue.generator.js +139 -0
  69. package/dist/infrastructure/generators/vue/command_query/generate-query-vue.generator.js +147 -0
  70. package/dist/infrastructure/generators/vue/env.generator.js +40 -0
  71. package/dist/infrastructure/generators/vue/index-html.generator.js +29 -0
  72. package/dist/infrastructure/generators/vue/index.js +28 -0
  73. package/dist/infrastructure/generators/vue/main.generator.js +45 -0
  74. package/dist/infrastructure/generators/vue/packagejson.generator.js +59 -0
  75. package/dist/infrastructure/generators/vue/readme.generator.js +394 -0
  76. package/dist/infrastructure/generators/vue/registry-ts.generator.js +28 -0
  77. package/dist/infrastructure/generators/vue/router.generator.js +33 -0
  78. package/dist/infrastructure/generators/vue/tailwind.generator.js +19 -0
  79. package/dist/infrastructure/generators/vue/tsconfig.generator.js +39 -0
  80. package/dist/infrastructure/generators/vue/use-app.generator.js +43 -0
  81. package/dist/infrastructure/generators/vue/vite-config.generator.js +39 -0
  82. package/dist/infrastructure/generators/vue-packagejson.generator.js +59 -0
  83. package/dist/infrastructure/generators/vue-readme.generator.js +53 -0
  84. package/dist/infrastructure/generators/vue-source-files.generator.js +136 -0
  85. package/dist/infrastructure/index.js +21 -0
  86. package/dist/infrastructure/strategies/core-scaffold.strategy.js +99 -0
  87. package/dist/infrastructure/strategies/index.js +18 -0
  88. package/dist/infrastructure/strategies/vue-scaffold.strategy.js +79 -0
  89. package/dist/scaffolding.engine.js +60 -0
  90. package/dist/shared/constants/command.constants.js +19 -0
  91. package/dist/shared/constants/core.constants.js +10 -0
  92. package/dist/shared/constants/index.js +18 -0
  93. package/dist/shared/index.js +18 -0
  94. package/dist/shared/types/common.types.js +2 -0
  95. package/dist/shared/types/index.js +2 -0
  96. package/dist/shared/utils/guards.utils.js +310 -0
  97. package/dist/shared/utils/index.js +18 -0
  98. package/dist/shared/utils/string.utils.js +62 -0
  99. package/eslint.config.mjs +305 -0
  100. package/lint-staged.config.mjs +5 -0
  101. package/logo/logo.png +0 -0
  102. package/package.json +65 -0
  103. package/src/domain/contracts/icommand-cli.contracts.ts +13 -0
  104. package/src/domain/contracts/icommand-runner.contracts.ts +3 -0
  105. package/src/domain/contracts/idispatch.contracts.ts +3 -0
  106. package/src/domain/contracts/ifile-service.contracts.ts +3 -0
  107. package/src/domain/contracts/igenerator.contracts.ts +5 -0
  108. package/src/domain/contracts/index.ts +6 -0
  109. package/src/domain/contracts/iscaffold-strategy.contracts.ts +7 -0
  110. package/src/domain/index.ts +1 -0
  111. package/src/index.ts +30 -0
  112. package/src/infrastructure/bootstrap/bootstrapper.ts +30 -0
  113. package/src/infrastructure/bootstrap/index.ts +1 -0
  114. package/src/infrastructure/builder/builder.test.ts +36 -0
  115. package/src/infrastructure/builder/builder.ts +15 -0
  116. package/src/infrastructure/builder/index.ts +1 -0
  117. package/src/infrastructure/command_launcher/command.utils.ts +31 -0
  118. package/src/infrastructure/command_launcher/index.ts +0 -0
  119. package/src/infrastructure/commands/command-runner.service.ts +36 -0
  120. package/src/infrastructure/commands/generate.command.ts +55 -0
  121. package/src/infrastructure/commands/help.command.ts +63 -0
  122. package/src/infrastructure/commands/index.ts +4 -0
  123. package/src/infrastructure/commands/new-project.command.ts +47 -0
  124. package/src/infrastructure/dispatcher/dispatcher.ts +45 -0
  125. package/src/infrastructure/dispatcher/index.ts +1 -0
  126. package/src/infrastructure/file/file.service.ts +14 -0
  127. package/src/infrastructure/file/index.ts +1 -0
  128. package/src/infrastructure/generators/core/bootstrap.generator.ts +118 -0
  129. package/src/infrastructure/generators/core/command/cqrs.generator.ts +63 -0
  130. package/src/infrastructure/generators/core/command/generate-command.generator.ts +191 -0
  131. package/src/infrastructure/generators/core/command/generate-query.generator.ts +150 -0
  132. package/src/infrastructure/generators/core/drizzle-sql-lite.generator.ts +76 -0
  133. package/src/infrastructure/generators/core/drizzle.generator.ts +86 -0
  134. package/src/infrastructure/generators/core/env.generator.ts +69 -0
  135. package/src/infrastructure/generators/core/index.ts +9 -0
  136. package/src/infrastructure/generators/core/main.generator.ts +41 -0
  137. package/src/infrastructure/generators/core/packagejson.generator.ts +70 -0
  138. package/src/infrastructure/generators/core/readme.generator.ts +427 -0
  139. package/src/infrastructure/generators/core/registry.generator.ts +40 -0
  140. package/src/infrastructure/generators/core/tsconfig.generator.ts +39 -0
  141. package/src/infrastructure/generators/gitignore.generator.ts +60 -0
  142. package/src/infrastructure/generators/vue/app-vue.generator.ts +21 -0
  143. package/src/infrastructure/generators/vue/bootstrap-ts.generator.ts +60 -0
  144. package/src/infrastructure/generators/vue/command_query/cqrs.generator.ts +36 -0
  145. package/src/infrastructure/generators/vue/command_query/generate-command-vue.generator.ts +153 -0
  146. package/src/infrastructure/generators/vue/command_query/generate-query-vue.generator.ts +161 -0
  147. package/src/infrastructure/generators/vue/env.generator.ts +39 -0
  148. package/src/infrastructure/generators/vue/index-html.generator.ts +24 -0
  149. package/src/infrastructure/generators/vue/index.ts +12 -0
  150. package/src/infrastructure/generators/vue/main.generator.ts +41 -0
  151. package/src/infrastructure/generators/vue/packagejson.generator.ts +50 -0
  152. package/src/infrastructure/generators/vue/readme.generator.ts +389 -0
  153. package/src/infrastructure/generators/vue/registry-ts.generator.ts +26 -0
  154. package/src/infrastructure/generators/vue/router.generator.ts +35 -0
  155. package/src/infrastructure/generators/vue/tailwind.generator.ts +17 -0
  156. package/src/infrastructure/generators/vue/tsconfig.generator.ts +38 -0
  157. package/src/infrastructure/generators/vue/use-app.generator.ts +38 -0
  158. package/src/infrastructure/generators/vue/vite-config.generator.ts +35 -0
  159. package/src/infrastructure/index.ts +5 -0
  160. package/src/infrastructure/strategies/core-scaffold.strategy.ts +100 -0
  161. package/src/infrastructure/strategies/index.ts +2 -0
  162. package/src/infrastructure/strategies/vue-scaffold.strategy.ts +79 -0
  163. package/src/shared/constants/command.constants.ts +18 -0
  164. package/src/shared/constants/core.constants.ts +7 -0
  165. package/src/shared/constants/index.ts +2 -0
  166. package/src/shared/index.ts +3 -0
  167. package/src/shared/types/common.types.ts +68 -0
  168. package/src/shared/types/index.ts +1 -0
  169. package/src/shared/utils/guards.utils.ts +334 -0
  170. package/src/shared/utils/index.ts +2 -0
  171. package/src/shared/utils/string.utils.ts +60 -0
  172. package/tsconfig.json +15 -0
@@ -0,0 +1,5 @@
1
+ export default {
2
+ 'src/**/*.ts': ['eslint --config eslint.config.mjs --fix --max-warnings=0', 'prettier --write'],
3
+ '*.{json,md,yml,yaml,css,scss,html}': ['prettier --write'],
4
+ '*.{js,mjs,cjs}': ['prettier --write'],
5
+ }
package/logo/logo.png ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@xeno-js/cli",
3
+ "version": "0.1.0",
4
+ "description": "Xeno CLI — Command Line Interface for Xeno projects",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/xeno-js/xeno-cli.git"
8
+ },
9
+ "bugs": {
10
+ "url": "https://github.com/xeno-js/xeno-cli/issues"
11
+ },
12
+ "homepage": "https://github.com/xeno-js/xeno-cli#readme",
13
+ "bin": {
14
+ "xeno-js": "./dist/index.js"
15
+ },
16
+ "scripts": {
17
+ "build": "tsc -p tsconfig.json",
18
+ "dev": "tsx src/index.ts",
19
+ "typecheck": "tsc -p tsconfig.json --noEmit",
20
+ "lint": "eslint --config eslint.config.mjs \"src/**/*.ts\" --max-warnings=0",
21
+ "lint:fix": "eslint --config eslint.config.mjs \"src/**/*.ts\" --fix --max-warnings=0",
22
+ "lint-staged": "lint-staged",
23
+ "commitlint": "commitlint",
24
+ "format": "prettier --write .",
25
+ "format:check": "prettier --check .",
26
+ "test": "vitest run src",
27
+ "test:watch": "vitest",
28
+ "test:coverage": "vitest run --coverage",
29
+ "check": "npm run typecheck && npm run lint:fix && npm run test",
30
+ "verify:commit": "npm run lint-staged && npm run test",
31
+ "verify:push": "npm run check",
32
+ "prepare": "husky",
33
+ "prepublishOnly": "npm run check && npm run build",
34
+ "release": "npm run prepublishOnly && npm publish"
35
+ },
36
+ "keywords": [],
37
+ "author": "Xeno",
38
+ "license": "ISC",
39
+ "engines": {
40
+ "node": ">=20.0.0"
41
+ },
42
+ "type": "commonjs",
43
+ "dependencies": {
44
+ "picocolors": "^1.1.1",
45
+ "prompts": "^2.4.2"
46
+ },
47
+ "devDependencies": {
48
+ "@commitlint/cli": "^21.0.1",
49
+ "@commitlint/config-conventional": "^21.0.1",
50
+ "@eslint/js": "^10.0.1",
51
+ "@types/node": "^26.6.1",
52
+ "@types/prompts": "^2.4.9",
53
+ "@vitest/coverage-v8": "^4.1.7",
54
+ "eslint": "^10.4.0",
55
+ "eslint-config-prettier": "^10.1.8",
56
+ "eslint-plugin-simple-import-sort": "^13.0.0",
57
+ "husky": "^9.1.7",
58
+ "lint-staged": "^17.0.5",
59
+ "prettier": "^3.8.3",
60
+ "tsx": "^4.22.4",
61
+ "typescript": "^6.0.3",
62
+ "typescript-eslint": "^8.60.0",
63
+ "vitest": "^4.1.7"
64
+ }
65
+ }
@@ -0,0 +1,13 @@
1
+ export interface ICliCommand {
2
+ /** Il nome principale del comando (es. 'new', 'generate') */
3
+ get name(): string
4
+
5
+ /** Eventuali alias (es. 'g' per 'generate') */
6
+ get aliases(): string[]
7
+
8
+ /**
9
+ * Il metodo esecutivo.
10
+ * Riceve solo gli argomenti di pertinenza (escludendo il nome del comando stesso).
11
+ */
12
+ execute(args: string[]): Promise<void>
13
+ }
@@ -0,0 +1,3 @@
1
+ export interface ICommandRunner {
2
+ run(command: string, args: string[], cwd: string): Promise<void>
3
+ }
@@ -0,0 +1,3 @@
1
+ export interface IDispatcher {
2
+ dispatch(rawArgs: string[]): Promise<void>
3
+ }
@@ -0,0 +1,3 @@
1
+ export interface IFileService {
2
+ writeFileRecursive(filePath: string, content: string): Promise<void>
3
+ }
@@ -0,0 +1,5 @@
1
+ import { type BaseOptions, type ScaffoldingOptions } from '../../shared'
2
+
3
+ export interface IGenerator<T extends BaseOptions = ScaffoldingOptions> {
4
+ generate(projectPath: string, options: T): Promise<void>
5
+ }
@@ -0,0 +1,6 @@
1
+ export type * from './icommand-cli.contracts'
2
+ export type * from './icommand-runner.contracts'
3
+ export type * from './idispatch.contracts'
4
+ export type * from './ifile-service.contracts'
5
+ export type * from './igenerator.contracts'
6
+ export type * from './iscaffold-strategy.contracts'
@@ -0,0 +1,7 @@
1
+ import { type BaseOptions, type ScaffoldingOptions } from '../../shared'
2
+ import { type IGenerator } from './igenerator.contracts'
3
+
4
+ export interface IScaffoldStrategy<T extends BaseOptions> {
5
+ promptOptions(targetDir: string): Promise<ScaffoldingOptions>
6
+ getGenerators(options: ScaffoldingOptions): Promise<IGenerator<T>[]>
7
+ }
@@ -0,0 +1 @@
1
+ export type * from './contracts'
package/src/index.ts ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import pc from 'picocolors'
3
+
4
+ import { Builder } from './infrastructure/builder'
5
+
6
+ /**
7
+ * @function init
8
+ * @description Initializes the scaffolding process for a new Xeno project.
9
+ *
10
+ * @author Xeno
11
+ * @version 1.0.0
12
+ * @license ISC
13
+ * @since 2025-09-30
14
+ * @link https://github.com/xeno-js/xeno-js
15
+ */
16
+ async function init() {
17
+ try {
18
+ const dispatcher = Builder.getDispatcher()
19
+
20
+ const rawArgs = process.argv.slice(2)
21
+
22
+ await dispatcher.dispatch(rawArgs)
23
+ } catch (error) {
24
+ console.error(pc.red('\n❌ Execution interrupted:'))
25
+ console.error(error instanceof Error ? error.message : error)
26
+ process.exit(1)
27
+ }
28
+ }
29
+
30
+ init().catch(console.error)
@@ -0,0 +1,30 @@
1
+ // src/core/bootstrapper.ts
2
+ import { mkdir } from 'node:fs/promises'
3
+ import { resolve } from 'node:path'
4
+
5
+ import pc from 'picocolors'
6
+
7
+ import type { IGenerator } from '../../domain'
8
+ import { type BaseOptions, type ScaffoldingOptions } from '../../shared'
9
+
10
+ export class Bootstrapper<T extends BaseOptions = ScaffoldingOptions> {
11
+ constructor(private readonly _generators: IGenerator<T>[]) {}
12
+
13
+ public async run(projectPath: string, options: T): Promise<void> {
14
+ console.info(pc.cyan('\n🚀 Initializing scaffolding...'))
15
+
16
+ const absolutePath = resolve(process.cwd(), projectPath)
17
+ await mkdir(absolutePath, { recursive: true })
18
+
19
+ for (const generator of this._generators) {
20
+ try {
21
+ await generator.generate(absolutePath, options)
22
+ } catch (error) {
23
+ console.error(pc.red(`\n❌ Error during execution of ${generator.constructor.name}`))
24
+ throw error
25
+ }
26
+ }
27
+
28
+ console.info(pc.green('\n✨ Scaffolding completed succesfully!'))
29
+ }
30
+ }
@@ -0,0 +1 @@
1
+ export * from './bootstrapper'
@@ -0,0 +1,36 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+
3
+ import { GenerateCommandQuery, HelpCommand, NewProjectCommand } from '../commands'
4
+ import { CliDispatcher } from '../dispatcher'
5
+ import { Builder } from './builder'
6
+
7
+ describe('Builder', () => {
8
+ it('should create a dispatcher with the CLI commands registered', () => {
9
+ const dispatcher = Builder.getDispatcher()
10
+
11
+ expect(dispatcher).toBeInstanceOf(CliDispatcher)
12
+ })
13
+
14
+ it('should dispatch the registered commands through the built dispatcher', async () => {
15
+ const dispatcher = Builder.getDispatcher()
16
+
17
+ const newSpy = vi.spyOn(NewProjectCommand.prototype, 'execute').mockResolvedValue()
18
+ const generateSpy = vi.spyOn(GenerateCommandQuery.prototype, 'execute').mockResolvedValue()
19
+ const helpSpy = vi.spyOn(HelpCommand.prototype, 'execute').mockResolvedValue()
20
+
21
+ await dispatcher.dispatch(['new', 'demo-app'])
22
+ await dispatcher.dispatch(['generate', 'command', 'User'])
23
+ await dispatcher.dispatch(['--help'])
24
+ await dispatcher.dispatch(['-h'])
25
+
26
+ expect(newSpy).toHaveBeenCalledWith(['demo-app'])
27
+ expect(generateSpy).toHaveBeenCalledWith(['command', 'User'])
28
+ expect(helpSpy).toHaveBeenCalledTimes(2)
29
+ expect(helpSpy).toHaveBeenNthCalledWith(1, [])
30
+ expect(helpSpy).toHaveBeenNthCalledWith(2, [])
31
+
32
+ newSpy.mockRestore()
33
+ generateSpy.mockRestore()
34
+ helpSpy.mockRestore()
35
+ })
36
+ })
@@ -0,0 +1,15 @@
1
+ import { type IDispatcher } from '../../domain'
2
+ import { GenerateCommandQuery, HelpCommand, NewProjectCommand } from '../commands'
3
+ import { CliDispatcher } from '../dispatcher'
4
+
5
+ export class Builder {
6
+ public static getDispatcher(): IDispatcher {
7
+ const dispatcher = new CliDispatcher([
8
+ new NewProjectCommand(),
9
+ new GenerateCommandQuery(),
10
+ new HelpCommand(),
11
+ ])
12
+
13
+ return dispatcher
14
+ }
15
+ }
@@ -0,0 +1 @@
1
+ export * from './builder'
@@ -0,0 +1,31 @@
1
+ // import { spawn } from 'node:child_process';
2
+
3
+ // /**
4
+ // * @class CommandUtils
5
+ // * @description Utility class for executing shell commands in a cross-platform manner.
6
+ // *
7
+ // * @author Xeno
8
+ // * @version 1.0.0
9
+ // * @license ISC
10
+ // * @since 2025-09-30
11
+ // * @link https://github.com/xeno-js/xeno-js
12
+ // */
13
+ // export const CommandLauncher = {
14
+ // runCommand: (command: string, args: string[], cwd: string): Promise<void> => {
15
+ // return new Promise((resolve, reject) => {
16
+ // const isWin = process.platform === "win32";
17
+ // const cmd = isWin ? `${command}.cmd` : command;
18
+
19
+ // const child = spawn(cmd, args, {
20
+ // cwd,
21
+ // stdio: 'inherit',
22
+ // shell: true
23
+ // });
24
+
25
+ // child.on('close', (code) => {
26
+ // if (code === 0) resolve();
27
+ // else reject(new Error(`Command ${command} failed with code ${code}`));
28
+ // });
29
+ // });
30
+ // }
31
+ // };
File without changes
@@ -0,0 +1,36 @@
1
+ import { spawn } from 'node:child_process'
2
+
3
+ import { type ICommandRunner } from '../../domain'
4
+
5
+ /**
6
+ * @class CommandUtils
7
+ * @description Utility class for executing shell commands in a cross-platform manner.
8
+ *
9
+ * @author Xeno
10
+ * @version 1.0.0
11
+ * @license ISC
12
+ * @since 2025-09-30
13
+ * @link https://github.com/xeno-js/xeno-js
14
+ */
15
+ export class CommandRunner implements ICommandRunner {
16
+ public async run(command: string, args: string[], cwd: string, isWin = false): Promise<void> {
17
+ return new Promise((resolve, reject) => {
18
+ const cmd = isWin ? `${command}.cmd` : command
19
+
20
+ const child = spawn(cmd, args, {
21
+ cwd,
22
+ stdio: 'inherit',
23
+ shell: false,
24
+ })
25
+
26
+ child.on('error', (error) => {
27
+ reject(new Error(`Error during execution of the command ${command}: ${error.message}`))
28
+ })
29
+
30
+ child.on('close', (code) => {
31
+ if (code === 0) resolve()
32
+ else reject(new Error(`Command ${command} failed with code ${code}`))
33
+ })
34
+ })
35
+ }
36
+ }
@@ -0,0 +1,55 @@
1
+ import pc from 'picocolors'
2
+
3
+ import type { ICliCommand } from '../../domain'
4
+ import { Guards } from '../../shared'
5
+ import { COMMAND_CONSTANTS } from '../../shared/constants/command.constants'
6
+ import { FileUtils } from '../file'
7
+
8
+ export class GenerateCommandQuery implements ICliCommand {
9
+ public readonly name: string = 'generate'
10
+ public readonly aliases: string[] = ['g']
11
+
12
+ public async execute(args: string[]): Promise<void> {
13
+ const type = args[0] // "command" o "query"
14
+ const componentName = args[1] // es: "User"
15
+ const isCore = args.includes('--core')
16
+ const isVue = args.includes('--vue')
17
+
18
+ if (Guards.isNullOrEmpty(type) || Guards.isNullOrEmpty(componentName)) {
19
+ throw new Error(`Invalid syntax. Usage: xeno-js g [command | query] <Name> [--core | --vue]`)
20
+ }
21
+
22
+ let outputPath: string | undefined = undefined
23
+ const outputIndex = args.findIndex((arg) => arg === '--output' || arg === '-o' || arg === '--o')
24
+ if (outputIndex !== -1 && args.length > outputIndex + 1) {
25
+ outputPath = args[outputIndex + 1]
26
+ }
27
+
28
+ const targetDir = process.cwd()
29
+ const fileService = new FileUtils()
30
+
31
+ if (isCore || !isVue) {
32
+ const cqrsType =
33
+ type === COMMAND_CONSTANTS.COMMAND ? COMMAND_CONSTANTS.COMMAND : COMMAND_CONSTANTS.QUERY
34
+ const { CqrsGenerator } = await import('../generators/core/command/cqrs.generator.js')
35
+ const generator = new CqrsGenerator(fileService)
36
+
37
+ await generator.generate(targetDir, componentName, cqrsType, outputPath)
38
+
39
+ console.info(pc.green(`\n Successfully generated ${type} component '${componentName}'!`))
40
+ } else if (isVue) {
41
+ const cqrsType =
42
+ type === COMMAND_CONSTANTS.COMMAND ? COMMAND_CONSTANTS.COMMAND : COMMAND_CONSTANTS.QUERY
43
+ const { CqrsVueGenerator } = await import('../generators/vue/command_query/cqrs.generator.js')
44
+ const generator = new CqrsVueGenerator(fileService)
45
+
46
+ await generator.generate(targetDir, componentName, cqrsType, outputPath)
47
+
48
+ console.info(
49
+ pc.green(`\n✅ Successfully generated Vue ${type} component '${componentName}'!`),
50
+ )
51
+ } else {
52
+ throw new Error(`Unsupported generator type: '${type}'. Use 'command' or 'query'.`)
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,63 @@
1
+ import pc from 'picocolors'
2
+
3
+ import type { ICliCommand } from '../../domain'
4
+ import { ARGS_CONSTANTS, COMMAND_CONSTANTS } from '../../shared'
5
+
6
+ export class HelpCommand implements ICliCommand {
7
+ public get name(): string {
8
+ return COMMAND_CONSTANTS.HELP
9
+ }
10
+ public get aliases(): string[] {
11
+ return [ARGS_CONSTANTS.HELP, '-h']
12
+ }
13
+
14
+ public async execute(): Promise<void> {
15
+ console.info(pc.greenBright('\n🚀 Xeno CLI - Usage Guide\n'))
16
+
17
+ // --- NEW PROJECT ---
18
+ console.info(pc.bold(pc.blue('1. Create a new project:')))
19
+ console.info(` xeno-js new ${pc.yellow('<name>')} ${pc.cyan('[--vue | --core]')}`)
20
+ console.info(` ${pc.gray('•')} ${pc.yellow('<name>')}: The name of your project directory.`)
21
+ console.info(
22
+ ` ${pc.gray('•')} ${pc.cyan('[--vue | --core]')}: Project type. ${pc.gray('(Default: --core)')}`,
23
+ )
24
+ console.info(` - ${pc.cyan('--core')}: Initializes a Backend project (Node.js/TS).`)
25
+ console.info(` - ${pc.cyan('--vue')}: Initializes a Frontend project (Vue.js).\n`)
26
+
27
+ // --- GENERATE ---
28
+ console.info(pc.bold(pc.blue('2. Generate CQRS components:')))
29
+ console.info(
30
+ ` xeno-js generate ${pc.magenta('<type>')} ${pc.yellow('<Name>')} ${pc.cyan('[--vue | --core]')} ${pc.green('[--output <path>]')}`,
31
+ )
32
+ console.info(
33
+ ` ${pc.gray('•')} ${pc.magenta('<type>')}: Component type to generate (${pc.magenta('command')} | ${pc.magenta('query')}).`,
34
+ )
35
+ console.info(
36
+ ` ${pc.gray('•')} ${pc.yellow('<Name>')}: The name of the component (e.g., UserCreate).`,
37
+ )
38
+ console.info(
39
+ ` ${pc.gray('•')} ${pc.cyan('[--vue | --core]')}: Target context. ${pc.gray('(Default: --core)')}`,
40
+ )
41
+ console.info(
42
+ ` - ${pc.cyan('--core')}: Generates Controller, Handler, Command/Query, Module, and Zod/DB schemas.`,
43
+ )
44
+ console.info(
45
+ ` - ${pc.cyan('--vue')}: Generates frontend-specific files (Stores, Services, etc.).`,
46
+ )
47
+ console.info(
48
+ ` ${pc.gray('•')} ${pc.green('[--output | -o]')}: Custom destination path inside 'src/'. ${pc.gray('(Default: src/<name-in-lowercase>)')}`,
49
+ )
50
+ console.info(
51
+ ` ${pc.gray('Alias:')} ${pc.gray('xeno-js g <type> <Name> [--vue | --core] [-o | --o <path>]')}\n`,
52
+ )
53
+
54
+ // --- HELP ---
55
+ console.info(pc.bold(pc.blue('3. Help:')))
56
+ console.info(` xeno-js --help ${pc.gray('Displays this guide.')}`)
57
+ console.info(` ${pc.gray('Alias:')} ${pc.gray('xeno-js --h | -h')}\n`)
58
+
59
+ // --- DOCS ---
60
+ console.info(pc.bold(pc.blue('Documentation:')))
61
+ console.info(` ${pc.underline('https://www.xeno-js.it')}\n`)
62
+ }
63
+ }
@@ -0,0 +1,4 @@
1
+ export * from './command-runner.service'
2
+ export * from './generate.command'
3
+ export * from './help.command'
4
+ export * from './new-project.command'
@@ -0,0 +1,47 @@
1
+ import pc from 'picocolors'
2
+
3
+ import type { ICliCommand } from '../../domain'
4
+ import type { IScaffoldStrategy } from '../../domain'
5
+ import { type ScaffoldingOptions } from '../../shared'
6
+ import { Bootstrapper } from '../bootstrap'
7
+ import { FileUtils } from '../file'
8
+ import { CommandRunner } from './command-runner.service'
9
+
10
+ export class NewProjectCommand implements ICliCommand {
11
+ public readonly name: string = 'new'
12
+ public readonly aliases: string[] = []
13
+
14
+ public async execute(args: string[]): Promise<void> {
15
+ const targetDir = args.find((arg) => !arg.startsWith('--')) ?? 'my-xeno-app'
16
+ const isVue = args.includes('--vue')
17
+ const fileService = new FileUtils()
18
+
19
+ let strategy: IScaffoldStrategy<ScaffoldingOptions>
20
+
21
+ if (isVue) {
22
+ const { VueScaffoldStrategy } = await import('../strategies/vue-scaffold.strategy.js')
23
+ strategy = new VueScaffoldStrategy(fileService)
24
+ } else {
25
+ const { CoreScaffoldStrategy } = await import('../strategies/core-scaffold.strategy.js')
26
+ strategy = new CoreScaffoldStrategy(fileService)
27
+ }
28
+
29
+ const options = await strategy.promptOptions(targetDir)
30
+
31
+ const generators = await strategy.getGenerators(options)
32
+ const bootstrapper = new Bootstrapper(generators)
33
+ await bootstrapper.run(targetDir, options)
34
+
35
+ const commandRunner = new CommandRunner()
36
+ if (options.gitInit) {
37
+ console.info(pc.greenBright('\n🗂️ Initializing Git repository...'))
38
+ await commandRunner.run('git', ['init'], targetDir)
39
+ }
40
+ console.info(pc.greenBright('\n📦 Installing dependencies...'))
41
+ const isWin = process.platform === 'win32'
42
+ await commandRunner.run('npm', ['install'], targetDir, isWin)
43
+
44
+ console.info(pc.green(`\n✅ Project successfully created in ./${targetDir}`))
45
+ console.info(pc.white(`\nNext steps:\n cd ${targetDir}\n npm run start\n`))
46
+ }
47
+ }
@@ -0,0 +1,45 @@
1
+ import pc from 'picocolors'
2
+
3
+ import type { ICliCommand, IDispatcher } from '../../domain'
4
+ import { ARGS_CONSTANTS, COMMAND_CONSTANTS, Guards } from '../../shared'
5
+
6
+ export class CliDispatcher implements IDispatcher {
7
+ private readonly _commands = new Map<string, ICliCommand>()
8
+
9
+ constructor(commands: ICliCommand[]) {
10
+ for (const command of commands) {
11
+ this._commands.set(command.name, command)
12
+ for (const alias of command.aliases) {
13
+ this._commands.set(alias, command)
14
+ }
15
+ }
16
+ }
17
+
18
+ public async dispatch(rawArgs: string[]): Promise<void> {
19
+ const action = rawArgs[0]
20
+
21
+ if (
22
+ !Guards.isDefined(action) ||
23
+ action === ARGS_CONSTANTS.HELP ||
24
+ action === '-h' ||
25
+ action === '--h'
26
+ ) {
27
+ const helpCommand = this._commands.get(COMMAND_CONSTANTS.HELP)
28
+ await helpCommand?.execute([])
29
+ return
30
+ }
31
+
32
+ const command = this._commands.get(action)
33
+
34
+ if (!Guards.isDefined(command)) {
35
+ console.error(pc.red(`\n❌ '${action}' is not a command`))
36
+ console.info(
37
+ pc.yellow(`See 'xeno-js --help' for cli usage guide or https://www.xeno-js.it for docs.\n`),
38
+ )
39
+ process.exit(1)
40
+ }
41
+
42
+ const commandArgs = rawArgs.slice(1)
43
+ await command.execute(commandArgs)
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ export * from './dispatcher'
@@ -0,0 +1,14 @@
1
+ import { mkdir, writeFile } from 'fs/promises'
2
+ import { dirname } from 'path'
3
+
4
+ import { type IFileService } from '../../domain'
5
+
6
+ export class FileUtils implements IFileService {
7
+ public async writeFileRecursive(filePath: string, content: string): Promise<void> {
8
+ const dir = dirname(filePath)
9
+
10
+ await mkdir(dir, { recursive: true })
11
+
12
+ await writeFile(filePath, content, 'utf8')
13
+ }
14
+ }
@@ -0,0 +1 @@
1
+ export * from './file.service'