@solid-stack/mason 1.0.11 → 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 path7 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 = path7.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 = path7.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 = path7.resolve(startDir);
190
+ let current = path2.resolve(startDir);
130
191
  while (true) {
131
- if (fs3.existsSync(path7.join(current, "package.json"))) {
192
+ if (fs12.existsSync(path2.join(current, "package.json"))) {
132
193
  return current;
133
194
  }
134
- const parent = path7.dirname(current);
195
+ const parent = path2.dirname(current);
135
196
  if (parent === current) {
136
- return path7.resolve(startDir);
197
+ return path2.resolve(startDir);
137
198
  }
138
199
  current = parent;
139
200
  }
140
201
  }
141
- function resolveFeaturesDir(projectRoot) {
142
- return path7.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 = path7.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 = path7.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 = path7.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: path7.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 = path7.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: path7.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,47 +390,112 @@ 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;
324
398
  }
325
399
  var DEFAULT_REGISTRY_BASE = "https://raw.githubusercontent.com/solid-stack-digital/mason-registry/main";
400
+ function normalizeRegistryUrl(urlOrPath, baseDir) {
401
+ let trimmed = urlOrPath.trim();
402
+ const gitSshRegex = /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/;
403
+ const sshMatch = trimmed.match(gitSshRegex);
404
+ if (sshMatch && sshMatch[1] && sshMatch[2]) {
405
+ return `https://raw.githubusercontent.com/${sshMatch[1]}/${sshMatch[2]}/main`;
406
+ }
407
+ const githubWebRegex = /^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+))?\/?$/;
408
+ const webMatch = trimmed.match(githubWebRegex);
409
+ if (webMatch && webMatch[1] && webMatch[2]) {
410
+ const owner = webMatch[1];
411
+ const repo = webMatch[2];
412
+ const branch = webMatch[3] || "main";
413
+ return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
414
+ }
415
+ if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
416
+ return path2.resolve(baseDir, trimmed);
417
+ }
418
+ return trimmed;
419
+ }
420
+ function findLocalRegistry(startDir = process.cwd()) {
421
+ let current = path2.resolve(startDir);
422
+ while (true) {
423
+ const candidate = path2.join(current, "mason-registry");
424
+ if (fs12.existsSync(candidate) && fs12.existsSync(path2.join(candidate, "index.json"))) {
425
+ return candidate;
426
+ }
427
+ const parent = path2.dirname(current);
428
+ if (parent === current) break;
429
+ current = parent;
430
+ }
431
+ return null;
432
+ }
326
433
  function getRegistryBase(projectRoot, override) {
434
+ const rootDir = projectRoot ? path2.resolve(projectRoot) : process.cwd();
327
435
  if (override && override.trim()) {
328
- return override.trim();
436
+ return normalizeRegistryUrl(override.trim(), rootDir);
329
437
  }
330
438
  if (process.env.MASON_REGISTRY_URL && process.env.MASON_REGISTRY_URL.trim()) {
331
- return process.env.MASON_REGISTRY_URL.trim();
439
+ return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
332
440
  }
333
- if (projectRoot) {
334
- const configPath = path7.join(projectRoot, "mason.config.json");
335
- if (fs3.existsSync(configPath)) {
441
+ const configLocations = [
442
+ path2.join(rootDir, "mason.config.json"),
443
+ path2.join(process.cwd(), "mason.config.json")
444
+ ];
445
+ for (const configPath of configLocations) {
446
+ if (fs12.existsSync(configPath)) {
336
447
  try {
337
- const config = JSON.parse(fs3.readFileSync(configPath, "utf-8"));
448
+ const config = JSON.parse(fs12.readFileSync(configPath, "utf-8"));
338
449
  if (config.registry && typeof config.registry === "string") {
339
- return config.registry.trim();
450
+ return normalizeRegistryUrl(config.registry.trim(), path2.dirname(configPath));
340
451
  }
341
452
  } catch {
342
453
  }
343
454
  }
344
455
  }
345
- if (projectRoot) {
346
- const siblingRegistry = path7.resolve(projectRoot, "../mason-registry");
347
- if (fs3.existsSync(siblingRegistry) && fs3.existsSync(path7.join(siblingRegistry, "index.json"))) {
348
- return siblingRegistry;
456
+ const pkgLocations = [
457
+ path2.join(rootDir, "package.json"),
458
+ path2.join(process.cwd(), "package.json")
459
+ ];
460
+ for (const pkgPath of pkgLocations) {
461
+ if (fs12.existsSync(pkgPath)) {
462
+ try {
463
+ const pkg = JSON.parse(fs12.readFileSync(pkgPath, "utf-8"));
464
+ if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
465
+ return normalizeRegistryUrl(pkg.mason.registry.trim(), path2.dirname(pkgPath));
466
+ }
467
+ } catch {
468
+ }
349
469
  }
350
470
  }
471
+ const localRegistry = findLocalRegistry(rootDir);
472
+ if (localRegistry) {
473
+ return localRegistry;
474
+ }
351
475
  return DEFAULT_REGISTRY_BASE;
352
476
  }
353
477
  async function fetchRegistryFile(registryBase, relPath) {
354
478
  const cleanRelPath = relPath.replace(/^\/+/, "");
355
479
  if (registryBase.startsWith("http://") || registryBase.startsWith("https://")) {
356
480
  const url = `${registryBase.replace(/\/+$/, "")}/${cleanRelPath}`;
357
- const res = await fetch(url);
481
+ const headers = {};
482
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
483
+ if (token) {
484
+ headers["Authorization"] = `Bearer ${token}`;
485
+ }
486
+ const res = await fetch(url, { headers });
358
487
  if (!res.ok) {
359
- throw new Error(`Failed to fetch ${cleanRelPath} from ${url} (HTTP ${res.status}: ${res.statusText})`);
488
+ let extraHint = "";
489
+ if (res.status === 404) {
490
+ extraHint = `
491
+ Possible reasons:
492
+ \u2022 If using a private GitHub repository, ensure GITHUB_TOKEN is set in your environment.
493
+ \u2022 If your registry has not yet been pushed to GitHub, point to your local directory:
494
+ - CLI flag: mason import ... --registry /path/to/mason-registry
495
+ - Environment: export MASON_REGISTRY_URL=/path/to/mason-registry
496
+ - Config file: Create mason.config.json with { "registry": "/path/to/mason-registry" }`;
497
+ }
498
+ throw new Error(`Failed to fetch ${cleanRelPath} from ${url} (HTTP ${res.status}: ${res.statusText})${extraHint}`);
360
499
  }
361
500
  return await res.text();
362
501
  }
@@ -364,11 +503,11 @@ async function fetchRegistryFile(registryBase, relPath) {
364
503
  if (baseDir.startsWith("file://")) {
365
504
  baseDir = fileURLToPath(baseDir);
366
505
  }
367
- const filePath = path7.resolve(baseDir, cleanRelPath);
368
- if (!fs3.existsSync(filePath)) {
506
+ const filePath = path2.resolve(baseDir, cleanRelPath);
507
+ if (!fs12.existsSync(filePath)) {
369
508
  throw new Error(`Registry file not found at ${filePath}`);
370
509
  }
371
- return fs3.promises.readFile(filePath, "utf-8");
510
+ return fs12.promises.readFile(filePath, "utf-8");
372
511
  }
373
512
  async function fetchRegistryIndex(registryBase) {
374
513
  const content = await fetchRegistryFile(registryBase, "index.json");
@@ -430,26 +569,26 @@ function findClosestMatch(target, candidates) {
430
569
  return null;
431
570
  }
432
571
  function detectPackageManager(projectRoot) {
433
- if (fs3.existsSync(path7.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
434
- if (fs3.existsSync(path7.join(projectRoot, "yarn.lock"))) return "yarn";
435
- if (fs3.existsSync(path7.join(projectRoot, "bun.lockb")) || fs3.existsSync(path7.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";
436
575
  return "npm";
437
576
  }
438
577
  function getMissingNpmPackages(projectRoot, requiredPackages) {
439
578
  if (!requiredPackages || requiredPackages.length === 0) return [];
440
- const pkgJsonPath = path7.join(projectRoot, "package.json");
441
- if (!fs3.existsSync(pkgJsonPath)) {
579
+ const pkgJsonPath = path2.join(projectRoot, "package.json");
580
+ if (!fs12.existsSync(pkgJsonPath)) {
442
581
  return requiredPackages;
443
582
  }
444
583
  try {
445
- const pkg = JSON.parse(fs3.readFileSync(pkgJsonPath, "utf-8"));
584
+ const pkg = JSON.parse(fs12.readFileSync(pkgJsonPath, "utf-8"));
446
585
  const installed = /* @__PURE__ */ new Set([
447
586
  ...Object.keys(pkg.dependencies || {}),
448
587
  ...Object.keys(pkg.devDependencies || {}),
449
588
  ...Object.keys(pkg.peerDependencies || {})
450
589
  ]);
451
- return requiredPackages.filter((p15) => {
452
- 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;
453
592
  return !installed.has(nameOnly);
454
593
  });
455
594
  } catch {
@@ -474,12 +613,47 @@ function installNpmPackages(projectRoot, packages, pm) {
474
613
  installCmd = `npm install ${packages.join(" ")}`;
475
614
  break;
476
615
  }
477
- 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(", "))}`);
478
617
  execSync(installCmd, {
479
618
  cwd: projectRoot,
480
619
  stdio: "inherit"
481
620
  });
482
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
+ }
483
657
  var hbs = Handlebars.create();
484
658
  hbs.registerHelper("eq", (a, b) => a === b);
485
659
  hbs.registerHelper("ne", (a, b) => a !== b);
@@ -496,33 +670,33 @@ hbs.registerHelper("join", (arr, sep) => {
496
670
  });
497
671
  var cachedTemplatesDir = null;
498
672
  function getTemplatesDir() {
499
- if (cachedTemplatesDir && fs3.existsSync(cachedTemplatesDir)) {
673
+ if (cachedTemplatesDir && fs12.existsSync(cachedTemplatesDir)) {
500
674
  return cachedTemplatesDir;
501
675
  }
502
- const currentDir = typeof __dirname !== "undefined" ? __dirname : path7.dirname(fileURLToPath(import.meta.url));
503
- const cand1 = path7.resolve(currentDir, "../../templates");
504
- 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)) {
505
679
  cachedTemplatesDir = cand1;
506
680
  return cand1;
507
681
  }
508
- const cand2 = path7.resolve(currentDir, "../templates");
509
- if (fs3.existsSync(cand2)) {
682
+ const cand2 = path2.resolve(currentDir, "../templates");
683
+ if (fs12.existsSync(cand2)) {
510
684
  cachedTemplatesDir = cand2;
511
685
  return cand2;
512
686
  }
513
- const cand3 = path7.resolve(currentDir, "templates");
514
- if (fs3.existsSync(cand3)) {
687
+ const cand3 = path2.resolve(currentDir, "templates");
688
+ if (fs12.existsSync(cand3)) {
515
689
  cachedTemplatesDir = cand3;
516
690
  return cand3;
517
691
  }
518
692
  let curr = currentDir;
519
693
  while (true) {
520
- const candTemplates = path7.join(curr, "templates");
521
- if (fs3.existsSync(candTemplates)) {
694
+ const candTemplates = path2.join(curr, "templates");
695
+ if (fs12.existsSync(candTemplates)) {
522
696
  cachedTemplatesDir = candTemplates;
523
697
  return candTemplates;
524
698
  }
525
- const parent = path7.dirname(curr);
699
+ const parent = path2.dirname(curr);
526
700
  if (parent === curr) break;
527
701
  curr = parent;
528
702
  }
@@ -530,8 +704,8 @@ function getTemplatesDir() {
530
704
  }
531
705
  var templateCache = /* @__PURE__ */ new Map();
532
706
  function loadTemplate(relativePath) {
533
- const fullPath = path7.isAbsolute(relativePath) ? relativePath : path7.join(getTemplatesDir(), relativePath);
534
- return fs3.readFileSync(fullPath, "utf-8");
707
+ const fullPath = path2.isAbsolute(relativePath) ? relativePath : path2.join(getTemplatesDir(), relativePath);
708
+ return fs12.readFileSync(fullPath, "utf-8");
535
709
  }
536
710
  function renderTemplateFile(relativePath, context) {
537
711
  let compiled = templateCache.get(relativePath);
@@ -616,8 +790,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
616
790
  function generateDomain(options) {
617
791
  const projectRoot = options.projectRoot || findProjectRoot();
618
792
  const entityName = toPascalCase(options.name);
619
- const targetDir = options.targetDir || path7.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
620
- const filePath = path7.join(targetDir, `${entityName}.ts`);
793
+ const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
794
+ const filePath = path2.join(targetDir, `${entityName}.ts`);
621
795
  const code = generateDomainEntityCode(
622
796
  entityName,
623
797
  options.kind || "interface",
@@ -641,7 +815,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
641
815
  const idKey = `${entityKey}Id`;
642
816
  const listKey = `${entityKey}s`;
643
817
  const formattedCustomMethods = customMethods.map((m) => {
644
- 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("; ") : "";
645
819
  const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
646
820
  return {
647
821
  name: m.name,
@@ -666,8 +840,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
666
840
  function generateInterface(options) {
667
841
  const projectRoot = options.projectRoot || findProjectRoot();
668
842
  const ifaceName = ensureInterfacePrefix(options.name);
669
- const targetDir = options.targetDir || path7.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
670
- const filePath = path7.join(targetDir, `${ifaceName}.ts`);
843
+ const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
844
+ const filePath = path2.join(targetDir, `${ifaceName}.ts`);
671
845
  const code = generateInterfaceCode(
672
846
  ifaceName,
673
847
  options.kind || "abstractClass",
@@ -902,7 +1076,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
902
1076
  };
903
1077
  }
904
1078
  function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
905
- if (customPath && fs3.existsSync(customPath)) {
1079
+ if (customPath && fs12.existsSync(customPath)) {
906
1080
  return { filePath: customPath, ifaceName };
907
1081
  }
908
1082
  const candidateNames = [
@@ -912,22 +1086,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
912
1086
  ];
913
1087
  const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
914
1088
  for (const feat of features) {
915
- const domainDir = path7.join(resolveFeaturesDir(projectRoot), feat, "domain");
916
- if (!fs3.existsSync(domainDir)) continue;
1089
+ const domainDir = path2.join(resolveFeaturesDir(projectRoot), feat, "domain");
1090
+ if (!fs12.existsSync(domainDir)) continue;
917
1091
  for (const cand of candidateNames) {
918
- const p1 = path7.join(domainDir, `${cand}.ts`);
919
- if (fs3.existsSync(p1)) {
1092
+ const p1 = path2.join(domainDir, `${cand}.ts`);
1093
+ if (fs12.existsSync(p1)) {
920
1094
  return { filePath: p1, ifaceName: cand, feature: feat };
921
1095
  }
922
1096
  }
923
1097
  }
924
1098
  for (const cand of candidateNames) {
925
- const p1 = path7.join(projectRoot, "domain", `${cand}.ts`);
926
- if (fs3.existsSync(p1)) return { filePath: p1, ifaceName: cand };
927
- const p22 = path7.join(projectRoot, "src", "domain", `${cand}.ts`);
928
- if (fs3.existsSync(p22)) return { filePath: p22, ifaceName: cand };
929
- const p32 = path7.join(projectRoot, "interfaces", `${cand}.ts`);
930
- 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 };
931
1105
  }
932
1106
  return null;
933
1107
  }
@@ -950,7 +1124,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
950
1124
  while (currentFile && !visited.has(currentFile)) {
951
1125
  visited.add(currentFile);
952
1126
  try {
953
- const content = fs3.readFileSync(currentFile, "utf-8");
1127
+ const content = fs12.readFileSync(currentFile, "utf-8");
954
1128
  const parsed = parseInterfaceMethodsAndImports(content, currentName);
955
1129
  for (const m of parsed.methods) {
956
1130
  if (!allMethods.some((existing) => existing.name === m.name)) {
@@ -1026,7 +1200,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
1026
1200
  const entitiesVar = `${entityVar}s`;
1027
1201
  const idKey = `${entityVar}Id`;
1028
1202
  const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
1029
- const entityImportPath = ifaceImportPath ? `${path7.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1203
+ const entityImportPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1030
1204
  const importsByFrom = /* @__PURE__ */ new Map();
1031
1205
  for (const imp of domainImports) {
1032
1206
  if (imp.name === ifaceName || imp.name === baseEntity) continue;
@@ -1075,7 +1249,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
1075
1249
  if (baseEntity) {
1076
1250
  finalMethods = synthesizeCrudMethods(baseEntity);
1077
1251
  if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
1078
- const importPath = ifaceImportPath ? `${path7.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1252
+ const importPath = ifaceImportPath ? `${path2.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1079
1253
  finalDomainImports.push({
1080
1254
  name: baseEntity,
1081
1255
  from: importPath,
@@ -1120,8 +1294,8 @@ function generateInfrastructure(options) {
1120
1294
  const projectRoot = options.projectRoot || findProjectRoot();
1121
1295
  const className = toPascalCase(options.name);
1122
1296
  const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
1123
- const targetDir = options.targetDir || (isFeatureScope ? path7.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path7.join(projectRoot, "infrastructure"));
1124
- const filePath = path7.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`);
1125
1299
  let code;
1126
1300
  if (options.implementsInterface) {
1127
1301
  const rawIfaceName = options.implementsInterface;
@@ -1135,7 +1309,7 @@ function generateInfrastructure(options) {
1135
1309
  const ifaceFilePath = hierarchy.ifaceFilePath;
1136
1310
  let ifaceImportPath;
1137
1311
  if (ifaceFilePath) {
1138
- let rel = path7.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
1312
+ let rel = path2.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
1139
1313
  if (!rel.startsWith(".")) rel = "./" + rel;
1140
1314
  ifaceImportPath = rel;
1141
1315
  } else if (isFeatureScope) {
@@ -1148,8 +1322,8 @@ function generateInfrastructure(options) {
1148
1322
  if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
1149
1323
  let fromPath;
1150
1324
  if (imp.specifier.startsWith(".") && ifaceFilePath) {
1151
- const absTarget = path7.resolve(path7.dirname(ifaceFilePath), imp.specifier);
1152
- let rel = path7.relative(targetDir, absTarget).replace(/\\/g, "/");
1325
+ const absTarget = path2.resolve(path2.dirname(ifaceFilePath), imp.specifier);
1326
+ let rel = path2.relative(targetDir, absTarget).replace(/\\/g, "/");
1153
1327
  if (!rel.startsWith(".")) rel = "./" + rel;
1154
1328
  fromPath = rel;
1155
1329
  } else {
@@ -1222,22 +1396,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
1222
1396
  from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
1223
1397
  }));
1224
1398
  const domainTypesToImport = new Set(domainImports);
1225
- for (const p15 of [...inputProps, ...outputProps]) {
1226
- const rawType = p15.type.trim();
1399
+ for (const p17 of [...inputProps, ...outputProps]) {
1400
+ const rawType = p17.type.trim();
1227
1401
  const baseType = rawType.replace(/\[\]$/, "").trim();
1228
1402
  if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
1229
1403
  domainTypesToImport.add(baseType);
1230
1404
  }
1231
1405
  }
1232
- const formattedInputProps = inputProps.map((p15) => ({
1233
- name: p15.name,
1234
- optional: p15.optional,
1235
- 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
1236
1410
  }));
1237
- const formattedOutputProps = outputProps.map((p15) => ({
1238
- name: p15.name,
1239
- optional: p15.optional,
1240
- 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
1241
1415
  }));
1242
1416
  const formattedDeps = dependencies.map((dep) => ({
1243
1417
  key: toCamelCase(dep),
@@ -1336,8 +1510,8 @@ function generateUseCase(options) {
1336
1510
  const files = [];
1337
1511
  const projectRoot = options.projectRoot || findProjectRoot();
1338
1512
  const ucName = toPascalCase(options.name);
1339
- const targetDir = options.targetDir || path7.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
1340
- const filePath = path7.join(targetDir, `${ucName}.ts`);
1513
+ const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
1514
+ const filePath = path2.join(targetDir, `${ucName}.ts`);
1341
1515
  const code = generateUseCaseCode(
1342
1516
  ucName,
1343
1517
  options.dependencies || [],
@@ -1353,7 +1527,7 @@ function generateUseCase(options) {
1353
1527
  })
1354
1528
  );
1355
1529
  if (options.includeTest) {
1356
- const testPath = path7.join(targetDir, `${ucName}.test.ts`);
1530
+ const testPath = path2.join(targetDir, `${ucName}.test.ts`);
1357
1531
  const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
1358
1532
  files.push(
1359
1533
  writeFileSafely(testPath, testCode, {
@@ -1421,10 +1595,10 @@ function generateHttpHandler(options) {
1421
1595
  const files = [];
1422
1596
  const projectRoot = options.projectRoot || findProjectRoot();
1423
1597
  const className = toPascalCase(options.name);
1424
- const pxDir = path7.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
1425
- const handlersDir = path7.join(pxDir, "handlers");
1426
- const pxIndexPath = path7.join(pxDir, "index.ts");
1427
- 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)) {
1428
1602
  const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
1429
1603
  const pxIndexContent = renderTemplate(expressIndexTemplate, {
1430
1604
  routePath: featurePath
@@ -1437,7 +1611,7 @@ function generateHttpHandler(options) {
1437
1611
  })
1438
1612
  );
1439
1613
  }
1440
- const handlerFilePath = path7.join(handlersDir, `${className}.ts`);
1614
+ const handlerFilePath = path2.join(handlersDir, `${className}.ts`);
1441
1615
  const code = generateHttpHandlerCode(
1442
1616
  className,
1443
1617
  options.method || "get",
@@ -1458,11 +1632,11 @@ function generateHttpHandler(options) {
1458
1632
  return files;
1459
1633
  }
1460
1634
  function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
1461
- const providerPath = path7.join(featureDir, "diProvider.ts");
1635
+ const providerPath = path2.join(featureDir, "diProvider.ts");
1462
1636
  const pascalFeature = toPascalCase(featureName);
1463
1637
  const providerModuleName = `${pascalFeature}Provider`;
1464
- if (fs3.existsSync(providerPath) && !options.overwrite) {
1465
- let content = fs3.readFileSync(providerPath, "utf-8");
1638
+ if (fs12.existsSync(providerPath) && !options.overwrite) {
1639
+ let content = fs12.readFileSync(providerPath, "utf-8");
1466
1640
  content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
1467
1641
  for (const b of bindings) {
1468
1642
  const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
@@ -1521,7 +1695,7 @@ function generateFeature(options) {
1521
1695
  const projectRoot = options.projectRoot || findProjectRoot();
1522
1696
  const rawName = options.name.trim();
1523
1697
  const featureName = toCamelCase(rawName);
1524
- const targetDir = options.targetDir || path7.join(resolveFeaturesDir(projectRoot), featureName);
1698
+ const targetDir = options.targetDir || path2.join(resolveFeaturesDir(projectRoot), featureName);
1525
1699
  const routePath = options.routePath || `/${featureName}`;
1526
1700
  const rawEntities = [];
1527
1701
  if (options.entityNames && options.entityNames.length > 0) {
@@ -1542,7 +1716,7 @@ function generateFeature(options) {
1542
1716
  const domainFile = generateDomain({
1543
1717
  name: ent,
1544
1718
  feature: featureName,
1545
- targetDir: path7.join(targetDir, "domain"),
1719
+ targetDir: path2.join(targetDir, "domain"),
1546
1720
  projectRoot,
1547
1721
  dryRun: options.dryRun,
1548
1722
  overwrite: options.overwrite,
@@ -1562,7 +1736,7 @@ function generateFeature(options) {
1562
1736
  const ifaceFile = generateInterface({
1563
1737
  name: ifaceName,
1564
1738
  feature: featureName,
1565
- targetDir: path7.join(targetDir, "domain"),
1739
+ targetDir: path2.join(targetDir, "domain"),
1566
1740
  entityName: ent,
1567
1741
  projectRoot,
1568
1742
  dryRun: options.dryRun,
@@ -1579,7 +1753,7 @@ function generateFeature(options) {
1579
1753
  const stubFile = generateInfrastructure({
1580
1754
  name: stubName,
1581
1755
  feature: featureName,
1582
- targetDir: path7.join(targetDir, "infrastructure"),
1756
+ targetDir: path2.join(targetDir, "infrastructure"),
1583
1757
  kind: "stub",
1584
1758
  implementsInterface: ifaceName,
1585
1759
  entityName: ent,
@@ -1621,7 +1795,7 @@ function generateFeature(options) {
1621
1795
  const ucFiles = generateUseCase({
1622
1796
  name: ucName,
1623
1797
  feature: featureName,
1624
- targetDir: path7.join(targetDir, "useCases"),
1798
+ targetDir: path2.join(targetDir, "useCases"),
1625
1799
  dependencies: options.includeInterface !== false ? [ifaceName] : [],
1626
1800
  inputProperties: [{ name: idParam, type: "string" }],
1627
1801
  outputProperties: [{ name: entVar, type: primaryEntity }],
@@ -1666,7 +1840,7 @@ function generateFeature(options) {
1666
1840
  const ucFiles = generateUseCase({
1667
1841
  name: ucName,
1668
1842
  feature: featureName,
1669
- targetDir: path7.join(targetDir, "useCases"),
1843
+ targetDir: path2.join(targetDir, "useCases"),
1670
1844
  dependencies: deps,
1671
1845
  inputProperties: [{ name: entVar, type: ent }],
1672
1846
  outputProperties: [{ name: entVar, type: ent }],
@@ -1699,7 +1873,7 @@ function generateFeature(options) {
1699
1873
  const ucFiles = generateUseCase({
1700
1874
  name: ucName,
1701
1875
  feature: featureName,
1702
- targetDir: path7.join(targetDir, "useCases"),
1876
+ targetDir: path2.join(targetDir, "useCases"),
1703
1877
  dependencies: deps,
1704
1878
  inputProperties: [{ name: idParam, type: "string" }],
1705
1879
  outputProperties: [{ name: entVar, type: ent }],
@@ -1732,7 +1906,7 @@ function generateFeature(options) {
1732
1906
  const ucFiles = generateUseCase({
1733
1907
  name: ucName,
1734
1908
  feature: featureName,
1735
- targetDir: path7.join(targetDir, "useCases"),
1909
+ targetDir: path2.join(targetDir, "useCases"),
1736
1910
  dependencies: deps,
1737
1911
  inputProperties: [
1738
1912
  { name: "page", type: "number", optional: true },
@@ -1776,7 +1950,7 @@ function generateFeature(options) {
1776
1950
  const ucFiles = generateUseCase({
1777
1951
  name: ucName,
1778
1952
  feature: featureName,
1779
- targetDir: path7.join(targetDir, "useCases"),
1953
+ targetDir: path2.join(targetDir, "useCases"),
1780
1954
  dependencies: deps,
1781
1955
  inputProperties: [{ name: entVar, type: ent }],
1782
1956
  outputProperties: [{ name: entVar, type: ent }],
@@ -1809,7 +1983,7 @@ function generateFeature(options) {
1809
1983
  const ucFiles = generateUseCase({
1810
1984
  name: ucName,
1811
1985
  feature: featureName,
1812
- targetDir: path7.join(targetDir, "useCases"),
1986
+ targetDir: path2.join(targetDir, "useCases"),
1813
1987
  dependencies: deps,
1814
1988
  inputProperties: [{ name: idParam, type: "string" }],
1815
1989
  outputProperties: [{ name: "success", type: "boolean" }],
@@ -1846,7 +2020,7 @@ function generateFeature(options) {
1846
2020
  const ucFiles = generateUseCase({
1847
2021
  name: ucName,
1848
2022
  feature: featureName,
1849
- targetDir: path7.join(targetDir, "useCases"),
2023
+ targetDir: path2.join(targetDir, "useCases"),
1850
2024
  dependencies: customUc.dependencies || [],
1851
2025
  inputProperties: customUc.inputProperties || [],
1852
2026
  outputProperties: customUc.outputProperties || [],
@@ -1878,8 +2052,8 @@ function generateFeature(options) {
1878
2052
  }
1879
2053
  }
1880
2054
  if (options.includeExpress !== false) {
1881
- const pxDir = path7.join(targetDir, "pxExpress");
1882
- const pxIndexPath = path7.join(pxDir, "index.ts");
2055
+ const pxDir = path2.join(targetDir, "pxExpress");
2056
+ const pxIndexPath = path2.join(pxDir, "index.ts");
1883
2057
  if (!files.some((f) => f.absolutePath === pxIndexPath)) {
1884
2058
  const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
1885
2059
  files.push(
@@ -1905,13 +2079,14 @@ function generateFeature(options) {
1905
2079
  // src/commands/feature.ts
1906
2080
  async function runFeatureCommand(featureNameArg, flags = {}) {
1907
2081
  const projectRoot = findProjectRoot();
2082
+ const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
1908
2083
  let name = featureNameArg?.trim();
1909
2084
  if (!name && flags.yes) {
1910
2085
  name = "feature";
1911
2086
  }
1912
2087
  if (!name) {
1913
2088
  name = handleCancel(
1914
- await p4.text({
2089
+ await p15.text({
1915
2090
  message: "What is the feature name?",
1916
2091
  placeholder: "e.g. users, auth, orders, billing",
1917
2092
  validate: (val) => {
@@ -1922,18 +2097,19 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
1922
2097
  ).trim();
1923
2098
  }
1924
2099
  const normalizedName = toCamelCase(name);
1925
- const defaultDir = path7.relative(projectRoot, path7.join(resolveFeaturesDir(projectRoot), normalizedName));
2100
+ const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
2101
+ const defaultDir = path2.relative(projectRoot, path2.join(baseFeaturesDir, normalizedName));
1926
2102
  let targetDirRel = defaultDir;
1927
- if (!flags.yes && !flags.target) {
2103
+ if (!flags.yes && !flags.target && !flags.dest) {
1928
2104
  targetDirRel = handleCancel(
1929
- await p4.text({
2105
+ await p15.text({
1930
2106
  message: "Target directory for the feature:",
1931
2107
  initialValue: defaultDir,
1932
2108
  defaultValue: defaultDir
1933
2109
  })
1934
2110
  ).trim();
1935
2111
  }
1936
- const targetDir = flags.target ? path7.resolve(projectRoot, flags.target) : path7.resolve(projectRoot, targetDirRel);
2112
+ const targetDir = flags.dest ? path2.resolve(projectRoot, flags.dest) : flags.target ? path2.resolve(projectRoot, flags.target) : path2.resolve(projectRoot, targetDirRel);
1937
2113
  let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
1938
2114
  let routePath = `/${normalizedName}`;
1939
2115
  let includeDomain = true;
@@ -1948,14 +2124,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
1948
2124
  const customUseCases = [];
1949
2125
  if (!flags.yes) {
1950
2126
  includeExpress = handleCancel(
1951
- await p4.confirm({
2127
+ await p15.confirm({
1952
2128
  message: "Include Express Presentation layer (pxExpress routes & handlers)?",
1953
2129
  initialValue: true
1954
2130
  })
1955
2131
  );
1956
2132
  if (includeExpress) {
1957
2133
  routePath = handleCancel(
1958
- await p4.text({
2134
+ await p15.text({
1959
2135
  message: "Express route prefix:",
1960
2136
  initialValue: `/${normalizedName}`,
1961
2137
  defaultValue: `/${normalizedName}`
@@ -1963,14 +2139,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
1963
2139
  ).trim();
1964
2140
  }
1965
2141
  includeDomain = handleCancel(
1966
- await p4.confirm({
2142
+ await p15.confirm({
1967
2143
  message: "Include Domain Entity/Entities?",
1968
2144
  initialValue: true
1969
2145
  })
1970
2146
  );
1971
2147
  if (includeDomain) {
1972
2148
  const domainInput = handleCancel(
1973
- await p4.text({
2149
+ await p15.text({
1974
2150
  message: "Domain entity name(s) (comma-separated):",
1975
2151
  initialValue: defaultEntity,
1976
2152
  defaultValue: defaultEntity,
@@ -1989,21 +2165,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
1989
2165
  entityNames = [defaultEntity];
1990
2166
  }
1991
2167
  includeInterface = handleCancel(
1992
- await p4.confirm({
2168
+ await p15.confirm({
1993
2169
  message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
1994
2170
  initialValue: true
1995
2171
  })
1996
2172
  );
1997
2173
  if (includeInterface) {
1998
2174
  includeInfra = handleCancel(
1999
- await p4.confirm({
2175
+ await p15.confirm({
2000
2176
  message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
2001
2177
  initialValue: true
2002
2178
  })
2003
2179
  );
2004
2180
  }
2005
2181
  includeUseCase = handleCancel(
2006
- await p4.confirm({
2182
+ await p15.confirm({
2007
2183
  message: "Include Domain Use Cases?",
2008
2184
  initialValue: true
2009
2185
  })
@@ -2017,7 +2193,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2017
2193
  { value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
2018
2194
  ];
2019
2195
  crudOperations = handleCancel(
2020
- await p4.multiselect({
2196
+ await p15.multiselect({
2021
2197
  message: "Select standard CRUD use cases to generate:",
2022
2198
  options: crudChoices,
2023
2199
  initialValues: ["create", "read", "list", "update", "delete"],
@@ -2025,13 +2201,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2025
2201
  })
2026
2202
  );
2027
2203
  includeTests = handleCancel(
2028
- await p4.confirm({
2204
+ await p15.confirm({
2029
2205
  message: "Generate Vitest unit tests for use cases?",
2030
2206
  initialValue: true
2031
2207
  })
2032
2208
  );
2033
2209
  const wantCustom = handleCancel(
2034
- await p4.confirm({
2210
+ await p15.confirm({
2035
2211
  message: "Do you want to add any custom use cases to this feature?",
2036
2212
  initialValue: false
2037
2213
  })
@@ -2041,7 +2217,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2041
2217
  const availableIfaces = entityNames.map((e) => `I${e}Repository`);
2042
2218
  while (addMore) {
2043
2219
  const customName = handleCancel(
2044
- await p4.text({
2220
+ await p15.text({
2045
2221
  message: "Custom Use Case name:",
2046
2222
  placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
2047
2223
  validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
@@ -2051,7 +2227,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2051
2227
  let selectedDeps = [];
2052
2228
  if (availableIfaces.length > 0) {
2053
2229
  selectedDeps = handleCancel(
2054
- await p4.multiselect({
2230
+ await p15.multiselect({
2055
2231
  message: `Select domain interfaces to inject into ${customUcName}:`,
2056
2232
  options: availableIfaces.map((i) => ({ value: i, label: i })),
2057
2233
  required: false
@@ -2060,7 +2236,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2060
2236
  }
2061
2237
  let customInputProps = [];
2062
2238
  const hasInput = handleCancel(
2063
- await p4.confirm({
2239
+ await p15.confirm({
2064
2240
  message: `Does ${customUcName} accept input arguments?`,
2065
2241
  initialValue: true
2066
2242
  })
@@ -2071,7 +2247,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2071
2247
  }
2072
2248
  let customOutputProps = [];
2073
2249
  const hasCustomOutput = handleCancel(
2074
- await p4.confirm({
2250
+ await p15.confirm({
2075
2251
  message: `Does ${customUcName} return custom output data?`,
2076
2252
  initialValue: false
2077
2253
  })
@@ -2081,7 +2257,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2081
2257
  });
2082
2258
  }
2083
2259
  const customIncludeTest = handleCancel(
2084
- await p4.confirm({
2260
+ await p15.confirm({
2085
2261
  message: `Generate Vitest unit test for ${customUcName}?`,
2086
2262
  initialValue: true
2087
2263
  })
@@ -2091,14 +2267,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2091
2267
  let customHttpPath = `/${toKebabCase(customUcName)}`;
2092
2268
  if (includeExpress) {
2093
2269
  customCreateHttp = handleCancel(
2094
- await p4.confirm({
2270
+ await p15.confirm({
2095
2271
  message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
2096
2272
  initialValue: true
2097
2273
  })
2098
2274
  );
2099
2275
  if (customCreateHttp) {
2100
2276
  customHttpMethod = handleCancel(
2101
- await p4.select({
2277
+ await p15.select({
2102
2278
  message: "HTTP method for route:",
2103
2279
  options: [
2104
2280
  { value: "post", label: "POST" },
@@ -2111,7 +2287,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2111
2287
  })
2112
2288
  );
2113
2289
  customHttpPath = handleCancel(
2114
- await p4.text({
2290
+ await p15.text({
2115
2291
  message: "Route subpath (relative to feature prefix):",
2116
2292
  initialValue: `/${toKebabCase(customUcName)}`,
2117
2293
  defaultValue: `/${toKebabCase(customUcName)}`
@@ -2132,7 +2308,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2132
2308
  validationFields: customInputProps
2133
2309
  });
2134
2310
  addMore = handleCancel(
2135
- await p4.confirm({
2311
+ await p15.confirm({
2136
2312
  message: "Add another custom use case?",
2137
2313
  initialValue: false
2138
2314
  })
@@ -2141,7 +2317,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2141
2317
  }
2142
2318
  }
2143
2319
  includeDiProvider = handleCancel(
2144
- await p4.confirm({
2320
+ await p15.confirm({
2145
2321
  message: "Generate / update feature DI provider (diProvider.ts)?",
2146
2322
  initialValue: true
2147
2323
  })
@@ -2153,8 +2329,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2153
2329
  entityNames = [defaultEntity];
2154
2330
  }
2155
2331
  }
2156
- const s = p4.spinner();
2157
- s.start(`Generating feature '${pc13.cyan(normalizedName)}'...`);
2332
+ const s = p15.spinner();
2333
+ s.start(`Generating feature '${pc15.cyan(normalizedName)}'...`);
2158
2334
  const options = {
2159
2335
  name: normalizedName,
2160
2336
  projectRoot,
@@ -2174,9 +2350,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2174
2350
  includeDiProvider
2175
2351
  };
2176
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
+ }
2177
2377
  s.stop("Feature scaffolded successfully!");
2178
2378
  printGeneratedFiles(files, options.dryRun);
2179
- 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
+ );
2180
2554
  }
2181
2555
  async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2182
2556
  const projectRoot = findProjectRoot();
@@ -2187,14 +2561,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2187
2561
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2188
2562
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2189
2563
  const selected = handleCancel(
2190
- await p4.select({
2564
+ await p15.select({
2191
2565
  message: "Select target feature:",
2192
2566
  options: featureOptions
2193
2567
  })
2194
2568
  );
2195
2569
  if (selected === "__custom__") {
2196
2570
  feature = handleCancel(
2197
- await p4.text({
2571
+ await p15.text({
2198
2572
  message: "Enter feature name:",
2199
2573
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2200
2574
  })
@@ -2204,7 +2578,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2204
2578
  }
2205
2579
  } else {
2206
2580
  feature = handleCancel(
2207
- await p4.text({
2581
+ await p15.text({
2208
2582
  message: "Enter target feature name:",
2209
2583
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2210
2584
  })
@@ -2215,7 +2589,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2215
2589
  let name = useCaseNameArg?.trim();
2216
2590
  if (!name) {
2217
2591
  name = handleCancel(
2218
- await p4.text({
2592
+ await p15.text({
2219
2593
  message: "Use Case name:",
2220
2594
  placeholder: "e.g. CreateUser, GetUser, CancelOrder",
2221
2595
  validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
@@ -2231,7 +2605,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2231
2605
  label: i.name
2232
2606
  }));
2233
2607
  selectedDeps = handleCancel(
2234
- await p4.multiselect({
2608
+ await p15.multiselect({
2235
2609
  message: "Select domain interfaces to inject into use case:",
2236
2610
  options: multiOptions,
2237
2611
  required: false
@@ -2246,7 +2620,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2246
2620
  let httpPath = "/";
2247
2621
  if (!flags.yes) {
2248
2622
  const hasInput = handleCancel(
2249
- await p4.confirm({
2623
+ await p15.confirm({
2250
2624
  message: "Does this use case accept input arguments?",
2251
2625
  initialValue: true
2252
2626
  })
@@ -2256,7 +2630,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2256
2630
  });
2257
2631
  }
2258
2632
  const hasCustomOutput = handleCancel(
2259
- await p4.confirm({
2633
+ await p15.confirm({
2260
2634
  message: "Does this use case return custom output data?",
2261
2635
  initialValue: false
2262
2636
  })
@@ -2266,20 +2640,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2266
2640
  });
2267
2641
  }
2268
2642
  includeTest = handleCancel(
2269
- await p4.confirm({
2643
+ await p15.confirm({
2270
2644
  message: "Generate Vitest unit test skeleton (.test.ts)?",
2271
2645
  initialValue: true
2272
2646
  })
2273
2647
  );
2274
2648
  createHttp = handleCancel(
2275
- await p4.confirm({
2649
+ await p15.confirm({
2276
2650
  message: "Generate a matching ExpressRoute HTTP handler?",
2277
2651
  initialValue: false
2278
2652
  })
2279
2653
  );
2280
2654
  if (createHttp) {
2281
2655
  httpMethod = handleCancel(
2282
- await p4.select({
2656
+ await p15.select({
2283
2657
  message: "HTTP method for route:",
2284
2658
  options: [
2285
2659
  { value: "post", label: "POST" },
@@ -2292,7 +2666,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2292
2666
  })
2293
2667
  );
2294
2668
  httpPath = handleCancel(
2295
- await p4.text({
2669
+ await p15.text({
2296
2670
  message: "Route subpath (relative to feature prefix):",
2297
2671
  initialValue: "/",
2298
2672
  defaultValue: "/"
@@ -2300,8 +2674,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2300
2674
  ).trim();
2301
2675
  }
2302
2676
  }
2303
- const s = p4.spinner();
2304
- s.start(`Generating use case '${pc13.cyan(ucName)}'...`);
2677
+ const s = p15.spinner();
2678
+ s.start(`Generating use case '${pc15.cyan(ucName)}'...`);
2305
2679
  const options = {
2306
2680
  name: ucName,
2307
2681
  feature,
@@ -2333,7 +2707,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2333
2707
  }
2334
2708
  s.stop("Use case generated successfully!");
2335
2709
  printGeneratedFiles(files, options.dryRun);
2336
- p4.outro(pc13.green(`Use case '${ucName}' created in feature '${feature}'!`));
2710
+ p15.outro(pc15.green(`Use case '${ucName}' created in feature '${feature}'!`));
2337
2711
  }
2338
2712
  async function runDomainCommand(domainNameArg, flags = {}) {
2339
2713
  const projectRoot = findProjectRoot();
@@ -2344,14 +2718,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2344
2718
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2345
2719
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2346
2720
  const selected = handleCancel(
2347
- await p4.select({
2721
+ await p15.select({
2348
2722
  message: "Select target feature:",
2349
2723
  options: featureOptions
2350
2724
  })
2351
2725
  );
2352
2726
  if (selected === "__custom__") {
2353
2727
  feature = handleCancel(
2354
- await p4.text({
2728
+ await p15.text({
2355
2729
  message: "Enter feature name:",
2356
2730
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2357
2731
  })
@@ -2361,7 +2735,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2361
2735
  }
2362
2736
  } else {
2363
2737
  feature = handleCancel(
2364
- await p4.text({
2738
+ await p15.text({
2365
2739
  message: "Enter target feature name:",
2366
2740
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2367
2741
  })
@@ -2372,7 +2746,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2372
2746
  let name = domainNameArg?.trim();
2373
2747
  if (!name) {
2374
2748
  name = handleCancel(
2375
- await p4.text({
2749
+ await p15.text({
2376
2750
  message: "Domain model / entity name:",
2377
2751
  placeholder: "e.g. User, Order, Product, Address",
2378
2752
  validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
@@ -2384,7 +2758,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2384
2758
  let properties = [];
2385
2759
  if (!flags.yes) {
2386
2760
  kind = handleCancel(
2387
- await p4.select({
2761
+ await p15.select({
2388
2762
  message: "Select domain model type:",
2389
2763
  options: [
2390
2764
  { value: "interface", label: "TypeScript Interface (pure data structure)" },
@@ -2403,8 +2777,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2403
2777
  defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
2404
2778
  });
2405
2779
  }
2406
- const s = p4.spinner();
2407
- s.start(`Generating domain model '${pc13.cyan(entityName)}'...`);
2780
+ const s = p15.spinner();
2781
+ s.start(`Generating domain model '${pc15.cyan(entityName)}'...`);
2408
2782
  const options = {
2409
2783
  name: entityName,
2410
2784
  feature,
@@ -2417,7 +2791,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2417
2791
  const file = generateDomain(options);
2418
2792
  s.stop("Domain model generated successfully!");
2419
2793
  printGeneratedFiles([file], options.dryRun);
2420
- p4.outro(pc13.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2794
+ p15.outro(pc15.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2421
2795
  }
2422
2796
  async function runInfrastructureCommand(classNameArg, flags = {}) {
2423
2797
  const projectRoot = findProjectRoot();
@@ -2426,7 +2800,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2426
2800
  let feature = flags.feature?.trim();
2427
2801
  if (!flags.yes && !flags.global && !flags.feature) {
2428
2802
  scope = handleCancel(
2429
- await p4.select({
2803
+ await p15.select({
2430
2804
  message: "Where should this infrastructure implementation be placed?",
2431
2805
  options: [
2432
2806
  { value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
@@ -2441,14 +2815,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2441
2815
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2442
2816
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2443
2817
  const selected = handleCancel(
2444
- await p4.select({
2818
+ await p15.select({
2445
2819
  message: "Select target feature:",
2446
2820
  options: featureOptions
2447
2821
  })
2448
2822
  );
2449
2823
  if (selected === "__custom__") {
2450
2824
  feature = handleCancel(
2451
- await p4.text({
2825
+ await p15.text({
2452
2826
  message: "Enter feature name:",
2453
2827
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2454
2828
  })
@@ -2458,7 +2832,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2458
2832
  }
2459
2833
  } else {
2460
2834
  feature = handleCancel(
2461
- await p4.text({
2835
+ await p15.text({
2462
2836
  message: "Enter target feature name:",
2463
2837
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2464
2838
  })
@@ -2471,7 +2845,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2471
2845
  let name = classNameArg?.trim();
2472
2846
  if (!name) {
2473
2847
  name = handleCancel(
2474
- await p4.text({
2848
+ await p15.text({
2475
2849
  message: "Infrastructure class name:",
2476
2850
  placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
2477
2851
  validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
@@ -2490,7 +2864,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2490
2864
  (i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
2491
2865
  );
2492
2866
  const ifaceChoice = handleCancel(
2493
- await p4.select({
2867
+ await p15.select({
2494
2868
  message: "Does this class implement a domain interface or abstract class?",
2495
2869
  options: [
2496
2870
  ...interfaces.map((i) => ({ value: i.name, label: i.name })),
@@ -2502,7 +2876,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2502
2876
  );
2503
2877
  if (ifaceChoice === "__custom__") {
2504
2878
  implementsInterface = handleCancel(
2505
- await p4.text({
2879
+ await p15.text({
2506
2880
  message: "Enter interface or abstract class name:",
2507
2881
  placeholder: "e.g. IUserRepository, BaseStorageAdapter",
2508
2882
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2513,14 +2887,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2513
2887
  }
2514
2888
  } else {
2515
2889
  const wantImpl = handleCancel(
2516
- await p4.confirm({
2890
+ await p15.confirm({
2517
2891
  message: "Does this class implement a domain interface or abstract class?",
2518
2892
  initialValue: false
2519
2893
  })
2520
2894
  );
2521
2895
  if (wantImpl) {
2522
2896
  implementsInterface = handleCancel(
2523
- await p4.text({
2897
+ await p15.text({
2524
2898
  message: "Enter interface or abstract class name:",
2525
2899
  placeholder: "e.g. IUserRepository, BaseStorageAdapter",
2526
2900
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2540,7 +2914,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2540
2914
  const allInterfaces = listDomainInterfaces(projectRoot);
2541
2915
  if (allInterfaces.length > 0) {
2542
2916
  const ifaceChoice = handleCancel(
2543
- await p4.select({
2917
+ await p15.select({
2544
2918
  message: "Does this class implement an interface or abstract class?",
2545
2919
  options: [
2546
2920
  ...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
@@ -2552,7 +2926,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2552
2926
  );
2553
2927
  if (ifaceChoice === "__custom__") {
2554
2928
  implementsInterface = handleCancel(
2555
- await p4.text({
2929
+ await p15.text({
2556
2930
  message: "Enter interface or abstract class name:",
2557
2931
  placeholder: "e.g. IAuthRepository",
2558
2932
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2565,7 +2939,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2565
2939
  }
2566
2940
  if (!flags.yes && !flags.kind) {
2567
2941
  kind = handleCancel(
2568
- await p4.select({
2942
+ await p15.select({
2569
2943
  message: "Select implementation kind:",
2570
2944
  options: [
2571
2945
  { value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
@@ -2576,7 +2950,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2576
2950
  );
2577
2951
  if (scope === "feature" && feature && implementsInterface) {
2578
2952
  registerInProvider = handleCancel(
2579
- await p4.confirm({
2953
+ await p15.confirm({
2580
2954
  message: "Register this implementation in feature diProvider.ts?",
2581
2955
  initialValue: true
2582
2956
  })
@@ -2585,8 +2959,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2585
2959
  } else if (flags.yes && scope === "feature" && feature && implementsInterface) {
2586
2960
  registerInProvider = !flags.noProvider;
2587
2961
  }
2588
- const s = p4.spinner();
2589
- s.start(`Generating infrastructure class '${pc13.cyan(className)}'...`);
2962
+ const s = p15.spinner();
2963
+ s.start(`Generating infrastructure class '${pc15.cyan(className)}'...`);
2590
2964
  const files = [];
2591
2965
  const options = {
2592
2966
  name: className,
@@ -2601,7 +2975,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2601
2975
  const infraFile = generateInfrastructure(options);
2602
2976
  files.push(infraFile);
2603
2977
  if (registerInProvider && scope === "feature" && feature && implementsInterface) {
2604
- const featureDir = path7.join(resolveFeaturesDir(projectRoot), feature);
2978
+ const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
2605
2979
  const providerFile = updateOrGenerateDiProvider(
2606
2980
  featureDir,
2607
2981
  feature,
@@ -2621,7 +2995,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2621
2995
  }
2622
2996
  s.stop("Infrastructure class generated successfully!");
2623
2997
  printGeneratedFiles(files, options.dryRun);
2624
- p4.outro(pc13.green(`Infrastructure class '${className}' created!`));
2998
+ p15.outro(pc15.green(`Infrastructure class '${className}' created!`));
2625
2999
  }
2626
3000
  async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2627
3001
  const projectRoot = findProjectRoot();
@@ -2632,14 +3006,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2632
3006
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2633
3007
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2634
3008
  const selected = handleCancel(
2635
- await p4.select({
3009
+ await p15.select({
2636
3010
  message: "Select target feature:",
2637
3011
  options: featureOptions
2638
3012
  })
2639
3013
  );
2640
3014
  if (selected === "__custom__") {
2641
3015
  feature = handleCancel(
2642
- await p4.text({
3016
+ await p15.text({
2643
3017
  message: "Enter feature name:",
2644
3018
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2645
3019
  })
@@ -2649,7 +3023,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2649
3023
  }
2650
3024
  } else {
2651
3025
  feature = handleCancel(
2652
- await p4.text({
3026
+ await p15.text({
2653
3027
  message: "Enter target feature name:",
2654
3028
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2655
3029
  })
@@ -2660,7 +3034,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2660
3034
  let name = interfaceNameArg?.trim();
2661
3035
  if (!name) {
2662
3036
  name = handleCancel(
2663
- await p4.text({
3037
+ await p15.text({
2664
3038
  message: "Domain interface name:",
2665
3039
  placeholder: "e.g. IUserRepository, IPaymentService",
2666
3040
  validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
@@ -2678,7 +3052,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2678
3052
  if (!flags.yes) {
2679
3053
  if (existingEntities.length > 0) {
2680
3054
  const selectedEntity = handleCancel(
2681
- await p4.select({
3055
+ await p15.select({
2682
3056
  message: "Select domain entity associated with this interface:",
2683
3057
  options: [
2684
3058
  ...existingEntities.map((e) => ({ value: e.name, label: e.name })),
@@ -2689,7 +3063,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2689
3063
  );
2690
3064
  if (selectedEntity === "__custom__") {
2691
3065
  entityName = handleCancel(
2692
- await p4.text({
3066
+ await p15.text({
2693
3067
  message: "Enter entity name:",
2694
3068
  defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
2695
3069
  })
@@ -2699,7 +3073,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2699
3073
  }
2700
3074
  }
2701
3075
  kind = handleCancel(
2702
- await p4.select({
3076
+ await p15.select({
2703
3077
  message: "Interface definition style:",
2704
3078
  options: [
2705
3079
  { value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
@@ -2709,7 +3083,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2709
3083
  })
2710
3084
  );
2711
3085
  methodsPreset = handleCancel(
2712
- await p4.select({
3086
+ await p15.select({
2713
3087
  message: "Method signature preset:",
2714
3088
  options: [
2715
3089
  { value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
@@ -2719,22 +3093,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2719
3093
  })
2720
3094
  );
2721
3095
  generateStub = handleCancel(
2722
- await p4.confirm({
3096
+ await p15.confirm({
2723
3097
  message: "Auto-generate Infrastructure Stub implementation?",
2724
3098
  initialValue: true
2725
3099
  })
2726
3100
  );
2727
3101
  if (generateStub) {
2728
3102
  registerInProvider = handleCancel(
2729
- await p4.confirm({
3103
+ await p15.confirm({
2730
3104
  message: "Auto-register interface & stub in feature diProvider.ts?",
2731
3105
  initialValue: true
2732
3106
  })
2733
3107
  );
2734
3108
  }
2735
3109
  }
2736
- const s = p4.spinner();
2737
- s.start(`Generating interface '${pc13.cyan(ifaceName)}'...`);
3110
+ const s = p15.spinner();
3111
+ s.start(`Generating interface '${pc15.cyan(ifaceName)}'...`);
2738
3112
  const files = [];
2739
3113
  const options = {
2740
3114
  name: ifaceName,
@@ -2763,7 +3137,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2763
3137
  files.push(stubFile);
2764
3138
  }
2765
3139
  if (registerInProvider && generateStub) {
2766
- const featureDir = path7.join(resolveFeaturesDir(projectRoot), feature);
3140
+ const featureDir = path2.join(resolveFeaturesDir(projectRoot), feature);
2767
3141
  const providerFile = updateOrGenerateDiProvider(
2768
3142
  featureDir,
2769
3143
  feature,
@@ -2783,7 +3157,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2783
3157
  }
2784
3158
  s.stop("Interface generated successfully!");
2785
3159
  printGeneratedFiles(files, options.dryRun);
2786
- p4.outro(pc13.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
3160
+ p15.outro(pc15.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
2787
3161
  }
2788
3162
  async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2789
3163
  const projectRoot = findProjectRoot();
@@ -2794,14 +3168,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2794
3168
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2795
3169
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2796
3170
  const selected = handleCancel(
2797
- await p4.select({
3171
+ await p15.select({
2798
3172
  message: "Select target feature:",
2799
3173
  options: featureOptions
2800
3174
  })
2801
3175
  );
2802
3176
  if (selected === "__custom__") {
2803
3177
  feature = handleCancel(
2804
- await p4.text({
3178
+ await p15.text({
2805
3179
  message: "Enter feature name:",
2806
3180
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2807
3181
  })
@@ -2811,7 +3185,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2811
3185
  }
2812
3186
  } else {
2813
3187
  feature = handleCancel(
2814
- await p4.text({
3188
+ await p15.text({
2815
3189
  message: "Enter target feature name:",
2816
3190
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2817
3191
  })
@@ -2822,7 +3196,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2822
3196
  let name = handlerNameArg?.trim();
2823
3197
  if (!name) {
2824
3198
  name = handleCancel(
2825
- await p4.text({
3199
+ await p15.text({
2826
3200
  message: "HTTP Route Handler class name:",
2827
3201
  placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
2828
3202
  validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
@@ -2841,7 +3215,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2841
3215
  let validationFields = [];
2842
3216
  if (!flags.yes) {
2843
3217
  method = handleCancel(
2844
- await p4.select({
3218
+ await p15.select({
2845
3219
  message: "HTTP Method:",
2846
3220
  options: [
2847
3221
  { value: "get", label: "GET" },
@@ -2855,7 +3229,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2855
3229
  );
2856
3230
  const defaultPath = method === "get" ? "/" : "/";
2857
3231
  subPath = handleCancel(
2858
- await p4.text({
3232
+ await p15.text({
2859
3233
  message: "Route subpath (relative to feature prefix):",
2860
3234
  initialValue: defaultPath,
2861
3235
  defaultValue: defaultPath
@@ -2866,7 +3240,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2866
3240
  const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
2867
3241
  ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
2868
3242
  const chosenUc = handleCancel(
2869
- await p4.select({
3243
+ await p15.select({
2870
3244
  message: "Connect this handler to a domain Use Case?",
2871
3245
  options: ucOptions,
2872
3246
  initialValue: ucOptions[0]?.value
@@ -2877,7 +3251,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2877
3251
  }
2878
3252
  }
2879
3253
  includeValidation = handleCancel(
2880
- await p4.confirm({
3254
+ await p15.confirm({
2881
3255
  message: "Include Zod inbound request validation?",
2882
3256
  initialValue: method !== "delete"
2883
3257
  })
@@ -2885,7 +3259,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2885
3259
  if (includeValidation) {
2886
3260
  const defaultSource = method === "get" ? "query" : "body";
2887
3261
  validationSource = handleCancel(
2888
- await p4.select({
3262
+ await p15.select({
2889
3263
  message: "Validation data source:",
2890
3264
  options: [
2891
3265
  { value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
@@ -2899,8 +3273,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2899
3273
  });
2900
3274
  }
2901
3275
  }
2902
- const s = p4.spinner();
2903
- s.start(`Generating HTTP handler '${pc13.cyan(className)}'...`);
3276
+ const s = p15.spinner();
3277
+ s.start(`Generating HTTP handler '${pc15.cyan(className)}'...`);
2904
3278
  const options = {
2905
3279
  name: className,
2906
3280
  feature,
@@ -2918,7 +3292,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2918
3292
  const files = generateHttpHandler(options);
2919
3293
  s.stop("HTTP handler generated successfully!");
2920
3294
  printGeneratedFiles(files, options.dryRun);
2921
- p4.outro(pc13.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3295
+ p15.outro(pc15.green(`HTTP handler '${className}' created in feature '${feature}'!`));
2922
3296
  }
2923
3297
  function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
2924
3298
  const formattedDeps = dependencies.map((dep) => ({
@@ -2928,7 +3302,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
2928
3302
  const formattedMethods = methods.map((m) => {
2929
3303
  const isAsync = m.async !== false;
2930
3304
  const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
2931
- 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(", ") : "";
2932
3306
  return {
2933
3307
  name: m.name,
2934
3308
  isAsync,
@@ -2951,21 +3325,21 @@ function generateInjectable(options) {
2951
3325
  const className = toPascalCase(options.name);
2952
3326
  let targetDir = "";
2953
3327
  if (options.customPath) {
2954
- targetDir = path7.isAbsolute(options.customPath) ? options.customPath : path7.join(projectRoot, options.customPath);
3328
+ targetDir = path2.isAbsolute(options.customPath) ? options.customPath : path2.join(projectRoot, options.customPath);
2955
3329
  } else if (options.location === "feature-service" && options.feature) {
2956
- targetDir = path7.join(resolveFeaturesDir(projectRoot), options.feature, "services");
3330
+ targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "services");
2957
3331
  } else if (options.location === "feature-transformer" && options.feature) {
2958
- targetDir = path7.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
3332
+ targetDir = path2.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
2959
3333
  } else if (options.location === "module") {
2960
- targetDir = path7.join(projectRoot, "modules", className);
3334
+ targetDir = path2.join(projectRoot, "modules", className);
2961
3335
  } else {
2962
- targetDir = options.feature ? path7.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path7.join(projectRoot, "shared", className.toLowerCase());
3336
+ targetDir = options.feature ? path2.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2.join(projectRoot, "shared", className.toLowerCase());
2963
3337
  }
2964
- const filePath = path7.join(targetDir, `${className}.ts`);
3338
+ const filePath = path2.join(targetDir, `${className}.ts`);
2965
3339
  let ifaceName;
2966
3340
  if (options.generateInterface) {
2967
3341
  ifaceName = `I${className}`;
2968
- const ifacePath = path7.join(targetDir, `${ifaceName}.ts`);
3342
+ const ifacePath = path2.join(targetDir, `${ifaceName}.ts`);
2969
3343
  const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
2970
3344
  files.push(
2971
3345
  writeFileSafely(ifacePath, ifaceContent, {
@@ -2998,7 +3372,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
2998
3372
  let name = classNameArg?.trim();
2999
3373
  if (!name) {
3000
3374
  name = handleCancel(
3001
- await p4.text({
3375
+ await p15.text({
3002
3376
  message: "Injectable class name:",
3003
3377
  placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
3004
3378
  validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
@@ -3011,7 +3385,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3011
3385
  let customPath;
3012
3386
  if (!flags.yes) {
3013
3387
  location = handleCancel(
3014
- await p4.select({
3388
+ await p15.select({
3015
3389
  message: "Select target architectural location:",
3016
3390
  options: [
3017
3391
  { value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
@@ -3029,14 +3403,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3029
3403
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
3030
3404
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
3031
3405
  const selected = handleCancel(
3032
- await p4.select({
3406
+ await p15.select({
3033
3407
  message: "Select target feature:",
3034
3408
  options: featureOptions
3035
3409
  })
3036
3410
  );
3037
3411
  if (selected === "__custom__") {
3038
3412
  feature = handleCancel(
3039
- await p4.text({
3413
+ await p15.text({
3040
3414
  message: "Enter feature name:",
3041
3415
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
3042
3416
  })
@@ -3046,7 +3420,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3046
3420
  }
3047
3421
  } else {
3048
3422
  feature = handleCancel(
3049
- await p4.text({
3423
+ await p15.text({
3050
3424
  message: "Enter target feature name:",
3051
3425
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
3052
3426
  })
@@ -3056,7 +3430,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3056
3430
  feature = toCamelCase(feature);
3057
3431
  } else if (location === "custom") {
3058
3432
  customPath = handleCancel(
3059
- await p4.text({
3433
+ await p15.text({
3060
3434
  message: "Enter destination directory path:",
3061
3435
  placeholder: "e.g. src/services or lib/utils",
3062
3436
  validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
@@ -3067,14 +3441,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3067
3441
  let generateInterface2 = false;
3068
3442
  if (!flags.yes) {
3069
3443
  generateInterface2 = handleCancel(
3070
- await p4.confirm({
3444
+ await p15.confirm({
3071
3445
  message: `Generate abstract class interface (I${className}) as runtime DI token?`,
3072
3446
  initialValue: false
3073
3447
  })
3074
3448
  );
3075
3449
  }
3076
- const s = p4.spinner();
3077
- s.start(`Generating injectable '${pc13.cyan(className)}'...`);
3450
+ const s = p15.spinner();
3451
+ s.start(`Generating injectable '${pc15.cyan(className)}'...`);
3078
3452
  const options = {
3079
3453
  name: className,
3080
3454
  feature,
@@ -3088,12 +3462,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3088
3462
  const files = generateInjectable(options);
3089
3463
  s.stop("Injectable class generated successfully!");
3090
3464
  printGeneratedFiles(files, options.dryRun);
3091
- p4.outro(pc13.green(`Injectable '${className}' created successfully!`));
3465
+ p15.outro(pc15.green(`Injectable '${className}' created successfully!`));
3092
3466
  }
3093
3467
  async function runInteractiveMenu(flags = {}) {
3094
3468
  printBanner();
3095
3469
  const choice = handleCancel(
3096
- await p4.select({
3470
+ await p15.select({
3097
3471
  message: "What would you like to create in your architecture?",
3098
3472
  options: [
3099
3473
  {
@@ -3101,6 +3475,11 @@ async function runInteractiveMenu(flags = {}) {
3101
3475
  label: "Feature",
3102
3476
  hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
3103
3477
  },
3478
+ {
3479
+ value: "shared",
3480
+ label: "Shared Module",
3481
+ hint: "Foundational utility or port with ports, infrastructure & diProvider"
3482
+ },
3104
3483
  {
3105
3484
  value: "usecase",
3106
3485
  label: "Use Case",
@@ -3138,6 +3517,9 @@ async function runInteractiveMenu(flags = {}) {
3138
3517
  case "feature":
3139
3518
  await runFeatureCommand(void 0, flags);
3140
3519
  break;
3520
+ case "shared":
3521
+ await runSharedCommand(void 0, flags);
3522
+ break;
3141
3523
  case "usecase":
3142
3524
  await runUseCaseCommand(void 0, flags);
3143
3525
  break;
@@ -3170,6 +3552,9 @@ async function handleCreate(type, name, flags = {}) {
3170
3552
  case "feature":
3171
3553
  await runFeatureCommand(name, flags);
3172
3554
  break;
3555
+ case "shared":
3556
+ await runSharedCommand(name, flags);
3557
+ break;
3173
3558
  case "usecase":
3174
3559
  await runUseCaseCommand(name, flags);
3175
3560
  break;
@@ -3190,21 +3575,172 @@ async function handleCreate(type, name, flags = {}) {
3190
3575
  await runInjectableCommand(name, flags);
3191
3576
  break;
3192
3577
  default:
3193
- p4.log.error(
3194
- pc13.red(
3195
- `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.`
3196
3581
  )
3197
3582
  );
3198
3583
  process.exit(1);
3199
3584
  }
3200
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
+ }
3201
3737
  async function handleImport(typeArg, nameArg, flags = {}) {
3202
3738
  const projectRoot = findProjectRoot();
3203
3739
  let type = typeArg?.trim().toLowerCase();
3204
3740
  if (type === "features") type = "feature";
3205
3741
  if (!type) {
3206
3742
  type = handleCancel(
3207
- await p4.select({
3743
+ await importPrompts.select({
3208
3744
  message: "What kind of module would you like to import?",
3209
3745
  options: [
3210
3746
  {
@@ -3222,19 +3758,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3222
3758
  );
3223
3759
  }
3224
3760
  if (type !== "shared" && type !== "feature") {
3225
- 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".`));
3226
3762
  process.exit(1);
3227
3763
  }
3228
3764
  const registryBase = getRegistryBase(projectRoot, flags.registry);
3229
- const s = p4.spinner();
3230
- s.start(`Connecting to registry at ${pc13.dim(registryBase)}...`);
3765
+ const s = p15.spinner();
3766
+ s.start(`Connecting to registry at ${pc15.dim(registryBase)}...`);
3231
3767
  let index;
3232
3768
  try {
3233
3769
  index = await fetchRegistryIndex(registryBase);
3234
3770
  s.stop("Registry index loaded.");
3235
3771
  } catch (err) {
3236
- s.stop(pc13.red("Failed to load registry index."));
3237
- 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)));
3238
3774
  process.exit(1);
3239
3775
  }
3240
3776
  const availableList = type === "shared" ? index.shared : index.features;
@@ -3242,11 +3778,11 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3242
3778
  let name = nameArg?.trim();
3243
3779
  if (!name) {
3244
3780
  if (availableList.length === 0) {
3245
- p4.log.warn(`No ${type} modules available in the registry.`);
3781
+ p15.log.warn(`No ${type} modules available in the registry.`);
3246
3782
  return;
3247
3783
  }
3248
3784
  name = handleCancel(
3249
- await p4.select({
3785
+ await importPrompts.select({
3250
3786
  message: `Select a ${type} to import:`,
3251
3787
  options: availableList.map((item) => ({
3252
3788
  value: item.name,
@@ -3258,122 +3794,769 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3258
3794
  }
3259
3795
  if (!candidateNames.includes(name)) {
3260
3796
  const closest = findClosestMatch(name, candidateNames);
3261
- 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.`));
3262
3798
  if (closest) {
3263
- p4.log.info(pc13.yellow(`Did you mean '${pc13.cyan(closest)}'?`));
3799
+ p15.log.info(pc15.yellow(`Did you mean '${pc15.cyan(closest)}'?`));
3264
3800
  }
3265
3801
  process.exit(1);
3266
3802
  }
3267
3803
  const importedModules = /* @__PURE__ */ new Set();
3268
3804
  const totalWrittenFiles = [];
3269
3805
  const requiredNpmPackages = /* @__PURE__ */ new Set();
3270
- async function importModuleRecursive(modType, modName) {
3271
- const modKey = `${modType}:${modName}`;
3806
+ async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
3807
+ const modKey = `${modType}:${targetName}`;
3272
3808
  if (importedModules.has(modKey)) return;
3273
- 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
+ );
3274
3812
  let manifest;
3275
3813
  try {
3276
- manifest = await fetchModuleManifest(registryBase, modType, modName);
3814
+ manifest = await fetchModuleManifest(registryBase, modType, registryName);
3277
3815
  } catch (err) {
3278
- 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}`));
3279
3817
  process.exit(1);
3280
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
+ }
3281
3824
  const sharedDeps = manifest.dependencies?.shared || [];
3282
3825
  for (const dep of sharedDeps) {
3283
- const depPath = path7.join(projectRoot, "src", "shared", dep);
3284
- if (!fs3.existsSync(depPath) && !importedModules.has(`shared:${dep}`)) {
3285
- p4.log.info(` \u21B3 Resolving required shared dependency: ${pc13.cyan(dep)}`);
3286
- await importModuleRecursive("shared", dep);
3287
- }
3826
+ await resolveDependency(
3827
+ "shared",
3828
+ dep,
3829
+ aliasMap,
3830
+ projectRoot,
3831
+ flags,
3832
+ importModuleRecursive,
3833
+ importedModules,
3834
+ registryBase,
3835
+ index
3836
+ );
3288
3837
  }
3289
3838
  const featureDeps = manifest.dependencies?.features || [];
3290
3839
  for (const dep of featureDeps) {
3291
- const depPath = path7.join(projectRoot, "src", "features", dep);
3292
- if (!fs3.existsSync(depPath) && !importedModules.has(`feature:${dep}`)) {
3293
- p4.log.info(` \u21B3 Resolving required feature dependency: ${pc13.cyan(dep)}`);
3294
- await importModuleRecursive("feature", dep);
3295
- }
3840
+ await resolveDependency(
3841
+ "feature",
3842
+ dep,
3843
+ aliasMap,
3844
+ projectRoot,
3845
+ flags,
3846
+ importModuleRecursive,
3847
+ importedModules,
3848
+ registryBase,
3849
+ index
3850
+ );
3296
3851
  }
3297
3852
  for (const pkg of manifest.dependencies?.npm || []) {
3298
3853
  requiredNpmPackages.add(pkg);
3299
3854
  }
3300
3855
  const folder = modType === "shared" ? "shared" : "features";
3301
- const targetDir = path7.join(projectRoot, "src", folder, modName);
3856
+ const targetDir = path2.join(getModuleBaseDir(projectRoot, modType), targetName);
3857
+ const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
3302
3858
  for (const file of manifest.files || []) {
3303
3859
  if (file === "registry.json") continue;
3304
- const remoteFilePath = `${folder}/${modName}/${file}`;
3305
- const localFilePath = path7.join(targetDir, file);
3306
- if (fs3.existsSync(localFilePath) && !flags.overwrite) {
3307
- p4.log.warn(` \u21B3 Skipping existing file: ${pc13.dim(path7.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
+ );
3308
3866
  continue;
3309
3867
  }
3310
- const content = await fetchRegistryFile(registryBase, remoteFilePath);
3868
+ let content = await fetchRegistryFile(registryBase, remoteFilePath);
3869
+ content = rewriteImports(content, aliasMap);
3311
3870
  if (!flags.dryRun) {
3312
- fs3.mkdirSync(path7.dirname(localFilePath), { recursive: true });
3313
- fs3.writeFileSync(localFilePath, content, "utf-8");
3871
+ fs12.mkdirSync(path2.dirname(localFilePath), { recursive: true });
3872
+ fs12.writeFileSync(localFilePath, content, "utf-8");
3314
3873
  }
3315
- const displayPath = path7.relative(projectRoot, localFilePath);
3874
+ const displayPath = path2.relative(projectRoot, localFilePath);
3316
3875
  totalWrittenFiles.push(displayPath);
3317
- p4.log.message(` ${pc13.green("+")} ${displayPath}`);
3876
+ p15.log.message(` ${pc15.green("+")} ${displayPath}`);
3318
3877
  }
3319
3878
  importedModules.add(modKey);
3320
3879
  }
3321
- await importModuleRecursive(type, name);
3880
+ await importModuleRecursive(type, name, name);
3322
3881
  const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
3323
3882
  if (missingNpm.length > 0) {
3324
3883
  if (flags.skipInstall || flags.dryRun) {
3325
- 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(", "))}`);
3326
3885
  } else {
3327
3886
  try {
3328
3887
  installNpmPackages(projectRoot, missingNpm);
3329
3888
  } catch (err) {
3330
- 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(" ")}`);
3331
3890
  }
3332
3891
  }
3333
3892
  }
3334
3893
  if (flags.dryRun) {
3335
- 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."));
3336
3895
  } else {
3337
- p4.log.success(
3338
- pc13.green(
3339
- `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})`
3340
3901
  )
3341
3902
  );
3342
3903
  }
3343
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
+ }
3344
4434
 
3345
4435
  // src/cli.ts
3346
4436
  function createCli() {
3347
4437
  const cli = cac("mason");
3348
- 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) => {
3349
4439
  if (args && args.length > 0) {
3350
- p4.log.error(
3351
- pc13.red(
4440
+ p15.log.error(
4441
+ pc15.red(
3352
4442
  `Unknown command: "${args.join(" ")}". Use --help to list available commands.`
3353
4443
  )
3354
4444
  );
3355
4445
  process.exit(1);
3356
4446
  }
3357
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
+ }
3358
4534
  const choice = handleCancel(
3359
- await p4.select({
4535
+ await p15.select({
3360
4536
  message: "\u{1F9F1} Mason: What would you like to do?",
3361
4537
  options: [
3362
4538
  {
3363
4539
  value: "create",
3364
4540
  label: "Create local component",
3365
- hint: "Scaffold a new feature, use case, domain entity, interface, etc."
4541
+ hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
3366
4542
  },
3367
4543
  {
3368
4544
  value: "import",
3369
4545
  label: "Import from registry",
3370
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"
3371
4552
  }
3372
4553
  ]
3373
4554
  })
3374
4555
  );
3375
4556
  if (choice === "create") {
3376
4557
  await handleCreate(void 0, void 0, flags);
4558
+ } else if (choice === "registry") {
4559
+ await handleRegistry(void 0, void 0, void 0, flags);
3377
4560
  } else {
3378
4561
  await handleImport(void 0, void 0, flags);
3379
4562
  }
@@ -3383,8 +4566,8 @@ function createCli() {
3383
4566
  });
3384
4567
  cli.command(
3385
4568
  "create [type] [name]",
3386
- "Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
3387
- ).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 = {}) => {
3388
4571
  try {
3389
4572
  await handleCreate(type, name, flags);
3390
4573
  } catch (err) {
@@ -3401,8 +4584,28 @@ function createCli() {
3401
4584
  handleCliError(err);
3402
4585
  }
3403
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
+ });
3404
4606
  const createSubtypes = [
3405
4607
  "feature",
4608
+ "shared",
3406
4609
  "usecase",
3407
4610
  "domain",
3408
4611
  "infrastructure",
@@ -3423,7 +4626,7 @@ function createCli() {
3423
4626
  });
3424
4627
  }
3425
4628
  cli.help();
3426
- cli.version("1.0.10");
4629
+ cli.version("1.0.13");
3427
4630
  return cli;
3428
4631
  }
3429
4632
  function handleCliError(err) {
@@ -3431,7 +4634,7 @@ function handleCliError(err) {
3431
4634
  process.exit(0);
3432
4635
  }
3433
4636
  const message = err instanceof Error ? err.message : String(err);
3434
- p4.log.error(pc13.red(`Error: ${message}`));
4637
+ p15.log.error(pc15.red(`Error: ${message}`));
3435
4638
  process.exit(1);
3436
4639
  }
3437
4640
  async function runCli(argv = process.argv) {
@@ -3439,6 +4642,6 @@ async function runCli(argv = process.argv) {
3439
4642
  cli.parse(argv, { run: true });
3440
4643
  }
3441
4644
 
3442
- export { DEFAULT_REGISTRY_BASE, PromptCancelledError, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, 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, 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 };
3443
4646
  //# sourceMappingURL=index.js.map
3444
4647
  //# sourceMappingURL=index.js.map