@xeno-js/cli 0.1.2 → 0.1.4
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.
- package/LICENSE +18 -12
- package/README.md +461 -236
- package/dist/index.js +1018 -688
- package/dist/index.js.map +1 -1
- package/package.json +91 -66
- package/.github/.copilot-instruction.md +0 -73
- package/.github/agents/git-operator.agent.md +0 -86
- package/.github/agents/unit-tester.agent.md +0 -68
- package/.husky/commit-msg +0 -1
- package/.husky/pre-commit +0 -1
- package/.husky/pre-push +0 -1
- package/.prettierignore +0 -7
- package/.prettierrc.json +0 -37
- package/commitlint.config.cjs +0 -39
- package/eslint.config.mjs +0 -306
- package/lint-staged.config.mjs +0 -5
- package/logo/logo.png +0 -0
- package/src/domain/contracts/icommand-cli.contracts.ts +0 -13
- package/src/domain/contracts/icommand-runner.contracts.ts +0 -3
- package/src/domain/contracts/idispatch.contracts.ts +0 -3
- package/src/domain/contracts/ifile-service.contracts.ts +0 -3
- package/src/domain/contracts/igenerator.contracts.ts +0 -5
- package/src/domain/contracts/index.ts +0 -6
- package/src/domain/contracts/iscaffold-strategy.contracts.ts +0 -7
- package/src/domain/index.ts +0 -1
- package/src/index.ts +0 -30
- package/src/infrastructure/bootstrap/bootstrapper.ts +0 -30
- package/src/infrastructure/bootstrap/index.ts +0 -1
- package/src/infrastructure/builder/builder.test.ts +0 -36
- package/src/infrastructure/builder/builder.ts +0 -15
- package/src/infrastructure/builder/index.ts +0 -1
- package/src/infrastructure/command_launcher/command.utils.ts +0 -31
- package/src/infrastructure/command_launcher/index.ts +0 -0
- package/src/infrastructure/commands/command-runner.service.ts +0 -36
- package/src/infrastructure/commands/generate.command.ts +0 -55
- package/src/infrastructure/commands/help.command.ts +0 -63
- package/src/infrastructure/commands/index.ts +0 -4
- package/src/infrastructure/commands/new-project.command.ts +0 -47
- package/src/infrastructure/dispatcher/dispatcher.ts +0 -45
- package/src/infrastructure/dispatcher/index.ts +0 -1
- package/src/infrastructure/file/file.service.ts +0 -14
- package/src/infrastructure/file/index.ts +0 -1
- package/src/infrastructure/generators/core/bootstrap.generator.ts +0 -118
- package/src/infrastructure/generators/core/command/cqrs.generator.ts +0 -63
- package/src/infrastructure/generators/core/command/generate-command.generator.ts +0 -191
- package/src/infrastructure/generators/core/command/generate-query.generator.ts +0 -150
- package/src/infrastructure/generators/core/drizzle-sql-lite.generator.ts +0 -76
- package/src/infrastructure/generators/core/drizzle.generator.ts +0 -86
- package/src/infrastructure/generators/core/env.generator.ts +0 -69
- package/src/infrastructure/generators/core/index.ts +0 -9
- package/src/infrastructure/generators/core/main.generator.ts +0 -41
- package/src/infrastructure/generators/core/packagejson.generator.ts +0 -70
- package/src/infrastructure/generators/core/readme.generator.ts +0 -427
- package/src/infrastructure/generators/core/registry.generator.ts +0 -40
- package/src/infrastructure/generators/core/tsconfig.generator.ts +0 -39
- package/src/infrastructure/generators/gitignore.generator.ts +0 -60
- package/src/infrastructure/generators/vue/app-vue.generator.ts +0 -21
- package/src/infrastructure/generators/vue/bootstrap-ts.generator.ts +0 -60
- package/src/infrastructure/generators/vue/command_query/cqrs.generator.ts +0 -36
- package/src/infrastructure/generators/vue/command_query/generate-command-vue.generator.ts +0 -153
- package/src/infrastructure/generators/vue/command_query/generate-query-vue.generator.ts +0 -161
- package/src/infrastructure/generators/vue/env.generator.ts +0 -39
- package/src/infrastructure/generators/vue/index-html.generator.ts +0 -24
- package/src/infrastructure/generators/vue/index.ts +0 -12
- package/src/infrastructure/generators/vue/main.generator.ts +0 -41
- package/src/infrastructure/generators/vue/packagejson.generator.ts +0 -50
- package/src/infrastructure/generators/vue/readme.generator.ts +0 -389
- package/src/infrastructure/generators/vue/registry-ts.generator.ts +0 -26
- package/src/infrastructure/generators/vue/router.generator.ts +0 -35
- package/src/infrastructure/generators/vue/tailwind.generator.ts +0 -17
- package/src/infrastructure/generators/vue/tsconfig.generator.ts +0 -38
- package/src/infrastructure/generators/vue/use-app.generator.ts +0 -38
- package/src/infrastructure/generators/vue/vite-config.generator.ts +0 -35
- package/src/infrastructure/index.ts +0 -5
- package/src/infrastructure/strategies/core-scaffold.strategy.ts +0 -100
- package/src/infrastructure/strategies/index.ts +0 -2
- package/src/infrastructure/strategies/vue-scaffold.strategy.ts +0 -79
- package/src/shared/constants/command.constants.ts +0 -18
- package/src/shared/constants/core.constants.ts +0 -7
- package/src/shared/constants/index.ts +0 -2
- package/src/shared/index.ts +0 -3
- package/src/shared/types/common.types.ts +0 -68
- package/src/shared/types/index.ts +0 -1
- package/src/shared/utils/guards.utils.ts +0 -334
- package/src/shared/utils/index.ts +0 -2
- package/src/shared/utils/string.utils.ts +0 -60
- package/tsconfig.json +0 -15
- package/tsup.config.ts +0 -13
|
@@ -1,63 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './dispatcher'
|
|
@@ -1,14 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from './file.service'
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
|
|
3
|
-
import { type IFileService, type IGenerator } from '../../../domain'
|
|
4
|
-
import { CORE_CONSTANTS, type CoreOptions } from '../../../shared'
|
|
5
|
-
|
|
6
|
-
export class BootstrapGenerator implements IGenerator<CoreOptions> {
|
|
7
|
-
constructor(private readonly _fileService: IFileService) {}
|
|
8
|
-
|
|
9
|
-
public async generate(projectPath: string, options: CoreOptions): Promise<void> {
|
|
10
|
-
// Costruisce in modo flessibile i nodi di configurazione
|
|
11
|
-
let builderNodes = `
|
|
12
|
-
const builder = new AppBuilder<MyRegistry>()
|
|
13
|
-
.addContext()
|
|
14
|
-
.addMiddlewares((opts) => {
|
|
15
|
-
// Add your options here
|
|
16
|
-
})
|
|
17
|
-
.addPipeline((opts) => {
|
|
18
|
-
// Add your options here
|
|
19
|
-
})`
|
|
20
|
-
|
|
21
|
-
if (options.database !== CORE_CONSTANTS.NONE) {
|
|
22
|
-
if (options.database === CORE_CONSTANTS.DRIZZLE) {
|
|
23
|
-
builderNodes += `
|
|
24
|
-
.addDb((opts, config) => {
|
|
25
|
-
opts.enableSqlLite = false
|
|
26
|
-
opts.connectionString = config.getOrThrow('DATABASE_URL');
|
|
27
|
-
})`
|
|
28
|
-
} else if (options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
29
|
-
builderNodes += `
|
|
30
|
-
.addDb((opts, config) => {
|
|
31
|
-
opts.enableSqlLite = true;
|
|
32
|
-
opts.connectionString = config.getOrThrow('SQLITE_DATABASE_URL');
|
|
33
|
-
})`
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (options.redis) {
|
|
38
|
-
builderNodes += `
|
|
39
|
-
.addCache((opts, config) => {
|
|
40
|
-
const isDev = config.get('NODE_ENV') === 'development';
|
|
41
|
-
opts.inMemory = isDev;
|
|
42
|
-
opts.redis = !isDev ? {
|
|
43
|
-
host: config.getOrThrow('REDIS_HOST'),
|
|
44
|
-
port: config.getNumber('REDIS_PORT') ?? 6379,
|
|
45
|
-
username: config.get('REDIS_USERNAME') ?? '',
|
|
46
|
-
password: config.get('REDIS_PASSWORD') ?? '',
|
|
47
|
-
tls: config.getBoolean('REDIS_TLS') ?? false,
|
|
48
|
-
maxRetriesPerRequest: 3
|
|
49
|
-
} : undefined
|
|
50
|
-
})`
|
|
51
|
-
} else {
|
|
52
|
-
// Default in-memory cache if no redis selected but CQRS pipeline exists
|
|
53
|
-
builderNodes += `
|
|
54
|
-
.addCache((opts) => {
|
|
55
|
-
opts.inMemory = true;
|
|
56
|
-
})`
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (options.supabase) {
|
|
60
|
-
builderNodes += `
|
|
61
|
-
.addAuth((opts, config) => {
|
|
62
|
-
opts.url = config.getOrThrow('SUPABASE_URL');
|
|
63
|
-
opts.key = config.getOrThrow('SUPABASE_KEY');
|
|
64
|
-
})`
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (options.pino || options.sentry) {
|
|
68
|
-
const pinoConfig = options.pino
|
|
69
|
-
? `opts.pino.config = !isDev ? { env: config.getOrThrow('NODE_ENV'), destination: 'stdout', prettyPrint: false } : undefined;`
|
|
70
|
-
: ''
|
|
71
|
-
const sentryConfig = options.sentry
|
|
72
|
-
? `opts.sentry.config = !isDev ? { dsn: config.getOrThrow('SENTRY_DSN'), environment: config.getOrThrow('NODE_ENV') } : undefined;`
|
|
73
|
-
: ''
|
|
74
|
-
|
|
75
|
-
builderNodes += `
|
|
76
|
-
.addLogger((opts, config) => {
|
|
77
|
-
const isDev = config.get('NODE_ENV') === 'development';
|
|
78
|
-
opts.level = LOG_LEVEL.INFO;
|
|
79
|
-
opts.console = isDev;
|
|
80
|
-
${pinoConfig}
|
|
81
|
-
${sentryConfig}
|
|
82
|
-
})`
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
builderNodes += `
|
|
86
|
-
.addServices((opts, config) => {
|
|
87
|
-
// Register custom repositories and handlers here
|
|
88
|
-
});`
|
|
89
|
-
|
|
90
|
-
const content = `import type { IServiceContainer } from '@xeno-js/core';
|
|
91
|
-
import { AppBuilder, LOG_LEVEL, Nullable, TOKENS } from '@xeno-js/core';
|
|
92
|
-
import type { MyRegistry } from './registry';
|
|
93
|
-
|
|
94
|
-
let containerPromise: Nullable<Promise<IServiceContainer<MyRegistry>>> = null;
|
|
95
|
-
|
|
96
|
-
export async function bootstrap() {
|
|
97
|
-
if (!containerPromise) {
|
|
98
|
-
containerPromise = (async () => {
|
|
99
|
-
const builder = getContainer();
|
|
100
|
-
return await builder.build();
|
|
101
|
-
})().catch((err) => {
|
|
102
|
-
containerPromise = null;
|
|
103
|
-
throw err;
|
|
104
|
-
});
|
|
105
|
-
}
|
|
106
|
-
return containerPromise;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function getContainer() {
|
|
110
|
-
${builderNodes}
|
|
111
|
-
return builder;
|
|
112
|
-
}
|
|
113
|
-
`
|
|
114
|
-
|
|
115
|
-
const filePath = path.join(projectPath, 'src', 'bootstrap.ts')
|
|
116
|
-
await this._fileService.writeFileRecursive(filePath, content)
|
|
117
|
-
}
|
|
118
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs/promises'
|
|
2
|
-
import path from 'node:path'
|
|
3
|
-
|
|
4
|
-
import type { IFileService } from '../../../../domain'
|
|
5
|
-
import { COMMAND_CONSTANTS, Guards, StringUtils } from '../../../../shared'
|
|
6
|
-
|
|
7
|
-
export class CqrsGenerator {
|
|
8
|
-
constructor(private readonly _fileService: IFileService) {}
|
|
9
|
-
|
|
10
|
-
public async generate(
|
|
11
|
-
targetDir: string,
|
|
12
|
-
name: string,
|
|
13
|
-
type: 'command' | 'query',
|
|
14
|
-
outputPath?: string,
|
|
15
|
-
): Promise<void> {
|
|
16
|
-
const pascalName = StringUtils.pascalCase(name)
|
|
17
|
-
const tokenPrefix = StringUtils.splitCamelCaseWithUnderscore(pascalName).toUpperCase()
|
|
18
|
-
const lowerName = name.toLowerCase()
|
|
19
|
-
const destinationFolder = outputPath ?? lowerName
|
|
20
|
-
const componentDir = path.join(targetDir, 'src', destinationFolder)
|
|
21
|
-
|
|
22
|
-
let hasZod = false
|
|
23
|
-
let hasDrizzle = false
|
|
24
|
-
|
|
25
|
-
try {
|
|
26
|
-
const pkgJsonStr = await fs.readFile(path.join(targetDir, 'package.json'), 'utf8')
|
|
27
|
-
const pkgJson = JSON.parse(pkgJsonStr) as {
|
|
28
|
-
dependencies?: Record<string, string>
|
|
29
|
-
devDependencies?: Record<string, string>
|
|
30
|
-
}
|
|
31
|
-
const deps = { ...(pkgJson.dependencies ?? {}), ...(pkgJson.devDependencies ?? {}) }
|
|
32
|
-
hasZod = !!Guards.isDefined(deps['zod'])
|
|
33
|
-
hasDrizzle = !!Guards.isDefined(deps['drizzle-orm'])
|
|
34
|
-
} catch (error) {
|
|
35
|
-
console.warn(
|
|
36
|
-
' Warning: Could not read package.json. Assuming Zod and Drizzle are not installed.',
|
|
37
|
-
error,
|
|
38
|
-
)
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
switch (type) {
|
|
42
|
-
case COMMAND_CONSTANTS.COMMAND: {
|
|
43
|
-
const { GenerateCommandCoreGenerator } = await import('./generate-command.generator.js')
|
|
44
|
-
const generator = new GenerateCommandCoreGenerator(this._fileService)
|
|
45
|
-
return generator.generate(
|
|
46
|
-
pascalName,
|
|
47
|
-
lowerName,
|
|
48
|
-
tokenPrefix,
|
|
49
|
-
componentDir,
|
|
50
|
-
hasZod,
|
|
51
|
-
hasDrizzle,
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
case COMMAND_CONSTANTS.QUERY: {
|
|
55
|
-
const { GenerateQueryCoreGenerator } = await import('./generate-query.generator.js')
|
|
56
|
-
const generatorQuery = new GenerateQueryCoreGenerator(this._fileService)
|
|
57
|
-
return generatorQuery.generate(pascalName, lowerName, tokenPrefix, componentDir, hasZod)
|
|
58
|
-
}
|
|
59
|
-
default:
|
|
60
|
-
throw new Error(`Unsupported generator type: '${String(type)}'. Use 'command' or 'query'.`)
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
import path from 'node:path'
|
|
2
|
-
|
|
3
|
-
import type { IFileService } from '../../../../domain'
|
|
4
|
-
|
|
5
|
-
export class GenerateCommandCoreGenerator {
|
|
6
|
-
constructor(private readonly _fileService: IFileService) {}
|
|
7
|
-
|
|
8
|
-
public async generate(
|
|
9
|
-
pascalName: string,
|
|
10
|
-
lowerName: string,
|
|
11
|
-
tokenPrefix: string,
|
|
12
|
-
componentDir: string,
|
|
13
|
-
hasZod: boolean,
|
|
14
|
-
hasDrizzle: boolean,
|
|
15
|
-
): Promise<void> {
|
|
16
|
-
// 1. Command
|
|
17
|
-
const commandContent = `import { Command } from '@xeno-js/core';
|
|
18
|
-
|
|
19
|
-
/*
|
|
20
|
-
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
21
|
-
* Please replace <any> with your specific payload and response types.
|
|
22
|
-
*/
|
|
23
|
-
export class ${pascalName}Command extends Command<any> {
|
|
24
|
-
constructor(public readonly payload: any) {
|
|
25
|
-
super('${tokenPrefix}_COMMAND_HANDLER');
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
`
|
|
29
|
-
await this._fileService.writeFileRecursive(
|
|
30
|
-
path.join(componentDir, `${lowerName}.command.ts`),
|
|
31
|
-
commandContent,
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
// 2. Handler
|
|
35
|
-
const handlerContent = `import type { IFactory, ResultType, UserContext } from '@xeno-js/core';
|
|
36
|
-
import { AppError, BaseHandler, Result } from '@xeno-js/core';
|
|
37
|
-
import { ${pascalName}Command } from './${lowerName}.command';
|
|
38
|
-
|
|
39
|
-
/*
|
|
40
|
-
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
41
|
-
* Please replace <any> with your specific response type.
|
|
42
|
-
*/
|
|
43
|
-
export class ${pascalName}Handler extends BaseHandler<${pascalName}Command, any> {
|
|
44
|
-
constructor(
|
|
45
|
-
identityFactory: IFactory<void, UserContext>
|
|
46
|
-
) {
|
|
47
|
-
super(identityFactory);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public async executeAsync(request: ${pascalName}Command, singal: AbortSignal): Promise<ResultType<any>> {
|
|
51
|
-
// TODO: Implement your business logic here
|
|
52
|
-
|
|
53
|
-
return Result.ok();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
`
|
|
57
|
-
await this._fileService.writeFileRecursive(
|
|
58
|
-
path.join(componentDir, `${lowerName}.handler.ts`),
|
|
59
|
-
handlerContent,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
// 3. Controller
|
|
63
|
-
const controllerContent = `import type { IContextAccessor, IMediator, RequestContext, ResponseDto } from '@xeno-js/core';
|
|
64
|
-
import { BaseController } from '@xeno-js/core';
|
|
65
|
-
import { ${pascalName}Command } from './${lowerName}.command';
|
|
66
|
-
|
|
67
|
-
/*
|
|
68
|
-
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
69
|
-
* Please replace <any, any> with your specific request and response types.
|
|
70
|
-
*/
|
|
71
|
-
export class ${pascalName}Controller extends BaseController<any, any> {
|
|
72
|
-
constructor(
|
|
73
|
-
requestContext: IContextAccessor<RequestContext>,
|
|
74
|
-
mediator: IMediator,
|
|
75
|
-
) {
|
|
76
|
-
super(requestContext, mediator);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
public async handle(request: any): Promise<ResponseDto<any>> {
|
|
80
|
-
const cmd = new ${pascalName}Command(request);
|
|
81
|
-
const result = await this._send(cmd);
|
|
82
|
-
|
|
83
|
-
if (!result.isOk()) {
|
|
84
|
-
return this.fail(result.getErrorOrThrow(), 'Error during ${pascalName} operation');
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
return this.ok(result.getValueOrThrow());
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
`
|
|
91
|
-
await this._fileService.writeFileRecursive(
|
|
92
|
-
path.join(componentDir, `${lowerName}.controller.ts`),
|
|
93
|
-
controllerContent,
|
|
94
|
-
)
|
|
95
|
-
|
|
96
|
-
// 4. Entity
|
|
97
|
-
const entityContent = `import { Entity } from '@xeno-js/core';
|
|
98
|
-
|
|
99
|
-
export interface ${pascalName}Props {
|
|
100
|
-
// TODO: Define your entity properties
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/*
|
|
104
|
-
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
105
|
-
* Adjust the properties and types according to your domain logic.
|
|
106
|
-
*/
|
|
107
|
-
export class ${pascalName} extends Entity<${pascalName}Props> {
|
|
108
|
-
private constructor(props: ${pascalName}Props, id?: string) {
|
|
109
|
-
super(props, id);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
static create(props: ${pascalName}Props, id?: string): ${pascalName} {
|
|
113
|
-
return new ${pascalName}(props, id);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
`
|
|
117
|
-
await this._fileService.writeFileRecursive(
|
|
118
|
-
path.join(componentDir, `${lowerName}.entity.ts`),
|
|
119
|
-
entityContent,
|
|
120
|
-
)
|
|
121
|
-
|
|
122
|
-
// 5. Schema Zod (Condizionale)
|
|
123
|
-
if (hasZod) {
|
|
124
|
-
const zodContent = `import { z } from 'zod';
|
|
125
|
-
import { ZodUtils } from '@xeno-js/core';
|
|
126
|
-
|
|
127
|
-
/*
|
|
128
|
-
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
129
|
-
* Define the actual Zod validation schema for your command payload.
|
|
130
|
-
*/
|
|
131
|
-
export const ${pascalName}Schema = ZodUtils.createCommandSchema({
|
|
132
|
-
payload: z.any() // TODO: Update with strict validation
|
|
133
|
-
});
|
|
134
|
-
`
|
|
135
|
-
await this._fileService.writeFileRecursive(
|
|
136
|
-
path.join(componentDir, `${lowerName}.schema-zod.ts`),
|
|
137
|
-
zodContent,
|
|
138
|
-
)
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
// 6. Schema DB (Condizionale)
|
|
142
|
-
if (hasDrizzle) {
|
|
143
|
-
const dbContent = `import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';
|
|
144
|
-
// Note: If you are using SQLite, change the import to 'drizzle-orm/sqlite-core' and update the table definition.
|
|
145
|
-
|
|
146
|
-
/*
|
|
147
|
-
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
148
|
-
* Update the schema definition with your actual database columns.
|
|
149
|
-
*/
|
|
150
|
-
export const ${lowerName}s = pgTable('${lowerName}s', {
|
|
151
|
-
id: text('id').primaryKey(),
|
|
152
|
-
createdAt: timestamp('created_at').defaultNow(),
|
|
153
|
-
updatedAt: timestamp('updated_at').defaultNow().$onUpdate(() => new Date()),
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
export type ${pascalName}Dto = typeof ${lowerName}s.$inferSelect;
|
|
157
|
-
export type New${pascalName}Dto = typeof ${lowerName}s.$inferInsert;
|
|
158
|
-
`
|
|
159
|
-
await this._fileService.writeFileRecursive(
|
|
160
|
-
path.join(componentDir, `${lowerName}.schema-db.ts`),
|
|
161
|
-
dbContent,
|
|
162
|
-
)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 7. Module
|
|
166
|
-
const moduleContent = `import type { IServiceContainer, IConfigurationService } from '@xeno-js/core';
|
|
167
|
-
import { TOKENS } from '@xeno-js/core';
|
|
168
|
-
import { ${pascalName}Controller } from './${lowerName}.controller';
|
|
169
|
-
import { ${pascalName}Handler } from './${lowerName}.handler';
|
|
170
|
-
|
|
171
|
-
export const ${pascalName}Module = Object.freeze({
|
|
172
|
-
register(opts: IServiceContainer<any>, _config: IConfigurationService): void {
|
|
173
|
-
opts.addTransient('${tokenPrefix}_COMMAND_CONTROLLER', (c) => new ${pascalName}Controller(c.resolve(TOKENS.REQUEST_CONTEXT), c.resolve(TOKENS.MEDIATOR)));
|
|
174
|
-
opts.addScoped('${tokenPrefix}_COMMAND_HANDLER', (c) => new ${pascalName}Handler(c.resolve(TOKENS.USER_CONTEXT_FACTORY)));
|
|
175
|
-
|
|
176
|
-
/*
|
|
177
|
-
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
178
|
-
* Please copy and paste the following tokens to your MyRegistry interface (usually in src/registry.ts):
|
|
179
|
-
*
|
|
180
|
-
* ${tokenPrefix}_COMMAND_CONTROLLER: IController<any, any>;
|
|
181
|
-
* ${tokenPrefix}_COMMAND_HANDLER: IHandler<${pascalName}Command, any>;
|
|
182
|
-
*/
|
|
183
|
-
}
|
|
184
|
-
} as const);
|
|
185
|
-
`
|
|
186
|
-
await this._fileService.writeFileRecursive(
|
|
187
|
-
path.join(componentDir, `${lowerName}.module.ts`),
|
|
188
|
-
moduleContent,
|
|
189
|
-
)
|
|
190
|
-
}
|
|
191
|
-
}
|