@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.
- package/.github/.copilot-instruction.md +73 -0
- package/.github/agents/git-operator.agent.md +86 -0
- package/.github/agents/unit-tester.agent.md +68 -0
- package/.husky/commit-msg +1 -0
- package/.husky/pre-commit +1 -0
- package/.husky/pre-push +1 -0
- package/.prettierignore +7 -0
- package/.prettierrc.json +37 -0
- package/LICENSE +15 -0
- package/README.md +236 -0
- package/commitlint.config.cjs +39 -0
- package/dist/domain/contracts/icommand-cli.contracts.js +2 -0
- package/dist/domain/contracts/icommand-runner.contracts.js +2 -0
- package/dist/domain/contracts/idispatch.contracts.js +2 -0
- package/dist/domain/contracts/ifile-service.contracts.js +2 -0
- package/dist/domain/contracts/igenerator.contracts.js +2 -0
- package/dist/domain/contracts/index.js +2 -0
- package/dist/domain/contracts/iscaffold-strategy.contracts.js +2 -0
- package/dist/domain/index.js +2 -0
- package/dist/generator.contracts.js +13 -0
- package/dist/index.js +31 -0
- package/dist/infrastructure/bootstrap/bootstrapper.js +32 -0
- package/dist/infrastructure/bootstrap/index.js +17 -0
- package/dist/infrastructure/builder/builder.js +16 -0
- package/dist/infrastructure/builder/builder.test.js +30 -0
- package/dist/infrastructure/builder/index.js +17 -0
- package/dist/infrastructure/command_launcher/command.utils.js +30 -0
- package/dist/infrastructure/command_launcher/index.js +2 -0
- package/dist/infrastructure/commands/command-runner.service.js +36 -0
- package/dist/infrastructure/commands/generate.command.js +48 -0
- package/dist/infrastructure/commands/help.command.js +44 -0
- package/dist/infrastructure/commands/index.js +20 -0
- package/dist/infrastructure/commands/new-project.command.js +43 -0
- package/dist/infrastructure/dispatcher/dispatcher.js +39 -0
- package/dist/infrastructure/dispatcher/index.js +17 -0
- package/dist/infrastructure/file/file.service.js +13 -0
- package/dist/infrastructure/file/index.js +17 -0
- package/dist/infrastructure/generators/bootstrap.generator.js +118 -0
- package/dist/infrastructure/generators/core/bootstrap.generator.js +118 -0
- package/dist/infrastructure/generators/core/command/cqrs.generator.js +49 -0
- package/dist/infrastructure/generators/core/command/generate-command.generator.js +163 -0
- package/dist/infrastructure/generators/core/command/generate-query.generator.js +131 -0
- package/dist/infrastructure/generators/core/drizzle-sql-lite.generator.js +78 -0
- package/dist/infrastructure/generators/core/drizzle.generator.js +85 -0
- package/dist/infrastructure/generators/core/env.generator.js +69 -0
- package/dist/infrastructure/generators/core/index.js +25 -0
- package/dist/infrastructure/generators/core/main.generator.js +45 -0
- package/dist/infrastructure/generators/core/packagejson.generator.js +78 -0
- package/dist/infrastructure/generators/core/readme.generator.js +431 -0
- package/dist/infrastructure/generators/core/registry.generator.js +46 -0
- package/dist/infrastructure/generators/core/tsconfig.generator.js +41 -0
- package/dist/infrastructure/generators/core-packagejson.generator.js +78 -0
- package/dist/infrastructure/generators/drizzle-sql-lite.generator.js +78 -0
- package/dist/infrastructure/generators/drizzle.generator.js +85 -0
- package/dist/infrastructure/generators/env-vue.generator.js +40 -0
- package/dist/infrastructure/generators/env.generator.js +71 -0
- package/dist/infrastructure/generators/gitignore.generator.js +62 -0
- package/dist/infrastructure/generators/index.js +26 -0
- package/dist/infrastructure/generators/main.generator.js +45 -0
- package/dist/infrastructure/generators/readme.generator.js +431 -0
- package/dist/infrastructure/generators/registry.generator.js +46 -0
- package/dist/infrastructure/generators/tsconfig-vue.generator.js +39 -0
- package/dist/infrastructure/generators/tsconfig.generator.js +41 -0
- package/dist/infrastructure/generators/vite-config.generator.js +39 -0
- package/dist/infrastructure/generators/vue/app-vue.generator.js +26 -0
- package/dist/infrastructure/generators/vue/bootstrap-ts.generator.js +59 -0
- package/dist/infrastructure/generators/vue/command_query/cqrs.generator.js +36 -0
- package/dist/infrastructure/generators/vue/command_query/generate-command-vue.generator.js +139 -0
- package/dist/infrastructure/generators/vue/command_query/generate-query-vue.generator.js +147 -0
- package/dist/infrastructure/generators/vue/env.generator.js +40 -0
- package/dist/infrastructure/generators/vue/index-html.generator.js +29 -0
- package/dist/infrastructure/generators/vue/index.js +28 -0
- package/dist/infrastructure/generators/vue/main.generator.js +45 -0
- package/dist/infrastructure/generators/vue/packagejson.generator.js +59 -0
- package/dist/infrastructure/generators/vue/readme.generator.js +394 -0
- package/dist/infrastructure/generators/vue/registry-ts.generator.js +28 -0
- package/dist/infrastructure/generators/vue/router.generator.js +33 -0
- package/dist/infrastructure/generators/vue/tailwind.generator.js +19 -0
- package/dist/infrastructure/generators/vue/tsconfig.generator.js +39 -0
- package/dist/infrastructure/generators/vue/use-app.generator.js +43 -0
- package/dist/infrastructure/generators/vue/vite-config.generator.js +39 -0
- package/dist/infrastructure/generators/vue-packagejson.generator.js +59 -0
- package/dist/infrastructure/generators/vue-readme.generator.js +53 -0
- package/dist/infrastructure/generators/vue-source-files.generator.js +136 -0
- package/dist/infrastructure/index.js +21 -0
- package/dist/infrastructure/strategies/core-scaffold.strategy.js +99 -0
- package/dist/infrastructure/strategies/index.js +18 -0
- package/dist/infrastructure/strategies/vue-scaffold.strategy.js +79 -0
- package/dist/scaffolding.engine.js +60 -0
- package/dist/shared/constants/command.constants.js +19 -0
- package/dist/shared/constants/core.constants.js +10 -0
- package/dist/shared/constants/index.js +18 -0
- package/dist/shared/index.js +18 -0
- package/dist/shared/types/common.types.js +2 -0
- package/dist/shared/types/index.js +2 -0
- package/dist/shared/utils/guards.utils.js +310 -0
- package/dist/shared/utils/index.js +18 -0
- package/dist/shared/utils/string.utils.js +62 -0
- package/eslint.config.mjs +305 -0
- package/lint-staged.config.mjs +5 -0
- package/logo/logo.png +0 -0
- package/package.json +65 -0
- package/src/domain/contracts/icommand-cli.contracts.ts +13 -0
- package/src/domain/contracts/icommand-runner.contracts.ts +3 -0
- package/src/domain/contracts/idispatch.contracts.ts +3 -0
- package/src/domain/contracts/ifile-service.contracts.ts +3 -0
- package/src/domain/contracts/igenerator.contracts.ts +5 -0
- package/src/domain/contracts/index.ts +6 -0
- package/src/domain/contracts/iscaffold-strategy.contracts.ts +7 -0
- package/src/domain/index.ts +1 -0
- package/src/index.ts +30 -0
- package/src/infrastructure/bootstrap/bootstrapper.ts +30 -0
- package/src/infrastructure/bootstrap/index.ts +1 -0
- package/src/infrastructure/builder/builder.test.ts +36 -0
- package/src/infrastructure/builder/builder.ts +15 -0
- package/src/infrastructure/builder/index.ts +1 -0
- package/src/infrastructure/command_launcher/command.utils.ts +31 -0
- package/src/infrastructure/command_launcher/index.ts +0 -0
- package/src/infrastructure/commands/command-runner.service.ts +36 -0
- package/src/infrastructure/commands/generate.command.ts +55 -0
- package/src/infrastructure/commands/help.command.ts +63 -0
- package/src/infrastructure/commands/index.ts +4 -0
- package/src/infrastructure/commands/new-project.command.ts +47 -0
- package/src/infrastructure/dispatcher/dispatcher.ts +45 -0
- package/src/infrastructure/dispatcher/index.ts +1 -0
- package/src/infrastructure/file/file.service.ts +14 -0
- package/src/infrastructure/file/index.ts +1 -0
- package/src/infrastructure/generators/core/bootstrap.generator.ts +118 -0
- package/src/infrastructure/generators/core/command/cqrs.generator.ts +63 -0
- package/src/infrastructure/generators/core/command/generate-command.generator.ts +191 -0
- package/src/infrastructure/generators/core/command/generate-query.generator.ts +150 -0
- package/src/infrastructure/generators/core/drizzle-sql-lite.generator.ts +76 -0
- package/src/infrastructure/generators/core/drizzle.generator.ts +86 -0
- package/src/infrastructure/generators/core/env.generator.ts +69 -0
- package/src/infrastructure/generators/core/index.ts +9 -0
- package/src/infrastructure/generators/core/main.generator.ts +41 -0
- package/src/infrastructure/generators/core/packagejson.generator.ts +70 -0
- package/src/infrastructure/generators/core/readme.generator.ts +427 -0
- package/src/infrastructure/generators/core/registry.generator.ts +40 -0
- package/src/infrastructure/generators/core/tsconfig.generator.ts +39 -0
- package/src/infrastructure/generators/gitignore.generator.ts +60 -0
- package/src/infrastructure/generators/vue/app-vue.generator.ts +21 -0
- package/src/infrastructure/generators/vue/bootstrap-ts.generator.ts +60 -0
- package/src/infrastructure/generators/vue/command_query/cqrs.generator.ts +36 -0
- package/src/infrastructure/generators/vue/command_query/generate-command-vue.generator.ts +153 -0
- package/src/infrastructure/generators/vue/command_query/generate-query-vue.generator.ts +161 -0
- package/src/infrastructure/generators/vue/env.generator.ts +39 -0
- package/src/infrastructure/generators/vue/index-html.generator.ts +24 -0
- package/src/infrastructure/generators/vue/index.ts +12 -0
- package/src/infrastructure/generators/vue/main.generator.ts +41 -0
- package/src/infrastructure/generators/vue/packagejson.generator.ts +50 -0
- package/src/infrastructure/generators/vue/readme.generator.ts +389 -0
- package/src/infrastructure/generators/vue/registry-ts.generator.ts +26 -0
- package/src/infrastructure/generators/vue/router.generator.ts +35 -0
- package/src/infrastructure/generators/vue/tailwind.generator.ts +17 -0
- package/src/infrastructure/generators/vue/tsconfig.generator.ts +38 -0
- package/src/infrastructure/generators/vue/use-app.generator.ts +38 -0
- package/src/infrastructure/generators/vue/vite-config.generator.ts +35 -0
- package/src/infrastructure/index.ts +5 -0
- package/src/infrastructure/strategies/core-scaffold.strategy.ts +100 -0
- package/src/infrastructure/strategies/index.ts +2 -0
- package/src/infrastructure/strategies/vue-scaffold.strategy.ts +79 -0
- package/src/shared/constants/command.constants.ts +18 -0
- package/src/shared/constants/core.constants.ts +7 -0
- package/src/shared/constants/index.ts +2 -0
- package/src/shared/index.ts +3 -0
- package/src/shared/types/common.types.ts +68 -0
- package/src/shared/types/index.ts +1 -0
- package/src/shared/utils/guards.utils.ts +334 -0
- package/src/shared/utils/index.ts +2 -0
- package/src/shared/utils/string.utils.ts +60 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import type { IFileService, IGenerator } from '../../../domain'
|
|
4
|
+
import { CORE_CONSTANTS, type CoreOptions } from '../../../shared'
|
|
5
|
+
|
|
6
|
+
export class TsconfigGenerator implements IGenerator<CoreOptions> {
|
|
7
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
8
|
+
|
|
9
|
+
public async generate(projectPath: string, options: CoreOptions): Promise<void> {
|
|
10
|
+
const tsconfig = {
|
|
11
|
+
compilerOptions: {
|
|
12
|
+
target: 'ES2022',
|
|
13
|
+
module: 'ESNext',
|
|
14
|
+
moduleResolution: 'bundler',
|
|
15
|
+
strict: true,
|
|
16
|
+
esModuleInterop: true,
|
|
17
|
+
skipLibCheck: true,
|
|
18
|
+
forceConsistentCasingInFileNames: true,
|
|
19
|
+
outDir: './dist',
|
|
20
|
+
rootDir: './src',
|
|
21
|
+
types: ['node'],
|
|
22
|
+
paths: {
|
|
23
|
+
'@/*': ['./src/*'],
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
include: ['src/**/*.ts'],
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (
|
|
30
|
+
options.database === CORE_CONSTANTS.DRIZZLE ||
|
|
31
|
+
options.database === CORE_CONSTANTS.SQL_LITE
|
|
32
|
+
) {
|
|
33
|
+
tsconfig.include.push('drizzle.config.ts')
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const filePath = path.join(projectPath, 'tsconfig.json')
|
|
37
|
+
await this._fileService.writeFileRecursive(filePath, JSON.stringify(tsconfig, null, 2))
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import type { IFileService, IGenerator } from '../../domain'
|
|
4
|
+
import type { CoreOptions, ScaffoldingOptions } from '../../shared'
|
|
5
|
+
import { CORE_CONSTANTS } from '../../shared'
|
|
6
|
+
|
|
7
|
+
export class GitIgnoreGenerator implements IGenerator<ScaffoldingOptions> {
|
|
8
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
9
|
+
|
|
10
|
+
public async generate(projectPath: string, options: ScaffoldingOptions): Promise<void> {
|
|
11
|
+
let content = `# Dependencies
|
|
12
|
+
node_modules/
|
|
13
|
+
|
|
14
|
+
# Build artifacts
|
|
15
|
+
dist/
|
|
16
|
+
out/
|
|
17
|
+
build/
|
|
18
|
+
|
|
19
|
+
# Environment variables
|
|
20
|
+
# We ignore actual .env files for security, but keep .env.example for documentation
|
|
21
|
+
.env
|
|
22
|
+
.env.local
|
|
23
|
+
.env.*
|
|
24
|
+
!.env.example
|
|
25
|
+
|
|
26
|
+
# Logs
|
|
27
|
+
*.log
|
|
28
|
+
logs/
|
|
29
|
+
npm-debug.log*
|
|
30
|
+
|
|
31
|
+
# Test coverage
|
|
32
|
+
coverage/
|
|
33
|
+
.nyc_output/
|
|
34
|
+
|
|
35
|
+
# OS and IDE
|
|
36
|
+
.DS_Store
|
|
37
|
+
Thumbs.db
|
|
38
|
+
.vscode/
|
|
39
|
+
.idea/
|
|
40
|
+
`
|
|
41
|
+
|
|
42
|
+
if ((options as CoreOptions).database === CORE_CONSTANTS.SQL_LITE) {
|
|
43
|
+
content += `\n# SQLite Database
|
|
44
|
+
*.sqlite
|
|
45
|
+
*.sqlite3
|
|
46
|
+
*.sqlite-journal
|
|
47
|
+
`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Ignora la cartella di compilazione locale di Vercel se l'ambiente lo richiede
|
|
51
|
+
if ((options as CoreOptions).targetEnv === CORE_CONSTANTS.VERCEL) {
|
|
52
|
+
content += `\n# Vercel
|
|
53
|
+
.vercel/
|
|
54
|
+
`
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const filePath = path.join(projectPath, '.gitignore')
|
|
58
|
+
await this._fileService.writeFileRecursive(filePath, content)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { type IFileService, type IGenerator } from '../../../domain'
|
|
4
|
+
import type { VueOptions } from '../../../shared'
|
|
5
|
+
|
|
6
|
+
export class AppVueGenerator implements IGenerator<VueOptions> {
|
|
7
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
8
|
+
|
|
9
|
+
public async generate(projectPath: string, options: VueOptions): Promise<void> {
|
|
10
|
+
// 2. src/App.vue
|
|
11
|
+
const appVue = `<script setup lang="ts">
|
|
12
|
+
</script>
|
|
13
|
+
|
|
14
|
+
<template>
|
|
15
|
+
<div id="xeno-app">
|
|
16
|
+
${options.router ? '<router-view />' : '<h1>Welcome to Xeno Vue App</h1>'}
|
|
17
|
+
</div>
|
|
18
|
+
</template>`
|
|
19
|
+
await this._fileService.writeFileRecursive(path.join(projectPath, 'src', 'App.vue'), appVue)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { type IFileService, type IGenerator } from '../../../domain'
|
|
4
|
+
import type { VueOptions } from '../../../shared'
|
|
5
|
+
|
|
6
|
+
export class BootstrapGenerator implements IGenerator<VueOptions> {
|
|
7
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
8
|
+
|
|
9
|
+
public async generate(projectPath: string, options: VueOptions): Promise<void> {
|
|
10
|
+
// 4. src/bootstrap.ts
|
|
11
|
+
let builderNodes = ` const builder = XenoAppBuilder.create<MyRegistry>()\n .addContext(() => {})`
|
|
12
|
+
|
|
13
|
+
if (options.supabase) {
|
|
14
|
+
builderNodes += `\n .addAuth((opts, config) => {
|
|
15
|
+
opts.url = config.getOrThrow('VITE_SUPABASE_URL');
|
|
16
|
+
opts.key = config.getOrThrow('VITE_SUPABASE_KEY');
|
|
17
|
+
})`
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (options.sentry) {
|
|
21
|
+
builderNodes += `\n .addLogger((opts, config) => {
|
|
22
|
+
const env = config.get(COMMON_CONSTANTS.ENV) ?? 'development'
|
|
23
|
+
const isDev = env.toLowerCase() === 'development'
|
|
24
|
+
opts.console = isDev
|
|
25
|
+
if (!isDev) {
|
|
26
|
+
opts.sentry = {
|
|
27
|
+
dsn: config.getOrThrow('VITE_SENTRY_DSN'),
|
|
28
|
+
env,
|
|
29
|
+
app,
|
|
30
|
+
router,
|
|
31
|
+
tracesSampleRate: 0.2,
|
|
32
|
+
level: LOG_LEVEL.WARN,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
})`
|
|
36
|
+
} else {
|
|
37
|
+
builderNodes += `\n .addLogger((opts, config) => {
|
|
38
|
+
opts.console = config.get('VITE_APP_ENV') === 'development';
|
|
39
|
+
})`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
builderNodes += `\n .addServices((config, register) => {
|
|
43
|
+
// Register custom stores, API clients, and CQRS services here
|
|
44
|
+
})`
|
|
45
|
+
|
|
46
|
+
const bootstrapTs = `import { XenoAppBuilder, LOG_LEVEL } from '@xeno-js/vue';
|
|
47
|
+
import type { MyRegistry } from './registry';
|
|
48
|
+
|
|
49
|
+
export async function bootstrap(app: any, router: any) {
|
|
50
|
+
${builderNodes}
|
|
51
|
+
|
|
52
|
+
return await builder.build();
|
|
53
|
+
}
|
|
54
|
+
`
|
|
55
|
+
await this._fileService.writeFileRecursive(
|
|
56
|
+
path.join(projectPath, 'src', 'bootstrap.ts'),
|
|
57
|
+
bootstrapTs,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import type { IFileService } from '../../../../domain'
|
|
4
|
+
import { COMMAND_CONSTANTS, StringUtils } from '../../../../shared'
|
|
5
|
+
|
|
6
|
+
export class CqrsVueGenerator {
|
|
7
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
8
|
+
|
|
9
|
+
public async generate(
|
|
10
|
+
targetDir: string,
|
|
11
|
+
name: string,
|
|
12
|
+
type: 'command' | 'query',
|
|
13
|
+
outputPath?: string,
|
|
14
|
+
): Promise<void> {
|
|
15
|
+
const pascalName = StringUtils.pascalCase(name)
|
|
16
|
+
const tokenPrefix = StringUtils.splitCamelCaseWithUnderscore(pascalName).toUpperCase()
|
|
17
|
+
const lowerName = name.toLowerCase()
|
|
18
|
+
const destinationFolder = outputPath ?? lowerName
|
|
19
|
+
const componentDir = path.join(targetDir, 'src', destinationFolder)
|
|
20
|
+
|
|
21
|
+
switch (type) {
|
|
22
|
+
case COMMAND_CONSTANTS.COMMAND: {
|
|
23
|
+
const { GenerateCommandVueGenerator } = await import('./generate-command-vue.generator.js')
|
|
24
|
+
const generator = new GenerateCommandVueGenerator(this._fileService)
|
|
25
|
+
return generator.generate(pascalName, lowerName, tokenPrefix, componentDir)
|
|
26
|
+
}
|
|
27
|
+
case COMMAND_CONSTANTS.QUERY: {
|
|
28
|
+
const { GenerateQueryVueGenerator } = await import('./generate-query-vue.generator.js')
|
|
29
|
+
const generatorQuery = new GenerateQueryVueGenerator(this._fileService)
|
|
30
|
+
return generatorQuery.generate(pascalName, lowerName, tokenPrefix, componentDir)
|
|
31
|
+
}
|
|
32
|
+
default:
|
|
33
|
+
throw new Error(`Unsupported generator type: '${String(type)}'. Use 'command' or 'query'.`)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import type { IFileService } from '../../../../domain'
|
|
4
|
+
|
|
5
|
+
export class GenerateCommandVueGenerator {
|
|
6
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
7
|
+
|
|
8
|
+
public async generate(
|
|
9
|
+
pascalName: string,
|
|
10
|
+
lowerName: string,
|
|
11
|
+
tokenPrefix: string,
|
|
12
|
+
componentDir: string,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
// 1. Command (Application)
|
|
15
|
+
const commandContent = `import { Command } from '@xeno-js/vue';
|
|
16
|
+
|
|
17
|
+
export class ${pascalName}Command extends Command {
|
|
18
|
+
constructor() {
|
|
19
|
+
super('${tokenPrefix}_COMMAND');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
`
|
|
23
|
+
await this._fileService.writeFileRecursive(
|
|
24
|
+
path.join(componentDir, `${lowerName}.command.ts`),
|
|
25
|
+
commandContent,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
const handlerContent = `import type { IHandler, ResultType } from '@xeno-js/vue';
|
|
29
|
+
import { AppError, Result } from '@xeno-js/vue';
|
|
30
|
+
import type { ${pascalName}Command } from './${lowerName}.command';
|
|
31
|
+
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
32
|
+
|
|
33
|
+
/*
|
|
34
|
+
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
35
|
+
*
|
|
36
|
+
* Please register your handler in the IoC container (e.g. src/bootstrap.ts) and add its token to MyRegistry.
|
|
37
|
+
*
|
|
38
|
+
* 1. In src/registry.ts:
|
|
39
|
+
* ${tokenPrefix}_HANDLER: IHandler<${pascalName}Command, ${pascalName}Response>;
|
|
40
|
+
*
|
|
41
|
+
* 2. In src/bootstrap.ts:
|
|
42
|
+
* .addServices((config, register) => {
|
|
43
|
+
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
44
|
+
* });
|
|
45
|
+
*/
|
|
46
|
+
export class ${pascalName}Handler implements IHandler<${pascalName}Command, ${pascalName}Response> {
|
|
47
|
+
constructor(
|
|
48
|
+
// TODO: Inject your remote DataSources or other services here
|
|
49
|
+
) {}
|
|
50
|
+
|
|
51
|
+
public async handle(command: ${pascalName}Command, signal: AbortSignal): Promise<ResultType<${pascalName}Response>> {
|
|
52
|
+
AppError.throwIfAborted(signal, this.constructor.name);
|
|
53
|
+
|
|
54
|
+
// TODO: Implement your frontend business logic or API calls here
|
|
55
|
+
return Result.ok();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
`
|
|
59
|
+
await this._fileService.writeFileRecursive(
|
|
60
|
+
path.join(componentDir, `${lowerName}.handler.ts`),
|
|
61
|
+
handlerContent,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
const composableContent = `import { ref } from 'vue';
|
|
65
|
+
import { AppError, Result, type ApiResponseDto, type ResultType } from '@xeno-js/vue';
|
|
66
|
+
import { ServicesUtils } from '@/use-app';
|
|
67
|
+
import { ${pascalName}Command } from './${lowerName}.command';
|
|
68
|
+
import type { ${pascalName}Request, ${pascalName}Response } from './${lowerName}.model';
|
|
69
|
+
|
|
70
|
+
/*
|
|
71
|
+
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
72
|
+
*
|
|
73
|
+
* Please register your handler in the IoC container (e.g. src/bootstrap.ts) and add its token to MyRegistry.
|
|
74
|
+
*
|
|
75
|
+
* 1. In src/registry.ts:
|
|
76
|
+
* ${tokenPrefix}_HANDLER: IHandler<${pascalName}Command, ${pascalName}Response>;
|
|
77
|
+
*
|
|
78
|
+
* 2. In src/bootstrap.ts:
|
|
79
|
+
* .addServices((config, register) => {
|
|
80
|
+
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
81
|
+
* });
|
|
82
|
+
*/
|
|
83
|
+
export function use${pascalName}() {
|
|
84
|
+
const loading = ref(false);
|
|
85
|
+
const error = ref<string | null>(null);
|
|
86
|
+
let abortController: AbortController | null = null;
|
|
87
|
+
|
|
88
|
+
const execute = async (payload: ${pascalName}Request): Promise<ResultType<${pascalName}Response>> => {
|
|
89
|
+
abortController = new AbortController();
|
|
90
|
+
|
|
91
|
+
if (loading.value) return Result.fail(AppError.create({
|
|
92
|
+
name: '${pascalName}Command',
|
|
93
|
+
message: '${pascalName}Command is already in progress',
|
|
94
|
+
code: '${tokenPrefix}_COMMAND_ALREADY_IN_PROGRESS',
|
|
95
|
+
status: 500,
|
|
96
|
+
cause: new Error('${tokenPrefix}_COMMAND_ALREADY_IN_PROGRESS'),
|
|
97
|
+
}));
|
|
98
|
+
loading.value = true;
|
|
99
|
+
error.value = null;
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
// Resolve mediator and handler from the container
|
|
103
|
+
const { mediator, ${tokenPrefix}_HANDLER: handler } = ServicesUtils.useApp();
|
|
104
|
+
const command = new ${pascalName}Command();
|
|
105
|
+
|
|
106
|
+
const result = await mediator.send(command, async () => {
|
|
107
|
+
return await handler.handle(command, abortController?.signal);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!result.isOk()) {
|
|
111
|
+
error.value = result.getErrorOrThrow().message;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return result;
|
|
115
|
+
} finally {
|
|
116
|
+
loading.value = false;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const abort = () => {
|
|
121
|
+
if (abortController) {
|
|
122
|
+
abortController.abort();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
// Automatically cancel pending operations when the component unmounts
|
|
127
|
+
onUnmounted(() => {
|
|
128
|
+
abort();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return { loading, error, execute };
|
|
132
|
+
}
|
|
133
|
+
`
|
|
134
|
+
await this._fileService.writeFileRecursive(
|
|
135
|
+
path.join(componentDir, `use-${lowerName}.composable.ts`),
|
|
136
|
+
composableContent,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
// 3. Domain Model (Domain)
|
|
140
|
+
const modelContent = `export interface ${pascalName}Request {
|
|
141
|
+
// TODO: Define your request payload properties here
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export interface ${pascalName}Response {
|
|
145
|
+
// TODO: Define your response properties here
|
|
146
|
+
}
|
|
147
|
+
`
|
|
148
|
+
await this._fileService.writeFileRecursive(
|
|
149
|
+
path.join(componentDir, `${lowerName}.model.ts`),
|
|
150
|
+
modelContent,
|
|
151
|
+
)
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import type { IFileService } from '../../../../domain'
|
|
4
|
+
|
|
5
|
+
export class GenerateQueryVueGenerator {
|
|
6
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
7
|
+
|
|
8
|
+
public async generate(
|
|
9
|
+
pascalName: string,
|
|
10
|
+
lowerName: string,
|
|
11
|
+
tokenPrefix: string,
|
|
12
|
+
componentDir: string,
|
|
13
|
+
): Promise<void> {
|
|
14
|
+
// 1. Query (Application)
|
|
15
|
+
const queryContent = `import { Query } from '@xeno-js/vue';
|
|
16
|
+
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
17
|
+
|
|
18
|
+
export class ${pascalName}Query extends Query<${pascalName}Response> {
|
|
19
|
+
constructor() {
|
|
20
|
+
super('${tokenPrefix}_QUERY', {
|
|
21
|
+
ttl: 60,
|
|
22
|
+
cacheKey: \`${lowerName}.query\`,
|
|
23
|
+
bypassCache: false,
|
|
24
|
+
consistentRead: false,
|
|
25
|
+
isUserScoped: true
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`
|
|
30
|
+
await this._fileService.writeFileRecursive(
|
|
31
|
+
path.join(componentDir, `${lowerName}.query.ts`),
|
|
32
|
+
queryContent,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
const handlerContent = `import type { IHandler, ResultType } from '@xeno-js/vue';
|
|
36
|
+
import { AppError, Result } from '@xeno-js/vue';
|
|
37
|
+
import type { ${pascalName}Query } from './${lowerName}.query';
|
|
38
|
+
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
39
|
+
|
|
40
|
+
/*
|
|
41
|
+
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
42
|
+
*
|
|
43
|
+
* Please register your handler in the IoC container (e.g. src/bootstrap.ts) and add its token to MyRegistry.
|
|
44
|
+
*
|
|
45
|
+
* 1. In src/registry.ts:
|
|
46
|
+
* ${tokenPrefix}_HANDLER: IHandler<${pascalName}Query, ${pascalName}Response>;
|
|
47
|
+
*
|
|
48
|
+
* 2. In src/bootstrap.ts:
|
|
49
|
+
* .addServices((config, register) => {
|
|
50
|
+
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
51
|
+
* });
|
|
52
|
+
*/
|
|
53
|
+
export class ${pascalName}Handler implements IHandler<${pascalName}Query, ${pascalName}Response> {
|
|
54
|
+
constructor(
|
|
55
|
+
// TODO: Inject your remote DataSources or other services here
|
|
56
|
+
) {}
|
|
57
|
+
|
|
58
|
+
public async handle(query: ${pascalName}Query, signal: AbortSignal): Promise<ResultType<${pascalName}Response>> {
|
|
59
|
+
AppError.throwIfAborted(signal, this.constructor.name);
|
|
60
|
+
|
|
61
|
+
// TODO: Implement your frontend business logic or API calls here
|
|
62
|
+
return Result.ok();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
`
|
|
66
|
+
await this._fileService.writeFileRecursive(
|
|
67
|
+
path.join(componentDir, `${lowerName}.handler.ts`),
|
|
68
|
+
handlerContent,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
// 2. Composable (Presentation)
|
|
72
|
+
const composableContent = `import { ref } from 'vue';
|
|
73
|
+
import { AppError, Result, type ApiResponseDto, type ResultType } from '@xeno-js/vue';
|
|
74
|
+
import { ServicesUtils } from '@/use-app';
|
|
75
|
+
import { ${pascalName}Query } from './${lowerName}.query';
|
|
76
|
+
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
77
|
+
|
|
78
|
+
export function use${pascalName}() {
|
|
79
|
+
const loading = ref(false);
|
|
80
|
+
const error = ref<string | null>(null);
|
|
81
|
+
const data = ref<${pascalName}Response | null>(null);
|
|
82
|
+
let abortController: AbortController | null = null;
|
|
83
|
+
|
|
84
|
+
/*
|
|
85
|
+
* ⚠️ WARNING: ACTION REQUIRED ⚠️
|
|
86
|
+
*
|
|
87
|
+
* Please register your handler in the IoC container (e.g. src/bootstrap.ts) and add its token to MyRegistry.
|
|
88
|
+
*
|
|
89
|
+
* 1. In src/registry.ts:
|
|
90
|
+
* ${tokenPrefix}_HANDLER: IHandler<${pascalName}Query, ${pascalName}Response>;
|
|
91
|
+
*
|
|
92
|
+
* 2. In src/bootstrap.ts:
|
|
93
|
+
* .addServices((config, register) => {
|
|
94
|
+
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
95
|
+
* });
|
|
96
|
+
*/
|
|
97
|
+
const fetch = async (): Promise<ResultType<${pascalName}Response>> => {
|
|
98
|
+
abortController = new AbortController();
|
|
99
|
+
|
|
100
|
+
if (loading.value) return Result.fail(AppError.create({
|
|
101
|
+
name: '${pascalName}Query',
|
|
102
|
+
message: '${pascalName}Query is already in progress',
|
|
103
|
+
code: '${tokenPrefix}_QUERY_ALREADY_IN_PROGRESS',
|
|
104
|
+
status: 500,
|
|
105
|
+
cause: new Error('${tokenPrefix}_QUERY_ALREADY_IN_PROGRESS'),
|
|
106
|
+
}));
|
|
107
|
+
loading.value = true;
|
|
108
|
+
error.value = null;
|
|
109
|
+
|
|
110
|
+
try {
|
|
111
|
+
try {
|
|
112
|
+
// Resolve mediator and handler from the container
|
|
113
|
+
const { mediator, ${tokenPrefix}_HANDLER: handler } = ServicesUtils.useApp();
|
|
114
|
+
const query = new ${pascalName}Query();
|
|
115
|
+
|
|
116
|
+
const result = await mediator.query(query, async () => {
|
|
117
|
+
return await handler.handle(query, abortController?.signal);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
if (result.isOk()) {
|
|
121
|
+
data.value = result.getValueOrThrow();
|
|
122
|
+
} else {
|
|
123
|
+
error.value = result.getErrorOrThrow().message;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return result;
|
|
127
|
+
} finally {
|
|
128
|
+
loading.value = false;
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const abort = () => {
|
|
133
|
+
if (abortController) {
|
|
134
|
+
abortController.abort();
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Automatically cancel pending operations when the component unmounts
|
|
139
|
+
onUnmounted(() => {
|
|
140
|
+
abort();
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
return { loading, error, data, fetch };
|
|
144
|
+
}
|
|
145
|
+
`
|
|
146
|
+
await this._fileService.writeFileRecursive(
|
|
147
|
+
path.join(componentDir, `use-${lowerName}.composable.ts`),
|
|
148
|
+
composableContent,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
// 3. Domain Model (Domain)
|
|
152
|
+
const modelContent = `export interface ${pascalName}Response {
|
|
153
|
+
// TODO: Define your response properties here
|
|
154
|
+
}
|
|
155
|
+
`
|
|
156
|
+
await this._fileService.writeFileRecursive(
|
|
157
|
+
path.join(componentDir, `${lowerName}.model.ts`),
|
|
158
|
+
modelContent,
|
|
159
|
+
)
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// src/infrastructure/generators/env-vue.generator.ts
|
|
2
|
+
import path from 'node:path'
|
|
3
|
+
|
|
4
|
+
import { type IFileService, type IGenerator } from '../../../domain'
|
|
5
|
+
import type { VueOptions } from '../../../shared'
|
|
6
|
+
|
|
7
|
+
export class EnvVueGenerator implements IGenerator<VueOptions> {
|
|
8
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
9
|
+
|
|
10
|
+
public async generate(projectPath: string, options: VueOptions): Promise<void> {
|
|
11
|
+
let content = `# ─────────────────────────────────────────────────────────────────────────────
|
|
12
|
+
# Xeno VUE APPLICATION ENVIRONMENT VARIABLES
|
|
13
|
+
# ─────────────────────────────────────────────────────────────────────────────
|
|
14
|
+
VITE_APP_ENV=development
|
|
15
|
+
`
|
|
16
|
+
|
|
17
|
+
if (options.axios) {
|
|
18
|
+
content += `\n# --- Axios ---
|
|
19
|
+
VITE_API_BASE_URL=http://localhost:3000
|
|
20
|
+
`
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (options.supabase) {
|
|
24
|
+
content += `\n# --- Authentication (Supabase) ---
|
|
25
|
+
VITE_SUPABASE_URL=https://your-project.supabase.co
|
|
26
|
+
VITE_SUPABASE_KEY=your-anon-key
|
|
27
|
+
`
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (options.sentry) {
|
|
31
|
+
content += `\n# --- Error Tracking (Sentry) ---
|
|
32
|
+
VITE_SENTRY_DSN=https://your-sentry-dsn@o0.ingest.sentry.io/0
|
|
33
|
+
`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
await this._fileService.writeFileRecursive(path.join(projectPath, '.env'), content)
|
|
37
|
+
await this._fileService.writeFileRecursive(path.join(projectPath, '.env.example'), content)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { type IFileService, type IGenerator } from '../../../domain'
|
|
4
|
+
import type { VueOptions } from '../../../shared'
|
|
5
|
+
|
|
6
|
+
export class IndexVueGenerator implements IGenerator<VueOptions> {
|
|
7
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
8
|
+
|
|
9
|
+
public async generate(projectPath: string, options: VueOptions): Promise<void> {
|
|
10
|
+
const indexHtml = `<!DOCTYPE html>
|
|
11
|
+
<html lang="en">
|
|
12
|
+
<head>
|
|
13
|
+
<meta charset="UTF-8" />
|
|
14
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
15
|
+
<title>${options.targetDir}</title>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="app"></div>
|
|
19
|
+
<script type="module" src="/src/main.ts"></script>
|
|
20
|
+
</body>
|
|
21
|
+
</html>`
|
|
22
|
+
await this._fileService.writeFileRecursive(path.join(projectPath, 'index.html'), indexHtml)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export * from './app-vue.generator'
|
|
2
|
+
export * from './bootstrap-ts.generator'
|
|
3
|
+
export * from './env.generator'
|
|
4
|
+
export * from './index-html.generator'
|
|
5
|
+
export * from './main.generator'
|
|
6
|
+
export * from './packagejson.generator'
|
|
7
|
+
export * from './readme.generator'
|
|
8
|
+
export * from './registry-ts.generator'
|
|
9
|
+
export * from './router.generator'
|
|
10
|
+
export * from './tailwind.generator'
|
|
11
|
+
export * from './tsconfig.generator'
|
|
12
|
+
export * from './vite-config.generator'
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import path from 'node:path'
|
|
2
|
+
|
|
3
|
+
import { type IFileService, type IGenerator } from '../../../domain'
|
|
4
|
+
import type { VueOptions } from '../../../shared'
|
|
5
|
+
|
|
6
|
+
export class MainGenerator implements IGenerator<VueOptions> {
|
|
7
|
+
constructor(private readonly _fileService: IFileService) {}
|
|
8
|
+
|
|
9
|
+
public async generate(projectPath: string, options: VueOptions): Promise<void> {
|
|
10
|
+
// 5. src/main.ts
|
|
11
|
+
const routerImport = options.router ? `\nimport router from './router';` : ''
|
|
12
|
+
const piniaImport = options.pinia ? `\nimport { createPinia } from 'pinia';` : ''
|
|
13
|
+
const tailwindImport = options.tailwind ? `\nimport './assets/style.css';` : ''
|
|
14
|
+
|
|
15
|
+
const mainTs = `import { createApp } from 'vue';
|
|
16
|
+
import App from './App.vue';
|
|
17
|
+
import { bootstrap } from './bootstrap';
|
|
18
|
+
import { XENO_SERVICES_KEY } from '@xeno-js/vue';${routerImport}${piniaImport}${tailwindImport}
|
|
19
|
+
|
|
20
|
+
async function mountApp() {
|
|
21
|
+
try {
|
|
22
|
+
const app = createApp(App);
|
|
23
|
+
${options.pinia ? '\n app.use(createPinia());' : ''}
|
|
24
|
+
const container = await bootstrap(app, router);
|
|
25
|
+
${options.router ? '\n app.use(router);' : ''}
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
// Provide the Xeno IoC container globally to all components
|
|
29
|
+
app.provide(XENO_SERVICES_KEY, container);
|
|
30
|
+
|
|
31
|
+
app.mount('#app');
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error('Critical error during frontend bootstrap:', error);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
mountApp();
|
|
38
|
+
`
|
|
39
|
+
await this._fileService.writeFileRecursive(path.join(projectPath, 'src', 'main.ts'), mainTs)
|
|
40
|
+
}
|
|
41
|
+
}
|