@xeno-js/cli 0.1.0 → 0.1.2
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/dist/index.js +3675 -18
- package/dist/index.js.map +1 -0
- package/eslint.config.mjs +1 -0
- package/package.json +4 -3
- package/tsup.config.ts +13 -0
- package/dist/domain/contracts/icommand-cli.contracts.js +0 -2
- package/dist/domain/contracts/icommand-runner.contracts.js +0 -2
- package/dist/domain/contracts/idispatch.contracts.js +0 -2
- package/dist/domain/contracts/ifile-service.contracts.js +0 -2
- package/dist/domain/contracts/igenerator.contracts.js +0 -2
- package/dist/domain/contracts/index.js +0 -2
- package/dist/domain/contracts/iscaffold-strategy.contracts.js +0 -2
- package/dist/domain/index.js +0 -2
- package/dist/generator.contracts.js +0 -13
- package/dist/infrastructure/bootstrap/bootstrapper.js +0 -32
- package/dist/infrastructure/bootstrap/index.js +0 -17
- package/dist/infrastructure/builder/builder.js +0 -16
- package/dist/infrastructure/builder/builder.test.js +0 -30
- package/dist/infrastructure/builder/index.js +0 -17
- package/dist/infrastructure/command_launcher/command.utils.js +0 -30
- package/dist/infrastructure/command_launcher/index.js +0 -2
- package/dist/infrastructure/commands/command-runner.service.js +0 -36
- package/dist/infrastructure/commands/generate.command.js +0 -48
- package/dist/infrastructure/commands/help.command.js +0 -44
- package/dist/infrastructure/commands/index.js +0 -20
- package/dist/infrastructure/commands/new-project.command.js +0 -43
- package/dist/infrastructure/dispatcher/dispatcher.js +0 -39
- package/dist/infrastructure/dispatcher/index.js +0 -17
- package/dist/infrastructure/file/file.service.js +0 -13
- package/dist/infrastructure/file/index.js +0 -17
- package/dist/infrastructure/generators/bootstrap.generator.js +0 -118
- package/dist/infrastructure/generators/core/bootstrap.generator.js +0 -118
- package/dist/infrastructure/generators/core/command/cqrs.generator.js +0 -49
- package/dist/infrastructure/generators/core/command/generate-command.generator.js +0 -163
- package/dist/infrastructure/generators/core/command/generate-query.generator.js +0 -131
- package/dist/infrastructure/generators/core/drizzle-sql-lite.generator.js +0 -78
- package/dist/infrastructure/generators/core/drizzle.generator.js +0 -85
- package/dist/infrastructure/generators/core/env.generator.js +0 -69
- package/dist/infrastructure/generators/core/index.js +0 -25
- package/dist/infrastructure/generators/core/main.generator.js +0 -45
- package/dist/infrastructure/generators/core/packagejson.generator.js +0 -78
- package/dist/infrastructure/generators/core/readme.generator.js +0 -431
- package/dist/infrastructure/generators/core/registry.generator.js +0 -46
- package/dist/infrastructure/generators/core/tsconfig.generator.js +0 -41
- package/dist/infrastructure/generators/core-packagejson.generator.js +0 -78
- package/dist/infrastructure/generators/drizzle-sql-lite.generator.js +0 -78
- package/dist/infrastructure/generators/drizzle.generator.js +0 -85
- package/dist/infrastructure/generators/env-vue.generator.js +0 -40
- package/dist/infrastructure/generators/env.generator.js +0 -71
- package/dist/infrastructure/generators/gitignore.generator.js +0 -62
- package/dist/infrastructure/generators/index.js +0 -26
- package/dist/infrastructure/generators/main.generator.js +0 -45
- package/dist/infrastructure/generators/readme.generator.js +0 -431
- package/dist/infrastructure/generators/registry.generator.js +0 -46
- package/dist/infrastructure/generators/tsconfig-vue.generator.js +0 -39
- package/dist/infrastructure/generators/tsconfig.generator.js +0 -41
- package/dist/infrastructure/generators/vite-config.generator.js +0 -39
- package/dist/infrastructure/generators/vue/app-vue.generator.js +0 -26
- package/dist/infrastructure/generators/vue/bootstrap-ts.generator.js +0 -59
- package/dist/infrastructure/generators/vue/command_query/cqrs.generator.js +0 -36
- package/dist/infrastructure/generators/vue/command_query/generate-command-vue.generator.js +0 -139
- package/dist/infrastructure/generators/vue/command_query/generate-query-vue.generator.js +0 -147
- package/dist/infrastructure/generators/vue/env.generator.js +0 -40
- package/dist/infrastructure/generators/vue/index-html.generator.js +0 -29
- package/dist/infrastructure/generators/vue/index.js +0 -28
- package/dist/infrastructure/generators/vue/main.generator.js +0 -45
- package/dist/infrastructure/generators/vue/packagejson.generator.js +0 -59
- package/dist/infrastructure/generators/vue/readme.generator.js +0 -394
- package/dist/infrastructure/generators/vue/registry-ts.generator.js +0 -28
- package/dist/infrastructure/generators/vue/router.generator.js +0 -33
- package/dist/infrastructure/generators/vue/tailwind.generator.js +0 -19
- package/dist/infrastructure/generators/vue/tsconfig.generator.js +0 -39
- package/dist/infrastructure/generators/vue/use-app.generator.js +0 -43
- package/dist/infrastructure/generators/vue/vite-config.generator.js +0 -39
- package/dist/infrastructure/generators/vue-packagejson.generator.js +0 -59
- package/dist/infrastructure/generators/vue-readme.generator.js +0 -53
- package/dist/infrastructure/generators/vue-source-files.generator.js +0 -136
- package/dist/infrastructure/index.js +0 -21
- package/dist/infrastructure/strategies/core-scaffold.strategy.js +0 -99
- package/dist/infrastructure/strategies/index.js +0 -18
- package/dist/infrastructure/strategies/vue-scaffold.strategy.js +0 -79
- package/dist/scaffolding.engine.js +0 -60
- package/dist/shared/constants/command.constants.js +0 -19
- package/dist/shared/constants/core.constants.js +0 -10
- package/dist/shared/constants/index.js +0 -18
- package/dist/shared/index.js +0 -18
- package/dist/shared/types/common.types.js +0 -2
- package/dist/shared/types/index.js +0 -2
- package/dist/shared/utils/guards.utils.js +0 -310
- package/dist/shared/utils/index.js +0 -18
- package/dist/shared/utils/string.utils.js +0 -62
package/dist/index.js
CHANGED
|
@@ -1,11 +1,3680 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
var
|
|
4
|
-
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __esm = (fn, res) => function __init() {
|
|
10
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
5
11
|
};
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
12
|
+
var __export = (target, all) => {
|
|
13
|
+
for (var name in all)
|
|
14
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
15
|
+
};
|
|
16
|
+
var __copyProps = (to, from, except, desc) => {
|
|
17
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
18
|
+
for (let key of __getOwnPropNames(from))
|
|
19
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
20
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
21
|
+
}
|
|
22
|
+
return to;
|
|
23
|
+
};
|
|
24
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
25
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
26
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
27
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
28
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
29
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
30
|
+
mod
|
|
31
|
+
));
|
|
32
|
+
|
|
33
|
+
// src/shared/constants/command.constants.ts
|
|
34
|
+
var ARGS_CONSTANTS, COMMAND_CONSTANTS, TARGET_ENV_CONSTANTS;
|
|
35
|
+
var init_command_constants = __esm({
|
|
36
|
+
"src/shared/constants/command.constants.ts"() {
|
|
37
|
+
"use strict";
|
|
38
|
+
ARGS_CONSTANTS = Object.freeze({
|
|
39
|
+
HELP: "--help",
|
|
40
|
+
VUE: "--vue",
|
|
41
|
+
CORE: "--core",
|
|
42
|
+
CONSOLE: "--console"
|
|
43
|
+
});
|
|
44
|
+
COMMAND_CONSTANTS = Object.freeze({
|
|
45
|
+
HELP: "help",
|
|
46
|
+
COMMAND: "command",
|
|
47
|
+
QUERY: "query"
|
|
48
|
+
});
|
|
49
|
+
TARGET_ENV_CONSTANTS = Object.freeze({
|
|
50
|
+
CONSOLE: "console",
|
|
51
|
+
VERCEL: "vercel",
|
|
52
|
+
FASTIFY: "fastify"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// src/shared/constants/core.constants.ts
|
|
58
|
+
var CORE_CONSTANTS;
|
|
59
|
+
var init_core_constants = __esm({
|
|
60
|
+
"src/shared/constants/core.constants.ts"() {
|
|
61
|
+
"use strict";
|
|
62
|
+
CORE_CONSTANTS = Object.freeze({
|
|
63
|
+
VERCEL: "vercel",
|
|
64
|
+
FASTIFY: "fastify",
|
|
65
|
+
DRIZZLE: "drizzle",
|
|
66
|
+
SQL_LITE: "sqlLite",
|
|
67
|
+
NONE: "none"
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// src/shared/constants/index.ts
|
|
73
|
+
var init_constants = __esm({
|
|
74
|
+
"src/shared/constants/index.ts"() {
|
|
75
|
+
"use strict";
|
|
76
|
+
init_command_constants();
|
|
77
|
+
init_core_constants();
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// src/shared/utils/guards.utils.ts
|
|
82
|
+
var OBJECT_TAG, DATE_TAG, Guards;
|
|
83
|
+
var init_guards_utils = __esm({
|
|
84
|
+
"src/shared/utils/guards.utils.ts"() {
|
|
85
|
+
"use strict";
|
|
86
|
+
OBJECT_TAG = "[object Object]";
|
|
87
|
+
DATE_TAG = "[object Date]";
|
|
88
|
+
Guards = Object.freeze({
|
|
89
|
+
/**
|
|
90
|
+
* @description Checks value is neither null nor undefined.
|
|
91
|
+
* @param value Candidate value.
|
|
92
|
+
* @returns True when value is defined.
|
|
93
|
+
|
|
94
|
+
*
|
|
95
|
+
* @author Xeno
|
|
96
|
+
* @version 1.0.0
|
|
97
|
+
* @since 2025-09-30
|
|
98
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
99
|
+
*/
|
|
100
|
+
isDefined(value) {
|
|
101
|
+
return value !== null && value !== void 0 && value !== "" && !Number.isNaN(value);
|
|
102
|
+
},
|
|
103
|
+
/**
|
|
104
|
+
* @description Checks value is null, undefined, empty string, or false.
|
|
105
|
+
* @param value Candidate value.
|
|
106
|
+
* @returns True when value is null, undefined, empty string, or false.
|
|
107
|
+
|
|
108
|
+
*
|
|
109
|
+
* @author Xeno
|
|
110
|
+
* @version 1.0.0
|
|
111
|
+
* @since 2025-09-30
|
|
112
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
113
|
+
*/
|
|
114
|
+
isNullOrEmpty(value) {
|
|
115
|
+
return !Guards.isDefined(value) || Guards.isString(value) && value.trim() === "" || Guards.isArray(value) && value.length === 0;
|
|
116
|
+
},
|
|
117
|
+
/**
|
|
118
|
+
* @description Throws an error if the value is null, undefined, empty string, or false.
|
|
119
|
+
* @param value Candidate value.
|
|
120
|
+
* @param errorMessage Error message to throw if the check fails.
|
|
121
|
+
* @throws Error with the provided message if the value is null, undefined, empty string, or false.
|
|
122
|
+
|
|
123
|
+
*
|
|
124
|
+
* @author Xeno
|
|
125
|
+
* @version 1.0.0
|
|
126
|
+
* @since 2025-09-30
|
|
127
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
128
|
+
*/
|
|
129
|
+
throwIfNullOrEmpty(value, errorMessage) {
|
|
130
|
+
if (Guards.isNullOrEmpty(value)) {
|
|
131
|
+
throw new Error(errorMessage);
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
/**
|
|
135
|
+
* @description Throws an error if the value is not a positive integer.
|
|
136
|
+
* @param value Candidate value.
|
|
137
|
+
* @param errorMessage Error message to throw if the check fails.
|
|
138
|
+
* @throws Error with the provided message if the value is not a positive integer.
|
|
139
|
+
|
|
140
|
+
*
|
|
141
|
+
* @author Xeno
|
|
142
|
+
* @version 1.0.0
|
|
143
|
+
* @since 2025-09-30
|
|
144
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
145
|
+
*/
|
|
146
|
+
throwIfNegative(value, errorMessage) {
|
|
147
|
+
if (Guards.isInteger(value) && value < 0) {
|
|
148
|
+
throw new Error(errorMessage);
|
|
149
|
+
}
|
|
150
|
+
},
|
|
151
|
+
/**
|
|
152
|
+
* @description Throws an error if the value is not an integer.
|
|
153
|
+
* @param value Candidate value.
|
|
154
|
+
* @param errorMessage Error message to throw if the check fails.
|
|
155
|
+
* @throws Error with the provided message if the value is not an integer.
|
|
156
|
+
|
|
157
|
+
*
|
|
158
|
+
* @author Xeno
|
|
159
|
+
* @version 1.0.0
|
|
160
|
+
* @since 2025-09-30
|
|
161
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
162
|
+
*/
|
|
163
|
+
throwIfNotInteger(value, errorMessage) {
|
|
164
|
+
if (!Guards.isInteger(value)) {
|
|
165
|
+
throw new Error(errorMessage);
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
/**
|
|
169
|
+
* @description Checks if an object has a method with the given name.
|
|
170
|
+
* @param obj Object to check.
|
|
171
|
+
* @param methodName Name of the method to look for.
|
|
172
|
+
* @returns True when obj has a function property named methodName.
|
|
173
|
+
|
|
174
|
+
*
|
|
175
|
+
* @author Xeno
|
|
176
|
+
* @version 1.0.0
|
|
177
|
+
* @since 2025-09-30
|
|
178
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
179
|
+
*/
|
|
180
|
+
hasMethod(obj, methodName) {
|
|
181
|
+
if (!this.isDefined(obj)) return false;
|
|
182
|
+
return Guards.isFunction(obj[methodName]);
|
|
183
|
+
},
|
|
184
|
+
/**
|
|
185
|
+
* @description Checks value is a string.
|
|
186
|
+
* @param value Candidate value.
|
|
187
|
+
* @returns True when value is string.
|
|
188
|
+
|
|
189
|
+
*
|
|
190
|
+
* @author Xeno
|
|
191
|
+
* @version 1.0.0
|
|
192
|
+
* @since 2025-09-30
|
|
193
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
194
|
+
*/
|
|
195
|
+
isString(value) {
|
|
196
|
+
return typeof value === "string";
|
|
197
|
+
},
|
|
198
|
+
/**
|
|
199
|
+
* @description Checks value is a finite number.
|
|
200
|
+
* @param value Candidate value.
|
|
201
|
+
* @returns True when value is finite number.
|
|
202
|
+
|
|
203
|
+
*
|
|
204
|
+
* @author Xeno
|
|
205
|
+
* @version 1.0.0
|
|
206
|
+
* @since 2025-09-30
|
|
207
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
208
|
+
*/
|
|
209
|
+
isNumber(value) {
|
|
210
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
211
|
+
},
|
|
212
|
+
/**
|
|
213
|
+
* @description Checks value is an integer number.
|
|
214
|
+
* @param value Candidate value.
|
|
215
|
+
* @returns True when value is integer number.
|
|
216
|
+
|
|
217
|
+
*
|
|
218
|
+
* @author Xeno
|
|
219
|
+
* @version 1.0.0
|
|
220
|
+
* @since 2025-09-30
|
|
221
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
222
|
+
*/
|
|
223
|
+
isInteger(value) {
|
|
224
|
+
return Guards.isNumber(value) && Number.isInteger(value);
|
|
225
|
+
},
|
|
226
|
+
/**
|
|
227
|
+
* @description Checks value is boolean.
|
|
228
|
+
* @param value Candidate value.
|
|
229
|
+
* @returns True when value is boolean.
|
|
230
|
+
|
|
231
|
+
*
|
|
232
|
+
* @author Xeno
|
|
233
|
+
* @version 1.0.0
|
|
234
|
+
* @since 2025-09-30
|
|
235
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
236
|
+
*/
|
|
237
|
+
isBoolean(value) {
|
|
238
|
+
return typeof value === "boolean";
|
|
239
|
+
},
|
|
240
|
+
/**
|
|
241
|
+
* @description Checks value is bigint.
|
|
242
|
+
* @param value Candidate value.
|
|
243
|
+
* @returns True when value is bigint.
|
|
244
|
+
|
|
245
|
+
*
|
|
246
|
+
* @author Xeno
|
|
247
|
+
* @version 1.0.0
|
|
248
|
+
* @since 2025-09-30
|
|
249
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
250
|
+
*/
|
|
251
|
+
isBigInt(value) {
|
|
252
|
+
return typeof value === "bigint";
|
|
253
|
+
},
|
|
254
|
+
/**
|
|
255
|
+
* @description Checks value is symbol.
|
|
256
|
+
* @param value Candidate value.
|
|
257
|
+
* @returns True when value is symbol.
|
|
258
|
+
|
|
259
|
+
*
|
|
260
|
+
* @author Xeno
|
|
261
|
+
* @version 1.0.0
|
|
262
|
+
* @since 2025-09-30
|
|
263
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
264
|
+
*/
|
|
265
|
+
isSymbol(value) {
|
|
266
|
+
return typeof value === "symbol";
|
|
267
|
+
},
|
|
268
|
+
/**
|
|
269
|
+
* @description Checks value is a function.
|
|
270
|
+
* @param value Candidate value.
|
|
271
|
+
* @returns True when value is function.
|
|
272
|
+
|
|
273
|
+
*
|
|
274
|
+
* @author Xeno
|
|
275
|
+
* @version 1.0.0
|
|
276
|
+
* @since 2025-09-30
|
|
277
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
278
|
+
*/
|
|
279
|
+
isFunction(value) {
|
|
280
|
+
return typeof value === "function";
|
|
281
|
+
},
|
|
282
|
+
/**
|
|
283
|
+
* @description Checks value is an array.
|
|
284
|
+
* @param value Candidate value.
|
|
285
|
+
* @returns True when value is array.
|
|
286
|
+
|
|
287
|
+
*
|
|
288
|
+
* @author Xeno
|
|
289
|
+
* @version 1.0.0
|
|
290
|
+
* @since 2025-09-30
|
|
291
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
292
|
+
*/
|
|
293
|
+
isArray(value) {
|
|
294
|
+
return Array.isArray(value);
|
|
295
|
+
},
|
|
296
|
+
/**
|
|
297
|
+
* @description Checks value is a Date instance with valid timestamp.
|
|
298
|
+
* @param value Candidate value.
|
|
299
|
+
* @returns True when value is valid Date.
|
|
300
|
+
|
|
301
|
+
*
|
|
302
|
+
* @author Xeno
|
|
303
|
+
* @version 1.0.0
|
|
304
|
+
* @since 2025-09-30
|
|
305
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
306
|
+
*/
|
|
307
|
+
isDate(value) {
|
|
308
|
+
if (Object.prototype.toString.call(value) !== DATE_TAG) {
|
|
309
|
+
return false;
|
|
310
|
+
}
|
|
311
|
+
return Number.isFinite(value.getTime());
|
|
312
|
+
},
|
|
313
|
+
/**
|
|
314
|
+
* @description Checks value is an Error instance.
|
|
315
|
+
* @param value Candidate value.
|
|
316
|
+
* @returns True when value is Error.
|
|
317
|
+
|
|
318
|
+
*
|
|
319
|
+
* @author Xeno
|
|
320
|
+
* @version 1.0.0
|
|
321
|
+
* @since 2025-09-30
|
|
322
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
323
|
+
*/
|
|
324
|
+
isError(value) {
|
|
325
|
+
return value instanceof Error;
|
|
326
|
+
},
|
|
327
|
+
/**
|
|
328
|
+
* @description Checks value is a plain object record.
|
|
329
|
+
* @param value Candidate value.
|
|
330
|
+
* @returns True when value is object record.
|
|
331
|
+
|
|
332
|
+
*
|
|
333
|
+
* @author Xeno
|
|
334
|
+
* @version 1.0.0
|
|
335
|
+
* @since 2025-09-30
|
|
336
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
337
|
+
*/
|
|
338
|
+
isObjectRecord(value) {
|
|
339
|
+
if (!Guards.isDefined(value)) {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
return Object.prototype.toString.call(value) === OBJECT_TAG;
|
|
343
|
+
},
|
|
344
|
+
/**
|
|
345
|
+
* @description Checks value is an object (not null).
|
|
346
|
+
* @param value Candidate value.
|
|
347
|
+
* @returns True when value is object.
|
|
348
|
+
|
|
349
|
+
*
|
|
350
|
+
* @author Xeno
|
|
351
|
+
* @version 1.0.0
|
|
352
|
+
* @since 2025-09-30
|
|
353
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
354
|
+
*/
|
|
355
|
+
isObject(value) {
|
|
356
|
+
return typeof value === "object" && Guards.isDefined(value);
|
|
357
|
+
},
|
|
358
|
+
/**
|
|
359
|
+
* @description Checks value is PromiseLike.
|
|
360
|
+
* @param value Candidate value.
|
|
361
|
+
* @returns True when value has then function.
|
|
362
|
+
|
|
363
|
+
*
|
|
364
|
+
* @author Xeno
|
|
365
|
+
* @version 1.0.0
|
|
366
|
+
* @since 2025-09-30
|
|
367
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
368
|
+
*/
|
|
369
|
+
isPromiseLike(value) {
|
|
370
|
+
if (!Guards.isDefined(value)) {
|
|
371
|
+
return false;
|
|
372
|
+
}
|
|
373
|
+
if (!Guards.isObject(value) && !Guards.isFunction(value)) {
|
|
374
|
+
return false;
|
|
375
|
+
}
|
|
376
|
+
const thenMember = value["then"];
|
|
377
|
+
return Guards.isFunction(thenMember);
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
// src/shared/utils/string.utils.ts
|
|
384
|
+
var StringUtils;
|
|
385
|
+
var init_string_utils = __esm({
|
|
386
|
+
"src/shared/utils/string.utils.ts"() {
|
|
387
|
+
"use strict";
|
|
388
|
+
init_guards_utils();
|
|
389
|
+
StringUtils = Object.freeze({
|
|
390
|
+
/**
|
|
391
|
+
* @description Inserisce un underscore (_) tra la fine di una parola e l'inizio della successiva in formato camelCase o PascalCase.
|
|
392
|
+
* @param input Stringa in input (es. "CreateUser").
|
|
393
|
+
* @returns Stringa separata da underscore (es. "Create_User").
|
|
394
|
+
*
|
|
395
|
+
* @author Xeno
|
|
396
|
+
* @version 1.0.0
|
|
397
|
+
* @since 2026-09-18
|
|
398
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
399
|
+
*/
|
|
400
|
+
splitCamelCaseWithUnderscore(input) {
|
|
401
|
+
if (!Guards.isString(input) || Guards.isNullOrEmpty(input)) return input;
|
|
402
|
+
return input.replace(/([a-z])([A-Z])/g, "$1_$2");
|
|
403
|
+
},
|
|
404
|
+
/**
|
|
405
|
+
* @description Converts a string to camelCase.
|
|
406
|
+
* @param input Input string (supports snake_case, kebab-case, or space-separated).
|
|
407
|
+
* @returns camelCase string.
|
|
408
|
+
|
|
409
|
+
*
|
|
410
|
+
* @author Xeno
|
|
411
|
+
* @version 1.0.0
|
|
412
|
+
* @since 2025-09-30
|
|
413
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
414
|
+
*/
|
|
415
|
+
camelCase(input) {
|
|
416
|
+
const segments = input.split(/[-_\s]+/);
|
|
417
|
+
const [first, ...rest] = segments;
|
|
418
|
+
const head = Guards.isDefined(first) ? first.toLowerCase() : "";
|
|
419
|
+
return head + rest.map((seg) => seg.charAt(0).toUpperCase() + seg.slice(1)).join("");
|
|
420
|
+
},
|
|
421
|
+
/**
|
|
422
|
+
* @description Converts a string to PascalCase.
|
|
423
|
+
* @param input Input string (supports snake_case, kebab-case, space-separated, or camelCase).
|
|
424
|
+
* @returns PascalCase string.
|
|
425
|
+
*
|
|
426
|
+
* @author Xeno
|
|
427
|
+
* @version 1.0.0
|
|
428
|
+
* @since 2026-09-18
|
|
429
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
430
|
+
*/
|
|
431
|
+
pascalCase(input) {
|
|
432
|
+
if (!Guards.isString(input) || Guards.isNullOrEmpty(input)) return "";
|
|
433
|
+
const segments = input.split(/[-_\s]+/);
|
|
434
|
+
return segments.filter((seg) => seg.length > 0).map((seg) => {
|
|
435
|
+
const normalized = seg === seg.toUpperCase() ? seg.toLowerCase() : seg;
|
|
436
|
+
return normalized.charAt(0).toUpperCase() + normalized.slice(1);
|
|
437
|
+
}).join("");
|
|
438
|
+
}
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// src/shared/utils/index.ts
|
|
444
|
+
var init_utils = __esm({
|
|
445
|
+
"src/shared/utils/index.ts"() {
|
|
446
|
+
"use strict";
|
|
447
|
+
init_guards_utils();
|
|
448
|
+
init_string_utils();
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// src/shared/index.ts
|
|
453
|
+
var init_shared = __esm({
|
|
454
|
+
"src/shared/index.ts"() {
|
|
455
|
+
"use strict";
|
|
456
|
+
init_constants();
|
|
457
|
+
init_utils();
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// src/infrastructure/generators/core/command/generate-command.generator.ts
|
|
462
|
+
var generate_command_generator_exports = {};
|
|
463
|
+
__export(generate_command_generator_exports, {
|
|
464
|
+
GenerateCommandCoreGenerator: () => GenerateCommandCoreGenerator
|
|
465
|
+
});
|
|
466
|
+
var import_node_path, GenerateCommandCoreGenerator;
|
|
467
|
+
var init_generate_command_generator = __esm({
|
|
468
|
+
"src/infrastructure/generators/core/command/generate-command.generator.ts"() {
|
|
469
|
+
"use strict";
|
|
470
|
+
import_node_path = __toESM(require("path"));
|
|
471
|
+
GenerateCommandCoreGenerator = class {
|
|
472
|
+
constructor(_fileService) {
|
|
473
|
+
this._fileService = _fileService;
|
|
474
|
+
}
|
|
475
|
+
_fileService;
|
|
476
|
+
async generate(pascalName, lowerName, tokenPrefix, componentDir, hasZod, hasDrizzle) {
|
|
477
|
+
const commandContent = `import { Command } from '@xeno-js/core';
|
|
478
|
+
|
|
479
|
+
/*
|
|
480
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
481
|
+
* Please replace <any> with your specific payload and response types.
|
|
482
|
+
*/
|
|
483
|
+
export class ${pascalName}Command extends Command<any> {
|
|
484
|
+
constructor(public readonly payload: any) {
|
|
485
|
+
super('${tokenPrefix}_COMMAND_HANDLER');
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
`;
|
|
489
|
+
await this._fileService.writeFileRecursive(
|
|
490
|
+
import_node_path.default.join(componentDir, `${lowerName}.command.ts`),
|
|
491
|
+
commandContent
|
|
492
|
+
);
|
|
493
|
+
const handlerContent = `import type { IFactory, ResultType, UserContext } from '@xeno-js/core';
|
|
494
|
+
import { AppError, BaseHandler, Result } from '@xeno-js/core';
|
|
495
|
+
import { ${pascalName}Command } from './${lowerName}.command';
|
|
496
|
+
|
|
497
|
+
/*
|
|
498
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
499
|
+
* Please replace <any> with your specific response type.
|
|
500
|
+
*/
|
|
501
|
+
export class ${pascalName}Handler extends BaseHandler<${pascalName}Command, any> {
|
|
502
|
+
constructor(
|
|
503
|
+
identityFactory: IFactory<void, UserContext>
|
|
504
|
+
) {
|
|
505
|
+
super(identityFactory);
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
public async executeAsync(request: ${pascalName}Command, singal: AbortSignal): Promise<ResultType<any>> {
|
|
509
|
+
// TODO: Implement your business logic here
|
|
510
|
+
|
|
511
|
+
return Result.ok();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
`;
|
|
515
|
+
await this._fileService.writeFileRecursive(
|
|
516
|
+
import_node_path.default.join(componentDir, `${lowerName}.handler.ts`),
|
|
517
|
+
handlerContent
|
|
518
|
+
);
|
|
519
|
+
const controllerContent = `import type { IContextAccessor, IMediator, RequestContext, ResponseDto } from '@xeno-js/core';
|
|
520
|
+
import { BaseController } from '@xeno-js/core';
|
|
521
|
+
import { ${pascalName}Command } from './${lowerName}.command';
|
|
522
|
+
|
|
523
|
+
/*
|
|
524
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
525
|
+
* Please replace <any, any> with your specific request and response types.
|
|
526
|
+
*/
|
|
527
|
+
export class ${pascalName}Controller extends BaseController<any, any> {
|
|
528
|
+
constructor(
|
|
529
|
+
requestContext: IContextAccessor<RequestContext>,
|
|
530
|
+
mediator: IMediator,
|
|
531
|
+
) {
|
|
532
|
+
super(requestContext, mediator);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
public async handle(request: any): Promise<ResponseDto<any>> {
|
|
536
|
+
const cmd = new ${pascalName}Command(request);
|
|
537
|
+
const result = await this._send(cmd);
|
|
538
|
+
|
|
539
|
+
if (!result.isOk()) {
|
|
540
|
+
return this.fail(result.getErrorOrThrow(), 'Error during ${pascalName} operation');
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
return this.ok(result.getValueOrThrow());
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
`;
|
|
547
|
+
await this._fileService.writeFileRecursive(
|
|
548
|
+
import_node_path.default.join(componentDir, `${lowerName}.controller.ts`),
|
|
549
|
+
controllerContent
|
|
550
|
+
);
|
|
551
|
+
const entityContent = `import { Entity } from '@xeno-js/core';
|
|
552
|
+
|
|
553
|
+
export interface ${pascalName}Props {
|
|
554
|
+
// TODO: Define your entity properties
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/*
|
|
558
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
559
|
+
* Adjust the properties and types according to your domain logic.
|
|
560
|
+
*/
|
|
561
|
+
export class ${pascalName} extends Entity<${pascalName}Props> {
|
|
562
|
+
private constructor(props: ${pascalName}Props, id?: string) {
|
|
563
|
+
super(props, id);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
static create(props: ${pascalName}Props, id?: string): ${pascalName} {
|
|
567
|
+
return new ${pascalName}(props, id);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
`;
|
|
571
|
+
await this._fileService.writeFileRecursive(
|
|
572
|
+
import_node_path.default.join(componentDir, `${lowerName}.entity.ts`),
|
|
573
|
+
entityContent
|
|
574
|
+
);
|
|
575
|
+
if (hasZod) {
|
|
576
|
+
const zodContent = `import { z } from 'zod';
|
|
577
|
+
import { ZodUtils } from '@xeno-js/core';
|
|
578
|
+
|
|
579
|
+
/*
|
|
580
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
581
|
+
* Define the actual Zod validation schema for your command payload.
|
|
582
|
+
*/
|
|
583
|
+
export const ${pascalName}Schema = ZodUtils.createCommandSchema({
|
|
584
|
+
payload: z.any() // TODO: Update with strict validation
|
|
585
|
+
});
|
|
586
|
+
`;
|
|
587
|
+
await this._fileService.writeFileRecursive(
|
|
588
|
+
import_node_path.default.join(componentDir, `${lowerName}.schema-zod.ts`),
|
|
589
|
+
zodContent
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (hasDrizzle) {
|
|
593
|
+
const dbContent = `import { pgTable, text, timestamp } from 'drizzle-orm/pg-core';
|
|
594
|
+
// Note: If you are using SQLite, change the import to 'drizzle-orm/sqlite-core' and update the table definition.
|
|
595
|
+
|
|
596
|
+
/*
|
|
597
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
598
|
+
* Update the schema definition with your actual database columns.
|
|
599
|
+
*/
|
|
600
|
+
export const ${lowerName}s = pgTable('${lowerName}s', {
|
|
601
|
+
id: text('id').primaryKey(),
|
|
602
|
+
createdAt: timestamp('created_at').defaultNow(),
|
|
603
|
+
updatedAt: timestamp('updated_at').defaultNow().$onUpdate(() => new Date()),
|
|
604
|
+
});
|
|
605
|
+
|
|
606
|
+
export type ${pascalName}Dto = typeof ${lowerName}s.$inferSelect;
|
|
607
|
+
export type New${pascalName}Dto = typeof ${lowerName}s.$inferInsert;
|
|
608
|
+
`;
|
|
609
|
+
await this._fileService.writeFileRecursive(
|
|
610
|
+
import_node_path.default.join(componentDir, `${lowerName}.schema-db.ts`),
|
|
611
|
+
dbContent
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
const moduleContent = `import type { IServiceContainer, IConfigurationService } from '@xeno-js/core';
|
|
615
|
+
import { TOKENS } from '@xeno-js/core';
|
|
616
|
+
import { ${pascalName}Controller } from './${lowerName}.controller';
|
|
617
|
+
import { ${pascalName}Handler } from './${lowerName}.handler';
|
|
618
|
+
|
|
619
|
+
export const ${pascalName}Module = Object.freeze({
|
|
620
|
+
register(opts: IServiceContainer<any>, _config: IConfigurationService): void {
|
|
621
|
+
opts.addTransient('${tokenPrefix}_COMMAND_CONTROLLER', (c) => new ${pascalName}Controller(c.resolve(TOKENS.REQUEST_CONTEXT), c.resolve(TOKENS.MEDIATOR)));
|
|
622
|
+
opts.addScoped('${tokenPrefix}_COMMAND_HANDLER', (c) => new ${pascalName}Handler(c.resolve(TOKENS.USER_CONTEXT_FACTORY)));
|
|
623
|
+
|
|
624
|
+
/*
|
|
625
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
626
|
+
* Please copy and paste the following tokens to your MyRegistry interface (usually in src/registry.ts):
|
|
627
|
+
*
|
|
628
|
+
* ${tokenPrefix}_COMMAND_CONTROLLER: IController<any, any>;
|
|
629
|
+
* ${tokenPrefix}_COMMAND_HANDLER: IHandler<${pascalName}Command, any>;
|
|
630
|
+
*/
|
|
631
|
+
}
|
|
632
|
+
} as const);
|
|
633
|
+
`;
|
|
634
|
+
await this._fileService.writeFileRecursive(
|
|
635
|
+
import_node_path.default.join(componentDir, `${lowerName}.module.ts`),
|
|
636
|
+
moduleContent
|
|
637
|
+
);
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// src/infrastructure/generators/core/command/generate-query.generator.ts
|
|
644
|
+
var generate_query_generator_exports = {};
|
|
645
|
+
__export(generate_query_generator_exports, {
|
|
646
|
+
GenerateQueryCoreGenerator: () => GenerateQueryCoreGenerator
|
|
647
|
+
});
|
|
648
|
+
var import_node_path2, GenerateQueryCoreGenerator;
|
|
649
|
+
var init_generate_query_generator = __esm({
|
|
650
|
+
"src/infrastructure/generators/core/command/generate-query.generator.ts"() {
|
|
651
|
+
"use strict";
|
|
652
|
+
import_node_path2 = __toESM(require("path"));
|
|
653
|
+
GenerateQueryCoreGenerator = class {
|
|
654
|
+
constructor(_fileService) {
|
|
655
|
+
this._fileService = _fileService;
|
|
656
|
+
}
|
|
657
|
+
_fileService;
|
|
658
|
+
async generate(pascalName, lowerName, tokenPrefix, componentDir, hasZod) {
|
|
659
|
+
const queryContent = `import { BaseQuery } from '@xeno-js/core';
|
|
660
|
+
|
|
661
|
+
/*
|
|
662
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
663
|
+
* Please replace <any> with your specific payload and response types.
|
|
664
|
+
*/
|
|
665
|
+
export class ${pascalName}Query extends BaseQuery<any> {
|
|
666
|
+
constructor(public readonly payload: any) {
|
|
667
|
+
super(
|
|
668
|
+
'${tokenPrefix}_QUERY_HANDLER',
|
|
669
|
+
// Default cache options:
|
|
670
|
+
{
|
|
671
|
+
ttl: 60,
|
|
672
|
+
cacheKey: \`${lowerName}:\${JSON.stringify(payload)}\`,
|
|
673
|
+
bypassCache: false,
|
|
674
|
+
consistentRead: false
|
|
675
|
+
isUserScoped: false,
|
|
676
|
+
}
|
|
677
|
+
);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
`;
|
|
681
|
+
await this._fileService.writeFileRecursive(
|
|
682
|
+
import_node_path2.default.join(componentDir, `${lowerName}.query.ts`),
|
|
683
|
+
queryContent
|
|
684
|
+
);
|
|
685
|
+
const handlerContent = `import type { IFactory, ResultType, UserContext } from '@xeno-js/core';
|
|
686
|
+
import { AppError, BaseHandler, Result } from '@xeno-js/core';
|
|
687
|
+
import { ${pascalName}Query } from './${lowerName}.query';
|
|
688
|
+
|
|
689
|
+
/*
|
|
690
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
691
|
+
* Please replace <any> with your specific response type.
|
|
692
|
+
*/
|
|
693
|
+
export class ${pascalName}Handler extends BaseHandler<${pascalName}Query, any> {
|
|
694
|
+
constructor(
|
|
695
|
+
identityFactory: IFactory<void, UserContext>
|
|
696
|
+
// TODO: Inject your ReadDao or DataSource here
|
|
697
|
+
) {
|
|
698
|
+
super(identityFactory);
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
public async executeAsync(request: ${pascalName}Query, singal: AbortSignal): Promise<ResultType<any>> {
|
|
702
|
+
// TODO: Implement your query logic here
|
|
703
|
+
|
|
704
|
+
return Result.ok();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
`;
|
|
708
|
+
await this._fileService.writeFileRecursive(
|
|
709
|
+
import_node_path2.default.join(componentDir, `${lowerName}.handler.ts`),
|
|
710
|
+
handlerContent
|
|
711
|
+
);
|
|
712
|
+
const controllerContent = `import type { IContextAccessor, IMediator, RequestContext, ResponseDto } from '@xeno-js/core';
|
|
713
|
+
import { BaseController } from '@xeno-js/core';
|
|
714
|
+
import { ${pascalName}Query } from './${lowerName}.query';
|
|
715
|
+
|
|
716
|
+
/*
|
|
717
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
718
|
+
* Please replace <any, any> with your specific request and response types.
|
|
719
|
+
*/
|
|
720
|
+
export class ${pascalName}Controller extends BaseController<any, any> {
|
|
721
|
+
constructor(
|
|
722
|
+
requestContext: IContextAccessor<RequestContext>,
|
|
723
|
+
mediator: IMediator,
|
|
724
|
+
) {
|
|
725
|
+
super(requestContext, mediator);
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
public async handle(request: any): Promise<ResponseDto<any>> {
|
|
729
|
+
const query = new ${pascalName}Query(request);
|
|
730
|
+
const result = await this._query(query);
|
|
731
|
+
|
|
732
|
+
if (!result.isOk()) {
|
|
733
|
+
return this.fail(result.getErrorOrThrow(), 'Error during ${pascalName} operation');
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
return this.ok(result.getValueOrThrow());
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
`;
|
|
740
|
+
await this._fileService.writeFileRecursive(
|
|
741
|
+
import_node_path2.default.join(componentDir, `${lowerName}.controller.ts`),
|
|
742
|
+
controllerContent
|
|
743
|
+
);
|
|
744
|
+
if (hasZod) {
|
|
745
|
+
const zodContent = `import { z } from 'zod';
|
|
746
|
+
import { ZodUtils } from '@xeno-js/core';
|
|
747
|
+
|
|
748
|
+
/*
|
|
749
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
750
|
+
* Define the actual Zod validation schema for your query payload.
|
|
751
|
+
*/
|
|
752
|
+
export const ${pascalName}Schema = ZodUtils.createQuerySchema({
|
|
753
|
+
payload: z.any() // TODO: Update with strict validation
|
|
754
|
+
});
|
|
755
|
+
`;
|
|
756
|
+
await this._fileService.writeFileRecursive(
|
|
757
|
+
import_node_path2.default.join(componentDir, `${lowerName}.schema-zod.ts`),
|
|
758
|
+
zodContent
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
const moduleContent = `import type { IServiceContainer, IConfigurationService } from '@xeno-js/core';
|
|
762
|
+
import { TOKENS } from '@xeno-js/core';
|
|
763
|
+
import { ${pascalName}Controller } from './${lowerName}.controller';
|
|
764
|
+
import { ${pascalName}Handler } from './${lowerName}.handler';
|
|
765
|
+
|
|
766
|
+
export const ${pascalName}Module = Object.freeze({
|
|
767
|
+
register(opts: IServiceContainer<any>, _config: IConfigurationService): void {
|
|
768
|
+
opts.addTransient('${tokenPrefix}_QUERY_CONTROLLER', (c) => new ${pascalName}Controller(c.resolve(TOKENS.REQUEST_CONTEXT), c.resolve(TOKENS.MEDIATOR)));
|
|
769
|
+
opts.addScoped('${tokenPrefix}_QUERY_HANDLER', (c) => new ${pascalName}Handler(c.resolve(TOKENS.USER_CONTEXT_FACTORY)));
|
|
770
|
+
|
|
771
|
+
/*
|
|
772
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
773
|
+
* Please copy and paste the following tokens to your MyRegistry interface (usually in src/registry.ts):
|
|
774
|
+
*
|
|
775
|
+
* ${tokenPrefix}_QUERY_CONTROLLER: IController<any, any>;
|
|
776
|
+
* ${tokenPrefix}_QUERY_HANDLER: IHandler<${pascalName}Query, any>;
|
|
777
|
+
*/
|
|
778
|
+
}
|
|
779
|
+
} as const);
|
|
780
|
+
`;
|
|
781
|
+
await this._fileService.writeFileRecursive(
|
|
782
|
+
import_node_path2.default.join(componentDir, `${lowerName}.module.ts`),
|
|
783
|
+
moduleContent
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
});
|
|
789
|
+
|
|
790
|
+
// src/infrastructure/generators/core/command/cqrs.generator.ts
|
|
791
|
+
var cqrs_generator_exports = {};
|
|
792
|
+
__export(cqrs_generator_exports, {
|
|
793
|
+
CqrsGenerator: () => CqrsGenerator
|
|
794
|
+
});
|
|
795
|
+
var import_promises2, import_node_path3, CqrsGenerator;
|
|
796
|
+
var init_cqrs_generator = __esm({
|
|
797
|
+
"src/infrastructure/generators/core/command/cqrs.generator.ts"() {
|
|
798
|
+
"use strict";
|
|
799
|
+
import_promises2 = __toESM(require("fs/promises"));
|
|
800
|
+
import_node_path3 = __toESM(require("path"));
|
|
801
|
+
init_shared();
|
|
802
|
+
CqrsGenerator = class {
|
|
803
|
+
constructor(_fileService) {
|
|
804
|
+
this._fileService = _fileService;
|
|
805
|
+
}
|
|
806
|
+
_fileService;
|
|
807
|
+
async generate(targetDir, name, type, outputPath) {
|
|
808
|
+
const pascalName = StringUtils.pascalCase(name);
|
|
809
|
+
const tokenPrefix = StringUtils.splitCamelCaseWithUnderscore(pascalName).toUpperCase();
|
|
810
|
+
const lowerName = name.toLowerCase();
|
|
811
|
+
const destinationFolder = outputPath ?? lowerName;
|
|
812
|
+
const componentDir = import_node_path3.default.join(targetDir, "src", destinationFolder);
|
|
813
|
+
let hasZod = false;
|
|
814
|
+
let hasDrizzle = false;
|
|
815
|
+
try {
|
|
816
|
+
const pkgJsonStr = await import_promises2.default.readFile(import_node_path3.default.join(targetDir, "package.json"), "utf8");
|
|
817
|
+
const pkgJson = JSON.parse(pkgJsonStr);
|
|
818
|
+
const deps = { ...pkgJson.dependencies ?? {}, ...pkgJson.devDependencies ?? {} };
|
|
819
|
+
hasZod = !!Guards.isDefined(deps["zod"]);
|
|
820
|
+
hasDrizzle = !!Guards.isDefined(deps["drizzle-orm"]);
|
|
821
|
+
} catch (error) {
|
|
822
|
+
console.warn(
|
|
823
|
+
" Warning: Could not read package.json. Assuming Zod and Drizzle are not installed.",
|
|
824
|
+
error
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
switch (type) {
|
|
828
|
+
case COMMAND_CONSTANTS.COMMAND: {
|
|
829
|
+
const { GenerateCommandCoreGenerator: GenerateCommandCoreGenerator2 } = await Promise.resolve().then(() => (init_generate_command_generator(), generate_command_generator_exports));
|
|
830
|
+
const generator = new GenerateCommandCoreGenerator2(this._fileService);
|
|
831
|
+
return generator.generate(
|
|
832
|
+
pascalName,
|
|
833
|
+
lowerName,
|
|
834
|
+
tokenPrefix,
|
|
835
|
+
componentDir,
|
|
836
|
+
hasZod,
|
|
837
|
+
hasDrizzle
|
|
838
|
+
);
|
|
839
|
+
}
|
|
840
|
+
case COMMAND_CONSTANTS.QUERY: {
|
|
841
|
+
const { GenerateQueryCoreGenerator: GenerateQueryCoreGenerator2 } = await Promise.resolve().then(() => (init_generate_query_generator(), generate_query_generator_exports));
|
|
842
|
+
const generatorQuery = new GenerateQueryCoreGenerator2(this._fileService);
|
|
843
|
+
return generatorQuery.generate(pascalName, lowerName, tokenPrefix, componentDir, hasZod);
|
|
844
|
+
}
|
|
845
|
+
default:
|
|
846
|
+
throw new Error(`Unsupported generator type: '${String(type)}'. Use 'command' or 'query'.`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
|
|
853
|
+
// src/infrastructure/generators/vue/command_query/generate-command-vue.generator.ts
|
|
854
|
+
var generate_command_vue_generator_exports = {};
|
|
855
|
+
__export(generate_command_vue_generator_exports, {
|
|
856
|
+
GenerateCommandVueGenerator: () => GenerateCommandVueGenerator
|
|
857
|
+
});
|
|
858
|
+
var import_node_path4, GenerateCommandVueGenerator;
|
|
859
|
+
var init_generate_command_vue_generator = __esm({
|
|
860
|
+
"src/infrastructure/generators/vue/command_query/generate-command-vue.generator.ts"() {
|
|
861
|
+
"use strict";
|
|
862
|
+
import_node_path4 = __toESM(require("path"));
|
|
863
|
+
GenerateCommandVueGenerator = class {
|
|
864
|
+
constructor(_fileService) {
|
|
865
|
+
this._fileService = _fileService;
|
|
866
|
+
}
|
|
867
|
+
_fileService;
|
|
868
|
+
async generate(pascalName, lowerName, tokenPrefix, componentDir) {
|
|
869
|
+
const commandContent = `import { Command } from '@xeno-js/vue';
|
|
870
|
+
|
|
871
|
+
export class ${pascalName}Command extends Command {
|
|
872
|
+
constructor() {
|
|
873
|
+
super('${tokenPrefix}_COMMAND');
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
`;
|
|
877
|
+
await this._fileService.writeFileRecursive(
|
|
878
|
+
import_node_path4.default.join(componentDir, `${lowerName}.command.ts`),
|
|
879
|
+
commandContent
|
|
880
|
+
);
|
|
881
|
+
const handlerContent = `import type { IHandler, ResultType } from '@xeno-js/vue';
|
|
882
|
+
import { AppError, Result } from '@xeno-js/vue';
|
|
883
|
+
import type { ${pascalName}Command } from './${lowerName}.command';
|
|
884
|
+
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
885
|
+
|
|
886
|
+
/*
|
|
887
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
888
|
+
*
|
|
889
|
+
* Please register your handler in the IoC container (e.g. src/bootstrap.ts) and add its token to MyRegistry.
|
|
890
|
+
*
|
|
891
|
+
* 1. In src/registry.ts:
|
|
892
|
+
* ${tokenPrefix}_HANDLER: IHandler<${pascalName}Command, ${pascalName}Response>;
|
|
893
|
+
*
|
|
894
|
+
* 2. In src/bootstrap.ts:
|
|
895
|
+
* .addServices((config, register) => {
|
|
896
|
+
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
897
|
+
* });
|
|
898
|
+
*/
|
|
899
|
+
export class ${pascalName}Handler implements IHandler<${pascalName}Command, ${pascalName}Response> {
|
|
900
|
+
constructor(
|
|
901
|
+
// TODO: Inject your remote DataSources or other services here
|
|
902
|
+
) {}
|
|
903
|
+
|
|
904
|
+
public async handle(command: ${pascalName}Command, signal: AbortSignal): Promise<ResultType<${pascalName}Response>> {
|
|
905
|
+
AppError.throwIfAborted(signal, this.constructor.name);
|
|
906
|
+
|
|
907
|
+
// TODO: Implement your frontend business logic or API calls here
|
|
908
|
+
return Result.ok();
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
`;
|
|
912
|
+
await this._fileService.writeFileRecursive(
|
|
913
|
+
import_node_path4.default.join(componentDir, `${lowerName}.handler.ts`),
|
|
914
|
+
handlerContent
|
|
915
|
+
);
|
|
916
|
+
const composableContent = `import { ref } from 'vue';
|
|
917
|
+
import { AppError, Result, type ApiResponseDto, type ResultType } from '@xeno-js/vue';
|
|
918
|
+
import { ServicesUtils } from '@/use-app';
|
|
919
|
+
import { ${pascalName}Command } from './${lowerName}.command';
|
|
920
|
+
import type { ${pascalName}Request, ${pascalName}Response } from './${lowerName}.model';
|
|
921
|
+
|
|
922
|
+
/*
|
|
923
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
924
|
+
*
|
|
925
|
+
* Please register your handler in the IoC container (e.g. src/bootstrap.ts) and add its token to MyRegistry.
|
|
926
|
+
*
|
|
927
|
+
* 1. In src/registry.ts:
|
|
928
|
+
* ${tokenPrefix}_HANDLER: IHandler<${pascalName}Command, ${pascalName}Response>;
|
|
929
|
+
*
|
|
930
|
+
* 2. In src/bootstrap.ts:
|
|
931
|
+
* .addServices((config, register) => {
|
|
932
|
+
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
933
|
+
* });
|
|
934
|
+
*/
|
|
935
|
+
export function use${pascalName}() {
|
|
936
|
+
const loading = ref(false);
|
|
937
|
+
const error = ref<string | null>(null);
|
|
938
|
+
let abortController: AbortController | null = null;
|
|
939
|
+
|
|
940
|
+
const execute = async (payload: ${pascalName}Request): Promise<ResultType<${pascalName}Response>> => {
|
|
941
|
+
abortController = new AbortController();
|
|
942
|
+
|
|
943
|
+
if (loading.value) return Result.fail(AppError.create({
|
|
944
|
+
name: '${pascalName}Command',
|
|
945
|
+
message: '${pascalName}Command is already in progress',
|
|
946
|
+
code: '${tokenPrefix}_COMMAND_ALREADY_IN_PROGRESS',
|
|
947
|
+
status: 500,
|
|
948
|
+
cause: new Error('${tokenPrefix}_COMMAND_ALREADY_IN_PROGRESS'),
|
|
949
|
+
}));
|
|
950
|
+
loading.value = true;
|
|
951
|
+
error.value = null;
|
|
952
|
+
|
|
953
|
+
try {
|
|
954
|
+
// Resolve mediator and handler from the container
|
|
955
|
+
const { mediator, ${tokenPrefix}_HANDLER: handler } = ServicesUtils.useApp();
|
|
956
|
+
const command = new ${pascalName}Command();
|
|
957
|
+
|
|
958
|
+
const result = await mediator.send(command, async () => {
|
|
959
|
+
return await handler.handle(command, abortController?.signal);
|
|
960
|
+
});
|
|
961
|
+
|
|
962
|
+
if (!result.isOk()) {
|
|
963
|
+
error.value = result.getErrorOrThrow().message;
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
return result;
|
|
967
|
+
} finally {
|
|
968
|
+
loading.value = false;
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
const abort = () => {
|
|
973
|
+
if (abortController) {
|
|
974
|
+
abortController.abort();
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
// Automatically cancel pending operations when the component unmounts
|
|
979
|
+
onUnmounted(() => {
|
|
980
|
+
abort();
|
|
981
|
+
});
|
|
982
|
+
|
|
983
|
+
return { loading, error, execute };
|
|
984
|
+
}
|
|
985
|
+
`;
|
|
986
|
+
await this._fileService.writeFileRecursive(
|
|
987
|
+
import_node_path4.default.join(componentDir, `use-${lowerName}.composable.ts`),
|
|
988
|
+
composableContent
|
|
989
|
+
);
|
|
990
|
+
const modelContent = `export interface ${pascalName}Request {
|
|
991
|
+
// TODO: Define your request payload properties here
|
|
992
|
+
}
|
|
993
|
+
|
|
994
|
+
export interface ${pascalName}Response {
|
|
995
|
+
// TODO: Define your response properties here
|
|
996
|
+
}
|
|
997
|
+
`;
|
|
998
|
+
await this._fileService.writeFileRecursive(
|
|
999
|
+
import_node_path4.default.join(componentDir, `${lowerName}.model.ts`),
|
|
1000
|
+
modelContent
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
};
|
|
1004
|
+
}
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
// src/infrastructure/generators/vue/command_query/generate-query-vue.generator.ts
|
|
1008
|
+
var generate_query_vue_generator_exports = {};
|
|
1009
|
+
__export(generate_query_vue_generator_exports, {
|
|
1010
|
+
GenerateQueryVueGenerator: () => GenerateQueryVueGenerator
|
|
1011
|
+
});
|
|
1012
|
+
var import_node_path5, GenerateQueryVueGenerator;
|
|
1013
|
+
var init_generate_query_vue_generator = __esm({
|
|
1014
|
+
"src/infrastructure/generators/vue/command_query/generate-query-vue.generator.ts"() {
|
|
1015
|
+
"use strict";
|
|
1016
|
+
import_node_path5 = __toESM(require("path"));
|
|
1017
|
+
GenerateQueryVueGenerator = class {
|
|
1018
|
+
constructor(_fileService) {
|
|
1019
|
+
this._fileService = _fileService;
|
|
1020
|
+
}
|
|
1021
|
+
_fileService;
|
|
1022
|
+
async generate(pascalName, lowerName, tokenPrefix, componentDir) {
|
|
1023
|
+
const queryContent = `import { Query } from '@xeno-js/vue';
|
|
1024
|
+
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
1025
|
+
|
|
1026
|
+
export class ${pascalName}Query extends Query<${pascalName}Response> {
|
|
1027
|
+
constructor() {
|
|
1028
|
+
super('${tokenPrefix}_QUERY', {
|
|
1029
|
+
ttl: 60,
|
|
1030
|
+
cacheKey: \`${lowerName}.query\`,
|
|
1031
|
+
bypassCache: false,
|
|
1032
|
+
consistentRead: false,
|
|
1033
|
+
isUserScoped: true
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
`;
|
|
1038
|
+
await this._fileService.writeFileRecursive(
|
|
1039
|
+
import_node_path5.default.join(componentDir, `${lowerName}.query.ts`),
|
|
1040
|
+
queryContent
|
|
1041
|
+
);
|
|
1042
|
+
const handlerContent = `import type { IHandler, ResultType } from '@xeno-js/vue';
|
|
1043
|
+
import { AppError, Result } from '@xeno-js/vue';
|
|
1044
|
+
import type { ${pascalName}Query } from './${lowerName}.query';
|
|
1045
|
+
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
1046
|
+
|
|
1047
|
+
/*
|
|
1048
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
1049
|
+
*
|
|
1050
|
+
* Please register your handler in the IoC container (e.g. src/bootstrap.ts) and add its token to MyRegistry.
|
|
1051
|
+
*
|
|
1052
|
+
* 1. In src/registry.ts:
|
|
1053
|
+
* ${tokenPrefix}_HANDLER: IHandler<${pascalName}Query, ${pascalName}Response>;
|
|
1054
|
+
*
|
|
1055
|
+
* 2. In src/bootstrap.ts:
|
|
1056
|
+
* .addServices((config, register) => {
|
|
1057
|
+
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
1058
|
+
* });
|
|
1059
|
+
*/
|
|
1060
|
+
export class ${pascalName}Handler implements IHandler<${pascalName}Query, ${pascalName}Response> {
|
|
1061
|
+
constructor(
|
|
1062
|
+
// TODO: Inject your remote DataSources or other services here
|
|
1063
|
+
) {}
|
|
1064
|
+
|
|
1065
|
+
public async handle(query: ${pascalName}Query, signal: AbortSignal): Promise<ResultType<${pascalName}Response>> {
|
|
1066
|
+
AppError.throwIfAborted(signal, this.constructor.name);
|
|
1067
|
+
|
|
1068
|
+
// TODO: Implement your frontend business logic or API calls here
|
|
1069
|
+
return Result.ok();
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
`;
|
|
1073
|
+
await this._fileService.writeFileRecursive(
|
|
1074
|
+
import_node_path5.default.join(componentDir, `${lowerName}.handler.ts`),
|
|
1075
|
+
handlerContent
|
|
1076
|
+
);
|
|
1077
|
+
const composableContent = `import { ref } from 'vue';
|
|
1078
|
+
import { AppError, Result, type ApiResponseDto, type ResultType } from '@xeno-js/vue';
|
|
1079
|
+
import { ServicesUtils } from '@/use-app';
|
|
1080
|
+
import { ${pascalName}Query } from './${lowerName}.query';
|
|
1081
|
+
import type { ${pascalName}Response } from './${lowerName}.model';
|
|
1082
|
+
|
|
1083
|
+
export function use${pascalName}() {
|
|
1084
|
+
const loading = ref(false);
|
|
1085
|
+
const error = ref<string | null>(null);
|
|
1086
|
+
const data = ref<${pascalName}Response | null>(null);
|
|
1087
|
+
let abortController: AbortController | null = null;
|
|
1088
|
+
|
|
1089
|
+
/*
|
|
1090
|
+
* \u26A0\uFE0F WARNING: ACTION REQUIRED \u26A0\uFE0F
|
|
1091
|
+
*
|
|
1092
|
+
* Please register your handler in the IoC container (e.g. src/bootstrap.ts) and add its token to MyRegistry.
|
|
1093
|
+
*
|
|
1094
|
+
* 1. In src/registry.ts:
|
|
1095
|
+
* ${tokenPrefix}_HANDLER: IHandler<${pascalName}Query, ${pascalName}Response>;
|
|
1096
|
+
*
|
|
1097
|
+
* 2. In src/bootstrap.ts:
|
|
1098
|
+
* .addServices((config, register) => {
|
|
1099
|
+
* register('${tokenPrefix}_HANDLER', new ${pascalName}Handler());
|
|
1100
|
+
* });
|
|
1101
|
+
*/
|
|
1102
|
+
const fetch = async (): Promise<ResultType<${pascalName}Response>> => {
|
|
1103
|
+
abortController = new AbortController();
|
|
1104
|
+
|
|
1105
|
+
if (loading.value) return Result.fail(AppError.create({
|
|
1106
|
+
name: '${pascalName}Query',
|
|
1107
|
+
message: '${pascalName}Query is already in progress',
|
|
1108
|
+
code: '${tokenPrefix}_QUERY_ALREADY_IN_PROGRESS',
|
|
1109
|
+
status: 500,
|
|
1110
|
+
cause: new Error('${tokenPrefix}_QUERY_ALREADY_IN_PROGRESS'),
|
|
1111
|
+
}));
|
|
1112
|
+
loading.value = true;
|
|
1113
|
+
error.value = null;
|
|
1114
|
+
|
|
1115
|
+
try {
|
|
1116
|
+
try {
|
|
1117
|
+
// Resolve mediator and handler from the container
|
|
1118
|
+
const { mediator, ${tokenPrefix}_HANDLER: handler } = ServicesUtils.useApp();
|
|
1119
|
+
const query = new ${pascalName}Query();
|
|
1120
|
+
|
|
1121
|
+
const result = await mediator.query(query, async () => {
|
|
1122
|
+
return await handler.handle(query, abortController?.signal);
|
|
1123
|
+
});
|
|
1124
|
+
|
|
1125
|
+
if (result.isOk()) {
|
|
1126
|
+
data.value = result.getValueOrThrow();
|
|
1127
|
+
} else {
|
|
1128
|
+
error.value = result.getErrorOrThrow().message;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
return result;
|
|
1132
|
+
} finally {
|
|
1133
|
+
loading.value = false;
|
|
1134
|
+
}
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
const abort = () => {
|
|
1138
|
+
if (abortController) {
|
|
1139
|
+
abortController.abort();
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
// Automatically cancel pending operations when the component unmounts
|
|
1144
|
+
onUnmounted(() => {
|
|
1145
|
+
abort();
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
return { loading, error, data, fetch };
|
|
1149
|
+
}
|
|
1150
|
+
`;
|
|
1151
|
+
await this._fileService.writeFileRecursive(
|
|
1152
|
+
import_node_path5.default.join(componentDir, `use-${lowerName}.composable.ts`),
|
|
1153
|
+
composableContent
|
|
1154
|
+
);
|
|
1155
|
+
const modelContent = `export interface ${pascalName}Response {
|
|
1156
|
+
// TODO: Define your response properties here
|
|
1157
|
+
}
|
|
1158
|
+
`;
|
|
1159
|
+
await this._fileService.writeFileRecursive(
|
|
1160
|
+
import_node_path5.default.join(componentDir, `${lowerName}.model.ts`),
|
|
1161
|
+
modelContent
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
// src/infrastructure/generators/vue/command_query/cqrs.generator.ts
|
|
1169
|
+
var cqrs_generator_exports2 = {};
|
|
1170
|
+
__export(cqrs_generator_exports2, {
|
|
1171
|
+
CqrsVueGenerator: () => CqrsVueGenerator
|
|
1172
|
+
});
|
|
1173
|
+
var import_node_path6, CqrsVueGenerator;
|
|
1174
|
+
var init_cqrs_generator2 = __esm({
|
|
1175
|
+
"src/infrastructure/generators/vue/command_query/cqrs.generator.ts"() {
|
|
1176
|
+
"use strict";
|
|
1177
|
+
import_node_path6 = __toESM(require("path"));
|
|
1178
|
+
init_shared();
|
|
1179
|
+
CqrsVueGenerator = class {
|
|
1180
|
+
constructor(_fileService) {
|
|
1181
|
+
this._fileService = _fileService;
|
|
1182
|
+
}
|
|
1183
|
+
_fileService;
|
|
1184
|
+
async generate(targetDir, name, type, outputPath) {
|
|
1185
|
+
const pascalName = StringUtils.pascalCase(name);
|
|
1186
|
+
const tokenPrefix = StringUtils.splitCamelCaseWithUnderscore(pascalName).toUpperCase();
|
|
1187
|
+
const lowerName = name.toLowerCase();
|
|
1188
|
+
const destinationFolder = outputPath ?? lowerName;
|
|
1189
|
+
const componentDir = import_node_path6.default.join(targetDir, "src", destinationFolder);
|
|
1190
|
+
switch (type) {
|
|
1191
|
+
case COMMAND_CONSTANTS.COMMAND: {
|
|
1192
|
+
const { GenerateCommandVueGenerator: GenerateCommandVueGenerator2 } = await Promise.resolve().then(() => (init_generate_command_vue_generator(), generate_command_vue_generator_exports));
|
|
1193
|
+
const generator = new GenerateCommandVueGenerator2(this._fileService);
|
|
1194
|
+
return generator.generate(pascalName, lowerName, tokenPrefix, componentDir);
|
|
1195
|
+
}
|
|
1196
|
+
case COMMAND_CONSTANTS.QUERY: {
|
|
1197
|
+
const { GenerateQueryVueGenerator: GenerateQueryVueGenerator2 } = await Promise.resolve().then(() => (init_generate_query_vue_generator(), generate_query_vue_generator_exports));
|
|
1198
|
+
const generatorQuery = new GenerateQueryVueGenerator2(this._fileService);
|
|
1199
|
+
return generatorQuery.generate(pascalName, lowerName, tokenPrefix, componentDir);
|
|
1200
|
+
}
|
|
1201
|
+
default:
|
|
1202
|
+
throw new Error(`Unsupported generator type: '${String(type)}'. Use 'command' or 'query'.`);
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
});
|
|
1208
|
+
|
|
1209
|
+
// src/infrastructure/generators/vue/packagejson.generator.ts
|
|
1210
|
+
var packagejson_generator_exports = {};
|
|
1211
|
+
__export(packagejson_generator_exports, {
|
|
1212
|
+
PackageJsonVueGenerator: () => PackageJsonVueGenerator
|
|
1213
|
+
});
|
|
1214
|
+
var import_node_path8, PackageJsonVueGenerator;
|
|
1215
|
+
var init_packagejson_generator = __esm({
|
|
1216
|
+
"src/infrastructure/generators/vue/packagejson.generator.ts"() {
|
|
1217
|
+
"use strict";
|
|
1218
|
+
import_node_path8 = __toESM(require("path"));
|
|
1219
|
+
PackageJsonVueGenerator = class {
|
|
1220
|
+
constructor(_fileService) {
|
|
1221
|
+
this._fileService = _fileService;
|
|
1222
|
+
}
|
|
1223
|
+
_fileService;
|
|
1224
|
+
async generate(projectPath, options) {
|
|
1225
|
+
const packageJson = {
|
|
1226
|
+
name: options.targetDir,
|
|
1227
|
+
version: "1.0.0",
|
|
1228
|
+
private: true,
|
|
1229
|
+
type: "module",
|
|
1230
|
+
scripts: {
|
|
1231
|
+
dev: "vite",
|
|
1232
|
+
build: "vue-tsc -b && vite build",
|
|
1233
|
+
preview: "vite preview",
|
|
1234
|
+
g: "xeno-js generate"
|
|
1235
|
+
},
|
|
1236
|
+
dependencies: {
|
|
1237
|
+
"@xeno-js/vue": "latest",
|
|
1238
|
+
"vue": "^3.5.0"
|
|
1239
|
+
},
|
|
1240
|
+
devDependencies: {
|
|
1241
|
+
"@types/node": "^20.0.0",
|
|
1242
|
+
"@vitejs/plugin-vue": "^5.1.0",
|
|
1243
|
+
"typescript": "^5.5.0",
|
|
1244
|
+
"vite": "^5.4.0",
|
|
1245
|
+
"vue-tsc": "^2.1.0"
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
if (options.axios) packageJson.dependencies["axios"] = "^1.7.0";
|
|
1249
|
+
if (options.cockatiel) packageJson.dependencies["cockatiel"] = "^4.0.0";
|
|
1250
|
+
if (options.supabase) packageJson.dependencies["@supabase/supabase-js"] = "^2.45.0";
|
|
1251
|
+
if (options.sentry) packageJson.dependencies["@sentry/vue"] = "^8.28.0";
|
|
1252
|
+
if (options.pinia) packageJson.dependencies["pinia"] = "^2.2.0";
|
|
1253
|
+
if (options.router) packageJson.dependencies["vue-router"] = "^4.4.0";
|
|
1254
|
+
if (options.zod) packageJson.dependencies["zod"] = "^4.4.3";
|
|
1255
|
+
if (options.tailwind) {
|
|
1256
|
+
packageJson.devDependencies["tailwindcss"] = "^4.0.0";
|
|
1257
|
+
packageJson.devDependencies["@tailwindcss/vite"] = "^4.0.0";
|
|
1258
|
+
}
|
|
1259
|
+
const filePath = import_node_path8.default.join(projectPath, "package.json");
|
|
1260
|
+
await this._fileService.writeFileRecursive(filePath, JSON.stringify(packageJson, null, 2));
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
}
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
// src/infrastructure/generators/vue/vite-config.generator.ts
|
|
1267
|
+
var vite_config_generator_exports = {};
|
|
1268
|
+
__export(vite_config_generator_exports, {
|
|
1269
|
+
ViteconfigGenerator: () => ViteconfigGenerator
|
|
1270
|
+
});
|
|
1271
|
+
var import_node_path9, ViteconfigGenerator;
|
|
1272
|
+
var init_vite_config_generator = __esm({
|
|
1273
|
+
"src/infrastructure/generators/vue/vite-config.generator.ts"() {
|
|
1274
|
+
"use strict";
|
|
1275
|
+
import_node_path9 = __toESM(require("path"));
|
|
1276
|
+
ViteconfigGenerator = class {
|
|
1277
|
+
constructor(_fileService) {
|
|
1278
|
+
this._fileService = _fileService;
|
|
1279
|
+
}
|
|
1280
|
+
_fileService;
|
|
1281
|
+
async generate(projectPath, options) {
|
|
1282
|
+
const tailwindImport = options.tailwind ? `
|
|
1283
|
+
import tailwindcss from '@tailwindcss/vite';` : "";
|
|
1284
|
+
const tailwindPlugin = options.tailwind ? `
|
|
1285
|
+
tailwindcss(),` : "";
|
|
1286
|
+
const content = `import { defineConfig } from 'vite';
|
|
1287
|
+
import vue from '@vitejs/plugin-vue';${tailwindImport}
|
|
1288
|
+
import path from 'node:path';
|
|
1289
|
+
|
|
1290
|
+
export default defineConfig({
|
|
1291
|
+
plugins: [
|
|
1292
|
+
vue(),${tailwindPlugin}
|
|
1293
|
+
],
|
|
1294
|
+
resolve: {
|
|
1295
|
+
alias: {
|
|
1296
|
+
'@': path.resolve(__dirname, './src'),
|
|
1297
|
+
},
|
|
1298
|
+
},
|
|
1299
|
+
server: {
|
|
1300
|
+
port: 5173,
|
|
1301
|
+
}
|
|
1302
|
+
});
|
|
1303
|
+
`;
|
|
1304
|
+
const filePath = import_node_path9.default.join(projectPath, "vite.config.ts");
|
|
1305
|
+
await this._fileService.writeFileRecursive(filePath, content);
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
|
|
1311
|
+
// src/infrastructure/generators/vue/env.generator.ts
|
|
1312
|
+
var env_generator_exports = {};
|
|
1313
|
+
__export(env_generator_exports, {
|
|
1314
|
+
EnvVueGenerator: () => EnvVueGenerator
|
|
1315
|
+
});
|
|
1316
|
+
var import_node_path10, EnvVueGenerator;
|
|
1317
|
+
var init_env_generator = __esm({
|
|
1318
|
+
"src/infrastructure/generators/vue/env.generator.ts"() {
|
|
1319
|
+
"use strict";
|
|
1320
|
+
import_node_path10 = __toESM(require("path"));
|
|
1321
|
+
EnvVueGenerator = class {
|
|
1322
|
+
constructor(_fileService) {
|
|
1323
|
+
this._fileService = _fileService;
|
|
1324
|
+
}
|
|
1325
|
+
_fileService;
|
|
1326
|
+
async generate(projectPath, options) {
|
|
1327
|
+
let content = `# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1328
|
+
# Xeno VUE APPLICATION ENVIRONMENT VARIABLES
|
|
1329
|
+
# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
1330
|
+
VITE_APP_ENV=development
|
|
1331
|
+
`;
|
|
1332
|
+
if (options.axios) {
|
|
1333
|
+
content += `
|
|
1334
|
+
# --- Axios ---
|
|
1335
|
+
VITE_API_BASE_URL=http://localhost:3000
|
|
1336
|
+
`;
|
|
1337
|
+
}
|
|
1338
|
+
if (options.supabase) {
|
|
1339
|
+
content += `
|
|
1340
|
+
# --- Authentication (Supabase) ---
|
|
1341
|
+
VITE_SUPABASE_URL=https://your-project.supabase.co
|
|
1342
|
+
VITE_SUPABASE_KEY=your-anon-key
|
|
1343
|
+
`;
|
|
1344
|
+
}
|
|
1345
|
+
if (options.sentry) {
|
|
1346
|
+
content += `
|
|
1347
|
+
# --- Error Tracking (Sentry) ---
|
|
1348
|
+
VITE_SENTRY_DSN=https://your-sentry-dsn@o0.ingest.sentry.io/0
|
|
1349
|
+
`;
|
|
1350
|
+
}
|
|
1351
|
+
await this._fileService.writeFileRecursive(import_node_path10.default.join(projectPath, ".env"), content);
|
|
1352
|
+
await this._fileService.writeFileRecursive(import_node_path10.default.join(projectPath, ".env.example"), content);
|
|
1353
|
+
}
|
|
1354
|
+
};
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
|
|
1358
|
+
// src/infrastructure/generators/vue/tsconfig.generator.ts
|
|
1359
|
+
var tsconfig_generator_exports = {};
|
|
1360
|
+
__export(tsconfig_generator_exports, {
|
|
1361
|
+
TsconfigVueGenerator: () => TsconfigVueGenerator
|
|
1362
|
+
});
|
|
1363
|
+
var import_node_path11, TsconfigVueGenerator;
|
|
1364
|
+
var init_tsconfig_generator = __esm({
|
|
1365
|
+
"src/infrastructure/generators/vue/tsconfig.generator.ts"() {
|
|
1366
|
+
"use strict";
|
|
1367
|
+
import_node_path11 = __toESM(require("path"));
|
|
1368
|
+
TsconfigVueGenerator = class {
|
|
1369
|
+
constructor(_fileService) {
|
|
1370
|
+
this._fileService = _fileService;
|
|
1371
|
+
}
|
|
1372
|
+
_fileService;
|
|
1373
|
+
async generate(projectPath, _options) {
|
|
1374
|
+
const tsconfig = {
|
|
1375
|
+
compilerOptions: {
|
|
1376
|
+
target: "ES2022",
|
|
1377
|
+
useDefineForClassFields: true,
|
|
1378
|
+
module: "ESNext",
|
|
1379
|
+
moduleResolution: "bundler",
|
|
1380
|
+
strict: true,
|
|
1381
|
+
jsx: "preserve",
|
|
1382
|
+
resolveJsonModule: true,
|
|
1383
|
+
isolatedModules: true,
|
|
1384
|
+
esModuleInterop: true,
|
|
1385
|
+
lib: ["ES2022", "DOM", "DOM.Iterable"],
|
|
1386
|
+
skipLibCheck: true,
|
|
1387
|
+
noEmit: true,
|
|
1388
|
+
types: ["vite/client"],
|
|
1389
|
+
paths: {
|
|
1390
|
+
"@/*": ["./src/*"]
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
include: ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"]
|
|
1394
|
+
};
|
|
1395
|
+
await this._fileService.writeFileRecursive(
|
|
1396
|
+
import_node_path11.default.join(projectPath, "tsconfig.json"),
|
|
1397
|
+
JSON.stringify(tsconfig, null, 2)
|
|
1398
|
+
);
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
});
|
|
1403
|
+
|
|
1404
|
+
// src/infrastructure/generators/vue/readme.generator.ts
|
|
1405
|
+
var readme_generator_exports = {};
|
|
1406
|
+
__export(readme_generator_exports, {
|
|
1407
|
+
ReadmeVueGenerator: () => ReadmeVueGenerator
|
|
1408
|
+
});
|
|
1409
|
+
var import_node_path12, ReadmeVueGenerator;
|
|
1410
|
+
var init_readme_generator = __esm({
|
|
1411
|
+
"src/infrastructure/generators/vue/readme.generator.ts"() {
|
|
1412
|
+
"use strict";
|
|
1413
|
+
import_node_path12 = __toESM(require("path"));
|
|
1414
|
+
ReadmeVueGenerator = class {
|
|
1415
|
+
constructor(_fileService) {
|
|
1416
|
+
this._fileService = _fileService;
|
|
1417
|
+
}
|
|
1418
|
+
_fileService;
|
|
1419
|
+
async generate(projectPath, options) {
|
|
1420
|
+
const content = `# ${options.targetDir}
|
|
1421
|
+
|
|
1422
|
+
<div align="center">
|
|
1423
|
+
<img src="logo/logo.png" alt="Xeno Logo" width="140" />
|
|
1424
|
+
|
|
1425
|
+
<h1>Xeno Vue</h1>
|
|
1426
|
+
|
|
1427
|
+
<p><em>Enterprise-grade DDD & CQRS framework for Vue.js</em></p>
|
|
1428
|
+
|
|
1429
|
+
<p>
|
|
1430
|
+
<a href="https://github.com/xeno-js/xeno-js">
|
|
1431
|
+
<img src="https://img.shields.io/badge/Powered%20by-Xeno-blueviolet?style=flat-square" alt="Powered by Xeno" />
|
|
1432
|
+
</a>
|
|
1433
|
+
<a href="https://github.com/xeno-js/xeno-fe/blob/main/LICENSE">
|
|
1434
|
+
<img src="https://img.shields.io/npm/l/@xeno-js/vue?style=flat-square" alt="License: ISC" />
|
|
1435
|
+
</a>
|
|
1436
|
+
<a href="https://www.npmjs.com/package/@xeno-js/vue">
|
|
1437
|
+
<img src="https://img.shields.io/npm/v/@xeno-js/vue?style=flat-square" alt="NPM Version" />
|
|
1438
|
+
</a>
|
|
1439
|
+
<a href="https://buymeacoffee.com/xenojs">
|
|
1440
|
+
<img src="https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-FFdd00?style=flat-square&logo=buy-me-a-coffee&logoColor=black" alt="Buy Me A Coffee" />
|
|
1441
|
+
</a>
|
|
1442
|
+
</p>
|
|
1443
|
+
</div>
|
|
1444
|
+
|
|
1445
|
+
---
|
|
1446
|
+
|
|
1447
|
+
## What is Xeno Vue?
|
|
1448
|
+
|
|
1449
|
+
**Xeno Vue** (\`@xeno-js/vue\`) is an enterprise-grade, deterministic
|
|
1450
|
+
architectural framework that brings the strictness of **Domain-Driven Design
|
|
1451
|
+
(DDD)** and **Command Query Responsibility Segregation (CQRS)** natively to the
|
|
1452
|
+
browser.
|
|
1453
|
+
|
|
1454
|
+
By shifting operational logic, remote data fetching, and state mutations away
|
|
1455
|
+
from Vue components and Pinia stores, Xeno ensures your frontend architecture
|
|
1456
|
+
remains pristine, highly testable, and completely decoupled from the UI layer.
|
|
1457
|
+
It treats the browser as a complex distributed client, not just a document
|
|
1458
|
+
viewer.
|
|
1459
|
+
|
|
1460
|
+
---
|
|
1461
|
+
|
|
1462
|
+
## \u{1F4A1} Why Choose Xeno Vue?
|
|
1463
|
+
|
|
1464
|
+
Modern frontend development often leads to "Spaghetti State" where API calls,
|
|
1465
|
+
business rules, and DOM manipulations are tightly coupled inside components.
|
|
1466
|
+
Xeno Vue fixes this with an emphasis on pure Dependency Injection (DI) and clean
|
|
1467
|
+
boundaries.
|
|
1468
|
+
|
|
1469
|
+
- **Zero-Magic Dependency Injection**: Xeno provides an explicit
|
|
1470
|
+
\`XenoAppBuilder\` to construct your IoC container at bootstrap. No hidden Vue
|
|
1471
|
+
plugins doing implicit injections. You have total control over the dependency
|
|
1472
|
+
graph.
|
|
1473
|
+
- **Total UI Decoupling**: Vue components act strictly as the Presentation
|
|
1474
|
+
Layer. Business logic, API calls, and CQRS handlers live in pure, isolated
|
|
1475
|
+
TypeScript classes. You can swap Vue for React tomorrow without touching your
|
|
1476
|
+
core domain.
|
|
1477
|
+
- **Enterprise-Grade Client Resiliency**: The browser is a hostile, unreliable
|
|
1478
|
+
environment. Xeno Vue natively integrates \`Cockatiel\` and \`Axios\` to provide
|
|
1479
|
+
out-of-the-box circuit breakers, retries with jitter, and bulkheads directly
|
|
1480
|
+
in the client.
|
|
1481
|
+
- **Frontend Middleware Pipeline**: Handle Authentication, CSRF validation,
|
|
1482
|
+
aggressive Query Caching, and Performance logging _before_ a command is
|
|
1483
|
+
executed or an API call fires, using the native \`Mediator\` pipeline.
|
|
1484
|
+
|
|
1485
|
+
---
|
|
1486
|
+
|
|
1487
|
+
## \u{1F4D6} Documentation & Getting Started
|
|
1488
|
+
|
|
1489
|
+
To explore the architecture, programmatic configurations, and extension
|
|
1490
|
+
workflows of Xeno Vue, read the full technical manuals located inside the main
|
|
1491
|
+
documentation hub:
|
|
1492
|
+
|
|
1493
|
+
- **[Framework Documentation Repository](https://www.xeno-js.it/vue/overview)**
|
|
1494
|
+
|
|
1495
|
+
Inside, you will find exhaustive, step-by-step assembly guides covering frontend
|
|
1496
|
+
IoC building (\`XenoAppBuilder\`), Vue \`Provide/Inject\` boundaries, and CQRS
|
|
1497
|
+
composables.
|
|
1498
|
+
|
|
1499
|
+
---
|
|
1500
|
+
|
|
1501
|
+
## \u{1F680} Live Executable Demos
|
|
1502
|
+
|
|
1503
|
+
Want to see how Xeno Vue works? Check out the functional example application
|
|
1504
|
+
showcasing end-to-end command/query segregation, resilient HTTP fetching, and
|
|
1505
|
+
pure Composition API integration.
|
|
1506
|
+
|
|
1507
|
+
- **[Xeno Vue Demo Architecture](https://www.xeno-js.it/demo)**
|
|
1508
|
+
|
|
1509
|
+
---
|
|
1510
|
+
|
|
1511
|
+
## \u{1F4E6} Installation
|
|
1512
|
+
|
|
1513
|
+
Install the Vue package:
|
|
1514
|
+
|
|
1515
|
+
\`\`\`bash
|
|
1516
|
+
npm install @xeno-js/vue
|
|
1517
|
+
|
|
1518
|
+
\`\`\`
|
|
1519
|
+
|
|
1520
|
+
Xeno uses **Optional Peer Dependencies**. You only install the external
|
|
1521
|
+
libraries you actually need.
|
|
1522
|
+
|
|
1523
|
+
\`\`\`bash
|
|
1524
|
+
# Example: Install tools only if you enable them in the builder
|
|
1525
|
+
npm install axios cockatiel zod @supabase/supabase-js @sentry/vue
|
|
1526
|
+
|
|
1527
|
+
\`\`\`
|
|
1528
|
+
|
|
1529
|
+
---
|
|
1530
|
+
|
|
1531
|
+
## \u26A1 Bootstrapping & Component Example
|
|
1532
|
+
|
|
1533
|
+
Below is an architectural example of how to configure the Xeno \`XenoAppBuilder\`,
|
|
1534
|
+
inject it into the Vue application, and consume it using Composition API
|
|
1535
|
+
Composables.
|
|
1536
|
+
|
|
1537
|
+
### 1. Initialize the Container (\`src/bootstrap.ts\`)
|
|
1538
|
+
|
|
1539
|
+
\`\`\`typescript
|
|
1540
|
+
import { XenoAppBuilder, LOG_LEVEL, TOKENS } from '@xeno-js/vue'
|
|
1541
|
+
import { BffRemoteDataSource } from './infrastructure'
|
|
1542
|
+
|
|
1543
|
+
import type { RemoteDataSource, XenoVueRegistry } from '@xeno-js/vue'
|
|
1544
|
+
|
|
1545
|
+
export type MyRegistry = XenoVueRegistry<{
|
|
1546
|
+
BFF_REMOTE_DS: RemoteDataSource
|
|
1547
|
+
}>
|
|
1548
|
+
|
|
1549
|
+
// Create the root IoC container context for the browser
|
|
1550
|
+
const builder = XenoAppBuilder.create<MyRegistry>()
|
|
1551
|
+
.addContext((opts) => {
|
|
1552
|
+
opts.contextAccessor = useContextStore()
|
|
1553
|
+
})
|
|
1554
|
+
.addLogger((opts, config) => {
|
|
1555
|
+
opts.console = config.get('VITE_APP_ENV') === 'development'
|
|
1556
|
+
opts.level = LOG_LEVEL.DEBUG
|
|
1557
|
+
})
|
|
1558
|
+
.addAuth((opts, config) => {
|
|
1559
|
+
opts.url = config.getOrThrow('VITE_SUPABASE_URL')
|
|
1560
|
+
opts.key = config.getOrThrow('VITE_SUPABASE_KEY')
|
|
1561
|
+
})
|
|
1562
|
+
.addHttpCore('BFF_REMOTE_DS', (opts, config) => {
|
|
1563
|
+
opts.client = {
|
|
1564
|
+
baseURL: config.getOrThrow('VITE_API_BASE_URL'),
|
|
1565
|
+
timeoutMs: 10000,
|
|
1566
|
+
}
|
|
1567
|
+
opts.resilience.retry.attempts = 3
|
|
1568
|
+
opts.factory = (http, resilience) =>
|
|
1569
|
+
new BffRemoteDataSource(http, resilience)
|
|
1570
|
+
})
|
|
1571
|
+
.addPipeline((config) => {
|
|
1572
|
+
config.queryCaching = true // Enable in-memory caching for Queries
|
|
1573
|
+
})
|
|
1574
|
+
|
|
1575
|
+
export async function bootstrap() {
|
|
1576
|
+
return await builder.build()
|
|
1577
|
+
}
|
|
1578
|
+
\`\`\`
|
|
1579
|
+
|
|
1580
|
+
### 2. Inject into Vue (\`src/main.ts\`)
|
|
1581
|
+
|
|
1582
|
+
\`\`\`typescript
|
|
1583
|
+
import { createApp } from 'vue'
|
|
1584
|
+
import App from './App.vue'
|
|
1585
|
+
import { bootstrap } from './bootstrap'
|
|
1586
|
+
import { XENO_SERVICES_KEY, ServicesUtils } from '@xeno-js/vue'
|
|
1587
|
+
|
|
1588
|
+
async function mountApp() {
|
|
1589
|
+
try {
|
|
1590
|
+
const app = createApp(App)
|
|
1591
|
+
const container = await bootstrap()
|
|
1592
|
+
|
|
1593
|
+
// Provide the Xeno IoC container globally to all components
|
|
1594
|
+
app.provide(XENO_SERVICES_KEY, container)
|
|
1595
|
+
|
|
1596
|
+
// Register global services utility for composables
|
|
1597
|
+
ServicesUtils.setGlobalServices(container)
|
|
1598
|
+
|
|
1599
|
+
app.mount('#app')
|
|
1600
|
+
} catch (error) {
|
|
1601
|
+
console.error('Critical error during frontend bootstrap:', error)
|
|
1602
|
+
}
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
mountApp()
|
|
1606
|
+
\`\`\`
|
|
1607
|
+
|
|
1608
|
+
### 3. CQRS Composable Usage (\`src/features/users/use-create-user.ts\`)
|
|
1609
|
+
|
|
1610
|
+
\`\`\`typescript
|
|
1611
|
+
import { ref } from 'vue'
|
|
1612
|
+
import { Result } from '@xeno-js/shared'
|
|
1613
|
+
import { ServicesUtils } from '@/use-app'
|
|
1614
|
+
import { CreateUserCommand } from './create-user.command'
|
|
1615
|
+
|
|
1616
|
+
export function useCreateUser() {
|
|
1617
|
+
const loading = ref(false)
|
|
1618
|
+
const error = ref<string | null>(null)
|
|
1619
|
+
|
|
1620
|
+
const execute = async (payload: { email: string; name: string }) => {
|
|
1621
|
+
if (loading.value) return Result.fail(new Error('Already executing'))
|
|
1622
|
+
loading.value = true
|
|
1623
|
+
error.value = null
|
|
1624
|
+
|
|
1625
|
+
try {
|
|
1626
|
+
// Safely resolve the Mediator from the Xeno Container
|
|
1627
|
+
const { mediator, BFF_REMOTE_DS } = ServicesUtils.useApp()
|
|
1628
|
+
const command = new CreateUserCommand()
|
|
1629
|
+
|
|
1630
|
+
const result = await mediator.send(command, async () => {
|
|
1631
|
+
const apiResult = await bffRemoteDs.post('/v1/users', payload)
|
|
1632
|
+
|
|
1633
|
+
if (!apiResult.isOk()) return Result.fail(apiResult.getErrorOrThrow())
|
|
1634
|
+
return Result.ok(apiResult.getValueOrThrow())
|
|
1635
|
+
})
|
|
1636
|
+
|
|
1637
|
+
if (!result.isOk()) {
|
|
1638
|
+
error.value = result.getErrorOrThrow().message
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
return result
|
|
1642
|
+
} finally {
|
|
1643
|
+
loading.value = false
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
return { loading, error, execute }
|
|
1648
|
+
}
|
|
1649
|
+
\`\`\`
|
|
1650
|
+
|
|
1651
|
+
---
|
|
1652
|
+
|
|
1653
|
+
## \u{1F6E0} Scaffold your project with CLI
|
|
1654
|
+
|
|
1655
|
+
Xeno includes an official CLI tool, \`@xeno-js/cli\`, designed to bootstrap your
|
|
1656
|
+
new application in seconds. It offers an interactive setup for Vue projects,
|
|
1657
|
+
instantly scaffolding the CQRS files, composables, and dependency injection
|
|
1658
|
+
boundaries.
|
|
1659
|
+
|
|
1660
|
+
\`\`\`bash
|
|
1661
|
+
# Generate a new Vue project
|
|
1662
|
+
npx @xeno-js/cli new my-frontend-app --vue
|
|
1663
|
+
|
|
1664
|
+
# Generate a Vue Command with its Composable
|
|
1665
|
+
npx @xeno-js/cli g command CreateUser --vue
|
|
1666
|
+
|
|
1667
|
+
\`\`\`
|
|
1668
|
+
|
|
1669
|
+
Check the
|
|
1670
|
+
**[CLI Documentation](https://www.google.com/search?q=https://www.xeno-js.it/cli/overview&utm_source=gemini)**
|
|
1671
|
+
for full options.
|
|
1672
|
+
|
|
1673
|
+
---
|
|
1674
|
+
|
|
1675
|
+
## \u{1F91D} For Contributors
|
|
1676
|
+
|
|
1677
|
+
We welcome contributions to Xeno! To maintain the highest code quality and
|
|
1678
|
+
stability of the core framework, **direct pushes to the \`main\` and \`develop\`
|
|
1679
|
+
branches are strictly prohibited.** Please follow this Git Flow to contribute:
|
|
1680
|
+
|
|
1681
|
+
1. **Branch off from \`develop**\`: Create a new branch for your feature or
|
|
1682
|
+
bugfix.
|
|
1683
|
+
|
|
1684
|
+
\`\`\`bash
|
|
1685
|
+
git checkout develop
|
|
1686
|
+
git pull origin develop
|
|
1687
|
+
git checkout -b feat/your-awesome-feature
|
|
1688
|
+
|
|
1689
|
+
\`\`\`
|
|
1690
|
+
|
|
1691
|
+
2. **Make your changes**: Write your code and ensure it passes all local checks
|
|
1692
|
+
(linting, types, and tests).
|
|
1693
|
+
|
|
1694
|
+
\`\`\`bash
|
|
1695
|
+
npm run check
|
|
1696
|
+
|
|
1697
|
+
\`\`\`
|
|
1698
|
+
|
|
1699
|
+
3. **Commit your changes**: We enforce
|
|
1700
|
+
[Conventional Commits](https://www.conventionalcommits.org/?utm_source=gemini).
|
|
1701
|
+
Husky will verify your commit message format.
|
|
1702
|
+
4. **Commit Format:**
|
|
1703
|
+
|
|
1704
|
+
\`\`\`bash
|
|
1705
|
+
feat(scope): add new feature
|
|
1706
|
+
fix(scope): resolve bug
|
|
1707
|
+
chore(scope): update dependencies
|
|
1708
|
+
|
|
1709
|
+
\`\`\`
|
|
1710
|
+
|
|
1711
|
+
5. **Submit a Pull Request (PR)**: Push your branch to GitHub and open a Pull
|
|
1712
|
+
Request targeting the **\`develop\`** branch.
|
|
1713
|
+
6. **Review**: The repository owner will review your code, run pipeline tests,
|
|
1714
|
+
and merge it into \`develop\`.
|
|
1715
|
+
|
|
1716
|
+
_Note: The \`main\` branch is strictly reserved for production releases. Code
|
|
1717
|
+
flows from feature branches \u27A1\uFE0F \`develop\` \u27A1\uFE0F \`main\`._
|
|
1718
|
+
|
|
1719
|
+
### Scripts
|
|
1720
|
+
|
|
1721
|
+
| Command | Description |
|
|
1722
|
+
| ----------------------- | ---------------------------------------------- |
|
|
1723
|
+
| \`npm run build\` | Builds the TypeScript source code into \`dist/\` |
|
|
1724
|
+
| \`npm run typecheck\` | Checks types without emitting files |
|
|
1725
|
+
| \`npm run lint\` | Runs ESLint |
|
|
1726
|
+
| \`npm run format\` | Formats code with Prettier |
|
|
1727
|
+
| \`npm run test\` | Runs the Vitest test suite |
|
|
1728
|
+
| \`npm run test:coverage\` | Runs tests and generates a coverage report |
|
|
1729
|
+
|
|
1730
|
+
### Code Quality (Husky & Git Hooks)
|
|
1731
|
+
|
|
1732
|
+
This project strictly enforces code quality rules before pushing to the
|
|
1733
|
+
repository:
|
|
1734
|
+
|
|
1735
|
+
- **\`pre-commit\`**: Runs \`lint-staged\` on staged files (ESLint + Prettier).
|
|
1736
|
+
- **\`commit-msg\`**: Checks commit messages with \`commitlint\` (we use
|
|
1737
|
+
Conventional Commits).
|
|
1738
|
+
- **\`pre-push\`**: Runs type checking, linting, and testing before code leaves
|
|
1739
|
+
your machine.
|
|
1740
|
+
|
|
1741
|
+
---
|
|
1742
|
+
|
|
1743
|
+
## \u{1F331} Support & Appreciation
|
|
1744
|
+
|
|
1745
|
+
Building, benchmarking, and maintaining a progressive, enterprise-ready
|
|
1746
|
+
open-source framework requires a massive amount of continuous dedication and
|
|
1747
|
+
architectural engineering.
|
|
1748
|
+
|
|
1749
|
+
If Xeno has brought value to your development workflows, helped decouple your
|
|
1750
|
+
core business logic, or simplified your system infrastructure layout, consider
|
|
1751
|
+
supporting its open-source lifecycle. Your backing directly accelerates our
|
|
1752
|
+
strategic roadmap for new out-of-the-box transport integrations and keeps the
|
|
1753
|
+
documentation pristine.
|
|
1754
|
+
|
|
1755
|
+
**Want to know how you can contribute or sponsor Xeno?** We rely on the
|
|
1756
|
+
commitment of our community to keep the project independent and thriving.
|
|
1757
|
+
Whether you are an individual developer or a business using Xeno, your support
|
|
1758
|
+
makes a real difference.
|
|
1759
|
+
|
|
1760
|
+
\u{1F449}
|
|
1761
|
+
**[Read our support guidelines and find out how to help](https://www.xeno-js.it/support-us)**
|
|
1762
|
+
|
|
1763
|
+
Thank you for being part of this decoupled open-source journey!
|
|
1764
|
+
|
|
1765
|
+
<amp-bounce>
|
|
1766
|
+
</amp-bounce>
|
|
1767
|
+
<a href="https://www.buymeacoffee.com/xenojs" target="_blank">
|
|
1768
|
+
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="42" style="height: 42px !important;" />
|
|
1769
|
+
</a>
|
|
1770
|
+
|
|
1771
|
+
---
|
|
1772
|
+
|
|
1773
|
+
## \u{1F6E1}\uFE0F Powered by Xeno
|
|
1774
|
+
|
|
1775
|
+
If you are using Xeno in your project, let the world know! Add this badge to
|
|
1776
|
+
your README:
|
|
1777
|
+
|
|
1778
|
+
\`\`\`html
|
|
1779
|
+
<a
|
|
1780
|
+
href="[https://github.com/xeno-js/xeno-fe](https://github.com/xeno-js/xeno-fe)"
|
|
1781
|
+
target="_blank"
|
|
1782
|
+
>
|
|
1783
|
+
<img
|
|
1784
|
+
src="[https://img.shields.io/badge/Powered%20by-Xeno-black?style=flat-square](https://img.shields.io/badge/Powered%20by-Xeno-black?style=flat-square)"
|
|
1785
|
+
alt="Powered by Xeno"
|
|
1786
|
+
height="20"
|
|
1787
|
+
/>
|
|
1788
|
+
</a>
|
|
1789
|
+
\`\`\`
|
|
1790
|
+
|
|
1791
|
+
## \u{1F4C4} License
|
|
1792
|
+
|
|
1793
|
+
Copyright (c) 2026 Xeno. Licensed under the
|
|
1794
|
+
[ISC License](https://www.google.com/search?q=LICENSE&utm_source=gemini).
|
|
1795
|
+
`;
|
|
1796
|
+
await this._fileService.writeFileRecursive(import_node_path12.default.join(projectPath, "README.md"), content);
|
|
1797
|
+
}
|
|
1798
|
+
};
|
|
1799
|
+
}
|
|
1800
|
+
});
|
|
1801
|
+
|
|
1802
|
+
// src/infrastructure/generators/gitignore.generator.ts
|
|
1803
|
+
var gitignore_generator_exports = {};
|
|
1804
|
+
__export(gitignore_generator_exports, {
|
|
1805
|
+
GitIgnoreGenerator: () => GitIgnoreGenerator
|
|
1806
|
+
});
|
|
1807
|
+
var import_node_path13, GitIgnoreGenerator;
|
|
1808
|
+
var init_gitignore_generator = __esm({
|
|
1809
|
+
"src/infrastructure/generators/gitignore.generator.ts"() {
|
|
1810
|
+
"use strict";
|
|
1811
|
+
import_node_path13 = __toESM(require("path"));
|
|
1812
|
+
init_shared();
|
|
1813
|
+
GitIgnoreGenerator = class {
|
|
1814
|
+
constructor(_fileService) {
|
|
1815
|
+
this._fileService = _fileService;
|
|
1816
|
+
}
|
|
1817
|
+
_fileService;
|
|
1818
|
+
async generate(projectPath, options) {
|
|
1819
|
+
let content = `# Dependencies
|
|
1820
|
+
node_modules/
|
|
1821
|
+
|
|
1822
|
+
# Build artifacts
|
|
1823
|
+
dist/
|
|
1824
|
+
out/
|
|
1825
|
+
build/
|
|
1826
|
+
|
|
1827
|
+
# Environment variables
|
|
1828
|
+
# We ignore actual .env files for security, but keep .env.example for documentation
|
|
1829
|
+
.env
|
|
1830
|
+
.env.local
|
|
1831
|
+
.env.*
|
|
1832
|
+
!.env.example
|
|
1833
|
+
|
|
1834
|
+
# Logs
|
|
1835
|
+
*.log
|
|
1836
|
+
logs/
|
|
1837
|
+
npm-debug.log*
|
|
1838
|
+
|
|
1839
|
+
# Test coverage
|
|
1840
|
+
coverage/
|
|
1841
|
+
.nyc_output/
|
|
1842
|
+
|
|
1843
|
+
# OS and IDE
|
|
1844
|
+
.DS_Store
|
|
1845
|
+
Thumbs.db
|
|
1846
|
+
.vscode/
|
|
1847
|
+
.idea/
|
|
1848
|
+
`;
|
|
1849
|
+
if (options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
1850
|
+
content += `
|
|
1851
|
+
# SQLite Database
|
|
1852
|
+
*.sqlite
|
|
1853
|
+
*.sqlite3
|
|
1854
|
+
*.sqlite-journal
|
|
1855
|
+
`;
|
|
1856
|
+
}
|
|
1857
|
+
if (options.targetEnv === CORE_CONSTANTS.VERCEL) {
|
|
1858
|
+
content += `
|
|
1859
|
+
# Vercel
|
|
1860
|
+
.vercel/
|
|
1861
|
+
`;
|
|
1862
|
+
}
|
|
1863
|
+
const filePath = import_node_path13.default.join(projectPath, ".gitignore");
|
|
1864
|
+
await this._fileService.writeFileRecursive(filePath, content);
|
|
1865
|
+
}
|
|
1866
|
+
};
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
|
|
1870
|
+
// src/infrastructure/generators/vue/app-vue.generator.ts
|
|
1871
|
+
var app_vue_generator_exports = {};
|
|
1872
|
+
__export(app_vue_generator_exports, {
|
|
1873
|
+
AppVueGenerator: () => AppVueGenerator
|
|
1874
|
+
});
|
|
1875
|
+
var import_node_path14, AppVueGenerator;
|
|
1876
|
+
var init_app_vue_generator = __esm({
|
|
1877
|
+
"src/infrastructure/generators/vue/app-vue.generator.ts"() {
|
|
1878
|
+
"use strict";
|
|
1879
|
+
import_node_path14 = __toESM(require("path"));
|
|
1880
|
+
AppVueGenerator = class {
|
|
1881
|
+
constructor(_fileService) {
|
|
1882
|
+
this._fileService = _fileService;
|
|
1883
|
+
}
|
|
1884
|
+
_fileService;
|
|
1885
|
+
async generate(projectPath, options) {
|
|
1886
|
+
const appVue = `<script setup lang="ts">
|
|
1887
|
+
</script>
|
|
1888
|
+
|
|
1889
|
+
<template>
|
|
1890
|
+
<div id="xeno-app">
|
|
1891
|
+
${options.router ? "<router-view />" : "<h1>Welcome to Xeno Vue App</h1>"}
|
|
1892
|
+
</div>
|
|
1893
|
+
</template>`;
|
|
1894
|
+
await this._fileService.writeFileRecursive(import_node_path14.default.join(projectPath, "src", "App.vue"), appVue);
|
|
1895
|
+
}
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
});
|
|
1899
|
+
|
|
1900
|
+
// src/infrastructure/generators/vue/bootstrap-ts.generator.ts
|
|
1901
|
+
var bootstrap_ts_generator_exports = {};
|
|
1902
|
+
__export(bootstrap_ts_generator_exports, {
|
|
1903
|
+
BootstrapGenerator: () => BootstrapGenerator
|
|
1904
|
+
});
|
|
1905
|
+
var import_node_path15, BootstrapGenerator;
|
|
1906
|
+
var init_bootstrap_ts_generator = __esm({
|
|
1907
|
+
"src/infrastructure/generators/vue/bootstrap-ts.generator.ts"() {
|
|
1908
|
+
"use strict";
|
|
1909
|
+
import_node_path15 = __toESM(require("path"));
|
|
1910
|
+
BootstrapGenerator = class {
|
|
1911
|
+
constructor(_fileService) {
|
|
1912
|
+
this._fileService = _fileService;
|
|
1913
|
+
}
|
|
1914
|
+
_fileService;
|
|
1915
|
+
async generate(projectPath, options) {
|
|
1916
|
+
let builderNodes = ` const builder = XenoAppBuilder.create<MyRegistry>()
|
|
1917
|
+
.addContext(() => {})`;
|
|
1918
|
+
if (options.supabase) {
|
|
1919
|
+
builderNodes += `
|
|
1920
|
+
.addAuth((opts, config) => {
|
|
1921
|
+
opts.url = config.getOrThrow('VITE_SUPABASE_URL');
|
|
1922
|
+
opts.key = config.getOrThrow('VITE_SUPABASE_KEY');
|
|
1923
|
+
})`;
|
|
1924
|
+
}
|
|
1925
|
+
if (options.sentry) {
|
|
1926
|
+
builderNodes += `
|
|
1927
|
+
.addLogger((opts, config) => {
|
|
1928
|
+
const env = config.get(COMMON_CONSTANTS.ENV) ?? 'development'
|
|
1929
|
+
const isDev = env.toLowerCase() === 'development'
|
|
1930
|
+
opts.console = isDev
|
|
1931
|
+
if (!isDev) {
|
|
1932
|
+
opts.sentry = {
|
|
1933
|
+
dsn: config.getOrThrow('VITE_SENTRY_DSN'),
|
|
1934
|
+
env,
|
|
1935
|
+
app,
|
|
1936
|
+
router,
|
|
1937
|
+
tracesSampleRate: 0.2,
|
|
1938
|
+
level: LOG_LEVEL.WARN,
|
|
1939
|
+
};
|
|
1940
|
+
}
|
|
1941
|
+
})`;
|
|
1942
|
+
} else {
|
|
1943
|
+
builderNodes += `
|
|
1944
|
+
.addLogger((opts, config) => {
|
|
1945
|
+
opts.console = config.get('VITE_APP_ENV') === 'development';
|
|
1946
|
+
})`;
|
|
1947
|
+
}
|
|
1948
|
+
builderNodes += `
|
|
1949
|
+
.addServices((config, register) => {
|
|
1950
|
+
// Register custom stores, API clients, and CQRS services here
|
|
1951
|
+
})`;
|
|
1952
|
+
const bootstrapTs = `import { XenoAppBuilder, LOG_LEVEL } from '@xeno-js/vue';
|
|
1953
|
+
import type { MyRegistry } from './registry';
|
|
1954
|
+
|
|
1955
|
+
export async function bootstrap(app: any, router: any) {
|
|
1956
|
+
${builderNodes}
|
|
1957
|
+
|
|
1958
|
+
return await builder.build();
|
|
1959
|
+
}
|
|
1960
|
+
`;
|
|
1961
|
+
await this._fileService.writeFileRecursive(
|
|
1962
|
+
import_node_path15.default.join(projectPath, "src", "bootstrap.ts"),
|
|
1963
|
+
bootstrapTs
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
};
|
|
1967
|
+
}
|
|
1968
|
+
});
|
|
1969
|
+
|
|
1970
|
+
// src/infrastructure/generators/vue/index-html.generator.ts
|
|
1971
|
+
var index_html_generator_exports = {};
|
|
1972
|
+
__export(index_html_generator_exports, {
|
|
1973
|
+
IndexVueGenerator: () => IndexVueGenerator
|
|
1974
|
+
});
|
|
1975
|
+
var import_node_path16, IndexVueGenerator;
|
|
1976
|
+
var init_index_html_generator = __esm({
|
|
1977
|
+
"src/infrastructure/generators/vue/index-html.generator.ts"() {
|
|
1978
|
+
"use strict";
|
|
1979
|
+
import_node_path16 = __toESM(require("path"));
|
|
1980
|
+
IndexVueGenerator = class {
|
|
1981
|
+
constructor(_fileService) {
|
|
1982
|
+
this._fileService = _fileService;
|
|
1983
|
+
}
|
|
1984
|
+
_fileService;
|
|
1985
|
+
async generate(projectPath, options) {
|
|
1986
|
+
const indexHtml = `<!DOCTYPE html>
|
|
1987
|
+
<html lang="en">
|
|
1988
|
+
<head>
|
|
1989
|
+
<meta charset="UTF-8" />
|
|
1990
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1991
|
+
<title>${options.targetDir}</title>
|
|
1992
|
+
</head>
|
|
1993
|
+
<body>
|
|
1994
|
+
<div id="app"></div>
|
|
1995
|
+
<script type="module" src="/src/main.ts"></script>
|
|
1996
|
+
</body>
|
|
1997
|
+
</html>`;
|
|
1998
|
+
await this._fileService.writeFileRecursive(import_node_path16.default.join(projectPath, "index.html"), indexHtml);
|
|
1999
|
+
}
|
|
2000
|
+
};
|
|
2001
|
+
}
|
|
2002
|
+
});
|
|
2003
|
+
|
|
2004
|
+
// src/infrastructure/generators/vue/main.generator.ts
|
|
2005
|
+
var main_generator_exports = {};
|
|
2006
|
+
__export(main_generator_exports, {
|
|
2007
|
+
MainGenerator: () => MainGenerator
|
|
2008
|
+
});
|
|
2009
|
+
var import_node_path17, MainGenerator;
|
|
2010
|
+
var init_main_generator = __esm({
|
|
2011
|
+
"src/infrastructure/generators/vue/main.generator.ts"() {
|
|
2012
|
+
"use strict";
|
|
2013
|
+
import_node_path17 = __toESM(require("path"));
|
|
2014
|
+
MainGenerator = class {
|
|
2015
|
+
constructor(_fileService) {
|
|
2016
|
+
this._fileService = _fileService;
|
|
2017
|
+
}
|
|
2018
|
+
_fileService;
|
|
2019
|
+
async generate(projectPath, options) {
|
|
2020
|
+
const routerImport = options.router ? `
|
|
2021
|
+
import router from './router';` : "";
|
|
2022
|
+
const piniaImport = options.pinia ? `
|
|
2023
|
+
import { createPinia } from 'pinia';` : "";
|
|
2024
|
+
const tailwindImport = options.tailwind ? `
|
|
2025
|
+
import './assets/style.css';` : "";
|
|
2026
|
+
const mainTs = `import { createApp } from 'vue';
|
|
2027
|
+
import App from './App.vue';
|
|
2028
|
+
import { bootstrap } from './bootstrap';
|
|
2029
|
+
import { XENO_SERVICES_KEY } from '@xeno-js/vue';${routerImport}${piniaImport}${tailwindImport}
|
|
2030
|
+
|
|
2031
|
+
async function mountApp() {
|
|
2032
|
+
try {
|
|
2033
|
+
const app = createApp(App);
|
|
2034
|
+
${options.pinia ? "\n app.use(createPinia());" : ""}
|
|
2035
|
+
const container = await bootstrap(app, router);
|
|
2036
|
+
${options.router ? "\n app.use(router);" : ""}
|
|
2037
|
+
|
|
2038
|
+
|
|
2039
|
+
// Provide the Xeno IoC container globally to all components
|
|
2040
|
+
app.provide(XENO_SERVICES_KEY, container);
|
|
2041
|
+
|
|
2042
|
+
app.mount('#app');
|
|
2043
|
+
} catch (error) {
|
|
2044
|
+
console.error('Critical error during frontend bootstrap:', error);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
mountApp();
|
|
2049
|
+
`;
|
|
2050
|
+
await this._fileService.writeFileRecursive(import_node_path17.default.join(projectPath, "src", "main.ts"), mainTs);
|
|
2051
|
+
}
|
|
2052
|
+
};
|
|
2053
|
+
}
|
|
2054
|
+
});
|
|
2055
|
+
|
|
2056
|
+
// src/infrastructure/generators/vue/registry-ts.generator.ts
|
|
2057
|
+
var registry_ts_generator_exports = {};
|
|
2058
|
+
__export(registry_ts_generator_exports, {
|
|
2059
|
+
RegistryGenerator: () => RegistryGenerator
|
|
2060
|
+
});
|
|
2061
|
+
var import_node_path18, RegistryGenerator;
|
|
2062
|
+
var init_registry_ts_generator = __esm({
|
|
2063
|
+
"src/infrastructure/generators/vue/registry-ts.generator.ts"() {
|
|
2064
|
+
"use strict";
|
|
2065
|
+
import_node_path18 = __toESM(require("path"));
|
|
2066
|
+
RegistryGenerator = class {
|
|
2067
|
+
constructor(_fileService) {
|
|
2068
|
+
this._fileService = _fileService;
|
|
2069
|
+
}
|
|
2070
|
+
_fileService;
|
|
2071
|
+
async generate(projectPath, _options) {
|
|
2072
|
+
const registryTs = `import type { XenoVueRegistry } from '@xeno-js/vue';
|
|
2073
|
+
|
|
2074
|
+
/**
|
|
2075
|
+
* @file registry.ts
|
|
2076
|
+
* @description Centralized Type Contract for Dependency Injection in Vue.
|
|
2077
|
+
*/
|
|
2078
|
+
export interface MyRegistry extends XenoVueRegistry {
|
|
2079
|
+
// Add your frontend injection tokens here
|
|
2080
|
+
// e.g., USER_DATA_SOURCE: IUserDataSource;
|
|
2081
|
+
}
|
|
2082
|
+
`;
|
|
2083
|
+
await this._fileService.writeFileRecursive(
|
|
2084
|
+
import_node_path18.default.join(projectPath, "src", "registry.ts"),
|
|
2085
|
+
registryTs
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
};
|
|
2089
|
+
}
|
|
2090
|
+
});
|
|
2091
|
+
|
|
2092
|
+
// src/infrastructure/generators/vue/router.generator.ts
|
|
2093
|
+
var router_generator_exports = {};
|
|
2094
|
+
__export(router_generator_exports, {
|
|
2095
|
+
RouterGenerator: () => RouterGenerator
|
|
2096
|
+
});
|
|
2097
|
+
var import_node_path19, RouterGenerator;
|
|
2098
|
+
var init_router_generator = __esm({
|
|
2099
|
+
"src/infrastructure/generators/vue/router.generator.ts"() {
|
|
2100
|
+
"use strict";
|
|
2101
|
+
import_node_path19 = __toESM(require("path"));
|
|
2102
|
+
RouterGenerator = class {
|
|
2103
|
+
constructor(_fileService) {
|
|
2104
|
+
this._fileService = _fileService;
|
|
2105
|
+
}
|
|
2106
|
+
_fileService;
|
|
2107
|
+
async generate(projectPath, options) {
|
|
2108
|
+
if (options.router) {
|
|
2109
|
+
const routerTs = `import { createRouter, createWebHistory } from 'vue-router';
|
|
2110
|
+
|
|
2111
|
+
export default createRouter({
|
|
2112
|
+
history: createWebHistory(),
|
|
2113
|
+
routes: [
|
|
2114
|
+
{
|
|
2115
|
+
path: '/',
|
|
2116
|
+
name: 'Home',
|
|
2117
|
+
component: () => import('../views/HomeView.vue')
|
|
2118
|
+
}
|
|
2119
|
+
]
|
|
2120
|
+
});`;
|
|
2121
|
+
await this._fileService.writeFileRecursive(
|
|
2122
|
+
import_node_path19.default.join(projectPath, "src", "router", "index.ts"),
|
|
2123
|
+
routerTs
|
|
2124
|
+
);
|
|
2125
|
+
await this._fileService.writeFileRecursive(
|
|
2126
|
+
import_node_path19.default.join(projectPath, "src", "views", "HomeView.vue"),
|
|
2127
|
+
`<template>
|
|
2128
|
+
<h1>Home View</h1>
|
|
2129
|
+
</template>`
|
|
2130
|
+
);
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
});
|
|
2136
|
+
|
|
2137
|
+
// src/infrastructure/generators/vue/tailwind.generator.ts
|
|
2138
|
+
var tailwind_generator_exports = {};
|
|
2139
|
+
__export(tailwind_generator_exports, {
|
|
2140
|
+
TailwindGenerator: () => TailwindGenerator
|
|
2141
|
+
});
|
|
2142
|
+
var import_node_path20, TailwindGenerator;
|
|
2143
|
+
var init_tailwind_generator = __esm({
|
|
2144
|
+
"src/infrastructure/generators/vue/tailwind.generator.ts"() {
|
|
2145
|
+
"use strict";
|
|
2146
|
+
import_node_path20 = __toESM(require("path"));
|
|
2147
|
+
TailwindGenerator = class {
|
|
2148
|
+
constructor(_fileService) {
|
|
2149
|
+
this._fileService = _fileService;
|
|
2150
|
+
}
|
|
2151
|
+
_fileService;
|
|
2152
|
+
async generate(projectPath, options) {
|
|
2153
|
+
if (options.tailwind) {
|
|
2154
|
+
await this._fileService.writeFileRecursive(
|
|
2155
|
+
import_node_path20.default.join(projectPath, "src", "assets", "style.css"),
|
|
2156
|
+
`@import "tailwindcss";
|
|
2157
|
+
`
|
|
2158
|
+
);
|
|
2159
|
+
}
|
|
2160
|
+
}
|
|
2161
|
+
};
|
|
2162
|
+
}
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
// src/infrastructure/generators/vue/use-app.generator.ts
|
|
2166
|
+
var use_app_generator_exports = {};
|
|
2167
|
+
__export(use_app_generator_exports, {
|
|
2168
|
+
UseAppVueGenerator: () => UseAppVueGenerator
|
|
2169
|
+
});
|
|
2170
|
+
var import_node_path21, UseAppVueGenerator;
|
|
2171
|
+
var init_use_app_generator = __esm({
|
|
2172
|
+
"src/infrastructure/generators/vue/use-app.generator.ts"() {
|
|
2173
|
+
"use strict";
|
|
2174
|
+
import_node_path21 = __toESM(require("path"));
|
|
2175
|
+
UseAppVueGenerator = class {
|
|
2176
|
+
constructor(_fileService) {
|
|
2177
|
+
this._fileService = _fileService;
|
|
2178
|
+
}
|
|
2179
|
+
_fileService;
|
|
2180
|
+
async generate(projectPath, _options) {
|
|
2181
|
+
const content = `import type { Nullable } from "@xeno-js/vue";
|
|
2182
|
+
import { Guards, XENO_SERVICES_KEY } from "@xeno-js/vue";
|
|
2183
|
+
import { inject } from "vue";
|
|
2184
|
+
import type { MyRegistry } from "./registry";
|
|
2185
|
+
|
|
2186
|
+
let globalServices: Nullable<MyRegistry> = null;
|
|
2187
|
+
|
|
2188
|
+
export const ServicesUtils = Object.freeze({
|
|
2189
|
+
// Metodo per registrare i servizi al bootstrap
|
|
2190
|
+
setGlobalServices(services: MyRegistry) {
|
|
2191
|
+
globalServices = services;
|
|
2192
|
+
},
|
|
2193
|
+
useApp(): MyRegistry {
|
|
2194
|
+
if (Guards.isDefined(globalServices))
|
|
2195
|
+
return globalServices;
|
|
2196
|
+
|
|
2197
|
+
const services = inject<MyRegistry>(XENO_SERVICES_KEY);
|
|
2198
|
+
if (!services)
|
|
2199
|
+
throw new Error(
|
|
2200
|
+
'[Xeno Error]: XenoServices not found. Did you provide them using app.provide(XENO_SERVICES_KEY, services)?',
|
|
2201
|
+
);
|
|
2202
|
+
|
|
2203
|
+
return services;
|
|
2204
|
+
}
|
|
2205
|
+
} as const);
|
|
2206
|
+
`;
|
|
2207
|
+
await this._fileService.writeFileRecursive(import_node_path21.default.join(projectPath, "src", "use-app.ts"), content);
|
|
2208
|
+
}
|
|
2209
|
+
};
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
|
|
2213
|
+
// src/infrastructure/strategies/vue-scaffold.strategy.ts
|
|
2214
|
+
var vue_scaffold_strategy_exports = {};
|
|
2215
|
+
__export(vue_scaffold_strategy_exports, {
|
|
2216
|
+
VueScaffoldStrategy: () => VueScaffoldStrategy
|
|
2217
|
+
});
|
|
2218
|
+
var import_prompts, VueScaffoldStrategy;
|
|
2219
|
+
var init_vue_scaffold_strategy = __esm({
|
|
2220
|
+
"src/infrastructure/strategies/vue-scaffold.strategy.ts"() {
|
|
2221
|
+
"use strict";
|
|
2222
|
+
import_prompts = __toESM(require("prompts"));
|
|
2223
|
+
VueScaffoldStrategy = class {
|
|
2224
|
+
constructor(_fileService) {
|
|
2225
|
+
this._fileService = _fileService;
|
|
2226
|
+
}
|
|
2227
|
+
_fileService;
|
|
2228
|
+
async promptOptions(targetDir) {
|
|
2229
|
+
const response = await (0, import_prompts.default)([
|
|
2230
|
+
{ type: "confirm", name: "axios", message: "Install Axios?", initial: true },
|
|
2231
|
+
{
|
|
2232
|
+
type: "confirm",
|
|
2233
|
+
name: "cockatiel",
|
|
2234
|
+
message: "Install Cockatiel (Resilience)?",
|
|
2235
|
+
initial: true
|
|
2236
|
+
},
|
|
2237
|
+
{ type: "confirm", name: "supabase", message: "Install Supabase Auth?", initial: true },
|
|
2238
|
+
{ type: "confirm", name: "sentry", message: "Install Sentry Logger?", initial: true },
|
|
2239
|
+
{
|
|
2240
|
+
type: "confirm",
|
|
2241
|
+
name: "pinia",
|
|
2242
|
+
message: "Install Pinia (State Management)?",
|
|
2243
|
+
initial: true
|
|
2244
|
+
},
|
|
2245
|
+
{ type: "confirm", name: "router", message: "Install vue-router?", initial: true },
|
|
2246
|
+
{ type: "confirm", name: "tailwind", message: "Install Tailwind CSS?", initial: true },
|
|
2247
|
+
{ type: "confirm", name: "zod", message: "Install Zod Validator?", initial: true },
|
|
2248
|
+
{
|
|
2249
|
+
type: "confirm",
|
|
2250
|
+
name: "gitInit",
|
|
2251
|
+
message: "Initialize a new Git repository?",
|
|
2252
|
+
initial: true
|
|
2253
|
+
}
|
|
2254
|
+
]);
|
|
2255
|
+
return {
|
|
2256
|
+
targetDir,
|
|
2257
|
+
projectType: "vue",
|
|
2258
|
+
...response
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
async getGenerators(_options) {
|
|
2262
|
+
const generators = [];
|
|
2263
|
+
const { PackageJsonVueGenerator: PackageJsonVueGenerator2 } = await Promise.resolve().then(() => (init_packagejson_generator(), packagejson_generator_exports));
|
|
2264
|
+
const { ViteconfigGenerator: ViteconfigGenerator2 } = await Promise.resolve().then(() => (init_vite_config_generator(), vite_config_generator_exports));
|
|
2265
|
+
const { EnvVueGenerator: EnvVueGenerator2 } = await Promise.resolve().then(() => (init_env_generator(), env_generator_exports));
|
|
2266
|
+
const { TsconfigVueGenerator: TsconfigVueGenerator2 } = await Promise.resolve().then(() => (init_tsconfig_generator(), tsconfig_generator_exports));
|
|
2267
|
+
const { ReadmeVueGenerator: ReadmeVueGenerator2 } = await Promise.resolve().then(() => (init_readme_generator(), readme_generator_exports));
|
|
2268
|
+
const { GitIgnoreGenerator: GitIgnoreGenerator2 } = await Promise.resolve().then(() => (init_gitignore_generator(), gitignore_generator_exports));
|
|
2269
|
+
const { AppVueGenerator: AppVueGenerator2 } = await Promise.resolve().then(() => (init_app_vue_generator(), app_vue_generator_exports));
|
|
2270
|
+
const { BootstrapGenerator: BootstrapGenerator3 } = await Promise.resolve().then(() => (init_bootstrap_ts_generator(), bootstrap_ts_generator_exports));
|
|
2271
|
+
const { IndexVueGenerator: IndexVueGenerator2 } = await Promise.resolve().then(() => (init_index_html_generator(), index_html_generator_exports));
|
|
2272
|
+
const { MainGenerator: MainGenerator3 } = await Promise.resolve().then(() => (init_main_generator(), main_generator_exports));
|
|
2273
|
+
const { RegistryGenerator: RegistryGenerator3 } = await Promise.resolve().then(() => (init_registry_ts_generator(), registry_ts_generator_exports));
|
|
2274
|
+
const { RouterGenerator: RouterGenerator2 } = await Promise.resolve().then(() => (init_router_generator(), router_generator_exports));
|
|
2275
|
+
const { TailwindGenerator: TailwindGenerator2 } = await Promise.resolve().then(() => (init_tailwind_generator(), tailwind_generator_exports));
|
|
2276
|
+
const { UseAppVueGenerator: UseAppVueGenerator2 } = await Promise.resolve().then(() => (init_use_app_generator(), use_app_generator_exports));
|
|
2277
|
+
generators.push(new PackageJsonVueGenerator2(this._fileService));
|
|
2278
|
+
generators.push(new ViteconfigGenerator2(this._fileService));
|
|
2279
|
+
generators.push(new EnvVueGenerator2(this._fileService));
|
|
2280
|
+
generators.push(new TsconfigVueGenerator2(this._fileService));
|
|
2281
|
+
generators.push(new AppVueGenerator2(this._fileService));
|
|
2282
|
+
generators.push(new BootstrapGenerator3(this._fileService));
|
|
2283
|
+
generators.push(new IndexVueGenerator2(this._fileService));
|
|
2284
|
+
generators.push(new MainGenerator3(this._fileService));
|
|
2285
|
+
generators.push(new RegistryGenerator3(this._fileService));
|
|
2286
|
+
generators.push(new RouterGenerator2(this._fileService));
|
|
2287
|
+
generators.push(new TailwindGenerator2(this._fileService));
|
|
2288
|
+
generators.push(new ReadmeVueGenerator2(this._fileService));
|
|
2289
|
+
generators.push(new GitIgnoreGenerator2(this._fileService));
|
|
2290
|
+
generators.push(new UseAppVueGenerator2(this._fileService));
|
|
2291
|
+
return generators;
|
|
2292
|
+
}
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2295
|
+
});
|
|
2296
|
+
|
|
2297
|
+
// src/infrastructure/generators/core/packagejson.generator.ts
|
|
2298
|
+
var packagejson_generator_exports2 = {};
|
|
2299
|
+
__export(packagejson_generator_exports2, {
|
|
2300
|
+
PackageJsonCoreGenerator: () => PackageJsonCoreGenerator
|
|
2301
|
+
});
|
|
2302
|
+
var import_node_path22, PackageJsonCoreGenerator;
|
|
2303
|
+
var init_packagejson_generator2 = __esm({
|
|
2304
|
+
"src/infrastructure/generators/core/packagejson.generator.ts"() {
|
|
2305
|
+
"use strict";
|
|
2306
|
+
import_node_path22 = __toESM(require("path"));
|
|
2307
|
+
init_shared();
|
|
2308
|
+
PackageJsonCoreGenerator = class {
|
|
2309
|
+
constructor(_fileService) {
|
|
2310
|
+
this._fileService = _fileService;
|
|
2311
|
+
}
|
|
2312
|
+
_fileService;
|
|
2313
|
+
async generate(projectPath, options) {
|
|
2314
|
+
const packageJson = {
|
|
2315
|
+
name: options.targetDir,
|
|
2316
|
+
version: "1.0.0",
|
|
2317
|
+
private: true,
|
|
2318
|
+
main: "./dist/index.js",
|
|
2319
|
+
module: "./dist/index.js",
|
|
2320
|
+
types: "./dist/index.d.ts",
|
|
2321
|
+
scripts: {
|
|
2322
|
+
start: "tsx src/main.ts",
|
|
2323
|
+
build: "tsc --project tsconfig.json",
|
|
2324
|
+
g: "xeno-js generate"
|
|
2325
|
+
},
|
|
2326
|
+
dependencies: {
|
|
2327
|
+
"@xeno-js/core": "latest",
|
|
2328
|
+
"dotenv": "^16.4.5"
|
|
2329
|
+
},
|
|
2330
|
+
devDependencies: {
|
|
2331
|
+
"@types/node": "^20.0.0",
|
|
2332
|
+
"tsx": "^4.7.0",
|
|
2333
|
+
"typescript": "^5.4.0"
|
|
2334
|
+
}
|
|
2335
|
+
};
|
|
2336
|
+
if (options.database !== CORE_CONSTANTS.NONE) {
|
|
2337
|
+
if (options.database === CORE_CONSTANTS.DRIZZLE) {
|
|
2338
|
+
packageJson.dependencies["drizzle-orm"] = "^0.45.2";
|
|
2339
|
+
packageJson.dependencies["pg"] = "^8.22.0";
|
|
2340
|
+
packageJson.dependencies["postgres"] = "^3.4.9";
|
|
2341
|
+
packageJson.devDependencies["@types/pg"] = "^8.11.0";
|
|
2342
|
+
packageJson.devDependencies["drizzle-kit"] = "^0.31.10";
|
|
2343
|
+
packageJson.scripts["db:migrate"] = "drizzle-kit migrate";
|
|
2344
|
+
} else if (options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
2345
|
+
packageJson.dependencies["drizzle-orm"] = "^0.45.2";
|
|
2346
|
+
packageJson.dependencies["@libsql/client"] = "^0.14.0";
|
|
2347
|
+
packageJson.devDependencies["drizzle-kit"] = "^0.31.10";
|
|
2348
|
+
}
|
|
2349
|
+
packageJson.scripts["db:generate"] = "drizzle-kit generate";
|
|
2350
|
+
packageJson.scripts["db:push"] = "drizzle-kit push";
|
|
2351
|
+
}
|
|
2352
|
+
if (options.axios) packageJson.dependencies["axios"] = "^1.16.1";
|
|
2353
|
+
if (options.cockatiel) packageJson.dependencies["cockatiel"] = "^4.0.0";
|
|
2354
|
+
if (options.pino) {
|
|
2355
|
+
packageJson.dependencies["pino"] = "^10.3.1";
|
|
2356
|
+
packageJson.devDependencies["pino-pretty"] = "^11.2.2";
|
|
2357
|
+
}
|
|
2358
|
+
if (options.sentry) packageJson.dependencies["@sentry/node"] = "^7.64.0";
|
|
2359
|
+
if (options.redis) packageJson.dependencies["ioredis"] = "^5.3.1";
|
|
2360
|
+
if (options.supabase) packageJson.dependencies["@supabase/supabase-js"] = "^2.35.0";
|
|
2361
|
+
if (options.zod) packageJson.dependencies["zod"] = "^4.4.3";
|
|
2362
|
+
const filePath = import_node_path22.default.join(projectPath, "package.json");
|
|
2363
|
+
await this._fileService.writeFileRecursive(filePath, JSON.stringify(packageJson, null, 2));
|
|
2364
|
+
}
|
|
2365
|
+
};
|
|
2366
|
+
}
|
|
2367
|
+
});
|
|
2368
|
+
|
|
2369
|
+
// src/infrastructure/generators/core/bootstrap.generator.ts
|
|
2370
|
+
var bootstrap_generator_exports = {};
|
|
2371
|
+
__export(bootstrap_generator_exports, {
|
|
2372
|
+
BootstrapGenerator: () => BootstrapGenerator2
|
|
2373
|
+
});
|
|
2374
|
+
var import_node_path23, BootstrapGenerator2;
|
|
2375
|
+
var init_bootstrap_generator = __esm({
|
|
2376
|
+
"src/infrastructure/generators/core/bootstrap.generator.ts"() {
|
|
2377
|
+
"use strict";
|
|
2378
|
+
import_node_path23 = __toESM(require("path"));
|
|
2379
|
+
init_shared();
|
|
2380
|
+
BootstrapGenerator2 = class {
|
|
2381
|
+
constructor(_fileService) {
|
|
2382
|
+
this._fileService = _fileService;
|
|
2383
|
+
}
|
|
2384
|
+
_fileService;
|
|
2385
|
+
async generate(projectPath, options) {
|
|
2386
|
+
let builderNodes = `
|
|
2387
|
+
const builder = new AppBuilder<MyRegistry>()
|
|
2388
|
+
.addContext()
|
|
2389
|
+
.addMiddlewares((opts) => {
|
|
2390
|
+
// Add your options here
|
|
2391
|
+
})
|
|
2392
|
+
.addPipeline((opts) => {
|
|
2393
|
+
// Add your options here
|
|
2394
|
+
})`;
|
|
2395
|
+
if (options.database !== CORE_CONSTANTS.NONE) {
|
|
2396
|
+
if (options.database === CORE_CONSTANTS.DRIZZLE) {
|
|
2397
|
+
builderNodes += `
|
|
2398
|
+
.addDb((opts, config) => {
|
|
2399
|
+
opts.enableSqlLite = false
|
|
2400
|
+
opts.connectionString = config.getOrThrow('DATABASE_URL');
|
|
2401
|
+
})`;
|
|
2402
|
+
} else if (options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
2403
|
+
builderNodes += `
|
|
2404
|
+
.addDb((opts, config) => {
|
|
2405
|
+
opts.enableSqlLite = true;
|
|
2406
|
+
opts.connectionString = config.getOrThrow('SQLITE_DATABASE_URL');
|
|
2407
|
+
})`;
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
if (options.redis) {
|
|
2411
|
+
builderNodes += `
|
|
2412
|
+
.addCache((opts, config) => {
|
|
2413
|
+
const isDev = config.get('NODE_ENV') === 'development';
|
|
2414
|
+
opts.inMemory = isDev;
|
|
2415
|
+
opts.redis = !isDev ? {
|
|
2416
|
+
host: config.getOrThrow('REDIS_HOST'),
|
|
2417
|
+
port: config.getNumber('REDIS_PORT') ?? 6379,
|
|
2418
|
+
username: config.get('REDIS_USERNAME') ?? '',
|
|
2419
|
+
password: config.get('REDIS_PASSWORD') ?? '',
|
|
2420
|
+
tls: config.getBoolean('REDIS_TLS') ?? false,
|
|
2421
|
+
maxRetriesPerRequest: 3
|
|
2422
|
+
} : undefined
|
|
2423
|
+
})`;
|
|
2424
|
+
} else {
|
|
2425
|
+
builderNodes += `
|
|
2426
|
+
.addCache((opts) => {
|
|
2427
|
+
opts.inMemory = true;
|
|
2428
|
+
})`;
|
|
2429
|
+
}
|
|
2430
|
+
if (options.supabase) {
|
|
2431
|
+
builderNodes += `
|
|
2432
|
+
.addAuth((opts, config) => {
|
|
2433
|
+
opts.url = config.getOrThrow('SUPABASE_URL');
|
|
2434
|
+
opts.key = config.getOrThrow('SUPABASE_KEY');
|
|
2435
|
+
})`;
|
|
2436
|
+
}
|
|
2437
|
+
if (options.pino || options.sentry) {
|
|
2438
|
+
const pinoConfig = options.pino ? `opts.pino.config = !isDev ? { env: config.getOrThrow('NODE_ENV'), destination: 'stdout', prettyPrint: false } : undefined;` : "";
|
|
2439
|
+
const sentryConfig = options.sentry ? `opts.sentry.config = !isDev ? { dsn: config.getOrThrow('SENTRY_DSN'), environment: config.getOrThrow('NODE_ENV') } : undefined;` : "";
|
|
2440
|
+
builderNodes += `
|
|
2441
|
+
.addLogger((opts, config) => {
|
|
2442
|
+
const isDev = config.get('NODE_ENV') === 'development';
|
|
2443
|
+
opts.level = LOG_LEVEL.INFO;
|
|
2444
|
+
opts.console = isDev;
|
|
2445
|
+
${pinoConfig}
|
|
2446
|
+
${sentryConfig}
|
|
2447
|
+
})`;
|
|
2448
|
+
}
|
|
2449
|
+
builderNodes += `
|
|
2450
|
+
.addServices((opts, config) => {
|
|
2451
|
+
// Register custom repositories and handlers here
|
|
2452
|
+
});`;
|
|
2453
|
+
const content = `import type { IServiceContainer } from '@xeno-js/core';
|
|
2454
|
+
import { AppBuilder, LOG_LEVEL, Nullable, TOKENS } from '@xeno-js/core';
|
|
2455
|
+
import type { MyRegistry } from './registry';
|
|
2456
|
+
|
|
2457
|
+
let containerPromise: Nullable<Promise<IServiceContainer<MyRegistry>>> = null;
|
|
2458
|
+
|
|
2459
|
+
export async function bootstrap() {
|
|
2460
|
+
if (!containerPromise) {
|
|
2461
|
+
containerPromise = (async () => {
|
|
2462
|
+
const builder = getContainer();
|
|
2463
|
+
return await builder.build();
|
|
2464
|
+
})().catch((err) => {
|
|
2465
|
+
containerPromise = null;
|
|
2466
|
+
throw err;
|
|
2467
|
+
});
|
|
2468
|
+
}
|
|
2469
|
+
return containerPromise;
|
|
2470
|
+
}
|
|
2471
|
+
|
|
2472
|
+
function getContainer() {
|
|
2473
|
+
${builderNodes}
|
|
2474
|
+
return builder;
|
|
2475
|
+
}
|
|
2476
|
+
`;
|
|
2477
|
+
const filePath = import_node_path23.default.join(projectPath, "src", "bootstrap.ts");
|
|
2478
|
+
await this._fileService.writeFileRecursive(filePath, content);
|
|
2479
|
+
}
|
|
2480
|
+
};
|
|
2481
|
+
}
|
|
2482
|
+
});
|
|
2483
|
+
|
|
2484
|
+
// src/infrastructure/generators/core/env.generator.ts
|
|
2485
|
+
var env_generator_exports2 = {};
|
|
2486
|
+
__export(env_generator_exports2, {
|
|
2487
|
+
EnvGenerator: () => EnvGenerator
|
|
2488
|
+
});
|
|
2489
|
+
var import_node_path24, EnvGenerator;
|
|
2490
|
+
var init_env_generator2 = __esm({
|
|
2491
|
+
"src/infrastructure/generators/core/env.generator.ts"() {
|
|
2492
|
+
"use strict";
|
|
2493
|
+
import_node_path24 = __toESM(require("path"));
|
|
2494
|
+
init_shared();
|
|
2495
|
+
EnvGenerator = class {
|
|
2496
|
+
constructor(_fileService) {
|
|
2497
|
+
this._fileService = _fileService;
|
|
2498
|
+
}
|
|
2499
|
+
_fileService;
|
|
2500
|
+
async generate(projectPath, options) {
|
|
2501
|
+
let content = `# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2502
|
+
# Xeno APPLICATION ENVIRONMENT VARIABLES
|
|
2503
|
+
# \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2504
|
+
NODE_ENV=development
|
|
2505
|
+
`;
|
|
2506
|
+
if (options.database !== CORE_CONSTANTS.NONE) {
|
|
2507
|
+
if (options.database === CORE_CONSTANTS.DRIZZLE) {
|
|
2508
|
+
content += `
|
|
2509
|
+
# --- Database (PostgreSQL) ---
|
|
2510
|
+
DATABASE_URL=postgres://postgres:password@localhost:5432/xeno_db
|
|
2511
|
+
`;
|
|
2512
|
+
} else if (options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
2513
|
+
content += `
|
|
2514
|
+
# --- Database (SQLite) ---
|
|
2515
|
+
SQLITE_DATABASE_URL=file:./dev.sqlite
|
|
2516
|
+
`;
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
if (options.axios) {
|
|
2520
|
+
content += `
|
|
2521
|
+
# --- Axios ---
|
|
2522
|
+
BASE_URL=
|
|
2523
|
+
`;
|
|
2524
|
+
}
|
|
2525
|
+
if (options.redis) {
|
|
2526
|
+
content += `
|
|
2527
|
+
# --- Cache (Redis) ---
|
|
2528
|
+
REDIS_HOST=localhost
|
|
2529
|
+
REDIS_PORT=6379
|
|
2530
|
+
REDIS_PASSWORD=
|
|
2531
|
+
REDIS_USERNAME=
|
|
2532
|
+
REDIS_TLS=false
|
|
2533
|
+
`;
|
|
2534
|
+
}
|
|
2535
|
+
if (options.supabase) {
|
|
2536
|
+
content += `
|
|
2537
|
+
# --- Authentication (Supabase) ---
|
|
2538
|
+
SUPABASE_URL=https://your-project.supabase.co
|
|
2539
|
+
SUPABASE_KEY=your-anon-key
|
|
2540
|
+
`;
|
|
2541
|
+
}
|
|
2542
|
+
if (options.sentry) {
|
|
2543
|
+
content += `
|
|
2544
|
+
# --- Error Tracking (Sentry) ---
|
|
2545
|
+
SENTRY_DSN=https://your-sentry-dsn@o0.ingest.sentry.io/0
|
|
2546
|
+
SENTRY_ENVIRONMENT=development
|
|
2547
|
+
`;
|
|
2548
|
+
}
|
|
2549
|
+
const envPath = import_node_path24.default.join(projectPath, ".env");
|
|
2550
|
+
const envExamplePath = import_node_path24.default.join(projectPath, ".env.example");
|
|
2551
|
+
await this._fileService.writeFileRecursive(envPath, content);
|
|
2552
|
+
await this._fileService.writeFileRecursive(envExamplePath, content);
|
|
2553
|
+
}
|
|
2554
|
+
};
|
|
2555
|
+
}
|
|
2556
|
+
});
|
|
2557
|
+
|
|
2558
|
+
// src/infrastructure/generators/core/readme.generator.ts
|
|
2559
|
+
var readme_generator_exports2 = {};
|
|
2560
|
+
__export(readme_generator_exports2, {
|
|
2561
|
+
ReadmeGenerator: () => ReadmeGenerator
|
|
2562
|
+
});
|
|
2563
|
+
var import_node_path25, ReadmeGenerator;
|
|
2564
|
+
var init_readme_generator2 = __esm({
|
|
2565
|
+
"src/infrastructure/generators/core/readme.generator.ts"() {
|
|
2566
|
+
"use strict";
|
|
2567
|
+
import_node_path25 = __toESM(require("path"));
|
|
2568
|
+
ReadmeGenerator = class {
|
|
2569
|
+
constructor(_fileService) {
|
|
2570
|
+
this._fileService = _fileService;
|
|
2571
|
+
}
|
|
2572
|
+
_fileService;
|
|
2573
|
+
async generate(projectPath, _options) {
|
|
2574
|
+
const content = `
|
|
2575
|
+
<div align="center">
|
|
2576
|
+
<img src="logo/logo.png" alt="Xeno Logo" width="140" />
|
|
2577
|
+
|
|
2578
|
+
<h1>Xeno</h1>
|
|
2579
|
+
|
|
2580
|
+
<p><em>Enterprise-grade DDD & CQRS framework for Node.js</em></p>
|
|
2581
|
+
|
|
2582
|
+
<p>
|
|
2583
|
+
<a href="https://github.com/xeno-js/xeno-js">
|
|
2584
|
+
<img src="https://img.shields.io/badge/Powered%20by-Xeno-blueviolet?style=flat-square" alt="Powered by Xeno" />
|
|
2585
|
+
</a>
|
|
2586
|
+
<a href="https://github.com/xeno-js/xeno-js/blob/main/LICENSE">
|
|
2587
|
+
<img src="https://img.shields.io/npm/l/@xeno?style=flat-square" alt="License: ISC" />
|
|
2588
|
+
</a>
|
|
2589
|
+
<a href="https://www.npmjs.com/package/@xeno/core">
|
|
2590
|
+
<img src="https://img.shields.io/npm/v/@xeno/core?style=flat-square" alt="NPM Version" />
|
|
2591
|
+
</a>
|
|
2592
|
+
<a href="https://buymeacoffee.com/xenojs">
|
|
2593
|
+
<img src="https://img.shields.io/badge/Buy%20Me%20A%20Coffee-Support-FFdd00?style=flat-square&logo=buy-me-a-coffee&logoColor=black" alt="Buy Me A Coffee" />
|
|
2594
|
+
</a>
|
|
2595
|
+
</p>
|
|
2596
|
+
</div>
|
|
2597
|
+
|
|
2598
|
+
---
|
|
2599
|
+
|
|
2600
|
+
## What is Xeno?
|
|
2601
|
+
|
|
2602
|
+
**Xeno** is an enterprise-grade, runtime-agnostic architectural framework for
|
|
2603
|
+
Node.js built natively with TypeScript. It provides structural primitives for
|
|
2604
|
+
implementing robust **Domain-Driven Design (DDD)** and **Command Query
|
|
2605
|
+
Responsibility Segregation (CQRS)** patterns. By shifting operational logic away
|
|
2606
|
+
from delivery mechanisms and transport frameworks, Xeno ensures your core
|
|
2607
|
+
application architecture remains pristine, testable, and completely isolated
|
|
2608
|
+
from external infrastructural churn.
|
|
2609
|
+
|
|
2610
|
+
---
|
|
2611
|
+
|
|
2612
|
+
## \u{1F4A1} Why Choose Xeno?
|
|
2613
|
+
|
|
2614
|
+
Modern Node.js frameworks often tie business workflows tightly to HTTP server
|
|
2615
|
+
abstractions or rely heavily on experimental language features. Xeno fixes this
|
|
2616
|
+
with an emphasis on developer experience, type safety, and clean separation of
|
|
2617
|
+
concerns.
|
|
2618
|
+
|
|
2619
|
+
- **Zero Decorators**: Xeno eliminates reliance on experimental or unstable TS
|
|
2620
|
+
decorator specifications (\`reflect-metadata\`). The IoC container
|
|
2621
|
+
(\`ServiceContainer\`) uses pure, explicit functional factories that optimize
|
|
2622
|
+
compilation speeds and eliminate runtime black-box behaviors.
|
|
2623
|
+
- **Complete Server Decoupling**: Xeno does not care if you use Fastify, Hono,
|
|
2624
|
+
Express, Koa, or AWS Lambda. The presentation layer handles incoming data
|
|
2625
|
+
using plain, primitive contracts, making migration or multi-runtime hosting
|
|
2626
|
+
completely seamless.
|
|
2627
|
+
- **Pay-For-What-You-Use (Opt-in Modularity)**: Core dependencies are
|
|
2628
|
+
strategically classified as optional peer dependencies. If your architecture
|
|
2629
|
+
doesn't use Redis, Sentry, or Supabase, you do not pull them into your node
|
|
2630
|
+
modules.
|
|
2631
|
+
- **Enterprise-Grade Resiliency & Cross-Cutting Pipelines**: Address complex
|
|
2632
|
+
distributed patterns natively without code duplication. Xeno provides
|
|
2633
|
+
out-of-the-box composite behaviors:
|
|
2634
|
+
- **Idempotency**: Implements multi-tenant logic keyspaces matching advanced
|
|
2635
|
+
SaaS factory patterns for logical partitioning.
|
|
2636
|
+
- **Concurrency Control**: Mitigates thundering herd impacts via advanced
|
|
2637
|
+
backoff retry strategies coupled with randomized jitter.
|
|
2638
|
+
- **Resilience Policies**: Deep integration with circuit breakers, bulkheads,
|
|
2639
|
+
and fallbacks.
|
|
2640
|
+
- **Deterministic Type Safety**: Strong infrastructure validation strategies
|
|
2641
|
+
using Zod schemas.
|
|
2642
|
+
|
|
2643
|
+
---
|
|
2644
|
+
|
|
2645
|
+
## \u{1F4D6} Documentation & Getting Started
|
|
2646
|
+
|
|
2647
|
+
To explore the architecture, programmatic configurations, and extension
|
|
2648
|
+
workflows of Xeno, read the full technical manuals located inside the main
|
|
2649
|
+
documentation hub:
|
|
2650
|
+
|
|
2651
|
+
- **[Framework Documentation Repository](https://www.xeno-js.it/introduction)**
|
|
2652
|
+
|
|
2653
|
+
Inside, you will find exhaustive, step-by-step assembly guides covering core
|
|
2654
|
+
host building (\`AppBuilder\`), isolated request middleware lifecycles, functional
|
|
2655
|
+
\`Result\` monads, and zero-trust authorization pipeline behavior tracks.
|
|
2656
|
+
|
|
2657
|
+
---
|
|
2658
|
+
|
|
2659
|
+
## \u{1F680} Live Executable Demos
|
|
2660
|
+
|
|
2661
|
+
Want to see how Xeno works? Check out the functional example application
|
|
2662
|
+
showcasing end-to-end command/query segregation, multi-tenant databases, and
|
|
2663
|
+
resilient schema handling.
|
|
2664
|
+
|
|
2665
|
+
You can dive straight into the explicit source code modules of specialized
|
|
2666
|
+
sandbox environments:
|
|
2667
|
+
|
|
2668
|
+
- **[\`pipelines_middleware_demo/\`](https://www.xeno-js.it/demo)**: Traces
|
|
2669
|
+
an execution thread from the raw HTTP transport presentation layer, executing
|
|
2670
|
+
automated header extraction and anchoring metadata variables into
|
|
2671
|
+
\`AsyncLocalStorage\` thread boundaries.
|
|
2672
|
+
|
|
2673
|
+
---
|
|
2674
|
+
|
|
2675
|
+
## \u{1F4E6} Installation
|
|
2676
|
+
|
|
2677
|
+
Install the core package:
|
|
2678
|
+
|
|
2679
|
+
\`\`\`bash
|
|
2680
|
+
npm install @xeno-js/core
|
|
2681
|
+
|
|
2682
|
+
\`\`\`
|
|
2683
|
+
|
|
2684
|
+
Xeno uses **Optional Peer Dependencies**. You only install the external
|
|
2685
|
+
libraries you actually need. Node.js will strictly lazy-load only the modules
|
|
2686
|
+
you enable in the configuration.
|
|
2687
|
+
|
|
2688
|
+
\`\`\`bash
|
|
2689
|
+
# Example: Install tools only if you enable them in the builder
|
|
2690
|
+
npm install zod pino cockatiel drizzle-orm
|
|
2691
|
+
|
|
2692
|
+
\`\`\`
|
|
2693
|
+
|
|
2694
|
+
---
|
|
2695
|
+
|
|
2696
|
+
## \u26A1 Bootstrapping & Middleware Example
|
|
2697
|
+
|
|
2698
|
+
Below is an architectural example of how to configure the Xeno
|
|
2699
|
+
\`ServiceContainer\`, load core modules, and process an incoming application
|
|
2700
|
+
payload natively inside a server middleware wrapper.
|
|
2701
|
+
|
|
2702
|
+
### 1. Initialize the Container and Configure Modules
|
|
2703
|
+
|
|
2704
|
+
\`\`\`typescript
|
|
2705
|
+
import { AppBuilder, LOG_LEVEL, TOKENS, XenoRegistry } from '@xeno-js/core'
|
|
2706
|
+
import { FindUserQueryHandler } from './user/cqrs/handlers/index'
|
|
2707
|
+
import { FindUserController } from './user/controllers/index'
|
|
2708
|
+
import { UserMapper } from './user/mappers/user.mapper'
|
|
2709
|
+
import { UserWriteRepository } from './user/repositories/user-write.repository'
|
|
2710
|
+
import { UserDataSource } from './user/datasources/user.datasource'
|
|
2711
|
+
|
|
2712
|
+
// Map your registry token with XenoRegistry<TSchemaDb, TExtension>
|
|
2713
|
+
type MyRegistry = XenoRegistry<{ /** Your Db Schema here **/}, {
|
|
2714
|
+
USER_MAPPER_TOKEN: UserMapper
|
|
2715
|
+
USER_DS_TOKEN: UserDataSource
|
|
2716
|
+
USER_REPOSITORY_TOKEN: UserWriteRepository
|
|
2717
|
+
FIND_USER_QUERY_HANDLER_TOKEN: FindUserQueryHandler
|
|
2718
|
+
FIND_USER_CONTROLLER_TOKEN: FindUserController
|
|
2719
|
+
}>
|
|
2720
|
+
|
|
2721
|
+
// Create the root IoC container context
|
|
2722
|
+
export const bootstrap = new AppBuilder<MyRegistry>()
|
|
2723
|
+
// Configure middleware and only PUBLIC routes
|
|
2724
|
+
.addMiddlewares(opts => {
|
|
2725
|
+
opts.routeRegistry = {
|
|
2726
|
+
'/api/user/:id': ['GET', 'POST'],
|
|
2727
|
+
}
|
|
2728
|
+
})
|
|
2729
|
+
// Configure the CQRS pipeline
|
|
2730
|
+
// Can register Policies for your intent
|
|
2731
|
+
.addPipeline((config) => {
|
|
2732
|
+
config.authorization.policies = {
|
|
2733
|
+
'FIND_USER_QUERY_HANDLER_TOKEN': {
|
|
2734
|
+
// Add authz by user id
|
|
2735
|
+
userId: true
|
|
2736
|
+
// Add authz by tenant id
|
|
2737
|
+
tenantId: true
|
|
2738
|
+
// Add authz by roles
|
|
2739
|
+
roles: ['admin']
|
|
2740
|
+
// Add authz by perissions
|
|
2741
|
+
permissions: ['read'],
|
|
2742
|
+
}
|
|
2743
|
+
}
|
|
2744
|
+
// Can add idempotency pipeline for command
|
|
2745
|
+
config.commandBus.idempotency = { lockTtlSeconds: 30, processedTtlSeconds: 60 }
|
|
2746
|
+
// Can add concurrency pipeline for command
|
|
2747
|
+
config.commandBus.concurrency = { delayConfig: { baseDelayMs: 100, maxJitterMs: 500 }, maxRetries: 3 }
|
|
2748
|
+
// Can add caching pipeline for query
|
|
2749
|
+
config.queryBus.isEnabled = true
|
|
2750
|
+
})
|
|
2751
|
+
// Configure Database with drizzle
|
|
2752
|
+
.addDb((opts, config) => {
|
|
2753
|
+
opts.connectionString = config.getOrThrow('DATABASE_URL')
|
|
2754
|
+
})
|
|
2755
|
+
// Configure Authentication with supabase
|
|
2756
|
+
.addAuth((opts, config) => {
|
|
2757
|
+
opts.key = 'demo-key'
|
|
2758
|
+
opts.url = config.getOrThrow('API_BASE_URL')
|
|
2759
|
+
})
|
|
2760
|
+
// Configure your logger (e.g. Console, Sentry, Pino or custom logger)
|
|
2761
|
+
.addLogger((config) => {
|
|
2762
|
+
config.level = LOG_LEVEL.INFO
|
|
2763
|
+
config.console = true
|
|
2764
|
+
})
|
|
2765
|
+
// Register your services
|
|
2766
|
+
.addServices((services) => {
|
|
2767
|
+
// REGISTER MAPPER
|
|
2768
|
+
services.addScoped('USER_MAPPER_TOKEN', () => new UserMapper())
|
|
2769
|
+
|
|
2770
|
+
// REGISTER DATASOURCES
|
|
2771
|
+
services.addScoped('USER_DS_TOKEN', (c) => new UserDataSource(c.resolve(TOKENS.DB_CONTEXT)))
|
|
2772
|
+
|
|
2773
|
+
// REGISTER REPOSITORIES
|
|
2774
|
+
services.addScoped('USER_REPOSITORY_TOKEN', (c) => new UserWriteRepository(c.resolve('USER_DS_TOKEN'), c.resolve('USER_MAPPER_TOKEN')))
|
|
2775
|
+
|
|
2776
|
+
// REGISTER HANDLERS
|
|
2777
|
+
services.addScoped('FIND_USER_QUERY_HANDLER_TOKEN', (c) => {
|
|
2778
|
+
const requestcontext = c.resolve('USER_CONTEXT_FACTORY')
|
|
2779
|
+
const repository = c.resolve('USER_READ_REPOSITORY')
|
|
2780
|
+
return new FindUserQueryHandler(repository, requestcontext)
|
|
2781
|
+
})
|
|
2782
|
+
|
|
2783
|
+
// REGISTER CONTROLLERS
|
|
2784
|
+
services.addTransient('FIND_USER_CONTROLLER_TOKEN', (c) => {
|
|
2785
|
+
return new FindUserController(c.resolve('CONTEXT_ACCESSOR'), c.resolve('MEDIATOR'))
|
|
2786
|
+
})
|
|
2787
|
+
})
|
|
2788
|
+
|
|
2789
|
+
\`\`\`
|
|
2790
|
+
|
|
2791
|
+
### 2. Wrap and Run within Server Middleware (e.g., Fastify / Hono)
|
|
2792
|
+
|
|
2793
|
+
\`\`\`typescript
|
|
2794
|
+
import 'dotenv/config'
|
|
2795
|
+
import { ContainerUtils } from '@xeno-js/core'
|
|
2796
|
+
import fastify from 'fastify'
|
|
2797
|
+
import { bootstrap } from './bootstrap'
|
|
2798
|
+
|
|
2799
|
+
async function runDemo() {
|
|
2800
|
+
console.log('\u2699\uFE0F Initialized Xeno Container...')
|
|
2801
|
+
try {
|
|
2802
|
+
// 1. Bootstrap the application and get the service container
|
|
2803
|
+
const xenoApp = await bootstrap.build()
|
|
2804
|
+
|
|
2805
|
+
console.log('\u{1F680} Starting Fastify server on http://localhost:3000...')
|
|
2806
|
+
|
|
2807
|
+
// 2. Resolve the middleware from the container
|
|
2808
|
+
const middleware = xeno.resolve('MIDDLEWARE')
|
|
2809
|
+
// 3. Create a Fastify instance to handle HTTP requests
|
|
2810
|
+
const app = fastify()
|
|
2811
|
+
|
|
2812
|
+
// \u2500\u2500\u2500 ENDPOINT 2: QUERY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2813
|
+
app.get('/api/user/:id', async (request, reply) => {
|
|
2814
|
+
// 4. Execute the middleware to handle the request context and authentication, then call the StatusController's handle method with the request payload.
|
|
2815
|
+
const responseDto = await middleware.execute(
|
|
2816
|
+
{ path: '/api/user/:id', method: 'GET', transport: { res: reply, req: request } },
|
|
2817
|
+
{ ...request.headers },
|
|
2818
|
+
async () => {
|
|
2819
|
+
const { id } = request.params as any
|
|
2820
|
+
const controller = ContainerUtils.resolveServiceScoped('FIND_USER_CONTROLLER_TOKEN', xenoApp)
|
|
2821
|
+
return await controller.handle({ id: id ?? '123' });
|
|
2822
|
+
}
|
|
2823
|
+
);
|
|
2824
|
+
|
|
2825
|
+
return reply
|
|
2826
|
+
.status(responseDto.status)
|
|
2827
|
+
.type('application/json')
|
|
2828
|
+
.send(responseDto.data)
|
|
2829
|
+
})
|
|
2830
|
+
|
|
2831
|
+
console.log('\u2705 Routes set up. Ready to accept requests.')
|
|
2832
|
+
|
|
2833
|
+
// \u2500\u2500\u2500 START SERVER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
2834
|
+
try {
|
|
2835
|
+
await app.listen({ port: 3000 })
|
|
2836
|
+
console.log('\u{1F680} Application running on http://localhost:3000')
|
|
2837
|
+
console.log('\u{1F449} GET /api/user/:id (GET: api/user/1)')
|
|
2838
|
+
} catch (err) {
|
|
2839
|
+
console.error('Error starting Fastify server:', err)
|
|
2840
|
+
app.log.error(err)
|
|
2841
|
+
process.exit(1)
|
|
2842
|
+
}
|
|
2843
|
+
} catch (error) {
|
|
2844
|
+
console.error('Error during bootstrap or server setup:', error)
|
|
2845
|
+
process.exit(1)
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
runDemo()
|
|
2850
|
+
\`\`\`
|
|
2851
|
+
|
|
2852
|
+
---
|
|
2853
|
+
|
|
2854
|
+
## \u{1F6E0} Scaffold your project with CLI
|
|
2855
|
+
|
|
2856
|
+
Xeno includes an official CLI tool, \`@xeno-js/cli\`, designed to bootstrap your
|
|
2857
|
+
new application in seconds. It offers an interactive setup to select exactly the
|
|
2858
|
+
modules you need (Database, HTTP, Auth, Logging, etc.), ensuring you start with
|
|
2859
|
+
a clean, pre-configured architecture tailored to your specific requirements.
|
|
2860
|
+
|
|
2861
|
+
If you want to learn how to use it, see the full options available, or
|
|
2862
|
+
understand how the scaffolding engine works, check the
|
|
2863
|
+
**[CLI Documentation](https://www.xeno-js.it/cli/overview)**.
|
|
2864
|
+
|
|
2865
|
+
---
|
|
2866
|
+
|
|
2867
|
+
## \u{1F91D} For Contributors
|
|
2868
|
+
|
|
2869
|
+
We welcome contributions to Xeno! To maintain the highest code quality and
|
|
2870
|
+
stability of the core framework, **direct pushes to the \`main\` and \`develop\`
|
|
2871
|
+
branches are strictly prohibited.** Please follow this Git Flow to contribute:
|
|
2872
|
+
|
|
2873
|
+
1. **Branch off from \`develop\`**: Create a new branch for your feature or
|
|
2874
|
+
bugfix.
|
|
2875
|
+
|
|
2876
|
+
\`\`\`bash
|
|
2877
|
+
git checkout develop
|
|
2878
|
+
git pull origin develop
|
|
2879
|
+
git checkout -b feat/your-awesome-feature
|
|
2880
|
+
\`\`\`
|
|
2881
|
+
|
|
2882
|
+
2. **Make your changes**: Write your code and ensure it passes all local checks
|
|
2883
|
+
(linting, types, and tests).
|
|
2884
|
+
|
|
2885
|
+
\`\`\`bash
|
|
2886
|
+
npm run check
|
|
2887
|
+
\`\`\`
|
|
2888
|
+
|
|
2889
|
+
3. **Commit your changes**: We enforce
|
|
2890
|
+
[Conventional Commits](https://www.conventionalcommits.org/). Husky will
|
|
2891
|
+
verify your commit message format.
|
|
2892
|
+
|
|
2893
|
+
4. **Commit Format:**
|
|
2894
|
+
|
|
2895
|
+
\`\`\`bash
|
|
2896
|
+
feat(scope): add new feature
|
|
2897
|
+
fix(scope): resolve bug
|
|
2898
|
+
chore(scope): update dependencies
|
|
2899
|
+
\`\`\`
|
|
2900
|
+
|
|
2901
|
+
5. **Submit a Pull Request (PR)**: Push your branch to GitHub and open a Pull
|
|
2902
|
+
Request targeting the **\`develop\`** branch.
|
|
2903
|
+
|
|
2904
|
+
6. **Review**: The repository owner will review your code, run pipeline tests,
|
|
2905
|
+
and merge it into \`develop\`.
|
|
2906
|
+
|
|
2907
|
+
_Note: The \`main\` branch is strictly reserved for production releases. Code
|
|
2908
|
+
flows from feature branches \u27A1\uFE0F \`develop\` \u27A1\uFE0F \`main\`._
|
|
2909
|
+
|
|
2910
|
+
### Scripts
|
|
2911
|
+
|
|
2912
|
+
| Command | Description |
|
|
2913
|
+
| ----------------------- | ---------------------------------------------- |
|
|
2914
|
+
| \`npm run build\` | Builds the TypeScript source code into \`dist/\` |
|
|
2915
|
+
| \`npm run typecheck\` | Checks types without emitting files |
|
|
2916
|
+
| \`npm run lint\` | Runs ESLint |
|
|
2917
|
+
| \`npm run format\` | Formats code with Prettier |
|
|
2918
|
+
| \`npm run test\` | Runs the Vitest test suite |
|
|
2919
|
+
| \`npm run test:coverage\` | Runs tests and generates a coverage report |
|
|
2920
|
+
|
|
2921
|
+
### Code Quality (Husky & Git Hooks)
|
|
2922
|
+
|
|
2923
|
+
This project strictly enforces code quality rules before pushing to the
|
|
2924
|
+
repository:
|
|
2925
|
+
|
|
2926
|
+
- **\`pre-commit\`**: Runs \`lint-staged\` on staged files (ESLint + Prettier).
|
|
2927
|
+
- **\`commit-msg\`**: Checks commit messages with \`commitlint\` (we use
|
|
2928
|
+
Conventional Commits).
|
|
2929
|
+
- **\`pre-push\`**: Runs type checking, linting, and testing before code leaves
|
|
2930
|
+
your machine.
|
|
2931
|
+
|
|
2932
|
+
---
|
|
2933
|
+
|
|
2934
|
+
## \u{1F331} Support & Appreciation
|
|
2935
|
+
|
|
2936
|
+
Building, benchmarking, and maintaining a progressive, enterprise-ready
|
|
2937
|
+
open-source framework requires a massive amount of continuous dedication and
|
|
2938
|
+
architectural engineering.
|
|
2939
|
+
|
|
2940
|
+
If Xeno has brought value to your development workflows, helped decouple your
|
|
2941
|
+
core business logic, or simplified your system infrastructure layout, consider
|
|
2942
|
+
supporting its open-source lifecycle. Your backing directly accelerates our
|
|
2943
|
+
strategic roadmap for new out-of-the-box transport integrations (such as gRPC,
|
|
2944
|
+
RabbitMQ, and GraphQL) and keeps the documentation pristine.
|
|
2945
|
+
|
|
2946
|
+
**Want to know how you can contribute or sponsor Xeno?** We rely on the
|
|
2947
|
+
commitment of our community to keep the project independent and thriving.
|
|
2948
|
+
Whether you are an individual developer or a business using Xeno, your support
|
|
2949
|
+
makes a real difference.
|
|
2950
|
+
|
|
2951
|
+
\u{1F449}
|
|
2952
|
+
**[Read our support guidelines and find out how to help](https://www.xeno-js.it/support-us)**
|
|
2953
|
+
|
|
2954
|
+
Thank you for being part of this decoupled open-source journey!
|
|
2955
|
+
|
|
2956
|
+
<amp-bounce>
|
|
2957
|
+
</amp-bounce>
|
|
2958
|
+
<a href="https://www.buymeacoffee.com/xenojs" target="_blank">
|
|
2959
|
+
<img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" height="42" style="height: 42px !important;" />
|
|
2960
|
+
</a>
|
|
2961
|
+
|
|
2962
|
+
---
|
|
2963
|
+
|
|
2964
|
+
## \u{1F6E1}\uFE0F Powered by Xeno
|
|
2965
|
+
|
|
2966
|
+
If you are using Xeno in your project, let the world know! Add this badge to
|
|
2967
|
+
your README:
|
|
2968
|
+
|
|
2969
|
+
\`\`\`html
|
|
2970
|
+
<a
|
|
2971
|
+
href="[https://github.com/xeno-js/xeno-js](https://github.com/xeno-js/xeno-js)"
|
|
2972
|
+
target="_blank"
|
|
2973
|
+
>
|
|
2974
|
+
<img
|
|
2975
|
+
src="[https://img.shields.io/badge/Powered%20by-Xeno-black?style=flat-square](https://img.shields.io/badge/Powered%20by-Xeno-black?style=flat-square)"
|
|
2976
|
+
alt="Powered by Xeno"
|
|
2977
|
+
height="20"
|
|
2978
|
+
/>
|
|
2979
|
+
</a>
|
|
2980
|
+
\`\`\`
|
|
2981
|
+
|
|
2982
|
+
## \u{1F4C4} License
|
|
2983
|
+
|
|
2984
|
+
Copyright (c) 2026 Xeno. Licensed under the [ISC License](LICENSE).
|
|
2985
|
+
|
|
2986
|
+
`;
|
|
2987
|
+
const filePath = import_node_path25.default.join(projectPath, "readme");
|
|
2988
|
+
await this._fileService.writeFileRecursive(filePath, content);
|
|
2989
|
+
}
|
|
2990
|
+
};
|
|
2991
|
+
}
|
|
2992
|
+
});
|
|
2993
|
+
|
|
2994
|
+
// src/infrastructure/generators/core/registry.generator.ts
|
|
2995
|
+
var registry_generator_exports = {};
|
|
2996
|
+
__export(registry_generator_exports, {
|
|
2997
|
+
RegistryGenerator: () => RegistryGenerator2
|
|
2998
|
+
});
|
|
2999
|
+
var import_node_path26, RegistryGenerator2;
|
|
3000
|
+
var init_registry_generator = __esm({
|
|
3001
|
+
"src/infrastructure/generators/core/registry.generator.ts"() {
|
|
3002
|
+
"use strict";
|
|
3003
|
+
import_node_path26 = __toESM(require("path"));
|
|
3004
|
+
init_shared();
|
|
3005
|
+
RegistryGenerator2 = class {
|
|
3006
|
+
constructor(_fileService) {
|
|
3007
|
+
this._fileService = _fileService;
|
|
3008
|
+
}
|
|
3009
|
+
_fileService;
|
|
3010
|
+
async generate(projectPath, options) {
|
|
3011
|
+
let dbImport = "";
|
|
3012
|
+
let registryType = "";
|
|
3013
|
+
if (options.database !== CORE_CONSTANTS.NONE) {
|
|
3014
|
+
if (options.database === CORE_CONSTANTS.DRIZZLE) {
|
|
3015
|
+
dbImport = `import type { DbSchema } from './schema';`;
|
|
3016
|
+
registryType = `XenoRegistry<DbSchema>`;
|
|
3017
|
+
} else if (options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
3018
|
+
dbImport = `import type { SqliteSchema } from './schema';`;
|
|
3019
|
+
registryType = `XenoRegistry<SqliteSchema>`;
|
|
3020
|
+
}
|
|
3021
|
+
} else {
|
|
3022
|
+
registryType = `XenoRegistry`;
|
|
3023
|
+
}
|
|
3024
|
+
const content = `import type { XenoRegistry } from '@xeno-js/core';
|
|
3025
|
+
${dbImport}
|
|
3026
|
+
|
|
3027
|
+
/**
|
|
3028
|
+
* @file registry.ts
|
|
3029
|
+
* @description Centralized Type Contract for Dependency Injection.
|
|
3030
|
+
*/
|
|
3031
|
+
export interface MyRegistry extends ${registryType} {
|
|
3032
|
+
// Add your application injection tokens here
|
|
3033
|
+
// e.g., USER_REPOSITORY: IUserRepository;
|
|
3034
|
+
}
|
|
3035
|
+
`;
|
|
3036
|
+
const filePath = import_node_path26.default.join(projectPath, "src", "registry.ts");
|
|
3037
|
+
await this._fileService.writeFileRecursive(filePath, content);
|
|
3038
|
+
}
|
|
3039
|
+
};
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
|
|
3043
|
+
// src/infrastructure/generators/core/tsconfig.generator.ts
|
|
3044
|
+
var tsconfig_generator_exports2 = {};
|
|
3045
|
+
__export(tsconfig_generator_exports2, {
|
|
3046
|
+
TsconfigGenerator: () => TsconfigGenerator
|
|
3047
|
+
});
|
|
3048
|
+
var import_node_path27, TsconfigGenerator;
|
|
3049
|
+
var init_tsconfig_generator2 = __esm({
|
|
3050
|
+
"src/infrastructure/generators/core/tsconfig.generator.ts"() {
|
|
3051
|
+
"use strict";
|
|
3052
|
+
import_node_path27 = __toESM(require("path"));
|
|
3053
|
+
init_shared();
|
|
3054
|
+
TsconfigGenerator = class {
|
|
3055
|
+
constructor(_fileService) {
|
|
3056
|
+
this._fileService = _fileService;
|
|
3057
|
+
}
|
|
3058
|
+
_fileService;
|
|
3059
|
+
async generate(projectPath, options) {
|
|
3060
|
+
const tsconfig = {
|
|
3061
|
+
compilerOptions: {
|
|
3062
|
+
target: "ES2022",
|
|
3063
|
+
module: "ESNext",
|
|
3064
|
+
moduleResolution: "bundler",
|
|
3065
|
+
strict: true,
|
|
3066
|
+
esModuleInterop: true,
|
|
3067
|
+
skipLibCheck: true,
|
|
3068
|
+
forceConsistentCasingInFileNames: true,
|
|
3069
|
+
outDir: "./dist",
|
|
3070
|
+
rootDir: "./src",
|
|
3071
|
+
types: ["node"],
|
|
3072
|
+
paths: {
|
|
3073
|
+
"@/*": ["./src/*"]
|
|
3074
|
+
}
|
|
3075
|
+
},
|
|
3076
|
+
include: ["src/**/*.ts"]
|
|
3077
|
+
};
|
|
3078
|
+
if (options.database === CORE_CONSTANTS.DRIZZLE || options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
3079
|
+
tsconfig.include.push("drizzle.config.ts");
|
|
3080
|
+
}
|
|
3081
|
+
const filePath = import_node_path27.default.join(projectPath, "tsconfig.json");
|
|
3082
|
+
await this._fileService.writeFileRecursive(filePath, JSON.stringify(tsconfig, null, 2));
|
|
3083
|
+
}
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
});
|
|
3087
|
+
|
|
3088
|
+
// src/infrastructure/generators/core/drizzle.generator.ts
|
|
3089
|
+
var drizzle_generator_exports = {};
|
|
3090
|
+
__export(drizzle_generator_exports, {
|
|
3091
|
+
DrizzleGenerator: () => DrizzleGenerator
|
|
3092
|
+
});
|
|
3093
|
+
var import_node_path28, DrizzleGenerator;
|
|
3094
|
+
var init_drizzle_generator = __esm({
|
|
3095
|
+
"src/infrastructure/generators/core/drizzle.generator.ts"() {
|
|
3096
|
+
"use strict";
|
|
3097
|
+
import_node_path28 = __toESM(require("path"));
|
|
3098
|
+
DrizzleGenerator = class {
|
|
3099
|
+
constructor(_fileService) {
|
|
3100
|
+
this._fileService = _fileService;
|
|
3101
|
+
}
|
|
3102
|
+
_fileService;
|
|
3103
|
+
async generate(projectPath, _options) {
|
|
3104
|
+
const configContent = this.composeDrizzleConfig();
|
|
3105
|
+
const filePath = import_node_path28.default.join(projectPath, "drizzle.config.ts");
|
|
3106
|
+
await this._fileService.writeFileRecursive(filePath, configContent);
|
|
3107
|
+
const schemaContent = this.composeSchema();
|
|
3108
|
+
await this._fileService.writeFileRecursive(
|
|
3109
|
+
import_node_path28.default.join(projectPath, "src", "schema.ts"),
|
|
3110
|
+
schemaContent
|
|
3111
|
+
);
|
|
3112
|
+
}
|
|
3113
|
+
composeDrizzleConfig() {
|
|
3114
|
+
return `import 'dotenv/config';
|
|
3115
|
+
import { defineConfig } from 'drizzle-kit';
|
|
3116
|
+
|
|
3117
|
+
/**
|
|
3118
|
+
* Drizzle Kit configuration file.
|
|
3119
|
+
* Defines the schema location, migration output directory, and database connection.
|
|
3120
|
+
*/
|
|
3121
|
+
export default defineConfig({
|
|
3122
|
+
schema: './src/schema.ts',
|
|
3123
|
+
out: './drizzle',
|
|
3124
|
+
dialect: 'postgresql',
|
|
3125
|
+
dbCredentials: {
|
|
3126
|
+
url: process.env.DATABASE_URL!,
|
|
3127
|
+
},
|
|
3128
|
+
verbose: true,
|
|
3129
|
+
strict: true,
|
|
3130
|
+
});
|
|
3131
|
+
`;
|
|
3132
|
+
}
|
|
3133
|
+
composeSchema() {
|
|
3134
|
+
return `import { boolean, pgTable, serial, text, timestamp } from 'drizzle-orm/pg-core';
|
|
3135
|
+
|
|
3136
|
+
/**
|
|
3137
|
+
* @description Define your database tables here.
|
|
3138
|
+
* The 'usersTable' serves as a baseline example.
|
|
3139
|
+
* You can add more tables, relations, and indexes according to your domain requirements.
|
|
3140
|
+
*/
|
|
3141
|
+
export const usersTable = pgTable('users', {
|
|
3142
|
+
id: serial('id').primaryKey(),
|
|
3143
|
+
name: text('name').notNull(),
|
|
3144
|
+
email: text('email').notNull().unique(),
|
|
3145
|
+
isDeleted: boolean('is_deleted').notNull().default(false),
|
|
3146
|
+
deletedAt: timestamp('deleted_at'),
|
|
3147
|
+
createdAt: timestamp('created_at').defaultNow(),
|
|
3148
|
+
});
|
|
3149
|
+
|
|
3150
|
+
/**
|
|
3151
|
+
* Types inferred from the schema.
|
|
3152
|
+
* - UserDto: Represents the shape of the data retrieved from the database ($inferSelect).
|
|
3153
|
+
* - NewUserDto: Represents the shape of the data required to insert a new record ($inferInsert).
|
|
3154
|
+
*/
|
|
3155
|
+
export type UserDto = typeof usersTable.$inferSelect;
|
|
3156
|
+
export type NewUserDto = typeof usersTable.$inferInsert;
|
|
3157
|
+
|
|
3158
|
+
/**
|
|
3159
|
+
* Centralized Database Schema dictionary.
|
|
3160
|
+
*/
|
|
3161
|
+
export type DbSchema = {
|
|
3162
|
+
users: typeof usersTable;
|
|
3163
|
+
};
|
|
3164
|
+
`;
|
|
3165
|
+
}
|
|
3166
|
+
};
|
|
3167
|
+
}
|
|
3168
|
+
});
|
|
3169
|
+
|
|
3170
|
+
// src/infrastructure/generators/core/drizzle-sql-lite.generator.ts
|
|
3171
|
+
var drizzle_sql_lite_generator_exports = {};
|
|
3172
|
+
__export(drizzle_sql_lite_generator_exports, {
|
|
3173
|
+
DrizzleSqlLiteGenerator: () => DrizzleSqlLiteGenerator
|
|
3174
|
+
});
|
|
3175
|
+
var import_node_path29, DrizzleSqlLiteGenerator;
|
|
3176
|
+
var init_drizzle_sql_lite_generator = __esm({
|
|
3177
|
+
"src/infrastructure/generators/core/drizzle-sql-lite.generator.ts"() {
|
|
3178
|
+
"use strict";
|
|
3179
|
+
import_node_path29 = __toESM(require("path"));
|
|
3180
|
+
DrizzleSqlLiteGenerator = class {
|
|
3181
|
+
constructor(_fileService) {
|
|
3182
|
+
this._fileService = _fileService;
|
|
3183
|
+
}
|
|
3184
|
+
_fileService;
|
|
3185
|
+
async generate(projectPath, _options) {
|
|
3186
|
+
const configContent = this.composeDrizzleConfig();
|
|
3187
|
+
const filePath = import_node_path29.default.join(projectPath, "drizzle.config.ts");
|
|
3188
|
+
await this._fileService.writeFileRecursive(filePath, configContent);
|
|
3189
|
+
const schemaPath = import_node_path29.default.join(projectPath, "src", "schema.ts");
|
|
3190
|
+
const schemaContent = this.composeSchema();
|
|
3191
|
+
await this._fileService.writeFileRecursive(schemaPath, schemaContent);
|
|
3192
|
+
}
|
|
3193
|
+
composeDrizzleConfig() {
|
|
3194
|
+
return `import 'dotenv/config';
|
|
3195
|
+
import { defineConfig } from 'drizzle-kit';
|
|
3196
|
+
|
|
3197
|
+
/**
|
|
3198
|
+
* Drizzle Kit configuration for SQLite / libSQL.
|
|
3199
|
+
* Uses environment variable SQLITE_DATABASE_URL to locate the database.
|
|
3200
|
+
*/
|
|
3201
|
+
export default defineConfig({
|
|
3202
|
+
schema: './src/schema.ts',
|
|
3203
|
+
out: './drizzle',
|
|
3204
|
+
dialect: 'sqlite',
|
|
3205
|
+
dbCredentials: {
|
|
3206
|
+
url: process.env.SQLITE_DATABASE_URL!,
|
|
3207
|
+
},
|
|
3208
|
+
verbose: true,
|
|
3209
|
+
strict: true,
|
|
3210
|
+
});
|
|
3211
|
+
`;
|
|
3212
|
+
}
|
|
3213
|
+
composeSchema() {
|
|
3214
|
+
return `import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
|
|
3215
|
+
|
|
3216
|
+
/**
|
|
3217
|
+
* Example SQLite schema using Drizzle's libsql primitives.
|
|
3218
|
+
* Place your application tables here (this file is scaffolded by the CLI when
|
|
3219
|
+
* the user opts into SQLite support).
|
|
3220
|
+
*/
|
|
3221
|
+
export const usersTable = sqliteTable('users', {
|
|
3222
|
+
id: integer('id').primaryKey().notNull(),
|
|
3223
|
+
name: text('name').notNull(),
|
|
3224
|
+
description: text('description'),
|
|
3225
|
+
});
|
|
3226
|
+
|
|
3227
|
+
/**
|
|
3228
|
+
* Types inferred from the schema.
|
|
3229
|
+
* - UserDto: Represents the shape of the data retrieved from the database ($inferSelect).
|
|
3230
|
+
* - NewUserDto: Represents the shape of the data required to insert a new record ($inferInsert).
|
|
3231
|
+
*/
|
|
3232
|
+
export type UserDto = typeof usersTable.$inferSelect;
|
|
3233
|
+
export type NewUserDto = typeof usersTable.$inferInsert;
|
|
3234
|
+
|
|
3235
|
+
/**
|
|
3236
|
+
* Centralized Database Schema dictionary.
|
|
3237
|
+
*/
|
|
3238
|
+
export type SqliteSchema = {
|
|
3239
|
+
users: typeof usersTable;
|
|
3240
|
+
};
|
|
3241
|
+
`;
|
|
3242
|
+
}
|
|
3243
|
+
};
|
|
3244
|
+
}
|
|
3245
|
+
});
|
|
3246
|
+
|
|
3247
|
+
// src/infrastructure/generators/core/main.generator.ts
|
|
3248
|
+
var main_generator_exports2 = {};
|
|
3249
|
+
__export(main_generator_exports2, {
|
|
3250
|
+
MainGenerator: () => MainGenerator2
|
|
3251
|
+
});
|
|
3252
|
+
var import_node_path30, MainGenerator2;
|
|
3253
|
+
var init_main_generator2 = __esm({
|
|
3254
|
+
"src/infrastructure/generators/core/main.generator.ts"() {
|
|
3255
|
+
"use strict";
|
|
3256
|
+
import_node_path30 = __toESM(require("path"));
|
|
3257
|
+
MainGenerator2 = class {
|
|
3258
|
+
constructor(_fileService) {
|
|
3259
|
+
this._fileService = _fileService;
|
|
3260
|
+
}
|
|
3261
|
+
_fileService;
|
|
3262
|
+
async generate(projectPath, options) {
|
|
3263
|
+
const content = `import 'dotenv/config';
|
|
3264
|
+
import { bootstrap } from './bootstrap';
|
|
3265
|
+
import { TOKENS } from '@xeno-js/core';
|
|
3266
|
+
|
|
3267
|
+
/**
|
|
3268
|
+
* Main Application Entry Point
|
|
3269
|
+
*/
|
|
3270
|
+
async function main() {
|
|
3271
|
+
try {
|
|
3272
|
+
console.info('\u23F3 Bootstrapping ${options.targetDir} application...');
|
|
3273
|
+
|
|
3274
|
+
// Initialize the dependency injection container and infrastructure modules
|
|
3275
|
+
const container = await bootstrap();
|
|
3276
|
+
|
|
3277
|
+
const logger = container.resolve(TOKENS.LOGGER)
|
|
3278
|
+
logger.info('\u2705 Application started successfully!');
|
|
3279
|
+
|
|
3280
|
+
// TODO: Implement your logic here
|
|
3281
|
+
|
|
3282
|
+
} catch (error) {
|
|
3283
|
+
console.error('\u274C Critical error during startup:', error);
|
|
3284
|
+
process.exit(1);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
|
|
3288
|
+
main();
|
|
3289
|
+
`;
|
|
3290
|
+
const filePath = import_node_path30.default.join(projectPath, "src", "main.ts");
|
|
3291
|
+
await this._fileService.writeFileRecursive(filePath, content);
|
|
3292
|
+
}
|
|
3293
|
+
};
|
|
3294
|
+
}
|
|
3295
|
+
});
|
|
3296
|
+
|
|
3297
|
+
// src/infrastructure/strategies/core-scaffold.strategy.ts
|
|
3298
|
+
var core_scaffold_strategy_exports = {};
|
|
3299
|
+
__export(core_scaffold_strategy_exports, {
|
|
3300
|
+
CoreScaffoldStrategy: () => CoreScaffoldStrategy
|
|
3301
|
+
});
|
|
3302
|
+
var import_prompts2, CoreScaffoldStrategy;
|
|
3303
|
+
var init_core_scaffold_strategy = __esm({
|
|
3304
|
+
"src/infrastructure/strategies/core-scaffold.strategy.ts"() {
|
|
3305
|
+
"use strict";
|
|
3306
|
+
import_prompts2 = __toESM(require("prompts"));
|
|
3307
|
+
init_shared();
|
|
3308
|
+
CoreScaffoldStrategy = class {
|
|
3309
|
+
constructor(_fileService) {
|
|
3310
|
+
this._fileService = _fileService;
|
|
3311
|
+
}
|
|
3312
|
+
_fileService;
|
|
3313
|
+
async promptOptions(targetDir) {
|
|
3314
|
+
const response = await (0, import_prompts2.default)([
|
|
3315
|
+
{ type: "confirm", name: "axios", message: "Install Axios?", initial: true },
|
|
3316
|
+
{ type: "confirm", name: "cockatiel", message: "Install Cockatiel?", initial: true },
|
|
3317
|
+
{ type: "confirm", name: "supabase", message: "Install Supabase Auth?", initial: true },
|
|
3318
|
+
{
|
|
3319
|
+
type: "select",
|
|
3320
|
+
name: "database",
|
|
3321
|
+
message: "Select database type",
|
|
3322
|
+
choices: [
|
|
3323
|
+
{ title: "Drizzle PostgreSQL", value: "drizzle" },
|
|
3324
|
+
{ title: "SQL Lite", value: "sqlLite" },
|
|
3325
|
+
{ title: "None", value: "none" }
|
|
3326
|
+
]
|
|
3327
|
+
},
|
|
3328
|
+
{ type: "confirm", name: "pino", message: "Install Pino Logger?", initial: true },
|
|
3329
|
+
{ type: "confirm", name: "sentry", message: "Install Sentry Logger?", initial: true },
|
|
3330
|
+
{ type: "confirm", name: "redis", message: "Install Redis?", initial: true },
|
|
3331
|
+
{ type: "confirm", name: "zod", message: "Install Zod?", initial: true },
|
|
3332
|
+
// {
|
|
3333
|
+
// type: 'select',
|
|
3334
|
+
// name: 'targetEnv',
|
|
3335
|
+
// message: 'Select target environment',
|
|
3336
|
+
// choices: [
|
|
3337
|
+
// { title: 'Console', value: 'console' },
|
|
3338
|
+
// { title: 'Vercel (Serverless)', value: 'vercel' },
|
|
3339
|
+
// { title: 'Fastify (Standalone)', value: 'fastify' },
|
|
3340
|
+
// ],
|
|
3341
|
+
// },
|
|
3342
|
+
{
|
|
3343
|
+
type: "confirm",
|
|
3344
|
+
name: "gitInit",
|
|
3345
|
+
message: "Initialize a new Git repository?",
|
|
3346
|
+
initial: true
|
|
3347
|
+
}
|
|
3348
|
+
]);
|
|
3349
|
+
return {
|
|
3350
|
+
targetDir,
|
|
3351
|
+
projectType: "core",
|
|
3352
|
+
targetEnv: "console",
|
|
3353
|
+
...response
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
async getGenerators(options) {
|
|
3357
|
+
const generators = [];
|
|
3358
|
+
const { PackageJsonCoreGenerator: PackageJsonCoreGenerator2 } = await Promise.resolve().then(() => (init_packagejson_generator2(), packagejson_generator_exports2));
|
|
3359
|
+
const { BootstrapGenerator: BootstrapGenerator3 } = await Promise.resolve().then(() => (init_bootstrap_generator(), bootstrap_generator_exports));
|
|
3360
|
+
const { EnvGenerator: EnvGenerator2 } = await Promise.resolve().then(() => (init_env_generator2(), env_generator_exports2));
|
|
3361
|
+
const { GitIgnoreGenerator: GitIgnoreGenerator2 } = await Promise.resolve().then(() => (init_gitignore_generator(), gitignore_generator_exports));
|
|
3362
|
+
const { ReadmeGenerator: ReadmeGenerator2 } = await Promise.resolve().then(() => (init_readme_generator2(), readme_generator_exports2));
|
|
3363
|
+
const { RegistryGenerator: RegistryGenerator3 } = await Promise.resolve().then(() => (init_registry_generator(), registry_generator_exports));
|
|
3364
|
+
const { TsconfigGenerator: TsconfigGenerator2 } = await Promise.resolve().then(() => (init_tsconfig_generator2(), tsconfig_generator_exports2));
|
|
3365
|
+
generators.push(new PackageJsonCoreGenerator2(this._fileService));
|
|
3366
|
+
generators.push(new BootstrapGenerator3(this._fileService));
|
|
3367
|
+
generators.push(new EnvGenerator2(this._fileService));
|
|
3368
|
+
generators.push(new GitIgnoreGenerator2(this._fileService));
|
|
3369
|
+
generators.push(new ReadmeGenerator2(this._fileService));
|
|
3370
|
+
generators.push(new RegistryGenerator3(this._fileService));
|
|
3371
|
+
generators.push(new TsconfigGenerator2(this._fileService));
|
|
3372
|
+
if (options.database !== CORE_CONSTANTS.NONE) {
|
|
3373
|
+
if (options.database === CORE_CONSTANTS.DRIZZLE) {
|
|
3374
|
+
const { DrizzleGenerator: DrizzleGenerator2 } = await Promise.resolve().then(() => (init_drizzle_generator(), drizzle_generator_exports));
|
|
3375
|
+
generators.push(new DrizzleGenerator2(this._fileService));
|
|
3376
|
+
} else if (options.database === CORE_CONSTANTS.SQL_LITE) {
|
|
3377
|
+
const { DrizzleSqlLiteGenerator: DrizzleSqlLiteGenerator2 } = await Promise.resolve().then(() => (init_drizzle_sql_lite_generator(), drizzle_sql_lite_generator_exports));
|
|
3378
|
+
generators.push(new DrizzleSqlLiteGenerator2(this._fileService));
|
|
3379
|
+
}
|
|
3380
|
+
}
|
|
3381
|
+
if (options.targetEnv === TARGET_ENV_CONSTANTS.CONSOLE) {
|
|
3382
|
+
const { MainGenerator: MainGenerator3 } = await Promise.resolve().then(() => (init_main_generator2(), main_generator_exports2));
|
|
3383
|
+
generators.push(new MainGenerator3(this._fileService));
|
|
3384
|
+
}
|
|
3385
|
+
return generators;
|
|
3386
|
+
}
|
|
3387
|
+
};
|
|
3388
|
+
}
|
|
3389
|
+
});
|
|
3390
|
+
|
|
3391
|
+
// src/index.ts
|
|
3392
|
+
var import_picocolors6 = __toESM(require("picocolors"));
|
|
3393
|
+
|
|
3394
|
+
// src/infrastructure/commands/command-runner.service.ts
|
|
3395
|
+
var import_node_child_process = require("child_process");
|
|
3396
|
+
var CommandRunner = class {
|
|
3397
|
+
async run(command, args, cwd, isWin = false) {
|
|
3398
|
+
return new Promise((resolve2, reject) => {
|
|
3399
|
+
const cmd = isWin ? `${command}.cmd` : command;
|
|
3400
|
+
const child = (0, import_node_child_process.spawn)(cmd, args, {
|
|
3401
|
+
cwd,
|
|
3402
|
+
stdio: "inherit",
|
|
3403
|
+
shell: false
|
|
3404
|
+
});
|
|
3405
|
+
child.on("error", (error) => {
|
|
3406
|
+
reject(new Error(`Error during execution of the command ${command}: ${error.message}`));
|
|
3407
|
+
});
|
|
3408
|
+
child.on("close", (code) => {
|
|
3409
|
+
if (code === 0) resolve2();
|
|
3410
|
+
else reject(new Error(`Command ${command} failed with code ${code}`));
|
|
3411
|
+
});
|
|
3412
|
+
});
|
|
3413
|
+
}
|
|
3414
|
+
};
|
|
3415
|
+
|
|
3416
|
+
// src/infrastructure/commands/generate.command.ts
|
|
3417
|
+
var import_picocolors = __toESM(require("picocolors"));
|
|
3418
|
+
init_shared();
|
|
3419
|
+
init_command_constants();
|
|
3420
|
+
|
|
3421
|
+
// src/infrastructure/file/file.service.ts
|
|
3422
|
+
var import_promises = require("fs/promises");
|
|
3423
|
+
var import_path = require("path");
|
|
3424
|
+
var FileUtils = class {
|
|
3425
|
+
async writeFileRecursive(filePath, content) {
|
|
3426
|
+
const dir = (0, import_path.dirname)(filePath);
|
|
3427
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
3428
|
+
await (0, import_promises.writeFile)(filePath, content, "utf8");
|
|
3429
|
+
}
|
|
3430
|
+
};
|
|
3431
|
+
|
|
3432
|
+
// src/infrastructure/commands/generate.command.ts
|
|
3433
|
+
var GenerateCommandQuery = class {
|
|
3434
|
+
name = "generate";
|
|
3435
|
+
aliases = ["g"];
|
|
3436
|
+
async execute(args) {
|
|
3437
|
+
const type = args[0];
|
|
3438
|
+
const componentName = args[1];
|
|
3439
|
+
const isCore = args.includes("--core");
|
|
3440
|
+
const isVue = args.includes("--vue");
|
|
3441
|
+
if (Guards.isNullOrEmpty(type) || Guards.isNullOrEmpty(componentName)) {
|
|
3442
|
+
throw new Error(`Invalid syntax. Usage: xeno-js g [command | query] <Name> [--core | --vue]`);
|
|
3443
|
+
}
|
|
3444
|
+
let outputPath = void 0;
|
|
3445
|
+
const outputIndex = args.findIndex((arg) => arg === "--output" || arg === "-o" || arg === "--o");
|
|
3446
|
+
if (outputIndex !== -1 && args.length > outputIndex + 1) {
|
|
3447
|
+
outputPath = args[outputIndex + 1];
|
|
3448
|
+
}
|
|
3449
|
+
const targetDir = process.cwd();
|
|
3450
|
+
const fileService = new FileUtils();
|
|
3451
|
+
if (isCore || !isVue) {
|
|
3452
|
+
const cqrsType = type === COMMAND_CONSTANTS.COMMAND ? COMMAND_CONSTANTS.COMMAND : COMMAND_CONSTANTS.QUERY;
|
|
3453
|
+
const { CqrsGenerator: CqrsGenerator2 } = await Promise.resolve().then(() => (init_cqrs_generator(), cqrs_generator_exports));
|
|
3454
|
+
const generator = new CqrsGenerator2(fileService);
|
|
3455
|
+
await generator.generate(targetDir, componentName, cqrsType, outputPath);
|
|
3456
|
+
console.info(import_picocolors.default.green(`
|
|
3457
|
+
Successfully generated ${type} component '${componentName}'!`));
|
|
3458
|
+
} else if (isVue) {
|
|
3459
|
+
const cqrsType = type === COMMAND_CONSTANTS.COMMAND ? COMMAND_CONSTANTS.COMMAND : COMMAND_CONSTANTS.QUERY;
|
|
3460
|
+
const { CqrsVueGenerator: CqrsVueGenerator2 } = await Promise.resolve().then(() => (init_cqrs_generator2(), cqrs_generator_exports2));
|
|
3461
|
+
const generator = new CqrsVueGenerator2(fileService);
|
|
3462
|
+
await generator.generate(targetDir, componentName, cqrsType, outputPath);
|
|
3463
|
+
console.info(
|
|
3464
|
+
import_picocolors.default.green(`
|
|
3465
|
+
\u2705 Successfully generated Vue ${type} component '${componentName}'!`)
|
|
3466
|
+
);
|
|
3467
|
+
} else {
|
|
3468
|
+
throw new Error(`Unsupported generator type: '${type}'. Use 'command' or 'query'.`);
|
|
3469
|
+
}
|
|
3470
|
+
}
|
|
3471
|
+
};
|
|
3472
|
+
|
|
3473
|
+
// src/infrastructure/commands/help.command.ts
|
|
3474
|
+
var import_picocolors2 = __toESM(require("picocolors"));
|
|
3475
|
+
init_shared();
|
|
3476
|
+
var HelpCommand = class {
|
|
3477
|
+
get name() {
|
|
3478
|
+
return COMMAND_CONSTANTS.HELP;
|
|
3479
|
+
}
|
|
3480
|
+
get aliases() {
|
|
3481
|
+
return [ARGS_CONSTANTS.HELP, "-h"];
|
|
3482
|
+
}
|
|
3483
|
+
async execute() {
|
|
3484
|
+
console.info(import_picocolors2.default.greenBright("\n\u{1F680} Xeno CLI - Usage Guide\n"));
|
|
3485
|
+
console.info(import_picocolors2.default.bold(import_picocolors2.default.blue("1. Create a new project:")));
|
|
3486
|
+
console.info(` xeno-js new ${import_picocolors2.default.yellow("<name>")} ${import_picocolors2.default.cyan("[--vue | --core]")}`);
|
|
3487
|
+
console.info(` ${import_picocolors2.default.gray("\u2022")} ${import_picocolors2.default.yellow("<name>")}: The name of your project directory.`);
|
|
3488
|
+
console.info(
|
|
3489
|
+
` ${import_picocolors2.default.gray("\u2022")} ${import_picocolors2.default.cyan("[--vue | --core]")}: Project type. ${import_picocolors2.default.gray("(Default: --core)")}`
|
|
3490
|
+
);
|
|
3491
|
+
console.info(` - ${import_picocolors2.default.cyan("--core")}: Initializes a Backend project (Node.js/TS).`);
|
|
3492
|
+
console.info(` - ${import_picocolors2.default.cyan("--vue")}: Initializes a Frontend project (Vue.js).
|
|
3493
|
+
`);
|
|
3494
|
+
console.info(import_picocolors2.default.bold(import_picocolors2.default.blue("2. Generate CQRS components:")));
|
|
3495
|
+
console.info(
|
|
3496
|
+
` xeno-js generate ${import_picocolors2.default.magenta("<type>")} ${import_picocolors2.default.yellow("<Name>")} ${import_picocolors2.default.cyan("[--vue | --core]")} ${import_picocolors2.default.green("[--output <path>]")}`
|
|
3497
|
+
);
|
|
3498
|
+
console.info(
|
|
3499
|
+
` ${import_picocolors2.default.gray("\u2022")} ${import_picocolors2.default.magenta("<type>")}: Component type to generate (${import_picocolors2.default.magenta("command")} | ${import_picocolors2.default.magenta("query")}).`
|
|
3500
|
+
);
|
|
3501
|
+
console.info(
|
|
3502
|
+
` ${import_picocolors2.default.gray("\u2022")} ${import_picocolors2.default.yellow("<Name>")}: The name of the component (e.g., UserCreate).`
|
|
3503
|
+
);
|
|
3504
|
+
console.info(
|
|
3505
|
+
` ${import_picocolors2.default.gray("\u2022")} ${import_picocolors2.default.cyan("[--vue | --core]")}: Target context. ${import_picocolors2.default.gray("(Default: --core)")}`
|
|
3506
|
+
);
|
|
3507
|
+
console.info(
|
|
3508
|
+
` - ${import_picocolors2.default.cyan("--core")}: Generates Controller, Handler, Command/Query, Module, and Zod/DB schemas.`
|
|
3509
|
+
);
|
|
3510
|
+
console.info(
|
|
3511
|
+
` - ${import_picocolors2.default.cyan("--vue")}: Generates frontend-specific files (Stores, Services, etc.).`
|
|
3512
|
+
);
|
|
3513
|
+
console.info(
|
|
3514
|
+
` ${import_picocolors2.default.gray("\u2022")} ${import_picocolors2.default.green("[--output | -o]")}: Custom destination path inside 'src/'. ${import_picocolors2.default.gray("(Default: src/<name-in-lowercase>)")}`
|
|
3515
|
+
);
|
|
3516
|
+
console.info(
|
|
3517
|
+
` ${import_picocolors2.default.gray("Alias:")} ${import_picocolors2.default.gray("xeno-js g <type> <Name> [--vue | --core] [-o | --o <path>]")}
|
|
3518
|
+
`
|
|
3519
|
+
);
|
|
3520
|
+
console.info(import_picocolors2.default.bold(import_picocolors2.default.blue("3. Help:")));
|
|
3521
|
+
console.info(` xeno-js --help ${import_picocolors2.default.gray("Displays this guide.")}`);
|
|
3522
|
+
console.info(` ${import_picocolors2.default.gray("Alias:")} ${import_picocolors2.default.gray("xeno-js --h | -h")}
|
|
3523
|
+
`);
|
|
3524
|
+
console.info(import_picocolors2.default.bold(import_picocolors2.default.blue("Documentation:")));
|
|
3525
|
+
console.info(` ${import_picocolors2.default.underline("https://www.xeno-js.it")}
|
|
3526
|
+
`);
|
|
3527
|
+
}
|
|
3528
|
+
};
|
|
3529
|
+
|
|
3530
|
+
// src/infrastructure/commands/new-project.command.ts
|
|
3531
|
+
var import_picocolors4 = __toESM(require("picocolors"));
|
|
3532
|
+
|
|
3533
|
+
// src/infrastructure/bootstrap/bootstrapper.ts
|
|
3534
|
+
var import_promises3 = require("fs/promises");
|
|
3535
|
+
var import_node_path7 = require("path");
|
|
3536
|
+
var import_picocolors3 = __toESM(require("picocolors"));
|
|
3537
|
+
var Bootstrapper = class {
|
|
3538
|
+
constructor(_generators) {
|
|
3539
|
+
this._generators = _generators;
|
|
3540
|
+
}
|
|
3541
|
+
_generators;
|
|
3542
|
+
async run(projectPath, options) {
|
|
3543
|
+
console.info(import_picocolors3.default.cyan("\n\u{1F680} Initializing scaffolding..."));
|
|
3544
|
+
const absolutePath = (0, import_node_path7.resolve)(process.cwd(), projectPath);
|
|
3545
|
+
await (0, import_promises3.mkdir)(absolutePath, { recursive: true });
|
|
3546
|
+
for (const generator of this._generators) {
|
|
3547
|
+
try {
|
|
3548
|
+
await generator.generate(absolutePath, options);
|
|
3549
|
+
} catch (error) {
|
|
3550
|
+
console.error(import_picocolors3.default.red(`
|
|
3551
|
+
\u274C Error during execution of ${generator.constructor.name}`));
|
|
3552
|
+
throw error;
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
console.info(import_picocolors3.default.green("\n\u2728 Scaffolding completed succesfully!"));
|
|
3556
|
+
}
|
|
3557
|
+
};
|
|
3558
|
+
|
|
3559
|
+
// src/infrastructure/commands/new-project.command.ts
|
|
3560
|
+
var NewProjectCommand = class {
|
|
3561
|
+
name = "new";
|
|
3562
|
+
aliases = [];
|
|
3563
|
+
async execute(args) {
|
|
3564
|
+
const targetDir = args.find((arg) => !arg.startsWith("--")) ?? "my-xeno-app";
|
|
3565
|
+
const isVue = args.includes("--vue");
|
|
3566
|
+
const fileService = new FileUtils();
|
|
3567
|
+
let strategy;
|
|
3568
|
+
if (isVue) {
|
|
3569
|
+
const { VueScaffoldStrategy: VueScaffoldStrategy2 } = await Promise.resolve().then(() => (init_vue_scaffold_strategy(), vue_scaffold_strategy_exports));
|
|
3570
|
+
strategy = new VueScaffoldStrategy2(fileService);
|
|
3571
|
+
} else {
|
|
3572
|
+
const { CoreScaffoldStrategy: CoreScaffoldStrategy2 } = await Promise.resolve().then(() => (init_core_scaffold_strategy(), core_scaffold_strategy_exports));
|
|
3573
|
+
strategy = new CoreScaffoldStrategy2(fileService);
|
|
3574
|
+
}
|
|
3575
|
+
const options = await strategy.promptOptions(targetDir);
|
|
3576
|
+
const generators = await strategy.getGenerators(options);
|
|
3577
|
+
const bootstrapper = new Bootstrapper(generators);
|
|
3578
|
+
await bootstrapper.run(targetDir, options);
|
|
3579
|
+
const commandRunner = new CommandRunner();
|
|
3580
|
+
if (options.gitInit) {
|
|
3581
|
+
console.info(import_picocolors4.default.greenBright("\n\u{1F5C2}\uFE0F Initializing Git repository..."));
|
|
3582
|
+
await commandRunner.run("git", ["init"], targetDir);
|
|
3583
|
+
}
|
|
3584
|
+
console.info(import_picocolors4.default.greenBright("\n\u{1F4E6} Installing dependencies..."));
|
|
3585
|
+
const isWin = process.platform === "win32";
|
|
3586
|
+
await commandRunner.run("npm", ["install"], targetDir, isWin);
|
|
3587
|
+
console.info(import_picocolors4.default.green(`
|
|
3588
|
+
\u2705 Project successfully created in ./${targetDir}`));
|
|
3589
|
+
console.info(import_picocolors4.default.white(`
|
|
3590
|
+
Next steps:
|
|
3591
|
+
cd ${targetDir}
|
|
3592
|
+
npm run start
|
|
3593
|
+
`));
|
|
3594
|
+
}
|
|
3595
|
+
};
|
|
3596
|
+
|
|
3597
|
+
// src/infrastructure/dispatcher/dispatcher.ts
|
|
3598
|
+
var import_picocolors5 = __toESM(require("picocolors"));
|
|
3599
|
+
init_shared();
|
|
3600
|
+
var CliDispatcher = class {
|
|
3601
|
+
_commands = /* @__PURE__ */ new Map();
|
|
3602
|
+
constructor(commands) {
|
|
3603
|
+
for (const command of commands) {
|
|
3604
|
+
this._commands.set(command.name, command);
|
|
3605
|
+
for (const alias of command.aliases) {
|
|
3606
|
+
this._commands.set(alias, command);
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
}
|
|
3610
|
+
async dispatch(rawArgs) {
|
|
3611
|
+
const action = rawArgs[0];
|
|
3612
|
+
if (!Guards.isDefined(action) || action === ARGS_CONSTANTS.HELP || action === "-h" || action === "--h") {
|
|
3613
|
+
const helpCommand = this._commands.get(COMMAND_CONSTANTS.HELP);
|
|
3614
|
+
await helpCommand?.execute([]);
|
|
3615
|
+
return;
|
|
3616
|
+
}
|
|
3617
|
+
const command = this._commands.get(action);
|
|
3618
|
+
if (!Guards.isDefined(command)) {
|
|
3619
|
+
console.error(import_picocolors5.default.red(`
|
|
3620
|
+
\u274C '${action}' is not a command`));
|
|
3621
|
+
console.info(
|
|
3622
|
+
import_picocolors5.default.yellow(`See 'xeno-js --help' for cli usage guide or https://www.xeno-js.it for docs.
|
|
3623
|
+
`)
|
|
3624
|
+
);
|
|
3625
|
+
process.exit(1);
|
|
3626
|
+
}
|
|
3627
|
+
const commandArgs = rawArgs.slice(1);
|
|
3628
|
+
await command.execute(commandArgs);
|
|
3629
|
+
}
|
|
3630
|
+
};
|
|
3631
|
+
|
|
3632
|
+
// src/infrastructure/builder/builder.ts
|
|
3633
|
+
var Builder = class {
|
|
3634
|
+
static getDispatcher() {
|
|
3635
|
+
const dispatcher = new CliDispatcher([
|
|
3636
|
+
new NewProjectCommand(),
|
|
3637
|
+
new GenerateCommandQuery(),
|
|
3638
|
+
new HelpCommand()
|
|
3639
|
+
]);
|
|
3640
|
+
return dispatcher;
|
|
3641
|
+
}
|
|
3642
|
+
};
|
|
3643
|
+
|
|
3644
|
+
// src/index.ts
|
|
3645
|
+
async function init() {
|
|
3646
|
+
try {
|
|
3647
|
+
const dispatcher = Builder.getDispatcher();
|
|
3648
|
+
const rawArgs = process.argv.slice(2);
|
|
3649
|
+
await dispatcher.dispatch(rawArgs);
|
|
3650
|
+
} catch (error) {
|
|
3651
|
+
console.error(import_picocolors6.default.red("\n\u274C Execution interrupted:"));
|
|
3652
|
+
console.error(error instanceof Error ? error.message : error);
|
|
3653
|
+
process.exit(1);
|
|
3654
|
+
}
|
|
3655
|
+
}
|
|
3656
|
+
init().catch(console.error);
|
|
3657
|
+
/**
|
|
3658
|
+
* @class DrizzleGenerator
|
|
3659
|
+
* @description Generates the Drizzle configuration file (drizzle.config.ts) and a basic schema file (src/schema.ts) for database scaffolding.
|
|
3660
|
+
* The generator is only invoked if the user has opted to include database support in their project.
|
|
3661
|
+
*
|
|
3662
|
+
* @author Xeno
|
|
3663
|
+
* @version 1.0.0
|
|
3664
|
+
* @license ISC
|
|
3665
|
+
* @since 2025-09-30
|
|
3666
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
3667
|
+
*/
|
|
3668
|
+
/**
|
|
3669
|
+
* @class CommandUtils
|
|
3670
|
+
* @description Utility class for executing shell commands in a cross-platform manner.
|
|
3671
|
+
*
|
|
3672
|
+
* @author Xeno
|
|
3673
|
+
* @version 1.0.0
|
|
3674
|
+
* @license ISC
|
|
3675
|
+
* @since 2025-09-30
|
|
3676
|
+
* @link https://github.com/xeno-js/xeno-js
|
|
3677
|
+
*/
|
|
9
3678
|
/**
|
|
10
3679
|
* @function init
|
|
11
3680
|
* @description Initializes the scaffolding process for a new Xeno project.
|
|
@@ -16,16 +3685,4 @@ const builder_1 = require("./infrastructure/builder");
|
|
|
16
3685
|
* @since 2025-09-30
|
|
17
3686
|
* @link https://github.com/xeno-js/xeno-js
|
|
18
3687
|
*/
|
|
19
|
-
|
|
20
|
-
try {
|
|
21
|
-
const dispatcher = builder_1.Builder.getDispatcher();
|
|
22
|
-
const rawArgs = process.argv.slice(2);
|
|
23
|
-
await dispatcher.dispatch(rawArgs);
|
|
24
|
-
}
|
|
25
|
-
catch (error) {
|
|
26
|
-
console.error(picocolors_1.default.red('\n❌ Execution interrupted:'));
|
|
27
|
-
console.error(error instanceof Error ? error.message : error);
|
|
28
|
-
process.exit(1);
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
init().catch(console.error);
|
|
3688
|
+
//# sourceMappingURL=index.js.map
|