drimion 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +955 -0
  3. package/dist/cli/index.js +1045 -0
  4. package/dist/cli/templates/aggregate.ts.hbs +22 -0
  5. package/dist/cli/templates/entity.ts.hbs +16 -0
  6. package/dist/cli/templates/repository.ts.hbs +24 -0
  7. package/dist/cli/templates/use-case.ts.hbs +20 -0
  8. package/dist/cli/templates/value-object.ts.hbs +16 -0
  9. package/dist/kernel/core/aggregate.ts +234 -0
  10. package/dist/kernel/core/entity.ts +348 -0
  11. package/dist/kernel/core/id.ts +207 -0
  12. package/dist/kernel/core/index.ts +5 -0
  13. package/dist/kernel/core/repository.ts +81 -0
  14. package/dist/kernel/core/value-object.ts +309 -0
  15. package/dist/kernel/events/browser-event-manager.ts +241 -0
  16. package/dist/kernel/events/domain-event.ts +76 -0
  17. package/dist/kernel/events/event-bus.ts +158 -0
  18. package/dist/kernel/events/event-context.ts +95 -0
  19. package/dist/kernel/events/event-manager.ts +20 -0
  20. package/dist/kernel/events/event-utils.ts +19 -0
  21. package/dist/kernel/events/index.ts +7 -0
  22. package/dist/kernel/events/server-event-manager.ts +169 -0
  23. package/dist/kernel/helpers/auto-mapper.ts +222 -0
  24. package/dist/kernel/helpers/domain-classes.ts +162 -0
  25. package/dist/kernel/helpers/domain-error.ts +52 -0
  26. package/dist/kernel/helpers/getters-setters.ts +385 -0
  27. package/dist/kernel/helpers/index.ts +7 -0
  28. package/dist/kernel/index.ts +73 -0
  29. package/dist/kernel/libs/crypto.ts +33 -0
  30. package/dist/kernel/libs/index.ts +5 -0
  31. package/dist/kernel/libs/iterator.ts +298 -0
  32. package/dist/kernel/libs/result.ts +252 -0
  33. package/dist/kernel/libs/utils.ts +260 -0
  34. package/dist/kernel/libs/validator.ts +353 -0
  35. package/dist/kernel/types/adapter.types.ts +26 -0
  36. package/dist/kernel/types/command.types.ts +37 -0
  37. package/dist/kernel/types/entity.types.ts +60 -0
  38. package/dist/kernel/types/event.types.ts +129 -0
  39. package/dist/kernel/types/index.ts +26 -0
  40. package/dist/kernel/types/iterator.types.ts +39 -0
  41. package/dist/kernel/types/result.types.ts +122 -0
  42. package/dist/kernel/types/uid.types.ts +18 -0
  43. package/dist/kernel/types/utils.types.ts +120 -0
  44. package/dist/kernel/types/value-object.types.ts +20 -0
  45. package/dist/kernel/utils/date.utils.ts +111 -0
  46. package/dist/kernel/utils/index.ts +32 -0
  47. package/dist/kernel/utils/number.utils.ts +341 -0
  48. package/dist/kernel/utils/object.utils.ts +61 -0
  49. package/dist/kernel/utils/string.utils.ts +128 -0
  50. package/dist/kernel/utils/type.utils.ts +33 -0
  51. package/package.json +59 -0
@@ -0,0 +1,1045 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { Command as Command7 } from "commander";
5
+
6
+ // src/cli/commands/generate.ts
7
+ import path3 from "path";
8
+ import chalk2 from "chalk";
9
+ import { Command } from "commander";
10
+
11
+ // src/cli/core/generator.ts
12
+ import path2 from "path";
13
+ import { fileURLToPath } from "url";
14
+ import fs2 from "fs-extra";
15
+ import Handlebars from "handlebars";
16
+
17
+ // src/cli/utils/naming.ts
18
+ function tokenize(input3) {
19
+ return input3.trim().replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[_-]+/g, " ").replace(/[^\w\s]/g, "").toLowerCase().split(/\s+/).filter(Boolean);
20
+ }
21
+ function toPascalCase(input3) {
22
+ const tokens = tokenize(input3);
23
+ if (tokens.length === 0) {
24
+ throw new Error("Invalid name: cannot normalize empty input");
25
+ }
26
+ return tokens.map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
27
+ }
28
+ function toKebabCase(tokens) {
29
+ return tokens.join("-");
30
+ }
31
+ function toSnakeCase(tokens) {
32
+ return tokens.join("_");
33
+ }
34
+ function toPascalFile(tokens) {
35
+ return tokens.map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
36
+ }
37
+ function toFileName(className, type, convention) {
38
+ if (!className) throw new Error("className is required");
39
+ if (!type) throw new Error("type is required");
40
+ let base;
41
+ let suffix;
42
+ switch (convention) {
43
+ case "kebab-case":
44
+ base = toKebabCase(tokenize(className));
45
+ suffix = toKebabCase(tokenize(type));
46
+ break;
47
+ case "snake_case":
48
+ base = toSnakeCase(tokenize(className));
49
+ suffix = toSnakeCase(tokenize(type));
50
+ break;
51
+ case "PascalCase":
52
+ base = toPascalFile(tokenize(className));
53
+ suffix = toPascalFile(tokenize(type));
54
+ break;
55
+ default:
56
+ throw new Error(`Unsupported naming convention: ${convention}`);
57
+ }
58
+ return `${base}.${suffix}.ts`;
59
+ }
60
+
61
+ // src/cli/core/resolver.ts
62
+ import path from "path";
63
+ import { pathToFileURL } from "url";
64
+ import fs from "fs-extra";
65
+
66
+ // src/cli/utils/type-map.ts
67
+ var TYPE_MAP = {
68
+ entity: "entity",
69
+ "value-object": "valueObject",
70
+ aggregate: "aggregate",
71
+ repository: "repository",
72
+ "use-case": "usecase"
73
+ };
74
+ function toConfigKey(type) {
75
+ return TYPE_MAP[type];
76
+ }
77
+
78
+ // src/cli/core/resolver.ts
79
+ var Resolver = class _Resolver {
80
+ static async loadConfig(cwd = process.cwd()) {
81
+ const configPath = path.resolve(cwd, "drimion.config.ts");
82
+ const exists = await fs.pathExists(configPath);
83
+ if (!exists) {
84
+ throw new Error(
85
+ "Config file `drimion.config.ts` not found. Run `npx drimion init` first."
86
+ );
87
+ }
88
+ try {
89
+ const module = await import(pathToFileURL(configPath).href);
90
+ const config = module.default;
91
+ if (!config?.drimion) {
92
+ throw new Error("Invalid config: missing `drimion` key");
93
+ }
94
+ if (!config.drimion.targets) {
95
+ throw new Error("Invalid config: missing `drimion.targets`");
96
+ }
97
+ return config;
98
+ } catch (err) {
99
+ throw new Error(
100
+ `Failed to load drimion.config.ts: ${err instanceof Error ? err.message : String(err)}`
101
+ );
102
+ }
103
+ }
104
+ static resolveTarget(type, target, config, cwd = process.cwd()) {
105
+ const key = toConfigKey(type);
106
+ const typeTargets = config.drimion.targets?.[key];
107
+ if (!typeTargets) {
108
+ throw new Error(`No targets defined for type "${type}"`);
109
+ }
110
+ const targetPath = typeTargets[target];
111
+ if (!targetPath) {
112
+ throw new Error(
113
+ `Target "${target}" not found for type "${type}" in config`
114
+ );
115
+ }
116
+ return path.resolve(cwd, targetPath);
117
+ }
118
+ static resolveLocation(location, cwd = process.cwd()) {
119
+ if (!location || location.trim() === "") {
120
+ throw new Error("Invalid location: cannot be empty");
121
+ }
122
+ return path.resolve(cwd, location);
123
+ }
124
+ static resolveOutputPath(opts, config, cwd = process.cwd()) {
125
+ const { type, target, location } = opts;
126
+ if (!type) {
127
+ throw new Error("Generator type is required");
128
+ }
129
+ if (location) {
130
+ return _Resolver.resolveLocation(location, cwd);
131
+ }
132
+ if (target) {
133
+ return _Resolver.resolveTarget(
134
+ type,
135
+ target,
136
+ config,
137
+ cwd
138
+ );
139
+ }
140
+ throw new Error("Either `target` or `location` must be provided");
141
+ }
142
+ static async ensureDir(dir) {
143
+ await fs.ensureDir(dir);
144
+ }
145
+ static resolveCorePath(config, cwd = process.cwd()) {
146
+ if (!config.drimion.corePath) {
147
+ throw new Error("Invalid config: missing `drimion.corePath`");
148
+ }
149
+ return path.resolve(cwd, config.drimion.corePath);
150
+ }
151
+ };
152
+
153
+ // src/cli/core/generator.ts
154
+ var Generator = class _Generator {
155
+ static async generate(opts) {
156
+ const className = toPascalCase(opts.name);
157
+ const fileName = toFileName(
158
+ className,
159
+ opts.type,
160
+ opts.config.drimion.naming
161
+ );
162
+ const templatePath = _Generator.resolveTemplatePath(opts.type);
163
+ const templateContent = await _Generator.loadTemplate(templatePath);
164
+ const compiled = _Generator.compileTemplate(templateContent);
165
+ const output = compiled(
166
+ _Generator.buildTemplateContext({
167
+ className,
168
+ fileName,
169
+ type: opts.type,
170
+ config: opts.config
171
+ })
172
+ );
173
+ const fullOutputPath = path2.join(opts.outputPath, fileName);
174
+ await Resolver.ensureDir(opts.outputPath);
175
+ await _Generator.writeFile(fullOutputPath, output);
176
+ }
177
+ static resolveTemplatePath(type) {
178
+ const __dirname = path2.dirname(fileURLToPath(import.meta.url));
179
+ const templateFile = `${type}.ts.hbs`;
180
+ return path2.resolve(__dirname, "..", "templates", templateFile);
181
+ }
182
+ static async loadTemplate(filePath) {
183
+ const exists = await fs2.pathExists(filePath);
184
+ if (!exists) {
185
+ throw new Error(`Template not found: ${filePath}`);
186
+ }
187
+ return fs2.readFile(filePath, "utf-8");
188
+ }
189
+ static compileTemplate(template) {
190
+ return Handlebars.compile(template, {
191
+ noEscape: true
192
+ });
193
+ }
194
+ static buildTemplateContext(opts) {
195
+ return {
196
+ className: opts.className,
197
+ fileName: opts.fileName,
198
+ type: opts.type,
199
+ importAlias: opts.config.drimion.importAlias,
200
+ lowerName: opts.className.toLowerCase()
201
+ };
202
+ }
203
+ static async writeFile(filePath, content) {
204
+ const exists = await fs2.pathExists(filePath);
205
+ if (exists) {
206
+ throw new Error(`File already exists: ${filePath}`);
207
+ }
208
+ await fs2.writeFile(filePath, content, "utf-8");
209
+ }
210
+ };
211
+
212
+ // src/cli/utils/logger.ts
213
+ import chalk from "chalk";
214
+ import ora from "ora";
215
+
216
+ // src/cli/utils/package-manager.ts
217
+ import { createRequire } from "module";
218
+ function resolvePackageManager() {
219
+ if (true) {
220
+ return {
221
+ __PKG_NAME__: "drimion",
222
+ __PKG_VERSION__: "0.1.0",
223
+ __PKG_REPOSITORY__: "git+https://github.com/bramadl/drimion.git"
224
+ };
225
+ }
226
+ try {
227
+ const require2 = createRequire(import.meta.url);
228
+ const locations = ["../../package.json", "../../../package.json"];
229
+ for (const loc of locations) {
230
+ try {
231
+ const pkg = require2(loc);
232
+ const repoUrl = typeof pkg.repository === "string" ? pkg.repository : pkg.repository?.url ?? "";
233
+ return {
234
+ __PKG_NAME__: pkg.name,
235
+ __PKG_VERSION__: pkg.version,
236
+ __PKG_REPOSITORY__: repoUrl
237
+ };
238
+ } catch {
239
+ }
240
+ }
241
+ } catch {
242
+ }
243
+ return {
244
+ __PKG_NAME__: "drimion",
245
+ __PKG_VERSION__: "?",
246
+ __PKG_REPOSITORY__: ""
247
+ };
248
+ }
249
+
250
+ // src/cli/utils/logger.ts
251
+ var { __PKG_NAME__: __PKG_NAME__2, __PKG_VERSION__: __PKG_VERSION__2, __PKG_REPOSITORY__: __PKG_REPOSITORY__2 } = resolvePackageManager();
252
+ var logger = {
253
+ banner(info = false) {
254
+ const title = ` @${__PKG_NAME__2}/cli \u2022 v${__PKG_VERSION__2} `;
255
+ const border = "\u2500".repeat(title.length);
256
+ console.log();
257
+ console.log(chalk.dim(` \u256D${border}\u256E`));
258
+ console.log(chalk.bold.cyan(` \u2502${title}\u2502`));
259
+ console.log(chalk.dim(` \u2570${border}\u256F`));
260
+ if (info) {
261
+ console.log();
262
+ console.log(
263
+ chalk.cyan.bold(
264
+ ` ${__PKG_NAME__2} - Headless DDD primitives CLI for TypeScript`
265
+ )
266
+ );
267
+ }
268
+ console.log();
269
+ },
270
+ info(message) {
271
+ console.log(chalk.cyan(` ${message}`));
272
+ },
273
+ success(message) {
274
+ console.log(chalk.green(` \u2714 ${message}`));
275
+ },
276
+ warn(message) {
277
+ console.log(chalk.yellow(` \u26A0 ${message}`));
278
+ },
279
+ error(message) {
280
+ console.error(chalk.red(` \u2716 ${message}`));
281
+ },
282
+ /**
283
+ * Print an indented block of lines — used for "next steps", summaries, etc.
284
+ * Each line is already pre-formatted by the caller (can include chalk).
285
+ */
286
+ hint(lines) {
287
+ for (const line of lines) {
288
+ console.log(` ${line}`);
289
+ }
290
+ },
291
+ divider() {
292
+ console.log(chalk.dim(` ${"\u2500".repeat(42)}`));
293
+ },
294
+ cancelled() {
295
+ console.log(chalk.yellow(" \u25CB Cancelled."));
296
+ },
297
+ spinner(message) {
298
+ return ora({
299
+ text: message,
300
+ spinner: "dots",
301
+ indent: 2
302
+ }).start();
303
+ }
304
+ };
305
+
306
+ // src/cli/utils/prompts.ts
307
+ import { input, select } from "@inquirer/prompts";
308
+ var prompts = {
309
+ async selectGenerator() {
310
+ return select({
311
+ message: "What do you want to generate?",
312
+ choices: [
313
+ { value: "entity" },
314
+ { value: "value-object" },
315
+ { value: "aggregate" },
316
+ { value: "repository" },
317
+ { value: "use-case" }
318
+ ]
319
+ });
320
+ },
321
+ async inputName() {
322
+ return input({
323
+ message: "Name:",
324
+ validate(value) {
325
+ if (!value || value.trim() === "") return "Name is required";
326
+ return true;
327
+ }
328
+ });
329
+ },
330
+ async selectTarget(targets) {
331
+ const keys = Object.keys(targets || {});
332
+ if (keys.length === 0) return null;
333
+ const result = await select({
334
+ message: "Where should this be created?",
335
+ choices: [...keys, "manual"].map((k) => ({ value: k }))
336
+ });
337
+ return result === "manual" ? null : result;
338
+ },
339
+ async inputLocation() {
340
+ return input({
341
+ message: "Enter output path:",
342
+ validate(value) {
343
+ if (!value || value.trim() === "") return "Location is required";
344
+ return true;
345
+ }
346
+ });
347
+ }
348
+ };
349
+
350
+ // src/cli/commands/generate.ts
351
+ function stripLeadingEquals(value) {
352
+ return value.startsWith("=") ? value.slice(1) : value;
353
+ }
354
+ var generateCommand = new Command("generate").description("Generate a DDD building block from a template").argument("[type]", "Generator type (entity, value-object, etc)").option("-n, --name <name>", "Name of the class").option("-t, --target <target>", "Target from config").option("-l, --location <location>", "Manual output path").action(async (typeArg, options) => {
355
+ logger.banner();
356
+ let spinner;
357
+ const isInteractive = !typeArg || !options.name || !options.target && !options.location;
358
+ try {
359
+ const config = await Resolver.loadConfig();
360
+ const type = typeArg || await prompts.selectGenerator();
361
+ const rawName = options.name ? stripLeadingEquals(options.name) : await prompts.inputName();
362
+ const normalized = toPascalCase(rawName);
363
+ let outputPath;
364
+ if (options.location) {
365
+ outputPath = Resolver.resolveLocation(
366
+ stripLeadingEquals(options.location)
367
+ );
368
+ } else if (options.target) {
369
+ outputPath = Resolver.resolveTarget(
370
+ type,
371
+ stripLeadingEquals(options.target),
372
+ config
373
+ );
374
+ } else {
375
+ const configKey = toConfigKey(type);
376
+ const targets = config.drimion.targets?.[configKey] || {};
377
+ const target = await prompts.selectTarget(targets);
378
+ if (target) {
379
+ outputPath = Resolver.resolveTarget(type, target, config);
380
+ } else {
381
+ const location = await prompts.inputLocation();
382
+ outputPath = Resolver.resolveLocation(location);
383
+ }
384
+ }
385
+ if (isInteractive) console.log();
386
+ spinner = logger.spinner("Generating...");
387
+ await Generator.generate({
388
+ type,
389
+ name: normalized,
390
+ outputPath,
391
+ config
392
+ });
393
+ spinner.stop();
394
+ const fileName = toFileName(normalized, type, config.drimion.naming);
395
+ const fullPath = path3.join(outputPath, fileName);
396
+ const relativePath = path3.relative(process.cwd(), fullPath);
397
+ logger.divider();
398
+ console.log();
399
+ logger.hint([
400
+ `${chalk2.green("\u2714")} ${chalk2.bold(normalized)} generated successfully.`,
401
+ "",
402
+ ` ${chalk2.dim("type")} ${chalk2.cyan(type)}`,
403
+ ` ${chalk2.dim("file")} ${chalk2.white(relativePath)}`
404
+ ]);
405
+ console.log();
406
+ } catch (err) {
407
+ spinner?.stop();
408
+ if (err instanceof Error && err.message.includes("force closed the prompt")) {
409
+ console.log();
410
+ logger.cancelled();
411
+ console.log();
412
+ process.exit(0);
413
+ }
414
+ logger.divider();
415
+ console.log();
416
+ if (err instanceof Error && err.message.startsWith("File already exists:")) {
417
+ const absolutePath = err.message.replace("File already exists: ", "");
418
+ const relativePath = path3.relative(process.cwd(), absolutePath);
419
+ logger.hint([
420
+ chalk2.dim(
421
+ "Already generated? Edit the file directly, or delete it to regenerate."
422
+ )
423
+ ]);
424
+ logger.error(`File already exists: ${relativePath}`);
425
+ } else {
426
+ logger.error(err instanceof Error ? err.message : String(err));
427
+ }
428
+ console.log();
429
+ process.exit(1);
430
+ }
431
+ });
432
+
433
+ // src/cli/commands/info.ts
434
+ import chalk3 from "chalk";
435
+ import { Command as Command2 } from "commander";
436
+ var { __PKG_REPOSITORY__: __PKG_REPOSITORY__3 } = resolvePackageManager();
437
+ var COMMANDS = [
438
+ {
439
+ name: "init",
440
+ description: "Initialize drimion in your project.",
441
+ flags: [
442
+ {
443
+ flag: "-y, --yes",
444
+ description: "Skip all prompts and use defaults"
445
+ }
446
+ ],
447
+ examples: ["npx drimion init", "npx drimion init -y"]
448
+ },
449
+ {
450
+ name: "generate",
451
+ description: "Generate a DDD building block from a template. Runs interactively if flags are omitted.",
452
+ arguments: "[type] entity | value-object | aggregate | repository | use-case",
453
+ flags: [
454
+ {
455
+ flag: "-n, --name <name>",
456
+ description: "Class name (auto-normalized to PascalCase)"
457
+ },
458
+ {
459
+ flag: "-t, --target <target>",
460
+ description: "Predefined target path from drimion.config.ts"
461
+ },
462
+ { flag: "-l, --location <path>", description: "Manual output directory" }
463
+ ],
464
+ examples: [
465
+ "npx drimion generate",
466
+ "npx drimion generate entity -n User -t user",
467
+ "npx drimion generate value-object -n Email -l src/modules/user/domain"
468
+ ]
469
+ },
470
+ {
471
+ name: "list",
472
+ description: "List all available generators and the targets configured in drimion.config.ts.",
473
+ examples: ["npx drimion list"]
474
+ },
475
+ {
476
+ name: "sync",
477
+ description: "Update the library kernel to the latest version. Offers backup before overwriting.",
478
+ flags: [
479
+ {
480
+ flag: "-f, --force",
481
+ description: "Overwrite everything \u2014 no prompts, no backup"
482
+ }
483
+ ],
484
+ examples: ["npx drimion sync", "npx drimion sync -f"]
485
+ },
486
+ {
487
+ name: "uninstall",
488
+ description: "Remove the library kernel and drimion.config.ts from your project.",
489
+ examples: ["npx drimion uninstall"]
490
+ },
491
+ {
492
+ name: "info",
493
+ description: "Display CLI information and command reference.",
494
+ examples: ["npx drimion info"]
495
+ }
496
+ ];
497
+ function printCommand(cmd, isLast) {
498
+ const connector = isLast ? "\u2570\u2500" : "\u251C\u2500";
499
+ const indent = isLast ? " " : "\u2502 ";
500
+ console.log(` ${chalk3.dim(connector)} ${chalk3.cyan.bold(cmd.name)}`);
501
+ console.log(` ${chalk3.dim(indent)} ${chalk3.white(cmd.description)}`);
502
+ if (cmd.arguments) {
503
+ console.log();
504
+ console.log(` ${chalk3.dim(indent)} ${chalk3.dim("argument")}`);
505
+ console.log(
506
+ ` ${chalk3.dim(indent)} ${chalk3.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`
507
+ );
508
+ console.log(` ${chalk3.dim(indent)} ${chalk3.white(cmd.arguments)}`);
509
+ }
510
+ if (cmd.flags && cmd.flags.length > 0) {
511
+ console.log();
512
+ console.log(` ${chalk3.dim(indent)} ${chalk3.dim("flags")}`);
513
+ console.log(
514
+ ` ${chalk3.dim(indent)} ${chalk3.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`
515
+ );
516
+ for (const { flag, description } of cmd.flags) {
517
+ const flagCol = chalk3.yellow(flag.padEnd(26));
518
+ console.log(
519
+ ` ${chalk3.dim(indent)} ${flagCol}${chalk3.dim(description)}`
520
+ );
521
+ }
522
+ }
523
+ console.log();
524
+ console.log(` ${chalk3.dim(indent)} ${chalk3.dim("examples")}`);
525
+ console.log(
526
+ ` ${chalk3.dim(indent)} ${chalk3.dim("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")}`
527
+ );
528
+ for (const example of cmd.examples) {
529
+ console.log(
530
+ ` ${chalk3.dim(indent)} ${chalk3.dim("$")} ${chalk3.white(example)}`
531
+ );
532
+ }
533
+ if (!isLast) console.log(` ${chalk3.dim("\u2502")}`);
534
+ }
535
+ var infoCommand = new Command2("info").description("Display CLI information and command reference").action(() => {
536
+ logger.banner(true);
537
+ if (__PKG_REPOSITORY__3) {
538
+ logger.hint([chalk3.bold("Docs & Source")]);
539
+ logger.hint([
540
+ `GitHub Repo ${chalk3.dim("\u2192")} ${chalk3.cyan(
541
+ __PKG_REPOSITORY__3.replace(/^git\+/, "").replace(/\.git$/, "")
542
+ )}`
543
+ ]);
544
+ console.log();
545
+ logger.divider();
546
+ console.log();
547
+ }
548
+ logger.hint([chalk3.bold("Command Reference")]);
549
+ console.log();
550
+ logger.divider();
551
+ console.log();
552
+ console.log(` ${chalk3.dim("\u2502")}`);
553
+ COMMANDS.forEach(
554
+ (cmd, i) => void printCommand(cmd, i === COMMANDS.length - 1)
555
+ );
556
+ console.log();
557
+ });
558
+
559
+ // src/cli/commands/init.ts
560
+ import path5 from "path";
561
+ import { input as input2, select as select2 } from "@inquirer/prompts";
562
+ import chalk4 from "chalk";
563
+ import { Command as Command3 } from "commander";
564
+ import fs4 from "fs-extra";
565
+
566
+ // src/cli/core/copier.ts
567
+ import path4 from "path";
568
+ import { fileURLToPath as fileURLToPath2 } from "url";
569
+ import fs3 from "fs-extra";
570
+ var { __PKG_VERSION__: __PKG_VERSION__3 } = resolvePackageManager();
571
+ var Copier = class {
572
+ static async copyKernel(targetPath) {
573
+ const __dirname = path4.dirname(fileURLToPath2(import.meta.url));
574
+ const source = path4.resolve(__dirname, "..", "..", "kernel");
575
+ const sourceExists = await fs3.pathExists(source);
576
+ if (!sourceExists) {
577
+ throw new Error(
578
+ `Kernel source not found at: ${source}
579
+ Make sure build step copied kernel into dist.`
580
+ );
581
+ }
582
+ const targetExists = await fs3.pathExists(targetPath);
583
+ if (targetExists) {
584
+ throw new Error(
585
+ `Target already exists: ${targetPath}
586
+ Run sync instead or remove it first.`
587
+ );
588
+ }
589
+ await fs3.copy(source, targetPath);
590
+ const version = typeof __PKG_VERSION__3 !== "undefined" ? __PKG_VERSION__3 : "?";
591
+ await fs3.writeFile(path4.join(targetPath, ".version"), version, "utf-8");
592
+ }
593
+ };
594
+
595
+ // src/cli/commands/init.ts
596
+ var DEFAULTS = {
597
+ corePath: "src/lib/drimion",
598
+ importAlias: "drimion",
599
+ naming: "kebab-case"
600
+ };
601
+ function isCancelled(err) {
602
+ return err instanceof Error && err.message.includes("force closed the prompt");
603
+ }
604
+ async function askInitQuestions() {
605
+ const corePath = await input2({
606
+ message: "Where should the library kernel be installed?",
607
+ default: DEFAULTS.corePath
608
+ });
609
+ const importAlias = await input2({
610
+ message: "Import alias for your project (configure in tsconfig paths):",
611
+ default: DEFAULTS.importAlias
612
+ });
613
+ const naming = await select2({
614
+ message: "File naming convention for generated files:",
615
+ choices: [
616
+ {
617
+ value: "kebab-case",
618
+ name: "kebab-case (email-address.value-object.ts)"
619
+ },
620
+ {
621
+ value: "snake_case",
622
+ name: "snake_case (email_address.value_object.ts)"
623
+ },
624
+ {
625
+ value: "PascalCase",
626
+ name: "PascalCase (EmailAddress.ValueObject.ts)"
627
+ }
628
+ ]
629
+ });
630
+ return { corePath, importAlias, naming };
631
+ }
632
+ function buildConfigTemplate(answers) {
633
+ return `export default {
634
+ drimion: {
635
+ corePath: "${answers.corePath}",
636
+ importAlias: "${answers.importAlias}",
637
+ naming: "${answers.naming}",
638
+ targets: {
639
+ entity: {},
640
+ valueObject: {},
641
+ aggregate: {},
642
+ repository: {},
643
+ usecase: {},
644
+ },
645
+ },
646
+ };`;
647
+ }
648
+ function step(n) {
649
+ return chalk4.bgCyan.black(` ${n} `);
650
+ }
651
+ function nextSteps(answers) {
652
+ return [
653
+ chalk4.bold("All done! Here's what to do next:"),
654
+ "",
655
+ `${step("1")} Add the import alias to your ${chalk4.cyan("tsconfig.json")}:`,
656
+ "",
657
+ chalk4.dim(' "paths": {'),
658
+ chalk4.dim(` "${answers.importAlias}": ["./${answers.corePath}"]`),
659
+ chalk4.dim(" }"),
660
+ "",
661
+ `${step("2")} Run your first generator:`,
662
+ "",
663
+ ` ${chalk4.cyan("npx drimion generate")}`,
664
+ "",
665
+ `${step("3")} List available generators:`,
666
+ "",
667
+ ` ${chalk4.cyan("npx drimion list")}`,
668
+ ""
669
+ ];
670
+ }
671
+ var initCommand = new Command3("init").description("Initialize drimion in your project").option("-y, --yes", "Skip prompts and use defaults").action(async (options) => {
672
+ logger.banner();
673
+ try {
674
+ const cwd = process.cwd();
675
+ const skipPrompts = options.yes ?? false;
676
+ let answers;
677
+ if (skipPrompts) {
678
+ answers = DEFAULTS;
679
+ logger.info("Using defaults (--yes).");
680
+ console.log();
681
+ } else {
682
+ try {
683
+ answers = await askInitQuestions();
684
+ console.log();
685
+ } catch (err) {
686
+ if (isCancelled(err)) {
687
+ console.log();
688
+ logger.cancelled();
689
+ console.log();
690
+ process.exit(0);
691
+ }
692
+ throw err;
693
+ }
694
+ }
695
+ const configPath = path5.resolve(cwd, "drimion.config.ts");
696
+ const kernelPath = path5.resolve(cwd, answers.corePath);
697
+ const configExists = await fs4.pathExists(configPath);
698
+ const kernelExists = await fs4.pathExists(kernelPath);
699
+ if (configExists && kernelExists) {
700
+ logger.warn("Already initialized.");
701
+ console.log();
702
+ logger.hint([
703
+ chalk4.bold("Files already in place:"),
704
+ "",
705
+ ` ${chalk4.dim("kernel \u2192")} ${answers.corePath}`,
706
+ ` ${chalk4.dim("config \u2192")} drimion.config.ts`,
707
+ "",
708
+ chalk4.dim("Nothing was changed. To reinstall, remove them first."),
709
+ "",
710
+ ...nextSteps(answers)
711
+ ]);
712
+ process.exit(0);
713
+ }
714
+ if (kernelExists) {
715
+ logger.warn("Kernel already exists \u2014 skipping copy.");
716
+ } else {
717
+ const spinner = logger.spinner("Installing library kernel...");
718
+ await Copier.copyKernel(kernelPath);
719
+ spinner.succeed(
720
+ `Kernel installed ${chalk4.dim("\u2192")} ${answers.corePath}`
721
+ );
722
+ }
723
+ if (configExists) {
724
+ logger.warn("drimion.config.ts already exists \u2014 skipping.");
725
+ } else {
726
+ await fs4.writeFile(configPath, buildConfigTemplate(answers), "utf-8");
727
+ logger.success(`Config created ${chalk4.dim("\u2192")} drimion.config.ts`);
728
+ }
729
+ console.log();
730
+ logger.divider();
731
+ console.log();
732
+ logger.hint(nextSteps(answers));
733
+ } catch (err) {
734
+ logger.error(err instanceof Error ? err.message : String(err));
735
+ process.exit(1);
736
+ }
737
+ });
738
+
739
+ // src/cli/commands/list.ts
740
+ import chalk5 from "chalk";
741
+ import { Command as Command4 } from "commander";
742
+ var GENERATORS = [
743
+ "entity",
744
+ "value-object",
745
+ "aggregate",
746
+ "repository",
747
+ "use-case"
748
+ ];
749
+ var COL = 20;
750
+ function lpad(s) {
751
+ return s.padEnd(COL);
752
+ }
753
+ var listCommand = new Command4("list").description("List available generators and configured targets").action(async () => {
754
+ logger.banner();
755
+ try {
756
+ const config = await Resolver.loadConfig();
757
+ console.log(` ${chalk5.bold(lpad("Generator")) + chalk5.bold("Targets")}`);
758
+ console.log();
759
+ logger.divider();
760
+ console.log();
761
+ for (const type of GENERATORS) {
762
+ const configKey = toConfigKey(type);
763
+ const targets = config.drimion.targets?.[configKey] || {};
764
+ const entries = Object.entries(targets);
765
+ if (entries.length === 0) {
766
+ console.log(
767
+ " " + chalk5.cyan(lpad(`\u2022 ${type}`)) + chalk5.dim("(no targets configured)")
768
+ );
769
+ continue;
770
+ }
771
+ entries.forEach(([name, targetPath], i) => {
772
+ const leftStyled = i === 0 ? chalk5.cyan(`\u2022 ${type}`) + " ".repeat(Math.max(0, COL - `\u2022 ${type}`.length)) : " ".repeat(COL);
773
+ const idx = chalk5.dim(`(${i + 1})`);
774
+ const targetName = chalk5.white(name);
775
+ const tPath = chalk5.dim(targetPath);
776
+ console.log(` ${leftStyled}${idx} ${targetName}: ${tPath}`);
777
+ });
778
+ }
779
+ console.log();
780
+ logger.divider();
781
+ console.log();
782
+ logger.hint([
783
+ `Run ${chalk5.cyan("npx drimion generate")} to create a new file.`
784
+ ]);
785
+ console.log();
786
+ } catch (err) {
787
+ logger.error(err instanceof Error ? err.message : String(err));
788
+ console.log();
789
+ process.exit(1);
790
+ }
791
+ });
792
+
793
+ // src/cli/commands/sync.ts
794
+ import path6 from "path";
795
+ import { fileURLToPath as fileURLToPath3 } from "url";
796
+ import { select as select3 } from "@inquirer/prompts";
797
+ import chalk6 from "chalk";
798
+ import { Command as Command5 } from "commander";
799
+ import fs5 from "fs-extra";
800
+ var { __PKG_VERSION__: __PKG_VERSION__4 } = resolvePackageManager();
801
+ function isCancelled2(err) {
802
+ return err instanceof Error && err.message.includes("force closed the prompt");
803
+ }
804
+ function resolveKernelSource() {
805
+ const __dirname = path6.dirname(fileURLToPath3(import.meta.url));
806
+ return path6.resolve(__dirname, "..", "..", "kernel");
807
+ }
808
+ async function readInstalledVersion(kernelPath) {
809
+ const versionFile = path6.join(kernelPath, ".version");
810
+ try {
811
+ const raw = await fs5.readFile(versionFile, "utf-8");
812
+ return raw.trim() || null;
813
+ } catch {
814
+ return null;
815
+ }
816
+ }
817
+ function getPackageVersion() {
818
+ return typeof __PKG_VERSION__4 !== "undefined" ? __PKG_VERSION__4 : "?";
819
+ }
820
+ var syncCommand = new Command5("sync").description("Update the library kernel to the latest version").option("-f, --force", "Overwrite everything \u2014 no prompts, no backup").action(async (options) => {
821
+ logger.banner();
822
+ const cwd = process.cwd();
823
+ const force = options.force ?? false;
824
+ try {
825
+ const config = await Resolver.loadConfig(cwd);
826
+ const kernelPath = Resolver.resolveCorePath(config, cwd);
827
+ const kernelSource = resolveKernelSource();
828
+ const sourceExists = await fs5.pathExists(kernelSource);
829
+ if (!sourceExists) {
830
+ logger.error(
831
+ `Kernel source not found at: ${kernelSource}
832
+ Make sure the package is up to date.`
833
+ );
834
+ console.log();
835
+ process.exit(1);
836
+ }
837
+ const kernelExists = await fs5.pathExists(kernelPath);
838
+ if (!kernelExists) {
839
+ logger.warn("Kernel not found \u2014 nothing to sync.");
840
+ console.log();
841
+ logger.hint([
842
+ chalk6.dim(`Expected at: ${path6.relative(cwd, kernelPath)}`),
843
+ chalk6.dim("Run `npx drimion init` to install first.")
844
+ ]);
845
+ console.log();
846
+ process.exit(0);
847
+ }
848
+ const installedVersion = await readInstalledVersion(kernelPath);
849
+ const availableVersion = getPackageVersion();
850
+ const relativeKernelPath = path6.relative(cwd, kernelPath);
851
+ if (installedVersion && installedVersion === availableVersion && !force) {
852
+ logger.hint([
853
+ `${chalk6.green("\u2714")} Kernel is already up to date.`,
854
+ "",
855
+ ` ${chalk6.dim("version")} ${chalk6.cyan(installedVersion)}`,
856
+ ` ${chalk6.dim("path")} ${chalk6.white(relativeKernelPath)}`
857
+ ]);
858
+ console.log();
859
+ process.exit(0);
860
+ }
861
+ if (installedVersion || availableVersion) {
862
+ logger.hint([
863
+ chalk6.bold("A new version of the library kernel is available."),
864
+ "",
865
+ ` ${chalk6.dim("installed")} ${chalk6.yellow(installedVersion ?? "unknown")}`,
866
+ ` ${chalk6.dim("available")} ${chalk6.cyan(availableVersion)}`
867
+ ]);
868
+ console.log();
869
+ }
870
+ if (force) {
871
+ const spinner2 = logger.spinner("Syncing kernel...");
872
+ await fs5.remove(kernelPath);
873
+ await fs5.copy(kernelSource, kernelPath);
874
+ spinner2.succeed(
875
+ `Kernel updated ${chalk6.dim("\u2192")} ${relativeKernelPath}`
876
+ );
877
+ console.log();
878
+ process.exit(0);
879
+ }
880
+ let choice;
881
+ try {
882
+ choice = await select3({
883
+ message: "What do you want to do?",
884
+ choices: [
885
+ { value: "overwrite", name: "Overwrite existing files" },
886
+ { value: "skip", name: "Skip update" },
887
+ {
888
+ value: "backup",
889
+ name: "Backup current version and install new"
890
+ }
891
+ ]
892
+ });
893
+ console.log();
894
+ } catch (err) {
895
+ if (isCancelled2(err)) {
896
+ console.log();
897
+ logger.cancelled();
898
+ console.log();
899
+ process.exit(0);
900
+ }
901
+ throw err;
902
+ }
903
+ if (choice === "skip") {
904
+ logger.cancelled();
905
+ console.log();
906
+ process.exit(0);
907
+ }
908
+ if (choice === "backup") {
909
+ const backupVersion = installedVersion ?? "unknown";
910
+ const backupPath = path6.join(
911
+ kernelPath,
912
+ "__backup__",
913
+ `v${backupVersion}`
914
+ );
915
+ const tempBackupPath = path6.join(cwd, `.drimion-backup-${Date.now()}`);
916
+ const relativeBackupPath = path6.relative(cwd, backupPath);
917
+ const spinner2 = logger.spinner("Backing up current kernel...");
918
+ await fs5.copy(kernelPath, tempBackupPath);
919
+ spinner2.succeed(
920
+ `Backup saved ${chalk6.dim("\u2192")} ${relativeBackupPath}`
921
+ );
922
+ const spinner22 = logger.spinner("Installing new kernel...");
923
+ await fs5.remove(kernelPath);
924
+ await Copier.copyKernel(kernelPath);
925
+ await fs5.move(tempBackupPath, backupPath);
926
+ spinner22.succeed(
927
+ `Kernel updated ${chalk6.dim("\u2192")} ${relativeKernelPath}`
928
+ );
929
+ console.log();
930
+ logger.divider();
931
+ console.log();
932
+ logger.hint([
933
+ chalk6.bold("Sync complete."),
934
+ "",
935
+ ` ${chalk6.dim("backup")} ${chalk6.white(relativeBackupPath)}`,
936
+ "",
937
+ chalk6.dim("Remember to add the backup directory to .gitignore:"),
938
+ "",
939
+ ` ${chalk6.dim(`${relativeKernelPath}/__backup__/`)}`
940
+ ]);
941
+ console.log();
942
+ process.exit(0);
943
+ }
944
+ const spinner = logger.spinner("Syncing kernel...");
945
+ await fs5.remove(kernelPath);
946
+ await Copier.copyKernel(kernelPath);
947
+ spinner.succeed(
948
+ `Kernel updated ${chalk6.dim("\u2192")} ${relativeKernelPath}`
949
+ );
950
+ console.log();
951
+ logger.divider();
952
+ console.log();
953
+ logger.hint([chalk6.bold("Sync complete.")]);
954
+ console.log();
955
+ } catch (err) {
956
+ logger.error(err instanceof Error ? err.message : String(err));
957
+ console.log();
958
+ process.exit(1);
959
+ }
960
+ });
961
+
962
+ // src/cli/commands/uninstall.ts
963
+ import path7 from "path";
964
+ import { confirm } from "@inquirer/prompts";
965
+ import chalk7 from "chalk";
966
+ import { Command as Command6 } from "commander";
967
+ import fs6 from "fs-extra";
968
+ function isCancelled3(err) {
969
+ return err instanceof Error && err.message.includes("force closed the prompt");
970
+ }
971
+ var uninstallCommand = new Command6("uninstall").description("Remove drimion kernel and config from your project").action(async () => {
972
+ logger.banner();
973
+ const cwd = process.cwd();
974
+ const configPath = path7.resolve(cwd, "drimion.config.ts");
975
+ const configExists = await fs6.pathExists(configPath);
976
+ let kernelPath;
977
+ try {
978
+ const config = await Resolver.loadConfig(cwd);
979
+ kernelPath = path7.resolve(cwd, config.drimion.corePath);
980
+ } catch {
981
+ kernelPath = path7.resolve(cwd, "src/lib/drimion");
982
+ }
983
+ const kernelExists = await fs6.pathExists(kernelPath);
984
+ if (!configExists && !kernelExists) {
985
+ logger.warn("Nothing to uninstall \u2014 no config or kernel found.");
986
+ console.log();
987
+ process.exit(0);
988
+ }
989
+ logger.hint([
990
+ chalk7.bold("The following will be permanently deleted:"),
991
+ "",
992
+ ...kernelExists ? [` ${chalk7.red("\u2716")} ${path7.relative(cwd, kernelPath)}`] : [],
993
+ ...configExists ? [` ${chalk7.red("\u2716")} drimion.config.ts`] : []
994
+ ]);
995
+ let confirmed;
996
+ try {
997
+ console.log();
998
+ confirmed = await confirm({
999
+ message: "Are you sure? This cannot be undone.",
1000
+ default: false
1001
+ });
1002
+ console.log();
1003
+ } catch (err) {
1004
+ if (isCancelled3(err)) {
1005
+ console.log();
1006
+ logger.cancelled();
1007
+ console.log();
1008
+ process.exit(0);
1009
+ }
1010
+ throw err;
1011
+ }
1012
+ if (!confirmed) {
1013
+ console.log();
1014
+ logger.cancelled();
1015
+ console.log();
1016
+ process.exit(0);
1017
+ }
1018
+ if (kernelExists) {
1019
+ await fs6.remove(kernelPath);
1020
+ logger.success(
1021
+ `Removed ${chalk7.dim("\u2192")} ${path7.relative(cwd, kernelPath)}`
1022
+ );
1023
+ }
1024
+ if (configExists) {
1025
+ await fs6.remove(configPath);
1026
+ logger.success(`Removed ${chalk7.dim("\u2192")} drimion.config.ts`);
1027
+ }
1028
+ console.log();
1029
+ logger.divider();
1030
+ console.log();
1031
+ logger.hint([chalk7.dim("Uninstall complete. Run init to start fresh.")]);
1032
+ console.log();
1033
+ });
1034
+
1035
+ // src/cli/index.ts
1036
+ var program = new Command7();
1037
+ var { __PKG_NAME__: __PKG_NAME__3, __PKG_VERSION__: __PKG_VERSION__5 } = resolvePackageManager();
1038
+ program.name(__PKG_NAME__3).description("Headless DDD Toolkit CLI").version(__PKG_VERSION__5);
1039
+ program.addCommand(infoCommand);
1040
+ program.addCommand(initCommand);
1041
+ program.addCommand(listCommand);
1042
+ program.addCommand(generateCommand);
1043
+ program.addCommand(syncCommand);
1044
+ program.addCommand(uninstallCommand);
1045
+ program.parse();