@solid-stack/mason 1.0.12 → 1.0.13

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 CHANGED
@@ -1,9 +1,10 @@
1
- import fs3 from 'fs';
2
- import path3 from 'path';
3
- import * as p4 from '@clack/prompts';
4
- import pc13 from 'picocolors';
1
+ import fs12 from 'fs';
2
+ import path2 from 'path';
3
+ import * as p15 from '@clack/prompts';
4
+ import pc15 from 'picocolors';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { execSync } from 'child_process';
7
+ import { createHash } from 'crypto';
7
8
  import Handlebars from 'handlebars';
8
9
  import { cac } from 'cac';
9
10
 
@@ -75,8 +76,8 @@ function isValidIdentifier(str) {
75
76
  }
76
77
  function writeFileSafely(absolutePath, content, options = {}) {
77
78
  const projectRoot = options.projectRoot || process.cwd();
78
- const relPath = path3.relative(projectRoot, absolutePath).replace(/\\/g, "/");
79
- const existedBefore = fs3.existsSync(absolutePath);
79
+ const relPath = path2.relative(projectRoot, absolutePath).replace(/\\/g, "/");
80
+ const existedBefore = fs12.existsSync(absolutePath);
80
81
  if (options.dryRun) {
81
82
  return {
82
83
  relativePath: relPath,
@@ -95,11 +96,11 @@ function writeFileSafely(absolutePath, content, options = {}) {
95
96
  existedBefore: true
96
97
  };
97
98
  }
98
- const dir = path3.dirname(absolutePath);
99
- if (!fs3.existsSync(dir)) {
100
- fs3.mkdirSync(dir, { recursive: true });
99
+ const dir = path2.dirname(absolutePath);
100
+ if (!fs12.existsSync(dir)) {
101
+ fs12.mkdirSync(dir, { recursive: true });
101
102
  }
102
- fs3.writeFileSync(absolutePath, content, "utf-8");
103
+ fs12.writeFileSync(absolutePath, content, "utf-8");
103
104
  return {
104
105
  relativePath: relPath,
105
106
  absolutePath,
@@ -111,41 +112,114 @@ function writeFileSafely(absolutePath, content, options = {}) {
111
112
  function printGeneratedFiles(files, isDryRun = false) {
112
113
  console.log("");
113
114
  if (isDryRun) {
114
- p4.log.warn(pc13.yellow(pc13.bold("[DRY RUN PREVIEW] No files were written to disk.")));
115
+ p15.log.warn(pc15.yellow(pc15.bold("[DRY RUN PREVIEW] No files were written to disk.")));
115
116
  }
116
117
  for (const f of files) {
117
118
  if (f.action === "created") {
118
- p4.log.success(`${pc13.bold(f.relativePath)} ${pc13.dim("(created)")}`);
119
+ p15.log.success(`${pc15.bold(f.relativePath)} ${pc15.dim("(created)")}`);
119
120
  } else if (f.action === "overwritten") {
120
- p4.log.warn(`${pc13.bold(f.relativePath)} ${pc13.dim("(overwritten)")}`);
121
+ p15.log.warn(`${pc15.bold(f.relativePath)} ${pc15.dim("(overwritten)")}`);
121
122
  } else if (f.action === "skipped") {
122
- p4.log.info(`${pc13.dim(f.relativePath)} ${pc13.dim("(skipped - already exists, pass -o to overwrite)")}`);
123
+ p15.log.info(`${pc15.dim(f.relativePath)} ${pc15.dim("(skipped - already exists, pass -o to overwrite)")}`);
123
124
  } else {
124
- p4.log.info(`${pc13.bold(f.relativePath)} ${pc13.dim("(preview)")}`);
125
+ p15.log.info(`${pc15.bold(f.relativePath)} ${pc15.dim("(preview)")}`);
125
126
  }
126
127
  }
127
128
  }
129
+ function loadMasonConfig(startDir = process.cwd()) {
130
+ let current = path2.resolve(startDir);
131
+ while (true) {
132
+ const configPath = path2.join(current, "mason.config.json");
133
+ if (fs12.existsSync(configPath)) {
134
+ try {
135
+ return JSON.parse(fs12.readFileSync(configPath, "utf-8"));
136
+ } catch {
137
+ return null;
138
+ }
139
+ }
140
+ const parent = path2.dirname(current);
141
+ if (parent === current) break;
142
+ current = parent;
143
+ }
144
+ return null;
145
+ }
146
+ function getEffectiveMode(projectRoot, flags) {
147
+ if (flags?.registry) return "registry";
148
+ const config = loadMasonConfig(projectRoot);
149
+ if (config?.mode) return config.mode;
150
+ if (fs12.existsSync(path2.join(projectRoot, "index.json")) && (fs12.existsSync(path2.join(projectRoot, "features")) || fs12.existsSync(path2.join(projectRoot, "shared"))) && !fs12.existsSync(path2.join(projectRoot, "src"))) {
151
+ return "registry";
152
+ }
153
+ return "app";
154
+ }
155
+ function getConfiguredFeaturesDir(projectRoot, flags) {
156
+ if (flags?.target) return path2.resolve(projectRoot, flags.target);
157
+ if (flags?.dest) return path2.resolve(projectRoot, flags.dest);
158
+ const config = loadMasonConfig(projectRoot);
159
+ if (config?.paths?.features) {
160
+ return path2.resolve(projectRoot, config.paths.features);
161
+ }
162
+ const mode = getEffectiveMode(projectRoot, flags);
163
+ if (mode === "registry") {
164
+ return path2.join(projectRoot, "features");
165
+ }
166
+ if (fs12.existsSync(path2.join(projectRoot, "src/features"))) {
167
+ return path2.join(projectRoot, "src/features");
168
+ }
169
+ return path2.join(projectRoot, "features");
170
+ }
171
+ function getConfiguredSharedDir(projectRoot, flags) {
172
+ if (flags?.target) return path2.resolve(projectRoot, flags.target);
173
+ if (flags?.dest) return path2.resolve(projectRoot, flags.dest);
174
+ const config = loadMasonConfig(projectRoot);
175
+ if (config?.paths?.shared) {
176
+ return path2.resolve(projectRoot, config.paths.shared);
177
+ }
178
+ const mode = getEffectiveMode(projectRoot, flags);
179
+ if (mode === "registry") {
180
+ return path2.join(projectRoot, "shared");
181
+ }
182
+ if (fs12.existsSync(path2.join(projectRoot, "src/shared"))) {
183
+ return path2.join(projectRoot, "src/shared");
184
+ }
185
+ return path2.join(projectRoot, "shared");
186
+ }
187
+
188
+ // src/utils/project.ts
128
189
  function findProjectRoot(startDir = process.cwd()) {
129
- let current = path3.resolve(startDir);
190
+ let current = path2.resolve(startDir);
130
191
  while (true) {
131
- if (fs3.existsSync(path3.join(current, "package.json"))) {
192
+ if (fs12.existsSync(path2.join(current, "package.json"))) {
132
193
  return current;
133
194
  }
134
- const parent = path3.dirname(current);
195
+ const parent = path2.dirname(current);
135
196
  if (parent === current) {
136
- return path3.resolve(startDir);
197
+ return path2.resolve(startDir);
137
198
  }
138
199
  current = parent;
139
200
  }
140
201
  }
141
- function resolveFeaturesDir(projectRoot) {
142
- return path3.join(projectRoot, "features");
202
+ function resolveFeaturesDir(projectRoot, flags) {
203
+ return getConfiguredFeaturesDir(projectRoot, flags);
204
+ }
205
+ function resolveSharedDir(projectRoot, flags) {
206
+ return getConfiguredSharedDir(projectRoot, flags);
207
+ }
208
+ function listExistingShared(projectRoot) {
209
+ const sharedDir = resolveSharedDir(projectRoot);
210
+ if (!fs12.existsSync(sharedDir)) return [];
211
+ try {
212
+ const entries = fs12.readdirSync(sharedDir, { withFileTypes: true });
213
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
214
+ } catch {
215
+ return [];
216
+ }
143
217
  }
144
218
  function listExistingFeatures(projectRoot) {
145
219
  const featuresDir = resolveFeaturesDir(projectRoot);
146
- if (!fs3.existsSync(featuresDir)) return [];
220
+ if (!fs12.existsSync(featuresDir)) return [];
147
221
  try {
148
- const entries = fs3.readdirSync(featuresDir, { withFileTypes: true });
222
+ const entries = fs12.readdirSync(featuresDir, { withFileTypes: true });
149
223
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
150
224
  } catch {
151
225
  return [];
@@ -155,14 +229,14 @@ function listDomainInterfaces(projectRoot, featureName) {
155
229
  const results = [];
156
230
  const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
157
231
  for (const feat of features) {
158
- const domainDir = path3.join(resolveFeaturesDir(projectRoot), feat, "domain");
159
- if (!fs3.existsSync(domainDir)) continue;
232
+ const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
233
+ if (!fs12.existsSync(domainDir)) continue;
160
234
  try {
161
- const files = fs3.readdirSync(domainDir);
235
+ const files = fs12.readdirSync(domainDir);
162
236
  for (const file of files) {
163
237
  if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
164
238
  const name = file.replace(/\.ts$/, "");
165
- const fullPath = path3.join(domainDir, file);
239
+ const fullPath = path2.join(domainDir, file);
166
240
  if (/^I[A-Z]/.test(name)) {
167
241
  results.push({
168
242
  name,
@@ -171,7 +245,7 @@ function listDomainInterfaces(projectRoot, featureName) {
171
245
  });
172
246
  } else {
173
247
  try {
174
- const content = fs3.readFileSync(fullPath, "utf-8");
248
+ const content = fs12.readFileSync(fullPath, "utf-8");
175
249
  if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
176
250
  results.push({
177
251
  name,
@@ -193,17 +267,17 @@ function listDomainEntities(projectRoot, featureName) {
193
267
  const results = [];
194
268
  const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
195
269
  for (const feat of features) {
196
- const domainDir = path3.join(resolveFeaturesDir(projectRoot), feat, "domain");
197
- if (!fs3.existsSync(domainDir)) continue;
270
+ const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
271
+ if (!fs12.existsSync(domainDir)) continue;
198
272
  try {
199
- const files = fs3.readdirSync(domainDir);
273
+ const files = fs12.readdirSync(domainDir);
200
274
  for (const file of files) {
201
275
  if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
202
276
  const name = file.replace(/\.ts$/, "");
203
277
  if (!/^I[A-Z]/.test(name)) {
204
278
  results.push({
205
279
  name,
206
- fullPath: path3.join(domainDir, file),
280
+ fullPath: path2.join(domainDir, file),
207
281
  feature: feat
208
282
  });
209
283
  }
@@ -218,16 +292,16 @@ function listUseCases(projectRoot, featureName) {
218
292
  const results = [];
219
293
  const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
220
294
  for (const feat of features) {
221
- const useCasesDir = path3.join(resolveFeaturesDir(projectRoot), feat, "useCases");
222
- if (!fs3.existsSync(useCasesDir)) continue;
295
+ const useCasesDir = path2.join(resolveFeaturesDir(projectRoot), feat, "useCases");
296
+ if (!fs12.existsSync(useCasesDir)) continue;
223
297
  try {
224
- const files = fs3.readdirSync(useCasesDir);
298
+ const files = fs12.readdirSync(useCasesDir);
225
299
  for (const file of files) {
226
300
  if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
227
301
  const name = file.replace(/\.ts$/, "");
228
302
  results.push({
229
303
  name,
230
- fullPath: path3.join(useCasesDir, file),
304
+ fullPath: path2.join(useCasesDir, file),
231
305
  feature: feat
232
306
  });
233
307
  }
@@ -244,23 +318,23 @@ var PromptCancelledError = class extends Error {
244
318
  }
245
319
  };
246
320
  function handleCancel(result) {
247
- if (p4.isCancel(result)) {
248
- p4.cancel("Operation cancelled.");
321
+ if (p15.isCancel(result)) {
322
+ p15.cancel("Operation cancelled.");
249
323
  throw new PromptCancelledError();
250
324
  }
251
325
  return result;
252
326
  }
253
327
  function printBanner() {
254
328
  console.log("");
255
- p4.intro(pc13.bgCyan(pc13.black(" @solid-stack/mason ")));
329
+ p15.intro(pc15.bgCyan(pc15.black(" @solid-stack/mason ")));
256
330
  }
257
331
  async function promptProperties(options = {}) {
258
332
  const properties = options.defaults ? [...options.defaults] : [];
259
333
  if (properties.length > 0) {
260
- const summary = properties.map((p15) => `${p15.name}: ${p15.type}${p15.optional ? "?" : ""}`).join(", ");
261
- p4.log.info(pc13.dim(`Pre-configured fields: ${summary}`));
334
+ const summary = properties.map((p17) => `${p17.name}: ${p17.type}${p17.optional ? "?" : ""}`).join(", ");
335
+ p15.log.info(pc15.dim(`Pre-configured fields: ${summary}`));
262
336
  const addMore = handleCancel(
263
- await p4.confirm({
337
+ await p15.confirm({
264
338
  message: "Would you like to add custom fields/properties?",
265
339
  initialValue: false
266
340
  })
@@ -269,10 +343,10 @@ async function promptProperties(options = {}) {
269
343
  return properties;
270
344
  }
271
345
  }
272
- p4.log.info(pc13.cyan("Enter properties (leave name blank when finished):"));
346
+ p15.log.info(pc15.cyan("Enter properties (leave name blank when finished):"));
273
347
  while (true) {
274
348
  const name = handleCancel(
275
- await p4.text({
349
+ await p15.text({
276
350
  message: "Property name (or press Enter to finish):",
277
351
  placeholder: "e.g. title, email, status"
278
352
  })
@@ -281,7 +355,7 @@ async function promptProperties(options = {}) {
281
355
  break;
282
356
  }
283
357
  const type = handleCancel(
284
- await p4.select({
358
+ await p15.select({
285
359
  message: `Type for property "${name.trim()}":`,
286
360
  options: [
287
361
  { value: "string", label: "string" },
@@ -298,7 +372,7 @@ async function promptProperties(options = {}) {
298
372
  let finalType = type;
299
373
  if (type === "custom") {
300
374
  finalType = handleCancel(
301
- await p4.text({
375
+ await p15.text({
302
376
  message: `Enter custom type for "${name.trim()}":`,
303
377
  placeholder: "e.g. UserRole, string | null",
304
378
  validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
@@ -306,7 +380,7 @@ async function promptProperties(options = {}) {
306
380
  );
307
381
  }
308
382
  const optional = handleCancel(
309
- await p4.confirm({
383
+ await p15.confirm({
310
384
  message: `Is "${name.trim()}" optional?`,
311
385
  initialValue: false
312
386
  })
@@ -316,8 +390,8 @@ async function promptProperties(options = {}) {
316
390
  type: finalType.trim(),
317
391
  optional
318
392
  });
319
- p4.log.success(
320
- pc13.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
393
+ p15.log.success(
394
+ pc15.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
321
395
  );
322
396
  }
323
397
  return properties;
@@ -339,25 +413,25 @@ function normalizeRegistryUrl(urlOrPath, baseDir) {
339
413
  return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
340
414
  }
341
415
  if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
342
- return path3.resolve(baseDir, trimmed);
416
+ return path2.resolve(baseDir, trimmed);
343
417
  }
344
418
  return trimmed;
345
419
  }
346
420
  function findLocalRegistry(startDir = process.cwd()) {
347
- let current = path3.resolve(startDir);
421
+ let current = path2.resolve(startDir);
348
422
  while (true) {
349
- const candidate = path3.join(current, "mason-registry");
350
- if (fs3.existsSync(candidate) && fs3.existsSync(path3.join(candidate, "index.json"))) {
423
+ const candidate = path2.join(current, "mason-registry");
424
+ if (fs12.existsSync(candidate) && fs12.existsSync(path2.join(candidate, "index.json"))) {
351
425
  return candidate;
352
426
  }
353
- const parent = path3.dirname(current);
427
+ const parent = path2.dirname(current);
354
428
  if (parent === current) break;
355
429
  current = parent;
356
430
  }
357
431
  return null;
358
432
  }
359
433
  function getRegistryBase(projectRoot, override) {
360
- const rootDir = projectRoot ? path3.resolve(projectRoot) : process.cwd();
434
+ const rootDir = projectRoot ? path2.resolve(projectRoot) : process.cwd();
361
435
  if (override && override.trim()) {
362
436
  return normalizeRegistryUrl(override.trim(), rootDir);
363
437
  }
@@ -365,30 +439,30 @@ function getRegistryBase(projectRoot, override) {
365
439
  return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
366
440
  }
367
441
  const configLocations = [
368
- path3.join(rootDir, "mason.config.json"),
369
- path3.join(process.cwd(), "mason.config.json")
442
+ path2.join(rootDir, "mason.config.json"),
443
+ path2.join(process.cwd(), "mason.config.json")
370
444
  ];
371
445
  for (const configPath of configLocations) {
372
- if (fs3.existsSync(configPath)) {
446
+ if (fs12.existsSync(configPath)) {
373
447
  try {
374
- const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
448
+ const config = JSON.parse(fs12.readFileSync(configPath, "utf-8"));
375
449
  if (config.registry && typeof config.registry === "string") {
376
- return normalizeRegistryUrl(config.registry.trim(), path3.dirname(configPath));
450
+ return normalizeRegistryUrl(config.registry.trim(), path2.dirname(configPath));
377
451
  }
378
452
  } catch {
379
453
  }
380
454
  }
381
455
  }
382
456
  const pkgLocations = [
383
- path3.join(rootDir, "package.json"),
384
- path3.join(process.cwd(), "package.json")
457
+ path2.join(rootDir, "package.json"),
458
+ path2.join(process.cwd(), "package.json")
385
459
  ];
386
460
  for (const pkgPath of pkgLocations) {
387
- if (fs3.existsSync(pkgPath)) {
461
+ if (fs12.existsSync(pkgPath)) {
388
462
  try {
389
- const pkg = JSON.parse(fs3.readFileSync(pkgPath, "utf-8"));
463
+ const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
390
464
  if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
391
- return normalizeRegistryUrl(pkg.mason.registry.trim(), path3.dirname(pkgPath));
465
+ return normalizeRegistryUrl(pkg.mason.registry.trim(), path2.dirname(pkgPath));
392
466
  }
393
467
  } catch {
394
468
  }
@@ -429,11 +503,11 @@ async function fetchRegistryFile(registryBase, relPath) {
429
503
  if (baseDir.startsWith("file://")) {
430
504
  baseDir = fileURLToPath(baseDir);
431
505
  }
432
- const filePath = path3.resolve(baseDir, cleanRelPath);
433
- if (!fs3.existsSync(filePath)) {
506
+ const filePath = path2.resolve(baseDir, cleanRelPath);
507
+ if (!fs12.existsSync(filePath)) {
434
508
  throw new Error(`Registry file not found at ${filePath}`);
435
509
  }
436
- return fs3.promises.readFile(filePath, "utf-8");
510
+ return fs12.promises.readFile(filePath, "utf-8");
437
511
  }
438
512
  async function fetchRegistryIndex(registryBase) {
439
513
  const content = await fetchRegistryFile(registryBase, "index.json");
@@ -495,26 +569,26 @@ function findClosestMatch(target, candidates) {
495
569
  return null;
496
570
  }
497
571
  function detectPackageManager(projectRoot) {
498
- if (fs3.existsSync(path3.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
499
- if (fs3.existsSync(path3.join(projectRoot, "yarn.lock"))) return "yarn";
500
- if (fs3.existsSync(path3.join(projectRoot, "bun.lockb")) || fs3.existsSync(path3.join(projectRoot, "bun.lock"))) return "bun";
572
+ if (fs12.existsSync(path2.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
573
+ if (fs12.existsSync(path2.join(projectRoot, "yarn.lock"))) return "yarn";
574
+ if (fs12.existsSync(path2.join(projectRoot, "bun.lockb")) || fs12.existsSync(path2.join(projectRoot, "bun.lock"))) return "bun";
501
575
  return "npm";
502
576
  }
503
577
  function getMissingNpmPackages(projectRoot, requiredPackages) {
504
578
  if (!requiredPackages || requiredPackages.length === 0) return [];
505
- const pkgJsonPath = path3.join(projectRoot, "package.json");
506
- if (!fs3.existsSync(pkgJsonPath)) {
579
+ const pkgJsonPath = path2.join(projectRoot, "package.json");
580
+ if (!fs12.existsSync(pkgJsonPath)) {
507
581
  return requiredPackages;
508
582
  }
509
583
  try {
510
- const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
584
+ const pkg = JSON.parse(fs12.readFileSync(pkgJsonPath, "utf-8"));
511
585
  const installed = /* @__PURE__ */ new Set([
512
586
  ...Object.keys(pkg.dependencies || {}),
513
587
  ...Object.keys(pkg.devDependencies || {}),
514
588
  ...Object.keys(pkg.peerDependencies || {})
515
589
  ]);
516
- return requiredPackages.filter((p15) => {
517
- const nameOnly = p15.startsWith("@") ? `@${p15.slice(1).split("@")[0] || ""}` : p15.split("@")[0] || p15;
590
+ return requiredPackages.filter((p17) => {
591
+ const nameOnly = p17.startsWith("@") ? `@${p17.slice(1).split("@")[0] || ""}` : p17.split("@")[0] || p17;
518
592
  return !installed.has(nameOnly);
519
593
  });
520
594
  } catch {
@@ -539,12 +613,47 @@ function installNpmPackages(projectRoot, packages, pm) {
539
613
  installCmd = `npm install ${packages.join(" ")}`;
540
614
  break;
541
615
  }
542
- p4.log.info(`Installing missing dependencies with ${manager}: ${pc13.cyan(packages.join(", "))}`);
616
+ p15.log.info(`Installing missing dependencies with ${manager}: ${pc15.cyan(packages.join(", "))}`);
543
617
  execSync(installCmd, {
544
618
  cwd: projectRoot,
545
619
  stdio: "inherit"
546
620
  });
547
621
  }
622
+ function getAllFiles(dir, baseDir = dir) {
623
+ if (!fs12.existsSync(dir)) return [];
624
+ const entries = fs12.readdirSync(dir, { withFileTypes: true });
625
+ const files = [];
626
+ for (const entry of entries) {
627
+ const fullPath = path2.join(dir, entry.name);
628
+ const relPath = path2.relative(baseDir, fullPath).replace(/\\/g, "/");
629
+ if (entry.isDirectory()) {
630
+ if (entry.name === "__tests__" || entry.name.startsWith(".")) {
631
+ continue;
632
+ }
633
+ files.push(...getAllFiles(fullPath, baseDir));
634
+ } else if (entry.isFile()) {
635
+ if (entry.name === "registry.json" || entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts") || entry.name.startsWith(".")) {
636
+ continue;
637
+ }
638
+ files.push(relPath);
639
+ }
640
+ }
641
+ return files.sort();
642
+ }
643
+ function computeDirectoryHash(dirPath, files) {
644
+ if (!fs12.existsSync(dirPath)) return "";
645
+ const hash = createHash("sha256");
646
+ const fileList = (files ?? getAllFiles(dirPath)).sort();
647
+ for (const relativePath of fileList) {
648
+ const absolutePath = path2.join(dirPath, relativePath);
649
+ if (!fs12.existsSync(absolutePath)) continue;
650
+ const content = fs12.readFileSync(absolutePath, "utf8");
651
+ const normalizedContent = content.replace(/\r\n/g, "\n");
652
+ hash.update(relativePath);
653
+ hash.update(normalizedContent);
654
+ }
655
+ return `sha256-${hash.digest("hex")}`;
656
+ }
548
657
  var hbs = Handlebars.create();
549
658
  hbs.registerHelper("eq", (a, b) => a === b);
550
659
  hbs.registerHelper("ne", (a, b) => a !== b);
@@ -561,33 +670,33 @@ hbs.registerHelper("join", (arr, sep) => {
561
670
  });
562
671
  var cachedTemplatesDir = null;
563
672
  function getTemplatesDir() {
564
- if (cachedTemplatesDir && fs3.existsSync(cachedTemplatesDir)) {
673
+ if (cachedTemplatesDir && fs12.existsSync(cachedTemplatesDir)) {
565
674
  return cachedTemplatesDir;
566
675
  }
567
- const currentDir = typeof __dirname !== "undefined" ? __dirname : path3.dirname(fileURLToPath(import.meta.url));
568
- const cand1 = path3.resolve(currentDir, "../../templates");
569
- if (fs3.existsSync(cand1)) {
676
+ const currentDir = typeof __dirname !== "undefined" ? __dirname : path2.dirname(fileURLToPath(import.meta.url));
677
+ const cand1 = path2.resolve(currentDir, "../../templates");
678
+ if (fs12.existsSync(cand1)) {
570
679
  cachedTemplatesDir = cand1;
571
680
  return cand1;
572
681
  }
573
- const cand2 = path3.resolve(currentDir, "../templates");
574
- if (fs3.existsSync(cand2)) {
682
+ const cand2 = path2.resolve(currentDir, "../templates");
683
+ if (fs12.existsSync(cand2)) {
575
684
  cachedTemplatesDir = cand2;
576
685
  return cand2;
577
686
  }
578
- const cand3 = path3.resolve(currentDir, "templates");
579
- if (fs3.existsSync(cand3)) {
687
+ const cand3 = path2.resolve(currentDir, "templates");
688
+ if (fs12.existsSync(cand3)) {
580
689
  cachedTemplatesDir = cand3;
581
690
  return cand3;
582
691
  }
583
692
  let curr = currentDir;
584
693
  while (true) {
585
- const candTemplates = path3.join(curr, "templates");
586
- if (fs3.existsSync(candTemplates)) {
694
+ const candTemplates = path2.join(curr, "templates");
695
+ if (fs12.existsSync(candTemplates)) {
587
696
  cachedTemplatesDir = candTemplates;
588
697
  return candTemplates;
589
698
  }
590
- const parent = path3.dirname(curr);
699
+ const parent = path2.dirname(curr);
591
700
  if (parent === curr) break;
592
701
  curr = parent;
593
702
  }
@@ -595,8 +704,8 @@ function getTemplatesDir() {
595
704
  }
596
705
  var templateCache = /* @__PURE__ */ new Map();
597
706
  function loadTemplate(relativePath) {
598
- const fullPath = path3.isAbsolute(relativePath) ? relativePath : path3.join(getTemplatesDir(), relativePath);
599
- return fs3.readFileSync(fullPath, "utf-8");
707
+ const fullPath = path2.isAbsolute(relativePath) ? relativePath : path2.join(getTemplatesDir(), relativePath);
708
+ return fs12.readFileSync(fullPath, "utf-8");
600
709
  }
601
710
  function renderTemplateFile(relativePath, context) {
602
711
  let compiled = templateCache.get(relativePath);
@@ -681,8 +790,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
681
790
  function generateDomain(options) {
682
791
  const projectRoot = options.projectRoot || findProjectRoot();
683
792
  const entityName = toPascalCase(options.name);
684
- const targetDir = options.targetDir || path3.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
685
- const filePath = path3.join(targetDir, `${entityName}.ts`);
793
+ const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
794
+ const filePath = path2.join(targetDir, `${entityName}.ts`);
686
795
  const code = generateDomainEntityCode(
687
796
  entityName,
688
797
  options.kind || "interface",
@@ -706,7 +815,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
706
815
  const idKey = `${entityKey}Id`;
707
816
  const listKey = `${entityKey}s`;
708
817
  const formattedCustomMethods = customMethods.map((m) => {
709
- const params = m.params && m.params.length > 0 ? m.params.map((p15) => `${p15.name}: ${p15.type}`).join("; ") : "";
818
+ const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join("; ") : "";
710
819
  const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
711
820
  return {
712
821
  name: m.name,
@@ -731,8 +840,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
731
840
  function generateInterface(options) {
732
841
  const projectRoot = options.projectRoot || findProjectRoot();
733
842
  const ifaceName = ensureInterfacePrefix(options.name);
734
- const targetDir = options.targetDir || path3.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
735
- const filePath = path3.join(targetDir, `${ifaceName}.ts`);
843
+ const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
844
+ const filePath = path2.join(targetDir, `${ifaceName}.ts`);
736
845
  const code = generateInterfaceCode(
737
846
  ifaceName,
738
847
  options.kind || "abstractClass",
@@ -967,7 +1076,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
967
1076
  };
968
1077
  }
969
1078
  function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
970
- if (customPath && fs3.existsSync(customPath)) {
1079
+ if (customPath && fs12.existsSync(customPath)) {
971
1080
  return { filePath: customPath, ifaceName };
972
1081
  }
973
1082
  const candidateNames = [
@@ -977,22 +1086,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
977
1086
  ];
978
1087
  const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
979
1088
  for (const feat of features) {
980
- const domainDir = path3.join(resolveFeaturesDir(projectRoot), feat, "domain");
981
- if (!fs3.existsSync(domainDir)) continue;
1089
+ const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
1090
+ if (!fs12.existsSync(domainDir)) continue;
982
1091
  for (const cand of candidateNames) {
983
- const p1 = path3.join(domainDir, `${cand}.ts`);
984
- if (fs3.existsSync(p1)) {
1092
+ const p1 = path2.join(domainDir, `${cand}.ts`);
1093
+ if (fs12.existsSync(p1)) {
985
1094
  return { filePath: p1, ifaceName: cand, feature: feat };
986
1095
  }
987
1096
  }
988
1097
  }
989
1098
  for (const cand of candidateNames) {
990
- const p1 = path3.join(projectRoot, "domain", `${cand}.ts`);
991
- if (fs3.existsSync(p1)) return { filePath: p1, ifaceName: cand };
992
- const p22 = path3.join(projectRoot, "src", "domain", `${cand}.ts`);
993
- if (fs3.existsSync(p22)) return { filePath: p22, ifaceName: cand };
994
- const p32 = path3.join(projectRoot, "interfaces", `${cand}.ts`);
995
- if (fs3.existsSync(p32)) return { filePath: p32, ifaceName: cand };
1099
+ const p1 = path2.join(projectRoot, "domain", `${cand}.ts`);
1100
+ if (fs12.existsSync(p1)) return { filePath: p1, ifaceName: cand };
1101
+ const p22 = path2.join(projectRoot, "src", "domain", `${cand}.ts`);
1102
+ if (fs12.existsSync(p22)) return { filePath: p22, ifaceName: cand };
1103
+ const p32 = path2.join(projectRoot, "interfaces", `${cand}.ts`);
1104
+ if (fs12.existsSync(p32)) return { filePath: p32, ifaceName: cand };
996
1105
  }
997
1106
  return null;
998
1107
  }
@@ -1015,7 +1124,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
1015
1124
  while (currentFile && !visited.has(currentFile)) {
1016
1125
  visited.add(currentFile);
1017
1126
  try {
1018
- const content = fs3.readFileSync(currentFile, "utf-8");
1127
+ const content = fs12.readFileSync(currentFile, "utf-8");
1019
1128
  const parsed = parseInterfaceMethodsAndImports(content, currentName);
1020
1129
  for (const m of parsed.methods) {
1021
1130
  if (!allMethods.some((existing) => existing.name === m.name)) {
@@ -1091,7 +1200,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
1091
1200
  const entitiesVar = `${entityVar}s`;
1092
1201
  const idKey = `${entityVar}Id`;
1093
1202
  const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
1094
- const entityImportPath = ifaceImportPath ? `${path3.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1203
+ const entityImportPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1095
1204
  const importsByFrom = /* @__PURE__ */ new Map();
1096
1205
  for (const imp of domainImports) {
1097
1206
  if (imp.name === ifaceName || imp.name === baseEntity) continue;
@@ -1140,7 +1249,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
1140
1249
  if (baseEntity) {
1141
1250
  finalMethods = synthesizeCrudMethods(baseEntity);
1142
1251
  if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
1143
- const importPath = ifaceImportPath ? `${path3.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1252
+ const importPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1144
1253
  finalDomainImports.push({
1145
1254
  name: baseEntity,
1146
1255
  from: importPath,
@@ -1185,8 +1294,8 @@ function generateInfrastructure(options) {
1185
1294
  const projectRoot = options.projectRoot || findProjectRoot();
1186
1295
  const className = toPascalCase(options.name);
1187
1296
  const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
1188
- const targetDir = options.targetDir || (isFeatureScope ? path3.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path3.join(projectRoot, "infrastructure"));
1189
- const filePath = path3.join(targetDir, `${className}.ts`);
1297
+ const targetDir = options.targetDir || (isFeatureScope ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path2.join(projectRoot, "infrastructure"));
1298
+ const filePath = path2.join(targetDir, `${className}.ts`);
1190
1299
  let code;
1191
1300
  if (options.implementsInterface) {
1192
1301
  const rawIfaceName = options.implementsInterface;
@@ -1200,7 +1309,7 @@ function generateInfrastructure(options) {
1200
1309
  const ifaceFilePath = hierarchy.ifaceFilePath;
1201
1310
  let ifaceImportPath;
1202
1311
  if (ifaceFilePath) {
1203
- let rel = path3.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
1312
+ let rel = path2.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
1204
1313
  if (!rel.startsWith(".")) rel = "./" + rel;
1205
1314
  ifaceImportPath = rel;
1206
1315
  } else if (isFeatureScope) {
@@ -1213,8 +1322,8 @@ function generateInfrastructure(options) {
1213
1322
  if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
1214
1323
  let fromPath;
1215
1324
  if (imp.specifier.startsWith(".") && ifaceFilePath) {
1216
- const absTarget = path3.resolve(path3.dirname(ifaceFilePath), imp.specifier);
1217
- let rel = path3.relative(targetDir, absTarget).replace(/\\/g, "/");
1325
+ const absTarget = path2.resolve(path2.dirname(ifaceFilePath), imp.specifier);
1326
+ let rel = path2.relative(targetDir, absTarget).replace(/\\/g, "/");
1218
1327
  if (!rel.startsWith(".")) rel = "./" + rel;
1219
1328
  fromPath = rel;
1220
1329
  } else {
@@ -1287,22 +1396,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
1287
1396
  from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
1288
1397
  }));
1289
1398
  const domainTypesToImport = new Set(domainImports);
1290
- for (const p15 of [...inputProps, ...outputProps]) {
1291
- const rawType = p15.type.trim();
1399
+ for (const p17 of [...inputProps, ...outputProps]) {
1400
+ const rawType = p17.type.trim();
1292
1401
  const baseType = rawType.replace(/\[\]$/, "").trim();
1293
1402
  if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
1294
1403
  domainTypesToImport.add(baseType);
1295
1404
  }
1296
1405
  }
1297
- const formattedInputProps = inputProps.map((p15) => ({
1298
- name: p15.name,
1299
- optional: p15.optional,
1300
- type: p15.optional && !p15.type.includes("undefined") ? `${p15.type} | undefined` : p15.type
1406
+ const formattedInputProps = inputProps.map((p17) => ({
1407
+ name: p17.name,
1408
+ optional: p17.optional,
1409
+ type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
1301
1410
  }));
1302
- const formattedOutputProps = outputProps.map((p15) => ({
1303
- name: p15.name,
1304
- optional: p15.optional,
1305
- type: p15.optional && !p15.type.includes("undefined") ? `${p15.type} | undefined` : p15.type
1411
+ const formattedOutputProps = outputProps.map((p17) => ({
1412
+ name: p17.name,
1413
+ optional: p17.optional,
1414
+ type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
1306
1415
  }));
1307
1416
  const formattedDeps = dependencies.map((dep) => ({
1308
1417
  key: toCamelCase(dep),
@@ -1401,8 +1510,8 @@ function generateUseCase(options) {
1401
1510
  const files = [];
1402
1511
  const projectRoot = options.projectRoot || findProjectRoot();
1403
1512
  const ucName = toPascalCase(options.name);
1404
- const targetDir = options.targetDir || path3.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
1405
- const filePath = path3.join(targetDir, `${ucName}.ts`);
1513
+ const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
1514
+ const filePath = path2.join(targetDir, `${ucName}.ts`);
1406
1515
  const code = generateUseCaseCode(
1407
1516
  ucName,
1408
1517
  options.dependencies || [],
@@ -1418,7 +1527,7 @@ function generateUseCase(options) {
1418
1527
  })
1419
1528
  );
1420
1529
  if (options.includeTest) {
1421
- const testPath = path3.join(targetDir, `${ucName}.test.ts`);
1530
+ const testPath = path2.join(targetDir, `${ucName}.test.ts`);
1422
1531
  const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
1423
1532
  files.push(
1424
1533
  writeFileSafely(testPath, testCode, {
@@ -1486,10 +1595,10 @@ function generateHttpHandler(options) {
1486
1595
  const files = [];
1487
1596
  const projectRoot = options.projectRoot || findProjectRoot();
1488
1597
  const className = toPascalCase(options.name);
1489
- const pxDir = path3.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
1490
- const handlersDir = path3.join(pxDir, "handlers");
1491
- const pxIndexPath = path3.join(pxDir, "index.ts");
1492
- if (!fs3.existsSync(pxIndexPath)) {
1598
+ const pxDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
1599
+ const handlersDir = path2.join(pxDir, "handlers");
1600
+ const pxIndexPath = path2.join(pxDir, "index.ts");
1601
+ if (!fs12.existsSync(pxIndexPath)) {
1493
1602
  const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
1494
1603
  const pxIndexContent = renderTemplate(expressIndexTemplate, {
1495
1604
  routePath: featurePath
@@ -1502,7 +1611,7 @@ function generateHttpHandler(options) {
1502
1611
  })
1503
1612
  );
1504
1613
  }
1505
- const handlerFilePath = path3.join(handlersDir, `${className}.ts`);
1614
+ const handlerFilePath = path2.join(handlersDir, `${className}.ts`);
1506
1615
  const code = generateHttpHandlerCode(
1507
1616
  className,
1508
1617
  options.method || "get",
@@ -1523,11 +1632,11 @@ function generateHttpHandler(options) {
1523
1632
  return files;
1524
1633
  }
1525
1634
  function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
1526
- const providerPath = path3.join(featureDir, "diProvider.ts");
1635
+ const providerPath = path2.join(featureDir, "diProvider.ts");
1527
1636
  const pascalFeature = toPascalCase(featureName);
1528
1637
  const providerModuleName = `${pascalFeature}Provider`;
1529
- if (fs3.existsSync(providerPath) && !options.overwrite) {
1530
- let content = fs3.readFileSync(providerPath, "utf-8");
1638
+ if (fs12.existsSync(providerPath) && !options.overwrite) {
1639
+ let content = fs12.readFileSync(providerPath, "utf-8");
1531
1640
  content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
1532
1641
  for (const b of bindings) {
1533
1642
  const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
@@ -1586,7 +1695,7 @@ function generateFeature(options) {
1586
1695
  const projectRoot = options.projectRoot || findProjectRoot();
1587
1696
  const rawName = options.name.trim();
1588
1697
  const featureName = toCamelCase(rawName);
1589
- const targetDir = options.targetDir || path3.join(resolveFeaturesDir(projectRoot), featureName);
1698
+ const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), featureName);
1590
1699
  const routePath = options.routePath || `/${featureName}`;
1591
1700
  const rawEntities = [];
1592
1701
  if (options.entityNames && options.entityNames.length > 0) {
@@ -1607,7 +1716,7 @@ function generateFeature(options) {
1607
1716
  const domainFile = generateDomain({
1608
1717
  name: ent,
1609
1718
  feature: featureName,
1610
- targetDir: path3.join(targetDir, "domain"),
1719
+ targetDir: path2.join(targetDir, "domain"),
1611
1720
  projectRoot,
1612
1721
  dryRun: options.dryRun,
1613
1722
  overwrite: options.overwrite,
@@ -1627,7 +1736,7 @@ function generateFeature(options) {
1627
1736
  const ifaceFile = generateInterface({
1628
1737
  name: ifaceName,
1629
1738
  feature: featureName,
1630
- targetDir: path3.join(targetDir, "domain"),
1739
+ targetDir: path2.join(targetDir, "domain"),
1631
1740
  entityName: ent,
1632
1741
  projectRoot,
1633
1742
  dryRun: options.dryRun,
@@ -1644,7 +1753,7 @@ function generateFeature(options) {
1644
1753
  const stubFile = generateInfrastructure({
1645
1754
  name: stubName,
1646
1755
  feature: featureName,
1647
- targetDir: path3.join(targetDir, "infrastructure"),
1756
+ targetDir: path2.join(targetDir, "infrastructure"),
1648
1757
  kind: "stub",
1649
1758
  implementsInterface: ifaceName,
1650
1759
  entityName: ent,
@@ -1686,7 +1795,7 @@ function generateFeature(options) {
1686
1795
  const ucFiles = generateUseCase({
1687
1796
  name: ucName,
1688
1797
  feature: featureName,
1689
- targetDir: path3.join(targetDir, "useCases"),
1798
+ targetDir: path2.join(targetDir, "useCases"),
1690
1799
  dependencies: options.includeInterface !== false ? [ifaceName] : [],
1691
1800
  inputProperties: [{ name: idParam, type: "string" }],
1692
1801
  outputProperties: [{ name: entVar, type: primaryEntity }],
@@ -1731,7 +1840,7 @@ function generateFeature(options) {
1731
1840
  const ucFiles = generateUseCase({
1732
1841
  name: ucName,
1733
1842
  feature: featureName,
1734
- targetDir: path3.join(targetDir, "useCases"),
1843
+ targetDir: path2.join(targetDir, "useCases"),
1735
1844
  dependencies: deps,
1736
1845
  inputProperties: [{ name: entVar, type: ent }],
1737
1846
  outputProperties: [{ name: entVar, type: ent }],
@@ -1764,7 +1873,7 @@ function generateFeature(options) {
1764
1873
  const ucFiles = generateUseCase({
1765
1874
  name: ucName,
1766
1875
  feature: featureName,
1767
- targetDir: path3.join(targetDir, "useCases"),
1876
+ targetDir: path2.join(targetDir, "useCases"),
1768
1877
  dependencies: deps,
1769
1878
  inputProperties: [{ name: idParam, type: "string" }],
1770
1879
  outputProperties: [{ name: entVar, type: ent }],
@@ -1797,7 +1906,7 @@ function generateFeature(options) {
1797
1906
  const ucFiles = generateUseCase({
1798
1907
  name: ucName,
1799
1908
  feature: featureName,
1800
- targetDir: path3.join(targetDir, "useCases"),
1909
+ targetDir: path2.join(targetDir, "useCases"),
1801
1910
  dependencies: deps,
1802
1911
  inputProperties: [
1803
1912
  { name: "page", type: "number", optional: true },
@@ -1841,7 +1950,7 @@ function generateFeature(options) {
1841
1950
  const ucFiles = generateUseCase({
1842
1951
  name: ucName,
1843
1952
  feature: featureName,
1844
- targetDir: path3.join(targetDir, "useCases"),
1953
+ targetDir: path2.join(targetDir, "useCases"),
1845
1954
  dependencies: deps,
1846
1955
  inputProperties: [{ name: entVar, type: ent }],
1847
1956
  outputProperties: [{ name: entVar, type: ent }],
@@ -1874,7 +1983,7 @@ function generateFeature(options) {
1874
1983
  const ucFiles = generateUseCase({
1875
1984
  name: ucName,
1876
1985
  feature: featureName,
1877
- targetDir: path3.join(targetDir, "useCases"),
1986
+ targetDir: path2.join(targetDir, "useCases"),
1878
1987
  dependencies: deps,
1879
1988
  inputProperties: [{ name: idParam, type: "string" }],
1880
1989
  outputProperties: [{ name: "success", type: "boolean" }],
@@ -1911,7 +2020,7 @@ function generateFeature(options) {
1911
2020
  const ucFiles = generateUseCase({
1912
2021
  name: ucName,
1913
2022
  feature: featureName,
1914
- targetDir: path3.join(targetDir, "useCases"),
2023
+ targetDir: path2.join(targetDir, "useCases"),
1915
2024
  dependencies: customUc.dependencies || [],
1916
2025
  inputProperties: customUc.inputProperties || [],
1917
2026
  outputProperties: customUc.outputProperties || [],
@@ -1943,8 +2052,8 @@ function generateFeature(options) {
1943
2052
  }
1944
2053
  }
1945
2054
  if (options.includeExpress !== false) {
1946
- const pxDir = path3.join(targetDir, "pxExpress");
1947
- const pxIndexPath = path3.join(pxDir, "index.ts");
2055
+ const pxDir = path2.join(targetDir, "pxExpress");
2056
+ const pxIndexPath = path2.join(pxDir, "index.ts");
1948
2057
  if (!files.some((f) => f.absolutePath === pxIndexPath)) {
1949
2058
  const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
1950
2059
  files.push(
@@ -1970,13 +2079,14 @@ function generateFeature(options) {
1970
2079
  // src/commands/feature.ts
1971
2080
  async function runFeatureCommand(featureNameArg, flags = {}) {
1972
2081
  const projectRoot = findProjectRoot();
2082
+ const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
1973
2083
  let name = featureNameArg?.trim();
1974
2084
  if (!name && flags.yes) {
1975
2085
  name = "feature";
1976
2086
  }
1977
2087
  if (!name) {
1978
2088
  name = handleCancel(
1979
- await p4.text({
2089
+ await p15.text({
1980
2090
  message: "What is the feature name?",
1981
2091
  placeholder: "e.g. users, auth, orders, billing",
1982
2092
  validate: (val) => {
@@ -1987,18 +2097,19 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
1987
2097
  ).trim();
1988
2098
  }
1989
2099
  const normalizedName = toCamelCase(name);
1990
- const defaultDir = path3.relative(projectRoot, path3.join(resolveFeaturesDir(projectRoot), normalizedName));
2100
+ const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
2101
+ const defaultDir = path2.relative(projectRoot, path2.join(baseFeaturesDir, normalizedName));
1991
2102
  let targetDirRel = defaultDir;
1992
- if (!flags.yes && !flags.target) {
2103
+ if (!flags.yes && !flags.target && !flags.dest) {
1993
2104
  targetDirRel = handleCancel(
1994
- await p4.text({
2105
+ await p15.text({
1995
2106
  message: "Target directory for the feature:",
1996
2107
  initialValue: defaultDir,
1997
2108
  defaultValue: defaultDir
1998
2109
  })
1999
2110
  ).trim();
2000
2111
  }
2001
- const targetDir = flags.target ? path3.resolve(projectRoot, flags.target) : path3.resolve(projectRoot, targetDirRel);
2112
+ const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
2002
2113
  let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
2003
2114
  let routePath = `/${normalizedName}`;
2004
2115
  let includeDomain = true;
@@ -2013,14 +2124,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2013
2124
  const customUseCases = [];
2014
2125
  if (!flags.yes) {
2015
2126
  includeExpress = handleCancel(
2016
- await p4.confirm({
2127
+ await p15.confirm({
2017
2128
  message: "Include Express Presentation layer (pxExpress routes & handlers)?",
2018
2129
  initialValue: true
2019
2130
  })
2020
2131
  );
2021
2132
  if (includeExpress) {
2022
2133
  routePath = handleCancel(
2023
- await p4.text({
2134
+ await p15.text({
2024
2135
  message: "Express route prefix:",
2025
2136
  initialValue: `/${normalizedName}`,
2026
2137
  defaultValue: `/${normalizedName}`
@@ -2028,14 +2139,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2028
2139
  ).trim();
2029
2140
  }
2030
2141
  includeDomain = handleCancel(
2031
- await p4.confirm({
2142
+ await p15.confirm({
2032
2143
  message: "Include Domain Entity/Entities?",
2033
2144
  initialValue: true
2034
2145
  })
2035
2146
  );
2036
2147
  if (includeDomain) {
2037
2148
  const domainInput = handleCancel(
2038
- await p4.text({
2149
+ await p15.text({
2039
2150
  message: "Domain entity name(s) (comma-separated):",
2040
2151
  initialValue: defaultEntity,
2041
2152
  defaultValue: defaultEntity,
@@ -2054,21 +2165,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2054
2165
  entityNames = [defaultEntity];
2055
2166
  }
2056
2167
  includeInterface = handleCancel(
2057
- await p4.confirm({
2168
+ await p15.confirm({
2058
2169
  message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
2059
2170
  initialValue: true
2060
2171
  })
2061
2172
  );
2062
2173
  if (includeInterface) {
2063
2174
  includeInfra = handleCancel(
2064
- await p4.confirm({
2175
+ await p15.confirm({
2065
2176
  message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
2066
2177
  initialValue: true
2067
2178
  })
2068
2179
  );
2069
2180
  }
2070
2181
  includeUseCase = handleCancel(
2071
- await p4.confirm({
2182
+ await p15.confirm({
2072
2183
  message: "Include Domain Use Cases?",
2073
2184
  initialValue: true
2074
2185
  })
@@ -2082,7 +2193,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2082
2193
  { value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
2083
2194
  ];
2084
2195
  crudOperations = handleCancel(
2085
- await p4.multiselect({
2196
+ await p15.multiselect({
2086
2197
  message: "Select standard CRUD use cases to generate:",
2087
2198
  options: crudChoices,
2088
2199
  initialValues: ["create", "read", "list", "update", "delete"],
@@ -2090,13 +2201,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2090
2201
  })
2091
2202
  );
2092
2203
  includeTests = handleCancel(
2093
- await p4.confirm({
2204
+ await p15.confirm({
2094
2205
  message: "Generate Vitest unit tests for use cases?",
2095
2206
  initialValue: true
2096
2207
  })
2097
2208
  );
2098
2209
  const wantCustom = handleCancel(
2099
- await p4.confirm({
2210
+ await p15.confirm({
2100
2211
  message: "Do you want to add any custom use cases to this feature?",
2101
2212
  initialValue: false
2102
2213
  })
@@ -2106,7 +2217,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2106
2217
  const availableIfaces = entityNames.map((e) => `I${e}Repository`);
2107
2218
  while (addMore) {
2108
2219
  const customName = handleCancel(
2109
- await p4.text({
2220
+ await p15.text({
2110
2221
  message: "Custom Use Case name:",
2111
2222
  placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
2112
2223
  validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
@@ -2116,7 +2227,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2116
2227
  let selectedDeps = [];
2117
2228
  if (availableIfaces.length > 0) {
2118
2229
  selectedDeps = handleCancel(
2119
- await p4.multiselect({
2230
+ await p15.multiselect({
2120
2231
  message: `Select domain interfaces to inject into ${customUcName}:`,
2121
2232
  options: availableIfaces.map((i) => ({ value: i, label: i })),
2122
2233
  required: false
@@ -2125,7 +2236,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2125
2236
  }
2126
2237
  let customInputProps = [];
2127
2238
  const hasInput = handleCancel(
2128
- await p4.confirm({
2239
+ await p15.confirm({
2129
2240
  message: `Does ${customUcName} accept input arguments?`,
2130
2241
  initialValue: true
2131
2242
  })
@@ -2136,7 +2247,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2136
2247
  }
2137
2248
  let customOutputProps = [];
2138
2249
  const hasCustomOutput = handleCancel(
2139
- await p4.confirm({
2250
+ await p15.confirm({
2140
2251
  message: `Does ${customUcName} return custom output data?`,
2141
2252
  initialValue: false
2142
2253
  })
@@ -2146,7 +2257,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2146
2257
  });
2147
2258
  }
2148
2259
  const customIncludeTest = handleCancel(
2149
- await p4.confirm({
2260
+ await p15.confirm({
2150
2261
  message: `Generate Vitest unit test for ${customUcName}?`,
2151
2262
  initialValue: true
2152
2263
  })
@@ -2156,14 +2267,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2156
2267
  let customHttpPath = `/${toKebabCase(customUcName)}`;
2157
2268
  if (includeExpress) {
2158
2269
  customCreateHttp = handleCancel(
2159
- await p4.confirm({
2270
+ await p15.confirm({
2160
2271
  message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
2161
2272
  initialValue: true
2162
2273
  })
2163
2274
  );
2164
2275
  if (customCreateHttp) {
2165
2276
  customHttpMethod = handleCancel(
2166
- await p4.select({
2277
+ await p15.select({
2167
2278
  message: "HTTP method for route:",
2168
2279
  options: [
2169
2280
  { value: "post", label: "POST" },
@@ -2176,7 +2287,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2176
2287
  })
2177
2288
  );
2178
2289
  customHttpPath = handleCancel(
2179
- await p4.text({
2290
+ await p15.text({
2180
2291
  message: "Route subpath (relative to feature prefix):",
2181
2292
  initialValue: `/${toKebabCase(customUcName)}`,
2182
2293
  defaultValue: `/${toKebabCase(customUcName)}`
@@ -2197,7 +2308,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2197
2308
  validationFields: customInputProps
2198
2309
  });
2199
2310
  addMore = handleCancel(
2200
- await p4.confirm({
2311
+ await p15.confirm({
2201
2312
  message: "Add another custom use case?",
2202
2313
  initialValue: false
2203
2314
  })
@@ -2206,7 +2317,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2206
2317
  }
2207
2318
  }
2208
2319
  includeDiProvider = handleCancel(
2209
- await p4.confirm({
2320
+ await p15.confirm({
2210
2321
  message: "Generate / update feature DI provider (diProvider.ts)?",
2211
2322
  initialValue: true
2212
2323
  })
@@ -2218,8 +2329,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2218
2329
  entityNames = [defaultEntity];
2219
2330
  }
2220
2331
  }
2221
- const s = p4.spinner();
2222
- s.start(`Generating feature '${pc13.cyan(normalizedName)}'...`);
2332
+ const s = p15.spinner();
2333
+ s.start(`Generating feature '${pc15.cyan(normalizedName)}'...`);
2223
2334
  const options = {
2224
2335
  name: normalizedName,
2225
2336
  projectRoot,
@@ -2239,9 +2350,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2239
2350
  includeDiProvider
2240
2351
  };
2241
2352
  const files = generateFeature(options);
2353
+ if (isRegistry) {
2354
+ const registryPath = path2.join(targetDir, "registry.json");
2355
+ if (!fs12.existsSync(registryPath)) {
2356
+ const manifest = {
2357
+ name: toKebabCase(normalizedName),
2358
+ type: "feature",
2359
+ description: flags.description || `${normalizedName} feature module`,
2360
+ version: "1.0.0",
2361
+ dependencies: {
2362
+ npm: includeExpress ? ["@solid-stack/agnos-express", "express"] : [],
2363
+ shared: [],
2364
+ features: []
2365
+ },
2366
+ files: []
2367
+ };
2368
+ const content = JSON.stringify(manifest, null, 2) + "\n";
2369
+ const genFile = writeFileSafely(registryPath, content, {
2370
+ projectRoot,
2371
+ overwrite: options.overwrite,
2372
+ dryRun: options.dryRun
2373
+ });
2374
+ files.push(genFile);
2375
+ }
2376
+ }
2242
2377
  s.stop("Feature scaffolded successfully!");
2243
2378
  printGeneratedFiles(files, options.dryRun);
2244
- p4.outro(pc13.green(`Feature '${normalizedName}' ready to use!`));
2379
+ p15.outro(
2380
+ pc15.green(
2381
+ `\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2382
+ )
2383
+ );
2384
+ }
2385
+ function generateShared(options) {
2386
+ const files = [];
2387
+ const projectRoot = options.projectRoot || findProjectRoot();
2388
+ const rawName = options.name.trim();
2389
+ const pascalName = toPascalCase(rawName);
2390
+ const camelName = toCamelCase(rawName);
2391
+ const kebabName = toKebabCase(rawName);
2392
+ const targetDir = options.targetDir || path2.join(resolveSharedDir(projectRoot), kebabName);
2393
+ const fileDefs = [];
2394
+ const facadeContent = `import { MakeInjectable, type DepsType } from "@solid-stack/di";
2395
+ import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
2396
+
2397
+ @MakeInjectable
2398
+ export class ${pascalName} {
2399
+ public static deps = { ${camelName}Engine: I${pascalName}Engine };
2400
+ constructor(public readonly deps: DepsType<typeof ${pascalName}.deps>) {}
2401
+ }
2402
+ `;
2403
+ fileDefs.push({
2404
+ absolutePath: path2.join(targetDir, `${pascalName}.ts`),
2405
+ content: facadeContent
2406
+ });
2407
+ const portContent = `export abstract class I${pascalName}Engine {
2408
+ // Define abstract port contracts for ${pascalName} here
2409
+ }
2410
+ `;
2411
+ fileDefs.push({
2412
+ absolutePath: path2.join(targetDir, "ports", `I${pascalName}Engine.ts`),
2413
+ content: portContent
2414
+ });
2415
+ const concreteContent = `import { MakeInjectable } from "@solid-stack/di";
2416
+ import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
2417
+
2418
+ @MakeInjectable
2419
+ export class Default${pascalName}Engine implements I${pascalName}Engine {
2420
+ // Default concrete implementation for production
2421
+ }
2422
+ `;
2423
+ fileDefs.push({
2424
+ absolutePath: path2.join(targetDir, "infrastructure", `Default${pascalName}Engine.ts`),
2425
+ content: concreteContent
2426
+ });
2427
+ if (options.includeStub !== false) {
2428
+ const stubContent = `import { MakeInjectable } from "@solid-stack/di";
2429
+ import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
2430
+
2431
+ @MakeInjectable
2432
+ export class Stub${pascalName}Engine implements I${pascalName}Engine {
2433
+ // In-memory or stub implementation for testing
2434
+ }
2435
+ `;
2436
+ fileDefs.push({
2437
+ absolutePath: path2.join(targetDir, "infrastructure", `Stub${pascalName}Engine.ts`),
2438
+ content: stubContent
2439
+ });
2440
+ }
2441
+ const providerContent = `import type { Container } from "@solid-stack/di";
2442
+ import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
2443
+ import { Default${pascalName}Engine } from "./infrastructure/Default${pascalName}Engine.js";
2444
+
2445
+ export const diProvider = (c: Container): void => {
2446
+ c.provide(I${pascalName}Engine, Default${pascalName}Engine);
2447
+ };
2448
+
2449
+ export default diProvider;
2450
+ `;
2451
+ fileDefs.push({
2452
+ absolutePath: path2.join(targetDir, "diProvider.ts"),
2453
+ content: providerContent
2454
+ });
2455
+ if (options.isRegistry) {
2456
+ const registryManifest = {
2457
+ name: kebabName,
2458
+ type: "shared",
2459
+ description: options.description || `${kebabName} shared module`,
2460
+ version: "1.0.0",
2461
+ dependencies: {
2462
+ npm: [],
2463
+ shared: [],
2464
+ features: []
2465
+ },
2466
+ files: []
2467
+ };
2468
+ fileDefs.push({
2469
+ absolutePath: path2.join(targetDir, "registry.json"),
2470
+ content: JSON.stringify(registryManifest, null, 2) + "\n"
2471
+ });
2472
+ }
2473
+ for (const def of fileDefs) {
2474
+ const genFile = writeFileSafely(def.absolutePath, def.content, {
2475
+ projectRoot,
2476
+ overwrite: options.overwrite,
2477
+ dryRun: options.dryRun
2478
+ });
2479
+ files.push(genFile);
2480
+ }
2481
+ return files;
2482
+ }
2483
+
2484
+ // src/commands/shared.ts
2485
+ async function runSharedCommand(sharedNameArg, flags = {}) {
2486
+ const projectRoot = findProjectRoot();
2487
+ const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
2488
+ let name = sharedNameArg?.trim();
2489
+ if (!name && flags.yes) {
2490
+ name = "shared";
2491
+ }
2492
+ if (!name) {
2493
+ name = handleCancel(
2494
+ await p15.text({
2495
+ message: "What is the shared module name?",
2496
+ placeholder: "e.g. hasher, time, uuid, cache, logger",
2497
+ validate: (val) => {
2498
+ if (!val || !val.trim()) return "Shared module name cannot be empty.";
2499
+ return void 0;
2500
+ }
2501
+ })
2502
+ ).trim();
2503
+ }
2504
+ const normalizedName = toKebabCase(name);
2505
+ const baseSharedDir = resolveSharedDir(projectRoot, flags);
2506
+ const defaultDir = path2.relative(projectRoot, path2.join(baseSharedDir, normalizedName));
2507
+ let targetDirRel = defaultDir;
2508
+ if (!flags.yes && !flags.target && !flags.dest) {
2509
+ targetDirRel = handleCancel(
2510
+ await p15.text({
2511
+ message: "Target directory for the shared module:",
2512
+ initialValue: defaultDir,
2513
+ defaultValue: defaultDir
2514
+ })
2515
+ ).trim();
2516
+ }
2517
+ const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
2518
+ let description = flags.description || "";
2519
+ let includeStub = true;
2520
+ if (!flags.yes) {
2521
+ description = handleCancel(
2522
+ await p15.text({
2523
+ message: "Module description (optional):",
2524
+ placeholder: "e.g. In-memory caching and Redis port abstraction",
2525
+ initialValue: ""
2526
+ })
2527
+ ).trim();
2528
+ includeStub = handleCancel(
2529
+ await p15.confirm({
2530
+ message: "Include stub implementation in infrastructure/?",
2531
+ initialValue: true
2532
+ })
2533
+ );
2534
+ }
2535
+ const s = p15.spinner();
2536
+ s.start(`Generating shared module '${pc15.cyan(normalizedName)}'...`);
2537
+ const files = generateShared({
2538
+ name: normalizedName,
2539
+ description,
2540
+ projectRoot,
2541
+ targetDir,
2542
+ dryRun: Boolean(flags.dryRun),
2543
+ overwrite: Boolean(flags.overwrite),
2544
+ includeStub,
2545
+ isRegistry
2546
+ });
2547
+ s.stop("Shared module generated successfully!");
2548
+ printGeneratedFiles(files, Boolean(flags.dryRun));
2549
+ p15.outro(
2550
+ pc15.green(
2551
+ `\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2552
+ )
2553
+ );
2245
2554
  }
2246
2555
  async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2247
2556
  const projectRoot = findProjectRoot();
@@ -2252,14 +2561,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2252
2561
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2253
2562
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2254
2563
  const selected = handleCancel(
2255
- await p4.select({
2564
+ await p15.select({
2256
2565
  message: "Select target feature:",
2257
2566
  options: featureOptions
2258
2567
  })
2259
2568
  );
2260
2569
  if (selected === "__custom__") {
2261
2570
  feature = handleCancel(
2262
- await p4.text({
2571
+ await p15.text({
2263
2572
  message: "Enter feature name:",
2264
2573
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2265
2574
  })
@@ -2269,7 +2578,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2269
2578
  }
2270
2579
  } else {
2271
2580
  feature = handleCancel(
2272
- await p4.text({
2581
+ await p15.text({
2273
2582
  message: "Enter target feature name:",
2274
2583
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2275
2584
  })
@@ -2280,7 +2589,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2280
2589
  let name = useCaseNameArg?.trim();
2281
2590
  if (!name) {
2282
2591
  name = handleCancel(
2283
- await p4.text({
2592
+ await p15.text({
2284
2593
  message: "Use Case name:",
2285
2594
  placeholder: "e.g. CreateUser, GetUser, CancelOrder",
2286
2595
  validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
@@ -2296,7 +2605,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2296
2605
  label: i.name
2297
2606
  }));
2298
2607
  selectedDeps = handleCancel(
2299
- await p4.multiselect({
2608
+ await p15.multiselect({
2300
2609
  message: "Select domain interfaces to inject into use case:",
2301
2610
  options: multiOptions,
2302
2611
  required: false
@@ -2311,7 +2620,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2311
2620
  let httpPath = "/";
2312
2621
  if (!flags.yes) {
2313
2622
  const hasInput = handleCancel(
2314
- await p4.confirm({
2623
+ await p15.confirm({
2315
2624
  message: "Does this use case accept input arguments?",
2316
2625
  initialValue: true
2317
2626
  })
@@ -2321,7 +2630,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2321
2630
  });
2322
2631
  }
2323
2632
  const hasCustomOutput = handleCancel(
2324
- await p4.confirm({
2633
+ await p15.confirm({
2325
2634
  message: "Does this use case return custom output data?",
2326
2635
  initialValue: false
2327
2636
  })
@@ -2331,20 +2640,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2331
2640
  });
2332
2641
  }
2333
2642
  includeTest = handleCancel(
2334
- await p4.confirm({
2643
+ await p15.confirm({
2335
2644
  message: "Generate Vitest unit test skeleton (.test.ts)?",
2336
2645
  initialValue: true
2337
2646
  })
2338
2647
  );
2339
2648
  createHttp = handleCancel(
2340
- await p4.confirm({
2649
+ await p15.confirm({
2341
2650
  message: "Generate a matching ExpressRoute HTTP handler?",
2342
2651
  initialValue: false
2343
2652
  })
2344
2653
  );
2345
2654
  if (createHttp) {
2346
2655
  httpMethod = handleCancel(
2347
- await p4.select({
2656
+ await p15.select({
2348
2657
  message: "HTTP method for route:",
2349
2658
  options: [
2350
2659
  { value: "post", label: "POST" },
@@ -2357,7 +2666,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2357
2666
  })
2358
2667
  );
2359
2668
  httpPath = handleCancel(
2360
- await p4.text({
2669
+ await p15.text({
2361
2670
  message: "Route subpath (relative to feature prefix):",
2362
2671
  initialValue: "/",
2363
2672
  defaultValue: "/"
@@ -2365,8 +2674,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2365
2674
  ).trim();
2366
2675
  }
2367
2676
  }
2368
- const s = p4.spinner();
2369
- s.start(`Generating use case '${pc13.cyan(ucName)}'...`);
2677
+ const s = p15.spinner();
2678
+ s.start(`Generating use case '${pc15.cyan(ucName)}'...`);
2370
2679
  const options = {
2371
2680
  name: ucName,
2372
2681
  feature,
@@ -2398,7 +2707,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2398
2707
  }
2399
2708
  s.stop("Use case generated successfully!");
2400
2709
  printGeneratedFiles(files, options.dryRun);
2401
- p4.outro(pc13.green(`Use case '${ucName}' created in feature '${feature}'!`));
2710
+ p15.outro(pc15.green(`Use case '${ucName}' created in feature '${feature}'!`));
2402
2711
  }
2403
2712
  async function runDomainCommand(domainNameArg, flags = {}) {
2404
2713
  const projectRoot = findProjectRoot();
@@ -2409,14 +2718,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2409
2718
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2410
2719
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2411
2720
  const selected = handleCancel(
2412
- await p4.select({
2721
+ await p15.select({
2413
2722
  message: "Select target feature:",
2414
2723
  options: featureOptions
2415
2724
  })
2416
2725
  );
2417
2726
  if (selected === "__custom__") {
2418
2727
  feature = handleCancel(
2419
- await p4.text({
2728
+ await p15.text({
2420
2729
  message: "Enter feature name:",
2421
2730
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2422
2731
  })
@@ -2426,7 +2735,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2426
2735
  }
2427
2736
  } else {
2428
2737
  feature = handleCancel(
2429
- await p4.text({
2738
+ await p15.text({
2430
2739
  message: "Enter target feature name:",
2431
2740
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2432
2741
  })
@@ -2437,7 +2746,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2437
2746
  let name = domainNameArg?.trim();
2438
2747
  if (!name) {
2439
2748
  name = handleCancel(
2440
- await p4.text({
2749
+ await p15.text({
2441
2750
  message: "Domain model / entity name:",
2442
2751
  placeholder: "e.g. User, Order, Product, Address",
2443
2752
  validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
@@ -2449,7 +2758,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2449
2758
  let properties = [];
2450
2759
  if (!flags.yes) {
2451
2760
  kind = handleCancel(
2452
- await p4.select({
2761
+ await p15.select({
2453
2762
  message: "Select domain model type:",
2454
2763
  options: [
2455
2764
  { value: "interface", label: "TypeScript Interface (pure data structure)" },
@@ -2468,8 +2777,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2468
2777
  defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
2469
2778
  });
2470
2779
  }
2471
- const s = p4.spinner();
2472
- s.start(`Generating domain model '${pc13.cyan(entityName)}'...`);
2780
+ const s = p15.spinner();
2781
+ s.start(`Generating domain model '${pc15.cyan(entityName)}'...`);
2473
2782
  const options = {
2474
2783
  name: entityName,
2475
2784
  feature,
@@ -2482,7 +2791,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2482
2791
  const file = generateDomain(options);
2483
2792
  s.stop("Domain model generated successfully!");
2484
2793
  printGeneratedFiles([file], options.dryRun);
2485
- p4.outro(pc13.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2794
+ p15.outro(pc15.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2486
2795
  }
2487
2796
  async function runInfrastructureCommand(classNameArg, flags = {}) {
2488
2797
  const projectRoot = findProjectRoot();
@@ -2491,7 +2800,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2491
2800
  let feature = flags.feature?.trim();
2492
2801
  if (!flags.yes && !flags.global && !flags.feature) {
2493
2802
  scope = handleCancel(
2494
- await p4.select({
2803
+ await p15.select({
2495
2804
  message: "Where should this infrastructure implementation be placed?",
2496
2805
  options: [
2497
2806
  { value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
@@ -2506,14 +2815,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2506
2815
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2507
2816
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2508
2817
  const selected = handleCancel(
2509
- await p4.select({
2818
+ await p15.select({
2510
2819
  message: "Select target feature:",
2511
2820
  options: featureOptions
2512
2821
  })
2513
2822
  );
2514
2823
  if (selected === "__custom__") {
2515
2824
  feature = handleCancel(
2516
- await p4.text({
2825
+ await p15.text({
2517
2826
  message: "Enter feature name:",
2518
2827
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2519
2828
  })
@@ -2523,7 +2832,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2523
2832
  }
2524
2833
  } else {
2525
2834
  feature = handleCancel(
2526
- await p4.text({
2835
+ await p15.text({
2527
2836
  message: "Enter target feature name:",
2528
2837
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2529
2838
  })
@@ -2536,7 +2845,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2536
2845
  let name = classNameArg?.trim();
2537
2846
  if (!name) {
2538
2847
  name = handleCancel(
2539
- await p4.text({
2848
+ await p15.text({
2540
2849
  message: "Infrastructure class name:",
2541
2850
  placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
2542
2851
  validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
@@ -2555,7 +2864,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2555
2864
  (i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
2556
2865
  );
2557
2866
  const ifaceChoice = handleCancel(
2558
- await p4.select({
2867
+ await p15.select({
2559
2868
  message: "Does this class implement a domain interface or abstract class?",
2560
2869
  options: [
2561
2870
  ...interfaces.map((i) => ({ value: i.name, label: i.name })),
@@ -2567,7 +2876,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2567
2876
  );
2568
2877
  if (ifaceChoice === "__custom__") {
2569
2878
  implementsInterface = handleCancel(
2570
- await p4.text({
2879
+ await p15.text({
2571
2880
  message: "Enter interface or abstract class name:",
2572
2881
  placeholder: "e.g. IUserRepository, BaseStorageAdapter",
2573
2882
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2578,14 +2887,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2578
2887
  }
2579
2888
  } else {
2580
2889
  const wantImpl = handleCancel(
2581
- await p4.confirm({
2890
+ await p15.confirm({
2582
2891
  message: "Does this class implement a domain interface or abstract class?",
2583
2892
  initialValue: false
2584
2893
  })
2585
2894
  );
2586
2895
  if (wantImpl) {
2587
2896
  implementsInterface = handleCancel(
2588
- await p4.text({
2897
+ await p15.text({
2589
2898
  message: "Enter interface or abstract class name:",
2590
2899
  placeholder: "e.g. IUserRepository, BaseStorageAdapter",
2591
2900
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2605,7 +2914,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2605
2914
  const allInterfaces = listDomainInterfaces(projectRoot);
2606
2915
  if (allInterfaces.length > 0) {
2607
2916
  const ifaceChoice = handleCancel(
2608
- await p4.select({
2917
+ await p15.select({
2609
2918
  message: "Does this class implement an interface or abstract class?",
2610
2919
  options: [
2611
2920
  ...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
@@ -2617,7 +2926,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2617
2926
  );
2618
2927
  if (ifaceChoice === "__custom__") {
2619
2928
  implementsInterface = handleCancel(
2620
- await p4.text({
2929
+ await p15.text({
2621
2930
  message: "Enter interface or abstract class name:",
2622
2931
  placeholder: "e.g. IAuthRepository",
2623
2932
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2630,7 +2939,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2630
2939
  }
2631
2940
  if (!flags.yes && !flags.kind) {
2632
2941
  kind = handleCancel(
2633
- await p4.select({
2942
+ await p15.select({
2634
2943
  message: "Select implementation kind:",
2635
2944
  options: [
2636
2945
  { value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
@@ -2641,7 +2950,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2641
2950
  );
2642
2951
  if (scope === "feature" && feature && implementsInterface) {
2643
2952
  registerInProvider = handleCancel(
2644
- await p4.confirm({
2953
+ await p15.confirm({
2645
2954
  message: "Register this implementation in feature diProvider.ts?",
2646
2955
  initialValue: true
2647
2956
  })
@@ -2650,8 +2959,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2650
2959
  } else if (flags.yes && scope === "feature" && feature && implementsInterface) {
2651
2960
  registerInProvider = !flags.noProvider;
2652
2961
  }
2653
- const s = p4.spinner();
2654
- s.start(`Generating infrastructure class '${pc13.cyan(className)}'...`);
2962
+ const s = p15.spinner();
2963
+ s.start(`Generating infrastructure class '${pc15.cyan(className)}'...`);
2655
2964
  const files = [];
2656
2965
  const options = {
2657
2966
  name: className,
@@ -2666,7 +2975,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2666
2975
  const infraFile = generateInfrastructure(options);
2667
2976
  files.push(infraFile);
2668
2977
  if (registerInProvider && scope === "feature" && feature && implementsInterface) {
2669
- const featureDir = path3.join(resolveFeaturesDir(projectRoot), feature);
2978
+ const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
2670
2979
  const providerFile = updateOrGenerateDiProvider(
2671
2980
  featureDir,
2672
2981
  feature,
@@ -2686,7 +2995,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2686
2995
  }
2687
2996
  s.stop("Infrastructure class generated successfully!");
2688
2997
  printGeneratedFiles(files, options.dryRun);
2689
- p4.outro(pc13.green(`Infrastructure class '${className}' created!`));
2998
+ p15.outro(pc15.green(`Infrastructure class '${className}' created!`));
2690
2999
  }
2691
3000
  async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2692
3001
  const projectRoot = findProjectRoot();
@@ -2697,14 +3006,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2697
3006
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2698
3007
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2699
3008
  const selected = handleCancel(
2700
- await p4.select({
3009
+ await p15.select({
2701
3010
  message: "Select target feature:",
2702
3011
  options: featureOptions
2703
3012
  })
2704
3013
  );
2705
3014
  if (selected === "__custom__") {
2706
3015
  feature = handleCancel(
2707
- await p4.text({
3016
+ await p15.text({
2708
3017
  message: "Enter feature name:",
2709
3018
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2710
3019
  })
@@ -2714,7 +3023,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2714
3023
  }
2715
3024
  } else {
2716
3025
  feature = handleCancel(
2717
- await p4.text({
3026
+ await p15.text({
2718
3027
  message: "Enter target feature name:",
2719
3028
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2720
3029
  })
@@ -2725,7 +3034,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2725
3034
  let name = interfaceNameArg?.trim();
2726
3035
  if (!name) {
2727
3036
  name = handleCancel(
2728
- await p4.text({
3037
+ await p15.text({
2729
3038
  message: "Domain interface name:",
2730
3039
  placeholder: "e.g. IUserRepository, IPaymentService",
2731
3040
  validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
@@ -2743,7 +3052,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2743
3052
  if (!flags.yes) {
2744
3053
  if (existingEntities.length > 0) {
2745
3054
  const selectedEntity = handleCancel(
2746
- await p4.select({
3055
+ await p15.select({
2747
3056
  message: "Select domain entity associated with this interface:",
2748
3057
  options: [
2749
3058
  ...existingEntities.map((e) => ({ value: e.name, label: e.name })),
@@ -2754,7 +3063,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2754
3063
  );
2755
3064
  if (selectedEntity === "__custom__") {
2756
3065
  entityName = handleCancel(
2757
- await p4.text({
3066
+ await p15.text({
2758
3067
  message: "Enter entity name:",
2759
3068
  defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
2760
3069
  })
@@ -2764,7 +3073,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2764
3073
  }
2765
3074
  }
2766
3075
  kind = handleCancel(
2767
- await p4.select({
3076
+ await p15.select({
2768
3077
  message: "Interface definition style:",
2769
3078
  options: [
2770
3079
  { value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
@@ -2774,7 +3083,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2774
3083
  })
2775
3084
  );
2776
3085
  methodsPreset = handleCancel(
2777
- await p4.select({
3086
+ await p15.select({
2778
3087
  message: "Method signature preset:",
2779
3088
  options: [
2780
3089
  { value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
@@ -2784,22 +3093,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2784
3093
  })
2785
3094
  );
2786
3095
  generateStub = handleCancel(
2787
- await p4.confirm({
3096
+ await p15.confirm({
2788
3097
  message: "Auto-generate Infrastructure Stub implementation?",
2789
3098
  initialValue: true
2790
3099
  })
2791
3100
  );
2792
3101
  if (generateStub) {
2793
3102
  registerInProvider = handleCancel(
2794
- await p4.confirm({
3103
+ await p15.confirm({
2795
3104
  message: "Auto-register interface & stub in feature diProvider.ts?",
2796
3105
  initialValue: true
2797
3106
  })
2798
3107
  );
2799
3108
  }
2800
3109
  }
2801
- const s = p4.spinner();
2802
- s.start(`Generating interface '${pc13.cyan(ifaceName)}'...`);
3110
+ const s = p15.spinner();
3111
+ s.start(`Generating interface '${pc15.cyan(ifaceName)}'...`);
2803
3112
  const files = [];
2804
3113
  const options = {
2805
3114
  name: ifaceName,
@@ -2828,7 +3137,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2828
3137
  files.push(stubFile);
2829
3138
  }
2830
3139
  if (registerInProvider && generateStub) {
2831
- const featureDir = path3.join(resolveFeaturesDir(projectRoot), feature);
3140
+ const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
2832
3141
  const providerFile = updateOrGenerateDiProvider(
2833
3142
  featureDir,
2834
3143
  feature,
@@ -2848,7 +3157,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2848
3157
  }
2849
3158
  s.stop("Interface generated successfully!");
2850
3159
  printGeneratedFiles(files, options.dryRun);
2851
- p4.outro(pc13.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
3160
+ p15.outro(pc15.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
2852
3161
  }
2853
3162
  async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2854
3163
  const projectRoot = findProjectRoot();
@@ -2859,14 +3168,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2859
3168
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2860
3169
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2861
3170
  const selected = handleCancel(
2862
- await p4.select({
3171
+ await p15.select({
2863
3172
  message: "Select target feature:",
2864
3173
  options: featureOptions
2865
3174
  })
2866
3175
  );
2867
3176
  if (selected === "__custom__") {
2868
3177
  feature = handleCancel(
2869
- await p4.text({
3178
+ await p15.text({
2870
3179
  message: "Enter feature name:",
2871
3180
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2872
3181
  })
@@ -2876,7 +3185,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2876
3185
  }
2877
3186
  } else {
2878
3187
  feature = handleCancel(
2879
- await p4.text({
3188
+ await p15.text({
2880
3189
  message: "Enter target feature name:",
2881
3190
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2882
3191
  })
@@ -2887,7 +3196,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2887
3196
  let name = handlerNameArg?.trim();
2888
3197
  if (!name) {
2889
3198
  name = handleCancel(
2890
- await p4.text({
3199
+ await p15.text({
2891
3200
  message: "HTTP Route Handler class name:",
2892
3201
  placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
2893
3202
  validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
@@ -2906,7 +3215,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2906
3215
  let validationFields = [];
2907
3216
  if (!flags.yes) {
2908
3217
  method = handleCancel(
2909
- await p4.select({
3218
+ await p15.select({
2910
3219
  message: "HTTP Method:",
2911
3220
  options: [
2912
3221
  { value: "get", label: "GET" },
@@ -2920,7 +3229,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2920
3229
  );
2921
3230
  const defaultPath = method === "get" ? "/" : "/";
2922
3231
  subPath = handleCancel(
2923
- await p4.text({
3232
+ await p15.text({
2924
3233
  message: "Route subpath (relative to feature prefix):",
2925
3234
  initialValue: defaultPath,
2926
3235
  defaultValue: defaultPath
@@ -2931,7 +3240,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2931
3240
  const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
2932
3241
  ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
2933
3242
  const chosenUc = handleCancel(
2934
- await p4.select({
3243
+ await p15.select({
2935
3244
  message: "Connect this handler to a domain Use Case?",
2936
3245
  options: ucOptions,
2937
3246
  initialValue: ucOptions[0]?.value
@@ -2942,7 +3251,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2942
3251
  }
2943
3252
  }
2944
3253
  includeValidation = handleCancel(
2945
- await p4.confirm({
3254
+ await p15.confirm({
2946
3255
  message: "Include Zod inbound request validation?",
2947
3256
  initialValue: method !== "delete"
2948
3257
  })
@@ -2950,7 +3259,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2950
3259
  if (includeValidation) {
2951
3260
  const defaultSource = method === "get" ? "query" : "body";
2952
3261
  validationSource = handleCancel(
2953
- await p4.select({
3262
+ await p15.select({
2954
3263
  message: "Validation data source:",
2955
3264
  options: [
2956
3265
  { value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
@@ -2964,8 +3273,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2964
3273
  });
2965
3274
  }
2966
3275
  }
2967
- const s = p4.spinner();
2968
- s.start(`Generating HTTP handler '${pc13.cyan(className)}'...`);
3276
+ const s = p15.spinner();
3277
+ s.start(`Generating HTTP handler '${pc15.cyan(className)}'...`);
2969
3278
  const options = {
2970
3279
  name: className,
2971
3280
  feature,
@@ -2983,7 +3292,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2983
3292
  const files = generateHttpHandler(options);
2984
3293
  s.stop("HTTP handler generated successfully!");
2985
3294
  printGeneratedFiles(files, options.dryRun);
2986
- p4.outro(pc13.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3295
+ p15.outro(pc15.green(`HTTP handler '${className}' created in feature '${feature}'!`));
2987
3296
  }
2988
3297
  function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
2989
3298
  const formattedDeps = dependencies.map((dep) => ({
@@ -2993,7 +3302,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
2993
3302
  const formattedMethods = methods.map((m) => {
2994
3303
  const isAsync = m.async !== false;
2995
3304
  const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
2996
- const params = m.params && m.params.length > 0 ? m.params.map((p15) => `${p15.name}: ${p15.type}`).join(", ") : "";
3305
+ const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join(", ") : "";
2997
3306
  return {
2998
3307
  name: m.name,
2999
3308
  isAsync,
@@ -3016,21 +3325,21 @@ function generateInjectable(options) {
3016
3325
  const className = toPascalCase(options.name);
3017
3326
  let targetDir = "";
3018
3327
  if (options.customPath) {
3019
- targetDir = path3.isAbsolute(options.customPath) ? options.customPath : path3.join(projectRoot, options.customPath);
3328
+ targetDir = path2.isAbsolute(options.customPath) ? options.customPath : path2.join(projectRoot, options.customPath);
3020
3329
  } else if (options.location === "feature-service" && options.feature) {
3021
- targetDir = path3.join(resolveFeaturesDir(projectRoot), options.feature, "services");
3330
+ targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "services");
3022
3331
  } else if (options.location === "feature-transformer" && options.feature) {
3023
- targetDir = path3.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
3332
+ targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
3024
3333
  } else if (options.location === "module") {
3025
- targetDir = path3.join(projectRoot, "modules", className);
3334
+ targetDir = path2.join(projectRoot, "modules", className);
3026
3335
  } else {
3027
- targetDir = options.feature ? path3.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path3.join(projectRoot, "shared", className.toLowerCase());
3336
+ targetDir = options.feature ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2.join(projectRoot, "shared", className.toLowerCase());
3028
3337
  }
3029
- const filePath = path3.join(targetDir, `${className}.ts`);
3338
+ const filePath = path2.join(targetDir, `${className}.ts`);
3030
3339
  let ifaceName;
3031
3340
  if (options.generateInterface) {
3032
3341
  ifaceName = `I${className}`;
3033
- const ifacePath = path3.join(targetDir, `${ifaceName}.ts`);
3342
+ const ifacePath = path2.join(targetDir, `${ifaceName}.ts`);
3034
3343
  const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
3035
3344
  files.push(
3036
3345
  writeFileSafely(ifacePath, ifaceContent, {
@@ -3063,7 +3372,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3063
3372
  let name = classNameArg?.trim();
3064
3373
  if (!name) {
3065
3374
  name = handleCancel(
3066
- await p4.text({
3375
+ await p15.text({
3067
3376
  message: "Injectable class name:",
3068
3377
  placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
3069
3378
  validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
@@ -3076,7 +3385,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3076
3385
  let customPath;
3077
3386
  if (!flags.yes) {
3078
3387
  location = handleCancel(
3079
- await p4.select({
3388
+ await p15.select({
3080
3389
  message: "Select target architectural location:",
3081
3390
  options: [
3082
3391
  { value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
@@ -3094,14 +3403,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3094
3403
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
3095
3404
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
3096
3405
  const selected = handleCancel(
3097
- await p4.select({
3406
+ await p15.select({
3098
3407
  message: "Select target feature:",
3099
3408
  options: featureOptions
3100
3409
  })
3101
3410
  );
3102
3411
  if (selected === "__custom__") {
3103
3412
  feature = handleCancel(
3104
- await p4.text({
3413
+ await p15.text({
3105
3414
  message: "Enter feature name:",
3106
3415
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
3107
3416
  })
@@ -3111,7 +3420,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3111
3420
  }
3112
3421
  } else {
3113
3422
  feature = handleCancel(
3114
- await p4.text({
3423
+ await p15.text({
3115
3424
  message: "Enter target feature name:",
3116
3425
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
3117
3426
  })
@@ -3121,7 +3430,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3121
3430
  feature = toCamelCase(feature);
3122
3431
  } else if (location === "custom") {
3123
3432
  customPath = handleCancel(
3124
- await p4.text({
3433
+ await p15.text({
3125
3434
  message: "Enter destination directory path:",
3126
3435
  placeholder: "e.g. src/services or lib/utils",
3127
3436
  validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
@@ -3132,14 +3441,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3132
3441
  let generateInterface2 = false;
3133
3442
  if (!flags.yes) {
3134
3443
  generateInterface2 = handleCancel(
3135
- await p4.confirm({
3444
+ await p15.confirm({
3136
3445
  message: `Generate abstract class interface (I${className}) as runtime DI token?`,
3137
3446
  initialValue: false
3138
3447
  })
3139
3448
  );
3140
3449
  }
3141
- const s = p4.spinner();
3142
- s.start(`Generating injectable '${pc13.cyan(className)}'...`);
3450
+ const s = p15.spinner();
3451
+ s.start(`Generating injectable '${pc15.cyan(className)}'...`);
3143
3452
  const options = {
3144
3453
  name: className,
3145
3454
  feature,
@@ -3153,12 +3462,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3153
3462
  const files = generateInjectable(options);
3154
3463
  s.stop("Injectable class generated successfully!");
3155
3464
  printGeneratedFiles(files, options.dryRun);
3156
- p4.outro(pc13.green(`Injectable '${className}' created successfully!`));
3465
+ p15.outro(pc15.green(`Injectable '${className}' created successfully!`));
3157
3466
  }
3158
3467
  async function runInteractiveMenu(flags = {}) {
3159
3468
  printBanner();
3160
3469
  const choice = handleCancel(
3161
- await p4.select({
3470
+ await p15.select({
3162
3471
  message: "What would you like to create in your architecture?",
3163
3472
  options: [
3164
3473
  {
@@ -3166,6 +3475,11 @@ async function runInteractiveMenu(flags = {}) {
3166
3475
  label: "Feature",
3167
3476
  hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
3168
3477
  },
3478
+ {
3479
+ value: "shared",
3480
+ label: "Shared Module",
3481
+ hint: "Foundational utility or port with ports, infrastructure & diProvider"
3482
+ },
3169
3483
  {
3170
3484
  value: "usecase",
3171
3485
  label: "Use Case",
@@ -3203,6 +3517,9 @@ async function runInteractiveMenu(flags = {}) {
3203
3517
  case "feature":
3204
3518
  await runFeatureCommand(void 0, flags);
3205
3519
  break;
3520
+ case "shared":
3521
+ await runSharedCommand(void 0, flags);
3522
+ break;
3206
3523
  case "usecase":
3207
3524
  await runUseCaseCommand(void 0, flags);
3208
3525
  break;
@@ -3235,6 +3552,9 @@ async function handleCreate(type, name, flags = {}) {
3235
3552
  case "feature":
3236
3553
  await runFeatureCommand(name, flags);
3237
3554
  break;
3555
+ case "shared":
3556
+ await runSharedCommand(name, flags);
3557
+ break;
3238
3558
  case "usecase":
3239
3559
  await runUseCaseCommand(name, flags);
3240
3560
  break;
@@ -3255,21 +3575,172 @@ async function handleCreate(type, name, flags = {}) {
3255
3575
  await runInjectableCommand(name, flags);
3256
3576
  break;
3257
3577
  default:
3258
- p4.log.error(
3259
- pc13.red(
3260
- `Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
3578
+ p15.log.error(
3579
+ pc15.red(
3580
+ `Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
3261
3581
  )
3262
3582
  );
3263
3583
  process.exit(1);
3264
3584
  }
3265
3585
  }
3586
+ var importPrompts = {
3587
+ select: p15.select,
3588
+ text: p15.text
3589
+ };
3590
+ function rewriteImports(content, aliasMap) {
3591
+ if (aliasMap.size === 0) return content;
3592
+ let rewritten = content;
3593
+ for (const [oldImport, newImport] of aliasMap.entries()) {
3594
+ const rawOld = oldImport.endsWith("/") ? oldImport.slice(0, -1) : oldImport;
3595
+ const rawNew = newImport.endsWith("/") ? newImport.slice(0, -1) : newImport;
3596
+ const escaped = rawOld.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3597
+ const regex = new RegExp(`${escaped}(?=[\\/'"\`]|$)`, "g");
3598
+ rewritten = rewritten.replace(regex, rawNew);
3599
+ }
3600
+ return rewritten;
3601
+ }
3602
+ function getModuleBaseDir(projectRoot, modType) {
3603
+ const folder = modType === "shared" ? "shared" : "features";
3604
+ return path2.join(projectRoot, "src", folder);
3605
+ }
3606
+ function getExistingModuleDir(projectRoot, modType, modName) {
3607
+ const folder = modType === "shared" ? "shared" : "features";
3608
+ const inSrc = path2.join(projectRoot, "src", folder, modName);
3609
+ if (fs12.existsSync(inSrc)) return inSrc;
3610
+ const inRoot = path2.join(projectRoot, folder, modName);
3611
+ if (fs12.existsSync(inRoot)) return inRoot;
3612
+ return null;
3613
+ }
3614
+ function checkModuleExists(projectRoot, modType, modName) {
3615
+ return getExistingModuleDir(projectRoot, modType, modName) !== null;
3616
+ }
3617
+ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags, importModuleRecursive, importedModules, registryBase, index) {
3618
+ const folder = depType === "shared" ? "shared" : "features";
3619
+ const isAlreadyImportedThisRun = importedModules.has(`${depType}:${depName}`);
3620
+ if (isAlreadyImportedThisRun) return;
3621
+ const programmaticAlias = flags.alias?.[depName] || flags.alias?.[`${depType}/${depName}`];
3622
+ if (programmaticAlias) {
3623
+ aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${programmaticAlias}/`);
3624
+ const baseDir = getModuleBaseDir(projectRoot, depType);
3625
+ const displayDest = path2.relative(
3626
+ projectRoot,
3627
+ path2.join(baseDir, programmaticAlias)
3628
+ );
3629
+ p15.log.step(`Importing '${depName}' into '${displayDest}'...`);
3630
+ await importModuleRecursive(depType, depName, programmaticAlias);
3631
+ return;
3632
+ }
3633
+ const existingDir = getExistingModuleDir(projectRoot, depType, depName);
3634
+ if (!existingDir) {
3635
+ p15.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc15.cyan(depName)}`);
3636
+ await importModuleRecursive(depType, depName, depName);
3637
+ return;
3638
+ }
3639
+ const entry = (depType === "shared" ? index?.shared : index?.features)?.find(
3640
+ (item) => item.name === depName
3641
+ );
3642
+ let remoteIntegrity = entry?.integrity;
3643
+ if (!remoteIntegrity) {
3644
+ try {
3645
+ const depManifest = await fetchModuleManifest(registryBase, depType, depName);
3646
+ remoteIntegrity = depManifest.integrity;
3647
+ } catch {
3648
+ }
3649
+ }
3650
+ const localHash = computeDirectoryHash(existingDir);
3651
+ if (remoteIntegrity && localHash === remoteIntegrity) {
3652
+ p15.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
3653
+ importedModules.add(`${depType}:${depName}`);
3654
+ return;
3655
+ }
3656
+ if (flags.overwrite) {
3657
+ p15.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
3658
+ const baseDir = getModuleBaseDir(projectRoot, depType);
3659
+ const targetDir = path2.join(baseDir, depName);
3660
+ if (!flags.dryRun) {
3661
+ if (existingDir !== targetDir) {
3662
+ fs12.rmSync(existingDir, { recursive: true, force: true });
3663
+ }
3664
+ fs12.rmSync(targetDir, { recursive: true, force: true });
3665
+ }
3666
+ await importModuleRecursive(depType, depName, depName, { overwrite: true });
3667
+ return;
3668
+ }
3669
+ if (flags.yes) {
3670
+ p15.log.info(`Using existing '${depType}/${depName}'`);
3671
+ importedModules.add(`${depType}:${depName}`);
3672
+ return;
3673
+ }
3674
+ const choice = handleCancel(
3675
+ await importPrompts.select({
3676
+ message: `Local '${depType}/${depName}' differs from the registry version.`,
3677
+ options: [
3678
+ {
3679
+ value: "keep_existing",
3680
+ label: `Keep existing local version (Recommended)`
3681
+ },
3682
+ {
3683
+ value: "rename",
3684
+ label: "Import fresh copy under a new name (alias)"
3685
+ },
3686
+ {
3687
+ value: "overwrite",
3688
+ label: "Overwrite local version with registry version"
3689
+ }
3690
+ ]
3691
+ })
3692
+ );
3693
+ if (choice === "keep_existing" || choice === "use_existing") {
3694
+ p15.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
3695
+ importedModules.add(`${depType}:${depName}`);
3696
+ return;
3697
+ }
3698
+ if (choice === "rename") {
3699
+ const newName = handleCancel(
3700
+ await importPrompts.text({
3701
+ message: "What should we name the new copy?",
3702
+ placeholder: `${depName}-copy`,
3703
+ validate(val) {
3704
+ const v = val?.trim();
3705
+ if (!v) return "Name cannot be empty";
3706
+ if (v === depName) return `Name must be different from '${depName}'`;
3707
+ if (!/^[a-zA-Z0-9_-]+$/.test(v)) {
3708
+ return "Name can only contain alphanumeric characters, dashes, and underscores";
3709
+ }
3710
+ if (checkModuleExists(projectRoot, depType, v)) {
3711
+ return `'${v}' already exists in ${folder}, please choose another name`;
3712
+ }
3713
+ }
3714
+ })
3715
+ ).trim();
3716
+ aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
3717
+ const baseDir = getModuleBaseDir(projectRoot, depType);
3718
+ const displayDest = path2.relative(projectRoot, path2.join(baseDir, newName));
3719
+ p15.log.step(`Importing '${depName}' into '${displayDest}'...`);
3720
+ await importModuleRecursive(depType, depName, newName);
3721
+ return;
3722
+ }
3723
+ if (choice === "overwrite") {
3724
+ p15.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
3725
+ const baseDir = getModuleBaseDir(projectRoot, depType);
3726
+ const targetDir = path2.join(baseDir, depName);
3727
+ if (!flags.dryRun) {
3728
+ if (existingDir !== targetDir) {
3729
+ fs12.rmSync(existingDir, { recursive: true, force: true });
3730
+ }
3731
+ fs12.rmSync(targetDir, { recursive: true, force: true });
3732
+ }
3733
+ await importModuleRecursive(depType, depName, depName, { overwrite: true });
3734
+ return;
3735
+ }
3736
+ }
3266
3737
  async function handleImport(typeArg, nameArg, flags = {}) {
3267
3738
  const projectRoot = findProjectRoot();
3268
3739
  let type = typeArg?.trim().toLowerCase();
3269
3740
  if (type === "features") type = "feature";
3270
3741
  if (!type) {
3271
3742
  type = handleCancel(
3272
- await p4.select({
3743
+ await importPrompts.select({
3273
3744
  message: "What kind of module would you like to import?",
3274
3745
  options: [
3275
3746
  {
@@ -3287,19 +3758,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3287
3758
  );
3288
3759
  }
3289
3760
  if (type !== "shared" && type !== "feature") {
3290
- p4.log.error(pc13.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3761
+ p15.log.error(pc15.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3291
3762
  process.exit(1);
3292
3763
  }
3293
3764
  const registryBase = getRegistryBase(projectRoot, flags.registry);
3294
- const s = p4.spinner();
3295
- s.start(`Connecting to registry at ${pc13.dim(registryBase)}...`);
3765
+ const s = p15.spinner();
3766
+ s.start(`Connecting to registry at ${pc15.dim(registryBase)}...`);
3296
3767
  let index;
3297
3768
  try {
3298
3769
  index = await fetchRegistryIndex(registryBase);
3299
3770
  s.stop("Registry index loaded.");
3300
3771
  } catch (err) {
3301
- s.stop(pc13.red("Failed to load registry index."));
3302
- p4.log.error(pc13.red(err.message || String(err)));
3772
+ s.stop(pc15.red("Failed to load registry index."));
3773
+ p15.log.error(pc15.red(err.message || String(err)));
3303
3774
  process.exit(1);
3304
3775
  }
3305
3776
  const availableList = type === "shared" ? index.shared : index.features;
@@ -3307,11 +3778,11 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3307
3778
  let name = nameArg?.trim();
3308
3779
  if (!name) {
3309
3780
  if (availableList.length === 0) {
3310
- p4.log.warn(`No ${type} modules available in the registry.`);
3781
+ p15.log.warn(`No ${type} modules available in the registry.`);
3311
3782
  return;
3312
3783
  }
3313
3784
  name = handleCancel(
3314
- await p4.select({
3785
+ await importPrompts.select({
3315
3786
  message: `Select a ${type} to import:`,
3316
3787
  options: availableList.map((item) => ({
3317
3788
  value: item.name,
@@ -3323,122 +3794,769 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3323
3794
  }
3324
3795
  if (!candidateNames.includes(name)) {
3325
3796
  const closest = findClosestMatch(name, candidateNames);
3326
- p4.log.error(pc13.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3797
+ p15.log.error(pc15.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3327
3798
  if (closest) {
3328
- p4.log.info(pc13.yellow(`Did you mean '${pc13.cyan(closest)}'?`));
3799
+ p15.log.info(pc15.yellow(`Did you mean '${pc15.cyan(closest)}'?`));
3329
3800
  }
3330
3801
  process.exit(1);
3331
3802
  }
3332
3803
  const importedModules = /* @__PURE__ */ new Set();
3333
3804
  const totalWrittenFiles = [];
3334
3805
  const requiredNpmPackages = /* @__PURE__ */ new Set();
3335
- async function importModuleRecursive(modType, modName) {
3336
- const modKey = `${modType}:${modName}`;
3806
+ async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
3807
+ const modKey = `${modType}:${targetName}`;
3337
3808
  if (importedModules.has(modKey)) return;
3338
- p4.log.step(`Importing ${modType}: ${pc13.cyan(modName)}...`);
3809
+ p15.log.step(
3810
+ `Importing ${modType}: ${pc15.cyan(targetName)}${targetName !== registryName ? pc15.dim(` (from ${registryName})`) : ""}...`
3811
+ );
3339
3812
  let manifest;
3340
3813
  try {
3341
- manifest = await fetchModuleManifest(registryBase, modType, modName);
3814
+ manifest = await fetchModuleManifest(registryBase, modType, registryName);
3342
3815
  } catch (err) {
3343
- p4.log.error(pc13.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
3816
+ p15.log.error(pc15.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
3344
3817
  process.exit(1);
3345
3818
  }
3819
+ const aliasMap = /* @__PURE__ */ new Map();
3820
+ if (targetName !== registryName) {
3821
+ const folder2 = modType === "shared" ? "shared" : "features";
3822
+ aliasMap.set(`@/${folder2}/${registryName}/`, `@/${folder2}/${targetName}/`);
3823
+ }
3346
3824
  const sharedDeps = manifest.dependencies?.shared || [];
3347
3825
  for (const dep of sharedDeps) {
3348
- const depPath = path3.join(projectRoot, "src", "shared", dep);
3349
- if (!fs3.existsSync(depPath) && !importedModules.has(`shared:${dep}`)) {
3350
- p4.log.info(` \u21B3 Resolving required shared dependency: ${pc13.cyan(dep)}`);
3351
- await importModuleRecursive("shared", dep);
3352
- }
3826
+ await resolveDependency(
3827
+ "shared",
3828
+ dep,
3829
+ aliasMap,
3830
+ projectRoot,
3831
+ flags,
3832
+ importModuleRecursive,
3833
+ importedModules,
3834
+ registryBase,
3835
+ index
3836
+ );
3353
3837
  }
3354
3838
  const featureDeps = manifest.dependencies?.features || [];
3355
3839
  for (const dep of featureDeps) {
3356
- const depPath = path3.join(projectRoot, "src", "features", dep);
3357
- if (!fs3.existsSync(depPath) && !importedModules.has(`feature:${dep}`)) {
3358
- p4.log.info(` \u21B3 Resolving required feature dependency: ${pc13.cyan(dep)}`);
3359
- await importModuleRecursive("feature", dep);
3360
- }
3840
+ await resolveDependency(
3841
+ "feature",
3842
+ dep,
3843
+ aliasMap,
3844
+ projectRoot,
3845
+ flags,
3846
+ importModuleRecursive,
3847
+ importedModules,
3848
+ registryBase,
3849
+ index
3850
+ );
3361
3851
  }
3362
3852
  for (const pkg of manifest.dependencies?.npm || []) {
3363
3853
  requiredNpmPackages.add(pkg);
3364
3854
  }
3365
3855
  const folder = modType === "shared" ? "shared" : "features";
3366
- const targetDir = path3.join(projectRoot, "src", folder, modName);
3856
+ const targetDir = path2.join(getModuleBaseDir(projectRoot, modType), targetName);
3857
+ const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
3367
3858
  for (const file of manifest.files || []) {
3368
3859
  if (file === "registry.json") continue;
3369
- const remoteFilePath = `${folder}/${modName}/${file}`;
3370
- const localFilePath = path3.join(targetDir, file);
3371
- if (fs3.existsSync(localFilePath) && !flags.overwrite) {
3372
- p4.log.warn(` \u21B3 Skipping existing file: ${pc13.dim(path3.relative(projectRoot, localFilePath))} (use --overwrite to replace)`);
3860
+ const remoteFilePath = `${folder}/${registryName}/${file}`;
3861
+ const localFilePath = path2.join(targetDir, file);
3862
+ if (fs12.existsSync(localFilePath) && !isOverwrite) {
3863
+ p15.log.warn(
3864
+ ` \u21B3 Skipping existing file: ${pc15.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
3865
+ );
3373
3866
  continue;
3374
3867
  }
3375
- const content = await fetchRegistryFile(registryBase, remoteFilePath);
3868
+ let content = await fetchRegistryFile(registryBase, remoteFilePath);
3869
+ content = rewriteImports(content, aliasMap);
3376
3870
  if (!flags.dryRun) {
3377
- fs3.mkdirSync(path3.dirname(localFilePath), { recursive: true });
3378
- fs3.writeFileSync(localFilePath, content, "utf-8");
3871
+ fs12.mkdirSync(path2.dirname(localFilePath), { recursive: true });
3872
+ fs12.writeFileSync(localFilePath, content, "utf-8");
3379
3873
  }
3380
- const displayPath = path3.relative(projectRoot, localFilePath);
3874
+ const displayPath = path2.relative(projectRoot, localFilePath);
3381
3875
  totalWrittenFiles.push(displayPath);
3382
- p4.log.message(` ${pc13.green("+")} ${displayPath}`);
3876
+ p15.log.message(` ${pc15.green("+")} ${displayPath}`);
3383
3877
  }
3384
3878
  importedModules.add(modKey);
3385
3879
  }
3386
- await importModuleRecursive(type, name);
3880
+ await importModuleRecursive(type, name, name);
3387
3881
  const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
3388
3882
  if (missingNpm.length > 0) {
3389
3883
  if (flags.skipInstall || flags.dryRun) {
3390
- p4.log.info(`Required npm dependencies to install: ${pc13.cyan(missingNpm.join(", "))}`);
3884
+ p15.log.info(`Required npm dependencies to install: ${pc15.cyan(missingNpm.join(", "))}`);
3391
3885
  } else {
3392
3886
  try {
3393
3887
  installNpmPackages(projectRoot, missingNpm);
3394
3888
  } catch (err) {
3395
- p4.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
3889
+ p15.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
3396
3890
  }
3397
3891
  }
3398
3892
  }
3399
3893
  if (flags.dryRun) {
3400
- p4.log.info(pc13.yellow("Dry run complete: No files were written to disk."));
3894
+ p15.log.info(pc15.yellow("Dry run complete: No files were written to disk."));
3401
3895
  } else {
3402
- p4.log.success(
3403
- pc13.green(
3404
- `Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in src/${type === "shared" ? "shared" : "features"}/${name})`
3896
+ const baseDir = getModuleBaseDir(projectRoot, type);
3897
+ const displayTarget = path2.relative(projectRoot, path2.join(baseDir, name));
3898
+ p15.log.success(
3899
+ pc15.green(
3900
+ `Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
3405
3901
  )
3406
3902
  );
3407
3903
  }
3408
3904
  }
3905
+ function loadMasterIndex(projectRoot) {
3906
+ const indexPath = path2.join(projectRoot, "index.json");
3907
+ if (!fs12.existsSync(indexPath)) {
3908
+ const initialIndex = { shared: [], features: [] };
3909
+ fs12.writeFileSync(indexPath, JSON.stringify(initialIndex, null, 2) + "\n", "utf-8");
3910
+ return initialIndex;
3911
+ }
3912
+ return JSON.parse(fs12.readFileSync(indexPath, "utf-8"));
3913
+ }
3914
+ function saveMasterIndex(projectRoot, index) {
3915
+ const indexPath = path2.join(projectRoot, "index.json");
3916
+ index.shared.sort((a, b) => a.name.localeCompare(b.name));
3917
+ index.features.sort((a, b) => a.name.localeCompare(b.name));
3918
+ fs12.writeFileSync(indexPath, JSON.stringify(index, null, 2) + "\n", "utf-8");
3919
+ }
3920
+ function discoverDiskModules(projectRoot) {
3921
+ const results = [];
3922
+ const sharedDir = resolveSharedDir(projectRoot);
3923
+ if (fs12.existsSync(sharedDir)) {
3924
+ const entries = fs12.readdirSync(sharedDir, { withFileTypes: true });
3925
+ for (const entry of entries) {
3926
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
3927
+ results.push({ name: entry.name, type: "shared", dir: path2.join(sharedDir, entry.name) });
3928
+ }
3929
+ }
3930
+ }
3931
+ const featuresDir = resolveFeaturesDir(projectRoot);
3932
+ if (fs12.existsSync(featuresDir)) {
3933
+ const entries = fs12.readdirSync(featuresDir, { withFileTypes: true });
3934
+ for (const entry of entries) {
3935
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
3936
+ results.push({ name: entry.name, type: "feature", dir: path2.join(featuresDir, entry.name) });
3937
+ }
3938
+ }
3939
+ }
3940
+ return results;
3941
+ }
3942
+ function checkGitStatusForDir(projectRoot, dirPath) {
3943
+ try {
3944
+ const relPath = path2.relative(projectRoot, dirPath);
3945
+ const output = execSync(`git status --porcelain -- "${relPath}"`, {
3946
+ cwd: projectRoot,
3947
+ stdio: ["ignore", "pipe", "ignore"],
3948
+ encoding: "utf-8"
3949
+ }).trim();
3950
+ if (!output) {
3951
+ return { hasChanges: false, filesCount: 0 };
3952
+ }
3953
+ const lines = output.split("\n").filter((l) => l.trim().length > 0);
3954
+ return { hasChanges: true, filesCount: lines.length };
3955
+ } catch {
3956
+ return { hasChanges: false, filesCount: 0 };
3957
+ }
3958
+ }
3959
+ function getRegistryDiff(projectRoot) {
3960
+ const index = loadMasterIndex(projectRoot);
3961
+ const diskModules = discoverDiskModules(projectRoot);
3962
+ const diskMap = /* @__PURE__ */ new Map();
3963
+ for (const mod of diskModules) {
3964
+ diskMap.set(`${mod.type}:${mod.name}`, mod);
3965
+ }
3966
+ const allKeys = /* @__PURE__ */ new Set();
3967
+ for (const item of index.shared) {
3968
+ allKeys.add(`shared:${item.name}`);
3969
+ }
3970
+ for (const item of index.features) {
3971
+ allKeys.add(`feature:${item.name}`);
3972
+ }
3973
+ for (const mod of diskModules) {
3974
+ allKeys.add(`${mod.type}:${mod.name}`);
3975
+ }
3976
+ const statuses = [];
3977
+ for (const key of allKeys) {
3978
+ const [typeStr, name] = key.split(":");
3979
+ const type = typeStr;
3980
+ const registeredEntry = (type === "shared" ? index.shared : index.features).find(
3981
+ (e) => e.name === name
3982
+ );
3983
+ const isRegistered = Boolean(registeredEntry);
3984
+ const diskInfo = diskMap.get(key);
3985
+ const existsOnDisk = Boolean(diskInfo && fs12.existsSync(diskInfo.dir));
3986
+ let hasUncommittedGitChanges = false;
3987
+ let uncommittedFilesCount = 0;
3988
+ let hasIntegrityDrift = false;
3989
+ let localIntegrity;
3990
+ if (existsOnDisk && diskInfo) {
3991
+ const git = checkGitStatusForDir(projectRoot, diskInfo.dir);
3992
+ hasUncommittedGitChanges = git.hasChanges;
3993
+ uncommittedFilesCount = git.filesCount;
3994
+ localIntegrity = computeDirectoryHash(diskInfo.dir);
3995
+ if (isRegistered && registeredEntry?.integrity) {
3996
+ hasIntegrityDrift = localIntegrity !== registeredEntry.integrity;
3997
+ }
3998
+ }
3999
+ statuses.push({
4000
+ name,
4001
+ type,
4002
+ isRegistered,
4003
+ existsOnDisk,
4004
+ hasUncommittedGitChanges,
4005
+ uncommittedFilesCount,
4006
+ hasIntegrityDrift,
4007
+ localIntegrity,
4008
+ registeredIntegrity: registeredEntry?.integrity
4009
+ });
4010
+ }
4011
+ return statuses.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
4012
+ }
4013
+ async function registerModule(typeArg, nameArg, flags = {}) {
4014
+ const projectRoot = findProjectRoot();
4015
+ const index = loadMasterIndex(projectRoot);
4016
+ let type = typeArg?.trim();
4017
+ if (!type) {
4018
+ type = handleCancel(
4019
+ await p15.select({
4020
+ message: "Module type to register:",
4021
+ options: [
4022
+ { value: "feature", label: "Feature" },
4023
+ { value: "shared", label: "Shared Module" }
4024
+ ]
4025
+ })
4026
+ );
4027
+ }
4028
+ const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
4029
+ const registeredNames = new Set((type === "shared" ? index.shared : index.features).map((m) => m.name));
4030
+ const unregistered = diskModules.filter((m) => !registeredNames.has(m.name));
4031
+ let name = nameArg?.trim();
4032
+ if (!name) {
4033
+ if (unregistered.length > 0) {
4034
+ const opts = unregistered.map((m) => ({ value: m.name, label: m.name, hint: "unregistered on disk" }));
4035
+ opts.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom name...", hint: "" });
4036
+ const choice = handleCancel(
4037
+ await p15.select({
4038
+ message: `Select a ${type} to register:`,
4039
+ options: opts
4040
+ })
4041
+ );
4042
+ if (choice === "__custom__") {
4043
+ name = handleCancel(
4044
+ await p15.text({
4045
+ message: `Enter ${type} name:`,
4046
+ validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
4047
+ })
4048
+ ).trim();
4049
+ } else {
4050
+ name = choice;
4051
+ }
4052
+ } else {
4053
+ name = handleCancel(
4054
+ await p15.text({
4055
+ message: `Enter ${type} name to register:`,
4056
+ validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
4057
+ })
4058
+ ).trim();
4059
+ }
4060
+ }
4061
+ const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
4062
+ const modDir = path2.join(folder, name);
4063
+ if (!fs12.existsSync(modDir)) {
4064
+ p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
4065
+ process.exit(1);
4066
+ }
4067
+ const files = getAllFiles(modDir);
4068
+ const integrity = computeDirectoryHash(modDir, files);
4069
+ const manifestPath = path2.join(modDir, "registry.json");
4070
+ let manifest;
4071
+ if (fs12.existsSync(manifestPath)) {
4072
+ manifest = JSON.parse(fs12.readFileSync(manifestPath, "utf-8"));
4073
+ } else {
4074
+ manifest = {
4075
+ name,
4076
+ type,
4077
+ description: `${name} ${type} module`,
4078
+ version: "1.0.0",
4079
+ dependencies: {
4080
+ npm: [],
4081
+ shared: [],
4082
+ features: []
4083
+ },
4084
+ files: []
4085
+ };
4086
+ }
4087
+ manifest.name = name;
4088
+ manifest.type = type;
4089
+ manifest.files = files;
4090
+ manifest.integrity = integrity;
4091
+ fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
4092
+ const targetList = type === "shared" ? index.shared : index.features;
4093
+ const existingIdx = targetList.findIndex((item) => item.name === name);
4094
+ if (existingIdx >= 0) {
4095
+ targetList[existingIdx] = {
4096
+ name,
4097
+ description: manifest.description,
4098
+ version: manifest.version,
4099
+ integrity
4100
+ };
4101
+ } else {
4102
+ targetList.push({
4103
+ name,
4104
+ description: manifest.description,
4105
+ version: manifest.version,
4106
+ integrity
4107
+ });
4108
+ }
4109
+ saveMasterIndex(projectRoot, index);
4110
+ p15.log.success(
4111
+ pc15.green(
4112
+ `\u2713 Module '${type}/${name}' registered in index.json (${files.length} files, ${integrity.slice(0, 18)}...)`
4113
+ )
4114
+ );
4115
+ }
4116
+ async function commitModule(typeArg, nameArg, flags = {}) {
4117
+ const projectRoot = findProjectRoot();
4118
+ let type = typeArg?.trim();
4119
+ if (!type) {
4120
+ type = handleCancel(
4121
+ await p15.select({
4122
+ message: "Module type to commit:",
4123
+ options: [
4124
+ { value: "feature", label: "Feature" },
4125
+ { value: "shared", label: "Shared Module" }
4126
+ ]
4127
+ })
4128
+ );
4129
+ }
4130
+ let name = nameArg?.trim();
4131
+ if (!name) {
4132
+ const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
4133
+ const opts = diskModules.map((m) => {
4134
+ const git = checkGitStatusForDir(projectRoot, m.dir);
4135
+ return {
4136
+ value: m.name,
4137
+ label: m.name,
4138
+ hint: git.hasChanges ? `${git.filesCount} uncommitted files` : "clean working tree"
4139
+ };
4140
+ });
4141
+ name = handleCancel(
4142
+ await p15.select({
4143
+ message: `Select ${type} to commit:`,
4144
+ options: opts
4145
+ })
4146
+ );
4147
+ }
4148
+ const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
4149
+ const modDir = path2.join(folder, name);
4150
+ if (!fs12.existsSync(modDir)) {
4151
+ p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
4152
+ process.exit(1);
4153
+ }
4154
+ const relModDir = path2.relative(projectRoot, modDir);
4155
+ const gitStatus = checkGitStatusForDir(projectRoot, modDir);
4156
+ if (!gitStatus.hasChanges) {
4157
+ p15.log.warn(pc15.yellow(`No uncommitted git changes found in '${type}/${name}'.`));
4158
+ return;
4159
+ }
4160
+ let message = flags.message?.trim();
4161
+ if (!message) {
4162
+ message = handleCancel(
4163
+ await p15.text({
4164
+ message: `Commit message for ${type}/${name}:`,
4165
+ placeholder: `e.g. feat(${name}): update use cases and dependencies`,
4166
+ validate: (v) => v && v.trim() ? void 0 : "Commit message cannot be empty"
4167
+ })
4168
+ ).trim();
4169
+ }
4170
+ const s = p15.spinner();
4171
+ s.start(`Committing '${type}/${name}' to git repository...`);
4172
+ try {
4173
+ execSync(`git add "${relModDir}"`, { cwd: projectRoot, stdio: ["ignore", "pipe", "pipe"] });
4174
+ execSync(`git commit -m "${message.replace(/"/g, '\\"')}" -- "${relModDir}"`, {
4175
+ cwd: projectRoot,
4176
+ stdio: ["ignore", "pipe", "pipe"]
4177
+ });
4178
+ s.stop(`Changes for '${type}/${name}' committed to git.`);
4179
+ p15.log.success(pc15.green(`\u2713 Committed '${type}/${name}': "${message}"`));
4180
+ } catch (err) {
4181
+ s.stop(pc15.red("Git commit failed."));
4182
+ p15.log.error(pc15.red(err.message || String(err)));
4183
+ process.exit(1);
4184
+ }
4185
+ }
4186
+ async function registerModuleChanges(typeArg, nameArg, flags = {}) {
4187
+ const projectRoot = findProjectRoot();
4188
+ const index = loadMasterIndex(projectRoot);
4189
+ let type = typeArg?.trim();
4190
+ if (!type) {
4191
+ type = handleCancel(
4192
+ await p15.select({
4193
+ message: "Module type to update:",
4194
+ options: [
4195
+ { value: "feature", label: "Feature" },
4196
+ { value: "shared", label: "Shared Module" }
4197
+ ]
4198
+ })
4199
+ );
4200
+ }
4201
+ let name = nameArg?.trim();
4202
+ if (!name) {
4203
+ const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
4204
+ const diffs = getRegistryDiff(projectRoot).filter((d) => d.type === type && d.isRegistered);
4205
+ const opts = diskModules.map((m) => {
4206
+ const d = diffs.find((item) => item.name === m.name);
4207
+ return {
4208
+ value: m.name,
4209
+ label: m.name,
4210
+ hint: d?.hasIntegrityDrift ? "drift detected (needs register)" : "up to date"
4211
+ };
4212
+ });
4213
+ name = handleCancel(
4214
+ await p15.select({
4215
+ message: `Select ${type} to register changes:`,
4216
+ options: opts
4217
+ })
4218
+ );
4219
+ }
4220
+ const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
4221
+ const modDir = path2.join(folder, name);
4222
+ if (!fs12.existsSync(modDir)) {
4223
+ p15.log.error(pc15.red(`Module directory does not exist: ${path2.relative(projectRoot, modDir)}`));
4224
+ process.exit(1);
4225
+ }
4226
+ const files = getAllFiles(modDir);
4227
+ const integrity = computeDirectoryHash(modDir, files);
4228
+ const manifestPath = path2.join(modDir, "registry.json");
4229
+ let manifest;
4230
+ if (fs12.existsSync(manifestPath)) {
4231
+ manifest = JSON.parse(fs12.readFileSync(manifestPath, "utf-8"));
4232
+ } else {
4233
+ manifest = {
4234
+ name,
4235
+ type,
4236
+ description: `${name} ${type} module`,
4237
+ version: "1.0.0",
4238
+ dependencies: { npm: [], shared: [], features: [] },
4239
+ files: []
4240
+ };
4241
+ }
4242
+ manifest.files = files;
4243
+ manifest.integrity = integrity;
4244
+ if (flags.version) {
4245
+ manifest.version = flags.version;
4246
+ }
4247
+ fs12.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
4248
+ const targetList = type === "shared" ? index.shared : index.features;
4249
+ const existingIdx = targetList.findIndex((item) => item.name === name);
4250
+ if (existingIdx >= 0) {
4251
+ targetList[existingIdx] = {
4252
+ name,
4253
+ description: manifest.description,
4254
+ version: manifest.version,
4255
+ integrity
4256
+ };
4257
+ } else {
4258
+ targetList.push({
4259
+ name,
4260
+ description: manifest.description,
4261
+ version: manifest.version,
4262
+ integrity
4263
+ });
4264
+ }
4265
+ saveMasterIndex(projectRoot, index);
4266
+ p15.log.success(
4267
+ pc15.green(`\u2713 Registered changes for '${type}/${name}' (new integrity: ${integrity.slice(0, 18)}...)`)
4268
+ );
4269
+ }
4270
+ async function unregisterModule(typeArg, nameArg, flags = {}) {
4271
+ const projectRoot = findProjectRoot();
4272
+ const index = loadMasterIndex(projectRoot);
4273
+ let type = typeArg?.trim();
4274
+ if (!type) {
4275
+ type = handleCancel(
4276
+ await p15.select({
4277
+ message: "Module type to unregister:",
4278
+ options: [
4279
+ { value: "feature", label: "Feature" },
4280
+ { value: "shared", label: "Shared Module" }
4281
+ ]
4282
+ })
4283
+ );
4284
+ }
4285
+ const registeredList = type === "shared" ? index.shared : index.features;
4286
+ if (registeredList.length === 0) {
4287
+ p15.log.warn(`No registered ${type} modules found in index.json.`);
4288
+ return;
4289
+ }
4290
+ let name = nameArg?.trim();
4291
+ if (!name) {
4292
+ name = handleCancel(
4293
+ await p15.select({
4294
+ message: `Select ${type} to unregister from index.json:`,
4295
+ options: registeredList.map((item) => ({
4296
+ value: item.name,
4297
+ label: item.name,
4298
+ hint: item.description
4299
+ }))
4300
+ })
4301
+ );
4302
+ }
4303
+ if (type === "shared") {
4304
+ index.shared = index.shared.filter((item) => item.name !== name);
4305
+ } else {
4306
+ index.features = index.features.filter((item) => item.name !== name);
4307
+ }
4308
+ saveMasterIndex(projectRoot, index);
4309
+ p15.log.success(
4310
+ pc15.green(
4311
+ `\u2713 Unregistered module '${type}/${name}' from index.json (source code retained on disk in ${type === "shared" ? "shared" : "features"}/${name})`
4312
+ )
4313
+ );
4314
+ }
4315
+ function diffRegistry(flags = {}) {
4316
+ const projectRoot = findProjectRoot();
4317
+ const diffs = getRegistryDiff(projectRoot);
4318
+ p15.log.step(pc15.bold("\u{1F4CB} Mason Registry Diff & Status Summary"));
4319
+ const unregistered = diffs.filter((d) => d.existsOnDisk && !d.isRegistered);
4320
+ const uncommitted = diffs.filter((d) => d.existsOnDisk && d.hasUncommittedGitChanges);
4321
+ const committedDrift = diffs.filter(
4322
+ (d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && d.hasIntegrityDrift
4323
+ );
4324
+ const upToDate = diffs.filter(
4325
+ (d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && !d.hasIntegrityDrift
4326
+ );
4327
+ const missing = diffs.filter((d) => !d.existsOnDisk && d.isRegistered);
4328
+ if (unregistered.length > 0) {
4329
+ p15.log.message(pc15.yellow(`
4330
+ \u{1F4E6} Unregistered Modules (${unregistered.length}):`));
4331
+ for (const mod of unregistered) {
4332
+ p15.log.message(` ${pc15.red("\u25CF")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim("(not registered in index.json)")}`);
4333
+ }
4334
+ }
4335
+ if (uncommitted.length > 0) {
4336
+ p15.log.message(pc15.yellow(`
4337
+ \u{1F4BE} Uncommitted Changes in Git (${uncommitted.length}):`));
4338
+ for (const mod of uncommitted) {
4339
+ p15.log.message(
4340
+ ` ${pc15.yellow("\u25B2")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim(`(${mod.uncommittedFilesCount} modified/untracked files)`)}`
4341
+ );
4342
+ }
4343
+ }
4344
+ if (committedDrift.length > 0) {
4345
+ p15.log.message(pc15.cyan(`
4346
+ \u{1F680} Committed But Unregistered Changes (${committedDrift.length}):`));
4347
+ for (const mod of committedDrift) {
4348
+ p15.log.message(
4349
+ ` ${pc15.cyan("\u25C6")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim(
4350
+ `local: ${mod.localIntegrity?.slice(0, 16)}... != registered: ${mod.registeredIntegrity?.slice(0, 16)}...`
4351
+ )}`
4352
+ );
4353
+ }
4354
+ }
4355
+ if (missing.length > 0) {
4356
+ p15.log.message(pc15.red(`
4357
+ \u26A0\uFE0F Missing on Disk (${missing.length}):`));
4358
+ for (const mod of missing) {
4359
+ p15.log.message(` ${pc15.red("\u2715")} ${pc15.bold(`${mod.type}/${mod.name}`)} ${pc15.dim("(in index.json, but folder missing)")}`);
4360
+ }
4361
+ }
4362
+ if (upToDate.length > 0) {
4363
+ p15.log.message(pc15.green(`
4364
+ \u2713 Up to Date & Registered (${upToDate.length}):`));
4365
+ for (const mod of upToDate) {
4366
+ p15.log.message(` ${pc15.green("\u2714")} ${mod.type}/${mod.name} ${pc15.dim(`(${mod.registeredIntegrity?.slice(0, 18)}...)`)}`);
4367
+ }
4368
+ }
4369
+ p15.log.message("");
4370
+ }
4371
+ async function handleRegistry(actionArg, typeArg, nameArg, flags = {}) {
4372
+ let action = actionArg?.trim().toLowerCase();
4373
+ if (!action) {
4374
+ action = handleCancel(
4375
+ await p15.select({
4376
+ message: "\u{1F3DB}\uFE0F Mason Registry: What would you like to do?",
4377
+ options: [
4378
+ {
4379
+ value: "diff",
4380
+ label: "Inspect Diff & Status",
4381
+ hint: "Summary of unregistered, uncommitted, and drifted modules"
4382
+ },
4383
+ {
4384
+ value: "register",
4385
+ label: "Register Module",
4386
+ hint: "Add an existing module to index.json with integrity checksum"
4387
+ },
4388
+ {
4389
+ value: "commit",
4390
+ label: "Commit Changes",
4391
+ hint: "Commit a module's working changes to git in the background"
4392
+ },
4393
+ {
4394
+ value: "register-changes",
4395
+ label: "Register Changes",
4396
+ hint: "Update index.json & registry.json with latest checksums"
4397
+ },
4398
+ {
4399
+ value: "unregister",
4400
+ label: "Unregister Module",
4401
+ hint: "Remove module from index.json (source code stays on disk)"
4402
+ }
4403
+ ]
4404
+ })
4405
+ );
4406
+ }
4407
+ switch (action) {
4408
+ case "diff":
4409
+ case "status":
4410
+ diffRegistry(flags);
4411
+ break;
4412
+ case "register":
4413
+ await registerModule(typeArg, nameArg, flags);
4414
+ break;
4415
+ case "commit":
4416
+ await commitModule(typeArg, nameArg, flags);
4417
+ break;
4418
+ case "register-changes":
4419
+ case "update":
4420
+ await registerModuleChanges(typeArg, nameArg, flags);
4421
+ break;
4422
+ case "unregister":
4423
+ await unregisterModule(typeArg, nameArg, flags);
4424
+ break;
4425
+ default:
4426
+ p15.log.error(
4427
+ pc15.red(
4428
+ `Unknown registry action: "${action}". Valid actions: diff, register, commit, register-changes, unregister.`
4429
+ )
4430
+ );
4431
+ process.exit(1);
4432
+ }
4433
+ }
3409
4434
 
3410
4435
  // src/cli.ts
3411
4436
  function createCli() {
3412
4437
  const cli = cac("mason");
3413
- cli.command("[...args]", "Interactive scaffolding and import wizard").option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").action(async (args, flags) => {
4438
+ cli.command("[...args]", "Interactive scaffolding and import wizard").option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("--registry", "Run in registry mode").action(async (args, flags) => {
3414
4439
  if (args && args.length > 0) {
3415
- p4.log.error(
3416
- pc13.red(
4440
+ p15.log.error(
4441
+ pc15.red(
3417
4442
  `Unknown command: "${args.join(" ")}". Use --help to list available commands.`
3418
4443
  )
3419
4444
  );
3420
4445
  process.exit(1);
3421
4446
  }
3422
4447
  try {
4448
+ const projectRoot = findProjectRoot();
4449
+ const mode = getEffectiveMode(projectRoot, flags);
4450
+ if (mode === "registry") {
4451
+ const choice2 = handleCancel(
4452
+ await p15.select({
4453
+ message: "\u{1F3DB}\uFE0F Mason Registry Maintainer: What would you like to do?",
4454
+ options: [
4455
+ {
4456
+ value: "diff",
4457
+ label: "Inspect Diff & Status",
4458
+ hint: "Overview of unregistered, uncommitted, or drifted modules"
4459
+ },
4460
+ {
4461
+ value: "create_feature",
4462
+ label: "Initialize New Feature",
4463
+ hint: "Scaffold a new feature slice under features/"
4464
+ },
4465
+ {
4466
+ value: "create_shared",
4467
+ label: "Initialize New Shared Module",
4468
+ hint: "Scaffold a new shared utility/port under shared/"
4469
+ },
4470
+ {
4471
+ value: "create_usecase",
4472
+ label: "Create New Use Case",
4473
+ hint: "Add a new use case to an existing feature"
4474
+ },
4475
+ {
4476
+ value: "register",
4477
+ label: "Register Module",
4478
+ hint: "Add a module to index.json with integrity checksum"
4479
+ },
4480
+ {
4481
+ value: "commit",
4482
+ label: "Commit Module Changes",
4483
+ hint: "Commit working changes in a module to git"
4484
+ },
4485
+ {
4486
+ value: "register_changes",
4487
+ label: "Register Changes",
4488
+ hint: "Recompute checksums and update index.json"
4489
+ },
4490
+ {
4491
+ value: "unregister",
4492
+ label: "Unregister Module",
4493
+ hint: "Remove from index.json but keep source on disk"
4494
+ },
4495
+ {
4496
+ value: "import",
4497
+ label: "Import from Remote Registry",
4498
+ hint: "Download a module from upstream"
4499
+ }
4500
+ ]
4501
+ })
4502
+ );
4503
+ switch (choice2) {
4504
+ case "diff":
4505
+ await handleRegistry("diff", void 0, void 0, flags);
4506
+ break;
4507
+ case "create_feature":
4508
+ await handleCreate("feature", void 0, flags);
4509
+ break;
4510
+ case "create_shared":
4511
+ await handleCreate("shared", void 0, flags);
4512
+ break;
4513
+ case "create_usecase":
4514
+ await handleCreate("usecase", void 0, flags);
4515
+ break;
4516
+ case "register":
4517
+ await handleRegistry("register", void 0, void 0, flags);
4518
+ break;
4519
+ case "commit":
4520
+ await handleRegistry("commit", void 0, void 0, flags);
4521
+ break;
4522
+ case "register_changes":
4523
+ await handleRegistry("register-changes", void 0, void 0, flags);
4524
+ break;
4525
+ case "unregister":
4526
+ await handleRegistry("unregister", void 0, void 0, flags);
4527
+ break;
4528
+ case "import":
4529
+ await handleImport(void 0, void 0, flags);
4530
+ break;
4531
+ }
4532
+ return;
4533
+ }
3423
4534
  const choice = handleCancel(
3424
- await p4.select({
4535
+ await p15.select({
3425
4536
  message: "\u{1F9F1} Mason: What would you like to do?",
3426
4537
  options: [
3427
4538
  {
3428
4539
  value: "create",
3429
4540
  label: "Create local component",
3430
- hint: "Scaffold a new feature, use case, domain entity, interface, etc."
4541
+ hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
3431
4542
  },
3432
4543
  {
3433
4544
  value: "import",
3434
4545
  label: "Import from registry",
3435
4546
  hint: "Download and link pre-built shared modules or features from the registry"
4547
+ },
4548
+ {
4549
+ value: "registry",
4550
+ label: "Registry management",
4551
+ hint: "Inspect diff, register modules, or commit module changes"
3436
4552
  }
3437
4553
  ]
3438
4554
  })
3439
4555
  );
3440
4556
  if (choice === "create") {
3441
4557
  await handleCreate(void 0, void 0, flags);
4558
+ } else if (choice === "registry") {
4559
+ await handleRegistry(void 0, void 0, void 0, flags);
3442
4560
  } else {
3443
4561
  await handleImport(void 0, void 0, flags);
3444
4562
  }
@@ -3448,8 +4566,8 @@ function createCli() {
3448
4566
  });
3449
4567
  cli.command(
3450
4568
  "create [type] [name]",
3451
- "Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
3452
- ).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("-f, --feature <feature>", "Target feature name").option("-t, --target <dir>", "Override target directory").option("--express", "Include Express presentation layer (pxExpress)").option("--no-express", "Skip Express presentation layer").option("-i, --interface <interface>", "Interface or abstract class to implement").option("-k, --kind <kind>", "Implementation kind ('stub' or 'concrete')").option("-g, --global", "Place in global infrastructure/ instead of feature").option("-m, --method <method>", "HTTP method (get, post, put, patch, delete)").option("-p, --path <path>", "Route subpath (e.g. /:id)").option("-u, --usecase <usecase>", "Connected use case name").option("--message <message>", "Custom success message").option("--domainNames <names>", "Comma-separated domain entity names").example("mason create").example("mason create feature users").example("mason create usecase PlaceOrder -f orders").example("mason create domain User -f users").example("mason create infrastructure PostgresUserRepository -f users -i IUserRepository").example("mason create interface IUserRepository -f users").example("mason create httpHandler GetUserHttp -f users -m get -p /:id").example("mason create injectable PasswordHasher").action(async (type, name, flags = {}) => {
4569
+ "Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
4570
+ ).option("-d, --dry-run", "Preview changes without writing to disk").option("-o, --overwrite", "Overwrite existing files").option("-y, --yes", "Accept defaults and skip interactive prompts").option("-f, --feature <feature>", "Target feature name").option("-t, --target <dir>", "Override target directory").option("--dest <dir>", "Override target directory (alias for --target)").option("--registry", "Scaffold in registry mode (stamps registry.json)").option("--express", "Include Express presentation layer (pxExpress)").option("--no-express", "Skip Express presentation layer").option("-i, --interface <interface>", "Interface or abstract class to implement").option("-k, --kind <kind>", "Implementation kind ('stub' or 'concrete')").option("-g, --global", "Place in global infrastructure/ instead of feature").option("-m, --method <method>", "HTTP method (get, post, put, patch, delete)").option("-p, --path <path>", "Route subpath (e.g. /:id)").option("-u, --usecase <usecase>", "Connected use case name").option("--message <message>", "Custom success message").option("--domainNames <names>", "Comma-separated domain entity names").example("mason create").example("mason create feature users").example("mason create usecase PlaceOrder -f orders").example("mason create domain User -f users").example("mason create infrastructure PostgresUserRepository -f users -i IUserRepository").example("mason create interface IUserRepository -f users").example("mason create httpHandler GetUserHttp -f users -m get -p /:id").example("mason create injectable PasswordHasher").action(async (type, name, flags = {}) => {
3453
4571
  try {
3454
4572
  await handleCreate(type, name, flags);
3455
4573
  } catch (err) {
@@ -3466,8 +4584,28 @@ function createCli() {
3466
4584
  handleCliError(err);
3467
4585
  }
3468
4586
  });
4587
+ cli.command(
4588
+ "registry [action] [type] [name]",
4589
+ "Registry & version management (diff, register, commit, register-changes, unregister)"
4590
+ ).option("-m, --message <message>", "Commit message for 'commit' command").option("-y, --yes", "Accept defaults and skip interactive prompts").example("mason registry diff").example("mason registry register feature authn").example("mason registry commit shared hasher -m 'Add bcrypt engine'").example("mason registry register-changes shared hasher").example("mason registry unregister feature oldFeature").action(
4591
+ async (action, type, name, flags = {}) => {
4592
+ try {
4593
+ await handleRegistry(action, type, name, flags);
4594
+ } catch (err) {
4595
+ handleCliError(err);
4596
+ }
4597
+ }
4598
+ );
4599
+ cli.command("diff", "Inspect registry status and diff (alias for 'mason registry diff')").allowUnknownOptions().action(async (flags = {}) => {
4600
+ try {
4601
+ await handleRegistry("diff", void 0, void 0, flags);
4602
+ } catch (err) {
4603
+ handleCliError(err);
4604
+ }
4605
+ });
3469
4606
  const createSubtypes = [
3470
4607
  "feature",
4608
+ "shared",
3471
4609
  "usecase",
3472
4610
  "domain",
3473
4611
  "infrastructure",
@@ -3488,7 +4626,7 @@ function createCli() {
3488
4626
  });
3489
4627
  }
3490
4628
  cli.help();
3491
- cli.version("1.0.10");
4629
+ cli.version("1.0.13");
3492
4630
  return cli;
3493
4631
  }
3494
4632
  function handleCliError(err) {
@@ -3496,7 +4634,7 @@ function handleCliError(err) {
3496
4634
  process.exit(0);
3497
4635
  }
3498
4636
  const message = err instanceof Error ? err.message : String(err);
3499
- p4.log.error(pc13.red(`Error: ${message}`));
4637
+ p15.log.error(pc15.red(`Error: ${message}`));
3500
4638
  process.exit(1);
3501
4639
  }
3502
4640
  async function runCli(argv = process.argv) {
@@ -3504,6 +4642,6 @@ async function runCli(argv = process.argv) {
3504
4642
  cli.parse(argv, { run: true });
3505
4643
  }
3506
4644
 
3507
- export { DEFAULT_REGISTRY_BASE, PromptCancelledError, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleImport, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, listDomainEntities, listDomainInterfaces, listExistingFeatures, listUseCases, loadInterfaceHierarchy, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveFeaturesDir, runCli, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
4645
+ export { DEFAULT_REGISTRY_BASE, PromptCancelledError, computeDirectoryHash, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateShared, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getAllFiles, getConfiguredFeaturesDir, getConfiguredSharedDir, getEffectiveMode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleImport, handleRegistry, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, listDomainEntities, listDomainInterfaces, listExistingFeatures, listExistingShared, listUseCases, loadInterfaceHierarchy, loadMasonConfig, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveFeaturesDir, resolveSharedDir, runCli, runSharedCommand, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
3508
4646
  //# sourceMappingURL=index.js.map
3509
4647
  //# sourceMappingURL=index.js.map