@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.cjs CHANGED
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
 
3
- var fs3 = require('fs');
4
- var path7 = require('path');
5
- var p4 = require('@clack/prompts');
6
- var pc13 = require('picocolors');
3
+ var fs12 = require('fs');
4
+ var path2 = require('path');
5
+ var p15 = require('@clack/prompts');
6
+ var pc15 = require('picocolors');
7
7
  var url = require('url');
8
8
  var child_process = require('child_process');
9
+ var crypto = require('crypto');
9
10
  var Handlebars = require('handlebars');
10
11
  var cac = require('cac');
11
12
 
@@ -30,10 +31,10 @@ function _interopNamespace(e) {
30
31
  return Object.freeze(n);
31
32
  }
32
33
 
33
- var fs3__default = /*#__PURE__*/_interopDefault(fs3);
34
- var path7__default = /*#__PURE__*/_interopDefault(path7);
35
- var p4__namespace = /*#__PURE__*/_interopNamespace(p4);
36
- var pc13__default = /*#__PURE__*/_interopDefault(pc13);
34
+ var fs12__default = /*#__PURE__*/_interopDefault(fs12);
35
+ var path2__default = /*#__PURE__*/_interopDefault(path2);
36
+ var p15__namespace = /*#__PURE__*/_interopNamespace(p15);
37
+ var pc15__default = /*#__PURE__*/_interopDefault(pc15);
37
38
  var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
38
39
 
39
40
  // src/utils/formatting.ts
@@ -104,8 +105,8 @@ function isValidIdentifier(str) {
104
105
  }
105
106
  function writeFileSafely(absolutePath, content, options = {}) {
106
107
  const projectRoot = options.projectRoot || process.cwd();
107
- const relPath = path7__default.default.relative(projectRoot, absolutePath).replace(/\\/g, "/");
108
- const existedBefore = fs3__default.default.existsSync(absolutePath);
108
+ const relPath = path2__default.default.relative(projectRoot, absolutePath).replace(/\\/g, "/");
109
+ const existedBefore = fs12__default.default.existsSync(absolutePath);
109
110
  if (options.dryRun) {
110
111
  return {
111
112
  relativePath: relPath,
@@ -124,11 +125,11 @@ function writeFileSafely(absolutePath, content, options = {}) {
124
125
  existedBefore: true
125
126
  };
126
127
  }
127
- const dir = path7__default.default.dirname(absolutePath);
128
- if (!fs3__default.default.existsSync(dir)) {
129
- fs3__default.default.mkdirSync(dir, { recursive: true });
128
+ const dir = path2__default.default.dirname(absolutePath);
129
+ if (!fs12__default.default.existsSync(dir)) {
130
+ fs12__default.default.mkdirSync(dir, { recursive: true });
130
131
  }
131
- fs3__default.default.writeFileSync(absolutePath, content, "utf-8");
132
+ fs12__default.default.writeFileSync(absolutePath, content, "utf-8");
132
133
  return {
133
134
  relativePath: relPath,
134
135
  absolutePath,
@@ -140,41 +141,114 @@ function writeFileSafely(absolutePath, content, options = {}) {
140
141
  function printGeneratedFiles(files, isDryRun = false) {
141
142
  console.log("");
142
143
  if (isDryRun) {
143
- p4__namespace.log.warn(pc13__default.default.yellow(pc13__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
144
+ p15__namespace.log.warn(pc15__default.default.yellow(pc15__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
144
145
  }
145
146
  for (const f of files) {
146
147
  if (f.action === "created") {
147
- p4__namespace.log.success(`${pc13__default.default.bold(f.relativePath)} ${pc13__default.default.dim("(created)")}`);
148
+ p15__namespace.log.success(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(created)")}`);
148
149
  } else if (f.action === "overwritten") {
149
- p4__namespace.log.warn(`${pc13__default.default.bold(f.relativePath)} ${pc13__default.default.dim("(overwritten)")}`);
150
+ p15__namespace.log.warn(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(overwritten)")}`);
150
151
  } else if (f.action === "skipped") {
151
- p4__namespace.log.info(`${pc13__default.default.dim(f.relativePath)} ${pc13__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
152
+ p15__namespace.log.info(`${pc15__default.default.dim(f.relativePath)} ${pc15__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
152
153
  } else {
153
- p4__namespace.log.info(`${pc13__default.default.bold(f.relativePath)} ${pc13__default.default.dim("(preview)")}`);
154
+ p15__namespace.log.info(`${pc15__default.default.bold(f.relativePath)} ${pc15__default.default.dim("(preview)")}`);
154
155
  }
155
156
  }
156
157
  }
158
+ function loadMasonConfig(startDir = process.cwd()) {
159
+ let current = path2__default.default.resolve(startDir);
160
+ while (true) {
161
+ const configPath = path2__default.default.join(current, "mason.config.json");
162
+ if (fs12__default.default.existsSync(configPath)) {
163
+ try {
164
+ return JSON.parse(fs12__default.default.readFileSync(configPath, "utf-8"));
165
+ } catch {
166
+ return null;
167
+ }
168
+ }
169
+ const parent = path2__default.default.dirname(current);
170
+ if (parent === current) break;
171
+ current = parent;
172
+ }
173
+ return null;
174
+ }
175
+ function getEffectiveMode(projectRoot, flags) {
176
+ if (flags?.registry) return "registry";
177
+ const config = loadMasonConfig(projectRoot);
178
+ if (config?.mode) return config.mode;
179
+ if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "index.json")) && (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "features")) || fs12__default.default.existsSync(path2__default.default.join(projectRoot, "shared"))) && !fs12__default.default.existsSync(path2__default.default.join(projectRoot, "src"))) {
180
+ return "registry";
181
+ }
182
+ return "app";
183
+ }
184
+ function getConfiguredFeaturesDir(projectRoot, flags) {
185
+ if (flags?.target) return path2__default.default.resolve(projectRoot, flags.target);
186
+ if (flags?.dest) return path2__default.default.resolve(projectRoot, flags.dest);
187
+ const config = loadMasonConfig(projectRoot);
188
+ if (config?.paths?.features) {
189
+ return path2__default.default.resolve(projectRoot, config.paths.features);
190
+ }
191
+ const mode = getEffectiveMode(projectRoot, flags);
192
+ if (mode === "registry") {
193
+ return path2__default.default.join(projectRoot, "features");
194
+ }
195
+ if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "src/features"))) {
196
+ return path2__default.default.join(projectRoot, "src/features");
197
+ }
198
+ return path2__default.default.join(projectRoot, "features");
199
+ }
200
+ function getConfiguredSharedDir(projectRoot, flags) {
201
+ if (flags?.target) return path2__default.default.resolve(projectRoot, flags.target);
202
+ if (flags?.dest) return path2__default.default.resolve(projectRoot, flags.dest);
203
+ const config = loadMasonConfig(projectRoot);
204
+ if (config?.paths?.shared) {
205
+ return path2__default.default.resolve(projectRoot, config.paths.shared);
206
+ }
207
+ const mode = getEffectiveMode(projectRoot, flags);
208
+ if (mode === "registry") {
209
+ return path2__default.default.join(projectRoot, "shared");
210
+ }
211
+ if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "src/shared"))) {
212
+ return path2__default.default.join(projectRoot, "src/shared");
213
+ }
214
+ return path2__default.default.join(projectRoot, "shared");
215
+ }
216
+
217
+ // src/utils/project.ts
157
218
  function findProjectRoot(startDir = process.cwd()) {
158
- let current = path7__default.default.resolve(startDir);
219
+ let current = path2__default.default.resolve(startDir);
159
220
  while (true) {
160
- if (fs3__default.default.existsSync(path7__default.default.join(current, "package.json"))) {
221
+ if (fs12__default.default.existsSync(path2__default.default.join(current, "package.json"))) {
161
222
  return current;
162
223
  }
163
- const parent = path7__default.default.dirname(current);
224
+ const parent = path2__default.default.dirname(current);
164
225
  if (parent === current) {
165
- return path7__default.default.resolve(startDir);
226
+ return path2__default.default.resolve(startDir);
166
227
  }
167
228
  current = parent;
168
229
  }
169
230
  }
170
- function resolveFeaturesDir(projectRoot) {
171
- return path7__default.default.join(projectRoot, "features");
231
+ function resolveFeaturesDir(projectRoot, flags) {
232
+ return getConfiguredFeaturesDir(projectRoot, flags);
233
+ }
234
+ function resolveSharedDir(projectRoot, flags) {
235
+ return getConfiguredSharedDir(projectRoot, flags);
236
+ }
237
+ function listExistingShared(projectRoot) {
238
+ const sharedDir = resolveSharedDir(projectRoot);
239
+ if (!fs12__default.default.existsSync(sharedDir)) return [];
240
+ try {
241
+ const entries = fs12__default.default.readdirSync(sharedDir, { withFileTypes: true });
242
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name).sort();
243
+ } catch {
244
+ return [];
245
+ }
172
246
  }
173
247
  function listExistingFeatures(projectRoot) {
174
248
  const featuresDir = resolveFeaturesDir(projectRoot);
175
- if (!fs3__default.default.existsSync(featuresDir)) return [];
249
+ if (!fs12__default.default.existsSync(featuresDir)) return [];
176
250
  try {
177
- const entries = fs3__default.default.readdirSync(featuresDir, { withFileTypes: true });
251
+ const entries = fs12__default.default.readdirSync(featuresDir, { withFileTypes: true });
178
252
  return entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
179
253
  } catch {
180
254
  return [];
@@ -184,14 +258,14 @@ function listDomainInterfaces(projectRoot, featureName) {
184
258
  const results = [];
185
259
  const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
186
260
  for (const feat of features) {
187
- const domainDir = path7__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
188
- if (!fs3__default.default.existsSync(domainDir)) continue;
261
+ const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
262
+ if (!fs12__default.default.existsSync(domainDir)) continue;
189
263
  try {
190
- const files = fs3__default.default.readdirSync(domainDir);
264
+ const files = fs12__default.default.readdirSync(domainDir);
191
265
  for (const file of files) {
192
266
  if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
193
267
  const name = file.replace(/\.ts$/, "");
194
- const fullPath = path7__default.default.join(domainDir, file);
268
+ const fullPath = path2__default.default.join(domainDir, file);
195
269
  if (/^I[A-Z]/.test(name)) {
196
270
  results.push({
197
271
  name,
@@ -200,7 +274,7 @@ function listDomainInterfaces(projectRoot, featureName) {
200
274
  });
201
275
  } else {
202
276
  try {
203
- const content = fs3__default.default.readFileSync(fullPath, "utf-8");
277
+ const content = fs12__default.default.readFileSync(fullPath, "utf-8");
204
278
  if (/export\s+(?:abstract\s+class|interface)\s+(\w+)/.test(content)) {
205
279
  results.push({
206
280
  name,
@@ -222,17 +296,17 @@ function listDomainEntities(projectRoot, featureName) {
222
296
  const results = [];
223
297
  const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
224
298
  for (const feat of features) {
225
- const domainDir = path7__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
226
- if (!fs3__default.default.existsSync(domainDir)) continue;
299
+ const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
300
+ if (!fs12__default.default.existsSync(domainDir)) continue;
227
301
  try {
228
- const files = fs3__default.default.readdirSync(domainDir);
302
+ const files = fs12__default.default.readdirSync(domainDir);
229
303
  for (const file of files) {
230
304
  if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
231
305
  const name = file.replace(/\.ts$/, "");
232
306
  if (!/^I[A-Z]/.test(name)) {
233
307
  results.push({
234
308
  name,
235
- fullPath: path7__default.default.join(domainDir, file),
309
+ fullPath: path2__default.default.join(domainDir, file),
236
310
  feature: feat
237
311
  });
238
312
  }
@@ -247,16 +321,16 @@ function listUseCases(projectRoot, featureName) {
247
321
  const results = [];
248
322
  const features = featureName ? [featureName] : listExistingFeatures(projectRoot);
249
323
  for (const feat of features) {
250
- const useCasesDir = path7__default.default.join(resolveFeaturesDir(projectRoot), feat, "useCases");
251
- if (!fs3__default.default.existsSync(useCasesDir)) continue;
324
+ const useCasesDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "useCases");
325
+ if (!fs12__default.default.existsSync(useCasesDir)) continue;
252
326
  try {
253
- const files = fs3__default.default.readdirSync(useCasesDir);
327
+ const files = fs12__default.default.readdirSync(useCasesDir);
254
328
  for (const file of files) {
255
329
  if (file.endsWith(".ts") && !file.endsWith(".test.ts") && !file.endsWith(".d.ts")) {
256
330
  const name = file.replace(/\.ts$/, "");
257
331
  results.push({
258
332
  name,
259
- fullPath: path7__default.default.join(useCasesDir, file),
333
+ fullPath: path2__default.default.join(useCasesDir, file),
260
334
  feature: feat
261
335
  });
262
336
  }
@@ -273,23 +347,23 @@ var PromptCancelledError = class extends Error {
273
347
  }
274
348
  };
275
349
  function handleCancel(result) {
276
- if (p4__namespace.isCancel(result)) {
277
- p4__namespace.cancel("Operation cancelled.");
350
+ if (p15__namespace.isCancel(result)) {
351
+ p15__namespace.cancel("Operation cancelled.");
278
352
  throw new PromptCancelledError();
279
353
  }
280
354
  return result;
281
355
  }
282
356
  function printBanner() {
283
357
  console.log("");
284
- p4__namespace.intro(pc13__default.default.bgCyan(pc13__default.default.black(" @solid-stack/mason ")));
358
+ p15__namespace.intro(pc15__default.default.bgCyan(pc15__default.default.black(" @solid-stack/mason ")));
285
359
  }
286
360
  async function promptProperties(options = {}) {
287
361
  const properties = options.defaults ? [...options.defaults] : [];
288
362
  if (properties.length > 0) {
289
- const summary = properties.map((p15) => `${p15.name}: ${p15.type}${p15.optional ? "?" : ""}`).join(", ");
290
- p4__namespace.log.info(pc13__default.default.dim(`Pre-configured fields: ${summary}`));
363
+ const summary = properties.map((p17) => `${p17.name}: ${p17.type}${p17.optional ? "?" : ""}`).join(", ");
364
+ p15__namespace.log.info(pc15__default.default.dim(`Pre-configured fields: ${summary}`));
291
365
  const addMore = handleCancel(
292
- await p4__namespace.confirm({
366
+ await p15__namespace.confirm({
293
367
  message: "Would you like to add custom fields/properties?",
294
368
  initialValue: false
295
369
  })
@@ -298,10 +372,10 @@ async function promptProperties(options = {}) {
298
372
  return properties;
299
373
  }
300
374
  }
301
- p4__namespace.log.info(pc13__default.default.cyan("Enter properties (leave name blank when finished):"));
375
+ p15__namespace.log.info(pc15__default.default.cyan("Enter properties (leave name blank when finished):"));
302
376
  while (true) {
303
377
  const name = handleCancel(
304
- await p4__namespace.text({
378
+ await p15__namespace.text({
305
379
  message: "Property name (or press Enter to finish):",
306
380
  placeholder: "e.g. title, email, status"
307
381
  })
@@ -310,7 +384,7 @@ async function promptProperties(options = {}) {
310
384
  break;
311
385
  }
312
386
  const type = handleCancel(
313
- await p4__namespace.select({
387
+ await p15__namespace.select({
314
388
  message: `Type for property "${name.trim()}":`,
315
389
  options: [
316
390
  { value: "string", label: "string" },
@@ -327,7 +401,7 @@ async function promptProperties(options = {}) {
327
401
  let finalType = type;
328
402
  if (type === "custom") {
329
403
  finalType = handleCancel(
330
- await p4__namespace.text({
404
+ await p15__namespace.text({
331
405
  message: `Enter custom type for "${name.trim()}":`,
332
406
  placeholder: "e.g. UserRole, string | null",
333
407
  validate: (val) => val && val.trim() ? void 0 : "Type cannot be empty"
@@ -335,7 +409,7 @@ async function promptProperties(options = {}) {
335
409
  );
336
410
  }
337
411
  const optional = handleCancel(
338
- await p4__namespace.confirm({
412
+ await p15__namespace.confirm({
339
413
  message: `Is "${name.trim()}" optional?`,
340
414
  initialValue: false
341
415
  })
@@ -345,47 +419,112 @@ async function promptProperties(options = {}) {
345
419
  type: finalType.trim(),
346
420
  optional
347
421
  });
348
- p4__namespace.log.success(
349
- pc13__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
422
+ p15__namespace.log.success(
423
+ pc15__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
350
424
  );
351
425
  }
352
426
  return properties;
353
427
  }
354
428
  var DEFAULT_REGISTRY_BASE = "https://raw.githubusercontent.com/solid-stack-digital/mason-registry/main";
429
+ function normalizeRegistryUrl(urlOrPath, baseDir) {
430
+ let trimmed = urlOrPath.trim();
431
+ const gitSshRegex = /^git@github\.com:([^/]+)\/([^/]+?)(?:\.git)?$/;
432
+ const sshMatch = trimmed.match(gitSshRegex);
433
+ if (sshMatch && sshMatch[1] && sshMatch[2]) {
434
+ return `https://raw.githubusercontent.com/${sshMatch[1]}/${sshMatch[2]}/main`;
435
+ }
436
+ const githubWebRegex = /^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?(?:\/tree\/([^/]+))?\/?$/;
437
+ const webMatch = trimmed.match(githubWebRegex);
438
+ if (webMatch && webMatch[1] && webMatch[2]) {
439
+ const owner = webMatch[1];
440
+ const repo = webMatch[2];
441
+ const branch = webMatch[3] || "main";
442
+ return `https://raw.githubusercontent.com/${owner}/${repo}/${branch}`;
443
+ }
444
+ if (baseDir && (trimmed.startsWith("./") || trimmed.startsWith("../") || trimmed === "." || trimmed === "..")) {
445
+ return path2__default.default.resolve(baseDir, trimmed);
446
+ }
447
+ return trimmed;
448
+ }
449
+ function findLocalRegistry(startDir = process.cwd()) {
450
+ let current = path2__default.default.resolve(startDir);
451
+ while (true) {
452
+ const candidate = path2__default.default.join(current, "mason-registry");
453
+ if (fs12__default.default.existsSync(candidate) && fs12__default.default.existsSync(path2__default.default.join(candidate, "index.json"))) {
454
+ return candidate;
455
+ }
456
+ const parent = path2__default.default.dirname(current);
457
+ if (parent === current) break;
458
+ current = parent;
459
+ }
460
+ return null;
461
+ }
355
462
  function getRegistryBase(projectRoot, override) {
463
+ const rootDir = projectRoot ? path2__default.default.resolve(projectRoot) : process.cwd();
356
464
  if (override && override.trim()) {
357
- return override.trim();
465
+ return normalizeRegistryUrl(override.trim(), rootDir);
358
466
  }
359
467
  if (process.env.MASON_REGISTRY_URL && process.env.MASON_REGISTRY_URL.trim()) {
360
- return process.env.MASON_REGISTRY_URL.trim();
468
+ return normalizeRegistryUrl(process.env.MASON_REGISTRY_URL.trim(), rootDir);
361
469
  }
362
- if (projectRoot) {
363
- const configPath = path7__default.default.join(projectRoot, "mason.config.json");
364
- if (fs3__default.default.existsSync(configPath)) {
470
+ const configLocations = [
471
+ path2__default.default.join(rootDir, "mason.config.json"),
472
+ path2__default.default.join(process.cwd(), "mason.config.json")
473
+ ];
474
+ for (const configPath of configLocations) {
475
+ if (fs12__default.default.existsSync(configPath)) {
365
476
  try {
366
- const config = JSON.parse(fs3__default.default.readFileSync(configPath, "utf-8"));
477
+ const config = JSON.parse(fs12__default.default.readFileSync(configPath, "utf-8"));
367
478
  if (config.registry && typeof config.registry === "string") {
368
- return config.registry.trim();
479
+ return normalizeRegistryUrl(config.registry.trim(), path2__default.default.dirname(configPath));
369
480
  }
370
481
  } catch {
371
482
  }
372
483
  }
373
484
  }
374
- if (projectRoot) {
375
- const siblingRegistry = path7__default.default.resolve(projectRoot, "../mason-registry");
376
- if (fs3__default.default.existsSync(siblingRegistry) && fs3__default.default.existsSync(path7__default.default.join(siblingRegistry, "index.json"))) {
377
- return siblingRegistry;
485
+ const pkgLocations = [
486
+ path2__default.default.join(rootDir, "package.json"),
487
+ path2__default.default.join(process.cwd(), "package.json")
488
+ ];
489
+ for (const pkgPath of pkgLocations) {
490
+ if (fs12__default.default.existsSync(pkgPath)) {
491
+ try {
492
+ const pkg = JSON.parse(fs12__default.default.readFileSync(pkgPath, "utf-8"));
493
+ if (pkg.mason?.registry && typeof pkg.mason.registry === "string") {
494
+ return normalizeRegistryUrl(pkg.mason.registry.trim(), path2__default.default.dirname(pkgPath));
495
+ }
496
+ } catch {
497
+ }
378
498
  }
379
499
  }
500
+ const localRegistry = findLocalRegistry(rootDir);
501
+ if (localRegistry) {
502
+ return localRegistry;
503
+ }
380
504
  return DEFAULT_REGISTRY_BASE;
381
505
  }
382
506
  async function fetchRegistryFile(registryBase, relPath) {
383
507
  const cleanRelPath = relPath.replace(/^\/+/, "");
384
508
  if (registryBase.startsWith("http://") || registryBase.startsWith("https://")) {
385
509
  const url = `${registryBase.replace(/\/+$/, "")}/${cleanRelPath}`;
386
- const res = await fetch(url);
510
+ const headers = {};
511
+ const token = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
512
+ if (token) {
513
+ headers["Authorization"] = `Bearer ${token}`;
514
+ }
515
+ const res = await fetch(url, { headers });
387
516
  if (!res.ok) {
388
- throw new Error(`Failed to fetch ${cleanRelPath} from ${url} (HTTP ${res.status}: ${res.statusText})`);
517
+ let extraHint = "";
518
+ if (res.status === 404) {
519
+ extraHint = `
520
+ Possible reasons:
521
+ \u2022 If using a private GitHub repository, ensure GITHUB_TOKEN is set in your environment.
522
+ \u2022 If your registry has not yet been pushed to GitHub, point to your local directory:
523
+ - CLI flag: mason import ... --registry /path/to/mason-registry
524
+ - Environment: export MASON_REGISTRY_URL=/path/to/mason-registry
525
+ - Config file: Create mason.config.json with { "registry": "/path/to/mason-registry" }`;
526
+ }
527
+ throw new Error(`Failed to fetch ${cleanRelPath} from ${url} (HTTP ${res.status}: ${res.statusText})${extraHint}`);
389
528
  }
390
529
  return await res.text();
391
530
  }
@@ -393,11 +532,11 @@ async function fetchRegistryFile(registryBase, relPath) {
393
532
  if (baseDir.startsWith("file://")) {
394
533
  baseDir = url.fileURLToPath(baseDir);
395
534
  }
396
- const filePath = path7__default.default.resolve(baseDir, cleanRelPath);
397
- if (!fs3__default.default.existsSync(filePath)) {
535
+ const filePath = path2__default.default.resolve(baseDir, cleanRelPath);
536
+ if (!fs12__default.default.existsSync(filePath)) {
398
537
  throw new Error(`Registry file not found at ${filePath}`);
399
538
  }
400
- return fs3__default.default.promises.readFile(filePath, "utf-8");
539
+ return fs12__default.default.promises.readFile(filePath, "utf-8");
401
540
  }
402
541
  async function fetchRegistryIndex(registryBase) {
403
542
  const content = await fetchRegistryFile(registryBase, "index.json");
@@ -459,26 +598,26 @@ function findClosestMatch(target, candidates) {
459
598
  return null;
460
599
  }
461
600
  function detectPackageManager(projectRoot) {
462
- if (fs3__default.default.existsSync(path7__default.default.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
463
- if (fs3__default.default.existsSync(path7__default.default.join(projectRoot, "yarn.lock"))) return "yarn";
464
- if (fs3__default.default.existsSync(path7__default.default.join(projectRoot, "bun.lockb")) || fs3__default.default.existsSync(path7__default.default.join(projectRoot, "bun.lock"))) return "bun";
601
+ if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "pnpm-lock.yaml"))) return "pnpm";
602
+ if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "yarn.lock"))) return "yarn";
603
+ if (fs12__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lockb")) || fs12__default.default.existsSync(path2__default.default.join(projectRoot, "bun.lock"))) return "bun";
465
604
  return "npm";
466
605
  }
467
606
  function getMissingNpmPackages(projectRoot, requiredPackages) {
468
607
  if (!requiredPackages || requiredPackages.length === 0) return [];
469
- const pkgJsonPath = path7__default.default.join(projectRoot, "package.json");
470
- if (!fs3__default.default.existsSync(pkgJsonPath)) {
608
+ const pkgJsonPath = path2__default.default.join(projectRoot, "package.json");
609
+ if (!fs12__default.default.existsSync(pkgJsonPath)) {
471
610
  return requiredPackages;
472
611
  }
473
612
  try {
474
- const pkg = JSON.parse(fs3__default.default.readFileSync(pkgJsonPath, "utf-8"));
613
+ const pkg = JSON.parse(fs12__default.default.readFileSync(pkgJsonPath, "utf-8"));
475
614
  const installed = /* @__PURE__ */ new Set([
476
615
  ...Object.keys(pkg.dependencies || {}),
477
616
  ...Object.keys(pkg.devDependencies || {}),
478
617
  ...Object.keys(pkg.peerDependencies || {})
479
618
  ]);
480
- return requiredPackages.filter((p15) => {
481
- const nameOnly = p15.startsWith("@") ? `@${p15.slice(1).split("@")[0] || ""}` : p15.split("@")[0] || p15;
619
+ return requiredPackages.filter((p17) => {
620
+ const nameOnly = p17.startsWith("@") ? `@${p17.slice(1).split("@")[0] || ""}` : p17.split("@")[0] || p17;
482
621
  return !installed.has(nameOnly);
483
622
  });
484
623
  } catch {
@@ -503,12 +642,47 @@ function installNpmPackages(projectRoot, packages, pm) {
503
642
  installCmd = `npm install ${packages.join(" ")}`;
504
643
  break;
505
644
  }
506
- p4__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc13__default.default.cyan(packages.join(", "))}`);
645
+ p15__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc15__default.default.cyan(packages.join(", "))}`);
507
646
  child_process.execSync(installCmd, {
508
647
  cwd: projectRoot,
509
648
  stdio: "inherit"
510
649
  });
511
650
  }
651
+ function getAllFiles(dir, baseDir = dir) {
652
+ if (!fs12__default.default.existsSync(dir)) return [];
653
+ const entries = fs12__default.default.readdirSync(dir, { withFileTypes: true });
654
+ const files = [];
655
+ for (const entry of entries) {
656
+ const fullPath = path2__default.default.join(dir, entry.name);
657
+ const relPath = path2__default.default.relative(baseDir, fullPath).replace(/\\/g, "/");
658
+ if (entry.isDirectory()) {
659
+ if (entry.name === "__tests__" || entry.name.startsWith(".")) {
660
+ continue;
661
+ }
662
+ files.push(...getAllFiles(fullPath, baseDir));
663
+ } else if (entry.isFile()) {
664
+ if (entry.name === "registry.json" || entry.name.endsWith(".test.ts") || entry.name.endsWith(".spec.ts") || entry.name.startsWith(".")) {
665
+ continue;
666
+ }
667
+ files.push(relPath);
668
+ }
669
+ }
670
+ return files.sort();
671
+ }
672
+ function computeDirectoryHash(dirPath, files) {
673
+ if (!fs12__default.default.existsSync(dirPath)) return "";
674
+ const hash = crypto.createHash("sha256");
675
+ const fileList = (files ?? getAllFiles(dirPath)).sort();
676
+ for (const relativePath of fileList) {
677
+ const absolutePath = path2__default.default.join(dirPath, relativePath);
678
+ if (!fs12__default.default.existsSync(absolutePath)) continue;
679
+ const content = fs12__default.default.readFileSync(absolutePath, "utf8");
680
+ const normalizedContent = content.replace(/\r\n/g, "\n");
681
+ hash.update(relativePath);
682
+ hash.update(normalizedContent);
683
+ }
684
+ return `sha256-${hash.digest("hex")}`;
685
+ }
512
686
  var hbs = Handlebars__default.default.create();
513
687
  hbs.registerHelper("eq", (a, b) => a === b);
514
688
  hbs.registerHelper("ne", (a, b) => a !== b);
@@ -525,33 +699,33 @@ hbs.registerHelper("join", (arr, sep) => {
525
699
  });
526
700
  var cachedTemplatesDir = null;
527
701
  function getTemplatesDir() {
528
- if (cachedTemplatesDir && fs3__default.default.existsSync(cachedTemplatesDir)) {
702
+ if (cachedTemplatesDir && fs12__default.default.existsSync(cachedTemplatesDir)) {
529
703
  return cachedTemplatesDir;
530
704
  }
531
- const currentDir = typeof __dirname !== "undefined" ? __dirname : path7__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
532
- const cand1 = path7__default.default.resolve(currentDir, "../../templates");
533
- if (fs3__default.default.existsSync(cand1)) {
705
+ const currentDir = typeof __dirname !== "undefined" ? __dirname : path2__default.default.dirname(url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))));
706
+ const cand1 = path2__default.default.resolve(currentDir, "../../templates");
707
+ if (fs12__default.default.existsSync(cand1)) {
534
708
  cachedTemplatesDir = cand1;
535
709
  return cand1;
536
710
  }
537
- const cand2 = path7__default.default.resolve(currentDir, "../templates");
538
- if (fs3__default.default.existsSync(cand2)) {
711
+ const cand2 = path2__default.default.resolve(currentDir, "../templates");
712
+ if (fs12__default.default.existsSync(cand2)) {
539
713
  cachedTemplatesDir = cand2;
540
714
  return cand2;
541
715
  }
542
- const cand3 = path7__default.default.resolve(currentDir, "templates");
543
- if (fs3__default.default.existsSync(cand3)) {
716
+ const cand3 = path2__default.default.resolve(currentDir, "templates");
717
+ if (fs12__default.default.existsSync(cand3)) {
544
718
  cachedTemplatesDir = cand3;
545
719
  return cand3;
546
720
  }
547
721
  let curr = currentDir;
548
722
  while (true) {
549
- const candTemplates = path7__default.default.join(curr, "templates");
550
- if (fs3__default.default.existsSync(candTemplates)) {
723
+ const candTemplates = path2__default.default.join(curr, "templates");
724
+ if (fs12__default.default.existsSync(candTemplates)) {
551
725
  cachedTemplatesDir = candTemplates;
552
726
  return candTemplates;
553
727
  }
554
- const parent = path7__default.default.dirname(curr);
728
+ const parent = path2__default.default.dirname(curr);
555
729
  if (parent === curr) break;
556
730
  curr = parent;
557
731
  }
@@ -559,8 +733,8 @@ function getTemplatesDir() {
559
733
  }
560
734
  var templateCache = /* @__PURE__ */ new Map();
561
735
  function loadTemplate(relativePath) {
562
- const fullPath = path7__default.default.isAbsolute(relativePath) ? relativePath : path7__default.default.join(getTemplatesDir(), relativePath);
563
- return fs3__default.default.readFileSync(fullPath, "utf-8");
736
+ const fullPath = path2__default.default.isAbsolute(relativePath) ? relativePath : path2__default.default.join(getTemplatesDir(), relativePath);
737
+ return fs12__default.default.readFileSync(fullPath, "utf-8");
564
738
  }
565
739
  function renderTemplateFile(relativePath, context) {
566
740
  let compiled = templateCache.get(relativePath);
@@ -645,8 +819,8 @@ function generateDomainEntityCode(name, kind = "interface", properties = []) {
645
819
  function generateDomain(options) {
646
820
  const projectRoot = options.projectRoot || findProjectRoot();
647
821
  const entityName = toPascalCase(options.name);
648
- const targetDir = options.targetDir || path7__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
649
- const filePath = path7__default.default.join(targetDir, `${entityName}.ts`);
822
+ const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
823
+ const filePath = path2__default.default.join(targetDir, `${entityName}.ts`);
650
824
  const code = generateDomainEntityCode(
651
825
  entityName,
652
826
  options.kind || "interface",
@@ -670,7 +844,7 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
670
844
  const idKey = `${entityKey}Id`;
671
845
  const listKey = `${entityKey}s`;
672
846
  const formattedCustomMethods = customMethods.map((m) => {
673
- const params = m.params && m.params.length > 0 ? m.params.map((p15) => `${p15.name}: ${p15.type}`).join("; ") : "";
847
+ const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join("; ") : "";
674
848
  const returnType = m.returnType ? `Promise<${m.returnType}>` : "Promise<void>";
675
849
  return {
676
850
  name: m.name,
@@ -695,8 +869,8 @@ function generateInterfaceCode(ifaceName, kind = "abstractClass", entityName, me
695
869
  function generateInterface(options) {
696
870
  const projectRoot = options.projectRoot || findProjectRoot();
697
871
  const ifaceName = ensureInterfacePrefix(options.name);
698
- const targetDir = options.targetDir || path7__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
699
- const filePath = path7__default.default.join(targetDir, `${ifaceName}.ts`);
872
+ const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "domain");
873
+ const filePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
700
874
  const code = generateInterfaceCode(
701
875
  ifaceName,
702
876
  options.kind || "abstractClass",
@@ -931,7 +1105,7 @@ function parseInterfaceMethodsAndImports(content, targetIfaceName) {
931
1105
  };
932
1106
  }
933
1107
  function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
934
- if (customPath && fs3__default.default.existsSync(customPath)) {
1108
+ if (customPath && fs12__default.default.existsSync(customPath)) {
935
1109
  return { filePath: customPath, ifaceName };
936
1110
  }
937
1111
  const candidateNames = [
@@ -941,22 +1115,22 @@ function findInterfaceFile(projectRoot, ifaceName, featureName, customPath) {
941
1115
  ];
942
1116
  const features = featureName ? [featureName, ...listExistingFeatures(projectRoot).filter((f) => f !== featureName)] : listExistingFeatures(projectRoot);
943
1117
  for (const feat of features) {
944
- const domainDir = path7__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
945
- if (!fs3__default.default.existsSync(domainDir)) continue;
1118
+ const domainDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feat, "domain");
1119
+ if (!fs12__default.default.existsSync(domainDir)) continue;
946
1120
  for (const cand of candidateNames) {
947
- const p1 = path7__default.default.join(domainDir, `${cand}.ts`);
948
- if (fs3__default.default.existsSync(p1)) {
1121
+ const p1 = path2__default.default.join(domainDir, `${cand}.ts`);
1122
+ if (fs12__default.default.existsSync(p1)) {
949
1123
  return { filePath: p1, ifaceName: cand, feature: feat };
950
1124
  }
951
1125
  }
952
1126
  }
953
1127
  for (const cand of candidateNames) {
954
- const p1 = path7__default.default.join(projectRoot, "domain", `${cand}.ts`);
955
- if (fs3__default.default.existsSync(p1)) return { filePath: p1, ifaceName: cand };
956
- const p22 = path7__default.default.join(projectRoot, "src", "domain", `${cand}.ts`);
957
- if (fs3__default.default.existsSync(p22)) return { filePath: p22, ifaceName: cand };
958
- const p32 = path7__default.default.join(projectRoot, "interfaces", `${cand}.ts`);
959
- if (fs3__default.default.existsSync(p32)) return { filePath: p32, ifaceName: cand };
1128
+ const p1 = path2__default.default.join(projectRoot, "domain", `${cand}.ts`);
1129
+ if (fs12__default.default.existsSync(p1)) return { filePath: p1, ifaceName: cand };
1130
+ const p22 = path2__default.default.join(projectRoot, "src", "domain", `${cand}.ts`);
1131
+ if (fs12__default.default.existsSync(p22)) return { filePath: p22, ifaceName: cand };
1132
+ const p32 = path2__default.default.join(projectRoot, "interfaces", `${cand}.ts`);
1133
+ if (fs12__default.default.existsSync(p32)) return { filePath: p32, ifaceName: cand };
960
1134
  }
961
1135
  return null;
962
1136
  }
@@ -979,7 +1153,7 @@ function loadInterfaceHierarchy(projectRoot, initialIfaceName, featureName, cust
979
1153
  while (currentFile && !visited.has(currentFile)) {
980
1154
  visited.add(currentFile);
981
1155
  try {
982
- const content = fs3__default.default.readFileSync(currentFile, "utf-8");
1156
+ const content = fs12__default.default.readFileSync(currentFile, "utf-8");
983
1157
  const parsed = parseInterfaceMethodsAndImports(content, currentName);
984
1158
  for (const m of parsed.methods) {
985
1159
  if (!allMethods.some((existing) => existing.name === m.name)) {
@@ -1055,7 +1229,7 @@ function generateStubCode(stubClassName, ifaceName, entityName, featureName, met
1055
1229
  const entitiesVar = `${entityVar}s`;
1056
1230
  const idKey = `${entityVar}Id`;
1057
1231
  const impPath = ifaceImportPath || `../domain/${ifaceName}.js`;
1058
- const entityImportPath = ifaceImportPath ? `${path7__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1232
+ const entityImportPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1059
1233
  const importsByFrom = /* @__PURE__ */ new Map();
1060
1234
  for (const imp of domainImports) {
1061
1235
  if (imp.name === ifaceName || imp.name === baseEntity) continue;
@@ -1104,7 +1278,7 @@ function generateConcreteInfraCode(className, ifaceName, dependencies = [], meth
1104
1278
  if (baseEntity) {
1105
1279
  finalMethods = synthesizeCrudMethods(baseEntity);
1106
1280
  if (!finalDomainImports.some((imp) => imp.name === baseEntity)) {
1107
- const importPath = ifaceImportPath ? `${path7__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1281
+ const importPath = ifaceImportPath ? `${path2__default.default.dirname(ifaceImportPath)}/${baseEntity}.js` : `../domain/${baseEntity}.js`;
1108
1282
  finalDomainImports.push({
1109
1283
  name: baseEntity,
1110
1284
  from: importPath,
@@ -1149,8 +1323,8 @@ function generateInfrastructure(options) {
1149
1323
  const projectRoot = options.projectRoot || findProjectRoot();
1150
1324
  const className = toPascalCase(options.name);
1151
1325
  const isFeatureScope = options.scope !== "global" && Boolean(options.feature);
1152
- const targetDir = options.targetDir || (isFeatureScope ? path7__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path7__default.default.join(projectRoot, "infrastructure"));
1153
- const filePath = path7__default.default.join(targetDir, `${className}.ts`);
1326
+ const targetDir = options.targetDir || (isFeatureScope ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "infrastructure") : path2__default.default.join(projectRoot, "infrastructure"));
1327
+ const filePath = path2__default.default.join(targetDir, `${className}.ts`);
1154
1328
  let code;
1155
1329
  if (options.implementsInterface) {
1156
1330
  const rawIfaceName = options.implementsInterface;
@@ -1164,7 +1338,7 @@ function generateInfrastructure(options) {
1164
1338
  const ifaceFilePath = hierarchy.ifaceFilePath;
1165
1339
  let ifaceImportPath;
1166
1340
  if (ifaceFilePath) {
1167
- let rel = path7__default.default.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
1341
+ let rel = path2__default.default.relative(targetDir, ifaceFilePath).replace(/\\/g, "/").replace(/\.ts$/, ".js");
1168
1342
  if (!rel.startsWith(".")) rel = "./" + rel;
1169
1343
  ifaceImportPath = rel;
1170
1344
  } else if (isFeatureScope) {
@@ -1177,8 +1351,8 @@ function generateInfrastructure(options) {
1177
1351
  if (new RegExp(`\\b${item.name}\\b`).test(allSignatures)) {
1178
1352
  let fromPath;
1179
1353
  if (imp.specifier.startsWith(".") && ifaceFilePath) {
1180
- const absTarget = path7__default.default.resolve(path7__default.default.dirname(ifaceFilePath), imp.specifier);
1181
- let rel = path7__default.default.relative(targetDir, absTarget).replace(/\\/g, "/");
1354
+ const absTarget = path2__default.default.resolve(path2__default.default.dirname(ifaceFilePath), imp.specifier);
1355
+ let rel = path2__default.default.relative(targetDir, absTarget).replace(/\\/g, "/");
1182
1356
  if (!rel.startsWith(".")) rel = "./" + rel;
1183
1357
  fromPath = rel;
1184
1358
  } else {
@@ -1251,22 +1425,22 @@ function generateUseCaseCode(useCaseName, dependencies = [], inputProps = [], ou
1251
1425
  from: /^I[A-Z]/.test(dep) ? `../domain/${dep}.js` : `./${dep}.js`
1252
1426
  }));
1253
1427
  const domainTypesToImport = new Set(domainImports);
1254
- for (const p15 of [...inputProps, ...outputProps]) {
1255
- const rawType = p15.type.trim();
1428
+ for (const p17 of [...inputProps, ...outputProps]) {
1429
+ const rawType = p17.type.trim();
1256
1430
  const baseType = rawType.replace(/\[\]$/, "").trim();
1257
1431
  if (!primitiveTypes.has(baseType) && /^[A-Z][A-Za-z0-9_]*$/.test(baseType) && !dependencies.includes(baseType)) {
1258
1432
  domainTypesToImport.add(baseType);
1259
1433
  }
1260
1434
  }
1261
- const formattedInputProps = inputProps.map((p15) => ({
1262
- name: p15.name,
1263
- optional: p15.optional,
1264
- type: p15.optional && !p15.type.includes("undefined") ? `${p15.type} | undefined` : p15.type
1435
+ const formattedInputProps = inputProps.map((p17) => ({
1436
+ name: p17.name,
1437
+ optional: p17.optional,
1438
+ type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
1265
1439
  }));
1266
- const formattedOutputProps = outputProps.map((p15) => ({
1267
- name: p15.name,
1268
- optional: p15.optional,
1269
- type: p15.optional && !p15.type.includes("undefined") ? `${p15.type} | undefined` : p15.type
1440
+ const formattedOutputProps = outputProps.map((p17) => ({
1441
+ name: p17.name,
1442
+ optional: p17.optional,
1443
+ type: p17.optional && !p17.type.includes("undefined") ? `${p17.type} | undefined` : p17.type
1270
1444
  }));
1271
1445
  const formattedDeps = dependencies.map((dep) => ({
1272
1446
  key: toCamelCase(dep),
@@ -1365,8 +1539,8 @@ function generateUseCase(options) {
1365
1539
  const files = [];
1366
1540
  const projectRoot = options.projectRoot || findProjectRoot();
1367
1541
  const ucName = toPascalCase(options.name);
1368
- const targetDir = options.targetDir || path7__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
1369
- const filePath = path7__default.default.join(targetDir, `${ucName}.ts`);
1542
+ const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "useCases");
1543
+ const filePath = path2__default.default.join(targetDir, `${ucName}.ts`);
1370
1544
  const code = generateUseCaseCode(
1371
1545
  ucName,
1372
1546
  options.dependencies || [],
@@ -1382,7 +1556,7 @@ function generateUseCase(options) {
1382
1556
  })
1383
1557
  );
1384
1558
  if (options.includeTest) {
1385
- const testPath = path7__default.default.join(targetDir, `${ucName}.test.ts`);
1559
+ const testPath = path2__default.default.join(targetDir, `${ucName}.test.ts`);
1386
1560
  const testCode = generateUseCaseTestCode(ucName, options.dependencies || []);
1387
1561
  files.push(
1388
1562
  writeFileSafely(testPath, testCode, {
@@ -1450,10 +1624,10 @@ function generateHttpHandler(options) {
1450
1624
  const files = [];
1451
1625
  const projectRoot = options.projectRoot || findProjectRoot();
1452
1626
  const className = toPascalCase(options.name);
1453
- const pxDir = path7__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
1454
- const handlersDir = path7__default.default.join(pxDir, "handlers");
1455
- const pxIndexPath = path7__default.default.join(pxDir, "index.ts");
1456
- if (!fs3__default.default.existsSync(pxIndexPath)) {
1627
+ const pxDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress");
1628
+ const handlersDir = path2__default.default.join(pxDir, "handlers");
1629
+ const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
1630
+ if (!fs12__default.default.existsSync(pxIndexPath)) {
1457
1631
  const featurePath = options.routePrefix || `/${options.feature.toLowerCase()}`;
1458
1632
  const pxIndexContent = renderTemplate(expressIndexTemplate, {
1459
1633
  routePath: featurePath
@@ -1466,7 +1640,7 @@ function generateHttpHandler(options) {
1466
1640
  })
1467
1641
  );
1468
1642
  }
1469
- const handlerFilePath = path7__default.default.join(handlersDir, `${className}.ts`);
1643
+ const handlerFilePath = path2__default.default.join(handlersDir, `${className}.ts`);
1470
1644
  const code = generateHttpHandlerCode(
1471
1645
  className,
1472
1646
  options.method || "get",
@@ -1487,11 +1661,11 @@ function generateHttpHandler(options) {
1487
1661
  return files;
1488
1662
  }
1489
1663
  function updateOrGenerateDiProvider(featureDir, featureName, bindings = [], options = {}) {
1490
- const providerPath = path7__default.default.join(featureDir, "diProvider.ts");
1664
+ const providerPath = path2__default.default.join(featureDir, "diProvider.ts");
1491
1665
  const pascalFeature = toPascalCase(featureName);
1492
1666
  const providerModuleName = `${pascalFeature}Provider`;
1493
- if (fs3__default.default.existsSync(providerPath) && !options.overwrite) {
1494
- let content = fs3__default.default.readFileSync(providerPath, "utf-8");
1667
+ if (fs12__default.default.existsSync(providerPath) && !options.overwrite) {
1668
+ let content = fs12__default.default.readFileSync(providerPath, "utf-8");
1495
1669
  content = content.replace(/\n*export\s+const\s+ScaffoldProvider\s*=[^;]+;?/g, "");
1496
1670
  for (const b of bindings) {
1497
1671
  const ifaceImp = b.ifaceImportPath || `./domain/${b.ifaceName}.js`;
@@ -1550,7 +1724,7 @@ function generateFeature(options) {
1550
1724
  const projectRoot = options.projectRoot || findProjectRoot();
1551
1725
  const rawName = options.name.trim();
1552
1726
  const featureName = toCamelCase(rawName);
1553
- const targetDir = options.targetDir || path7__default.default.join(resolveFeaturesDir(projectRoot), featureName);
1727
+ const targetDir = options.targetDir || path2__default.default.join(resolveFeaturesDir(projectRoot), featureName);
1554
1728
  const routePath = options.routePath || `/${featureName}`;
1555
1729
  const rawEntities = [];
1556
1730
  if (options.entityNames && options.entityNames.length > 0) {
@@ -1571,7 +1745,7 @@ function generateFeature(options) {
1571
1745
  const domainFile = generateDomain({
1572
1746
  name: ent,
1573
1747
  feature: featureName,
1574
- targetDir: path7__default.default.join(targetDir, "domain"),
1748
+ targetDir: path2__default.default.join(targetDir, "domain"),
1575
1749
  projectRoot,
1576
1750
  dryRun: options.dryRun,
1577
1751
  overwrite: options.overwrite,
@@ -1591,7 +1765,7 @@ function generateFeature(options) {
1591
1765
  const ifaceFile = generateInterface({
1592
1766
  name: ifaceName,
1593
1767
  feature: featureName,
1594
- targetDir: path7__default.default.join(targetDir, "domain"),
1768
+ targetDir: path2__default.default.join(targetDir, "domain"),
1595
1769
  entityName: ent,
1596
1770
  projectRoot,
1597
1771
  dryRun: options.dryRun,
@@ -1608,7 +1782,7 @@ function generateFeature(options) {
1608
1782
  const stubFile = generateInfrastructure({
1609
1783
  name: stubName,
1610
1784
  feature: featureName,
1611
- targetDir: path7__default.default.join(targetDir, "infrastructure"),
1785
+ targetDir: path2__default.default.join(targetDir, "infrastructure"),
1612
1786
  kind: "stub",
1613
1787
  implementsInterface: ifaceName,
1614
1788
  entityName: ent,
@@ -1650,7 +1824,7 @@ function generateFeature(options) {
1650
1824
  const ucFiles = generateUseCase({
1651
1825
  name: ucName,
1652
1826
  feature: featureName,
1653
- targetDir: path7__default.default.join(targetDir, "useCases"),
1827
+ targetDir: path2__default.default.join(targetDir, "useCases"),
1654
1828
  dependencies: options.includeInterface !== false ? [ifaceName] : [],
1655
1829
  inputProperties: [{ name: idParam, type: "string" }],
1656
1830
  outputProperties: [{ name: entVar, type: primaryEntity }],
@@ -1695,7 +1869,7 @@ function generateFeature(options) {
1695
1869
  const ucFiles = generateUseCase({
1696
1870
  name: ucName,
1697
1871
  feature: featureName,
1698
- targetDir: path7__default.default.join(targetDir, "useCases"),
1872
+ targetDir: path2__default.default.join(targetDir, "useCases"),
1699
1873
  dependencies: deps,
1700
1874
  inputProperties: [{ name: entVar, type: ent }],
1701
1875
  outputProperties: [{ name: entVar, type: ent }],
@@ -1728,7 +1902,7 @@ function generateFeature(options) {
1728
1902
  const ucFiles = generateUseCase({
1729
1903
  name: ucName,
1730
1904
  feature: featureName,
1731
- targetDir: path7__default.default.join(targetDir, "useCases"),
1905
+ targetDir: path2__default.default.join(targetDir, "useCases"),
1732
1906
  dependencies: deps,
1733
1907
  inputProperties: [{ name: idParam, type: "string" }],
1734
1908
  outputProperties: [{ name: entVar, type: ent }],
@@ -1761,7 +1935,7 @@ function generateFeature(options) {
1761
1935
  const ucFiles = generateUseCase({
1762
1936
  name: ucName,
1763
1937
  feature: featureName,
1764
- targetDir: path7__default.default.join(targetDir, "useCases"),
1938
+ targetDir: path2__default.default.join(targetDir, "useCases"),
1765
1939
  dependencies: deps,
1766
1940
  inputProperties: [
1767
1941
  { name: "page", type: "number", optional: true },
@@ -1805,7 +1979,7 @@ function generateFeature(options) {
1805
1979
  const ucFiles = generateUseCase({
1806
1980
  name: ucName,
1807
1981
  feature: featureName,
1808
- targetDir: path7__default.default.join(targetDir, "useCases"),
1982
+ targetDir: path2__default.default.join(targetDir, "useCases"),
1809
1983
  dependencies: deps,
1810
1984
  inputProperties: [{ name: entVar, type: ent }],
1811
1985
  outputProperties: [{ name: entVar, type: ent }],
@@ -1838,7 +2012,7 @@ function generateFeature(options) {
1838
2012
  const ucFiles = generateUseCase({
1839
2013
  name: ucName,
1840
2014
  feature: featureName,
1841
- targetDir: path7__default.default.join(targetDir, "useCases"),
2015
+ targetDir: path2__default.default.join(targetDir, "useCases"),
1842
2016
  dependencies: deps,
1843
2017
  inputProperties: [{ name: idParam, type: "string" }],
1844
2018
  outputProperties: [{ name: "success", type: "boolean" }],
@@ -1875,7 +2049,7 @@ function generateFeature(options) {
1875
2049
  const ucFiles = generateUseCase({
1876
2050
  name: ucName,
1877
2051
  feature: featureName,
1878
- targetDir: path7__default.default.join(targetDir, "useCases"),
2052
+ targetDir: path2__default.default.join(targetDir, "useCases"),
1879
2053
  dependencies: customUc.dependencies || [],
1880
2054
  inputProperties: customUc.inputProperties || [],
1881
2055
  outputProperties: customUc.outputProperties || [],
@@ -1907,8 +2081,8 @@ function generateFeature(options) {
1907
2081
  }
1908
2082
  }
1909
2083
  if (options.includeExpress !== false) {
1910
- const pxDir = path7__default.default.join(targetDir, "pxExpress");
1911
- const pxIndexPath = path7__default.default.join(pxDir, "index.ts");
2084
+ const pxDir = path2__default.default.join(targetDir, "pxExpress");
2085
+ const pxIndexPath = path2__default.default.join(pxDir, "index.ts");
1912
2086
  if (!files.some((f) => f.absolutePath === pxIndexPath)) {
1913
2087
  const pxIndexContent = renderTemplate(expressIndexTemplate, { routePath });
1914
2088
  files.push(
@@ -1934,13 +2108,14 @@ function generateFeature(options) {
1934
2108
  // src/commands/feature.ts
1935
2109
  async function runFeatureCommand(featureNameArg, flags = {}) {
1936
2110
  const projectRoot = findProjectRoot();
2111
+ const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
1937
2112
  let name = featureNameArg?.trim();
1938
2113
  if (!name && flags.yes) {
1939
2114
  name = "feature";
1940
2115
  }
1941
2116
  if (!name) {
1942
2117
  name = handleCancel(
1943
- await p4__namespace.text({
2118
+ await p15__namespace.text({
1944
2119
  message: "What is the feature name?",
1945
2120
  placeholder: "e.g. users, auth, orders, billing",
1946
2121
  validate: (val) => {
@@ -1951,18 +2126,19 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
1951
2126
  ).trim();
1952
2127
  }
1953
2128
  const normalizedName = toCamelCase(name);
1954
- const defaultDir = path7__default.default.relative(projectRoot, path7__default.default.join(resolveFeaturesDir(projectRoot), normalizedName));
2129
+ const baseFeaturesDir = resolveFeaturesDir(projectRoot, flags);
2130
+ const defaultDir = path2__default.default.relative(projectRoot, path2__default.default.join(baseFeaturesDir, normalizedName));
1955
2131
  let targetDirRel = defaultDir;
1956
- if (!flags.yes && !flags.target) {
2132
+ if (!flags.yes && !flags.target && !flags.dest) {
1957
2133
  targetDirRel = handleCancel(
1958
- await p4__namespace.text({
2134
+ await p15__namespace.text({
1959
2135
  message: "Target directory for the feature:",
1960
2136
  initialValue: defaultDir,
1961
2137
  defaultValue: defaultDir
1962
2138
  })
1963
2139
  ).trim();
1964
2140
  }
1965
- const targetDir = flags.target ? path7__default.default.resolve(projectRoot, flags.target) : path7__default.default.resolve(projectRoot, targetDirRel);
2141
+ const targetDir = flags.dest ? path2__default.default.resolve(projectRoot, flags.dest) : flags.target ? path2__default.default.resolve(projectRoot, flags.target) : path2__default.default.resolve(projectRoot, targetDirRel);
1966
2142
  let includeExpress = flags.express !== void 0 ? Boolean(flags.express) : true;
1967
2143
  let routePath = `/${normalizedName}`;
1968
2144
  let includeDomain = true;
@@ -1977,14 +2153,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
1977
2153
  const customUseCases = [];
1978
2154
  if (!flags.yes) {
1979
2155
  includeExpress = handleCancel(
1980
- await p4__namespace.confirm({
2156
+ await p15__namespace.confirm({
1981
2157
  message: "Include Express Presentation layer (pxExpress routes & handlers)?",
1982
2158
  initialValue: true
1983
2159
  })
1984
2160
  );
1985
2161
  if (includeExpress) {
1986
2162
  routePath = handleCancel(
1987
- await p4__namespace.text({
2163
+ await p15__namespace.text({
1988
2164
  message: "Express route prefix:",
1989
2165
  initialValue: `/${normalizedName}`,
1990
2166
  defaultValue: `/${normalizedName}`
@@ -1992,14 +2168,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
1992
2168
  ).trim();
1993
2169
  }
1994
2170
  includeDomain = handleCancel(
1995
- await p4__namespace.confirm({
2171
+ await p15__namespace.confirm({
1996
2172
  message: "Include Domain Entity/Entities?",
1997
2173
  initialValue: true
1998
2174
  })
1999
2175
  );
2000
2176
  if (includeDomain) {
2001
2177
  const domainInput = handleCancel(
2002
- await p4__namespace.text({
2178
+ await p15__namespace.text({
2003
2179
  message: "Domain entity name(s) (comma-separated):",
2004
2180
  initialValue: defaultEntity,
2005
2181
  defaultValue: defaultEntity,
@@ -2018,21 +2194,21 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2018
2194
  entityNames = [defaultEntity];
2019
2195
  }
2020
2196
  includeInterface = handleCancel(
2021
- await p4__namespace.confirm({
2197
+ await p15__namespace.confirm({
2022
2198
  message: `Include Domain Repository interface(s) for ${entityNames.join(", ")}?`,
2023
2199
  initialValue: true
2024
2200
  })
2025
2201
  );
2026
2202
  if (includeInterface) {
2027
2203
  includeInfra = handleCancel(
2028
- await p4__namespace.confirm({
2204
+ await p15__namespace.confirm({
2029
2205
  message: `Include Infrastructure Stub implementation(s) for ${entityNames.join(", ")}?`,
2030
2206
  initialValue: true
2031
2207
  })
2032
2208
  );
2033
2209
  }
2034
2210
  includeUseCase = handleCancel(
2035
- await p4__namespace.confirm({
2211
+ await p15__namespace.confirm({
2036
2212
  message: "Include Domain Use Cases?",
2037
2213
  initialValue: true
2038
2214
  })
@@ -2046,7 +2222,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2046
2222
  { value: "delete", label: "Delete (Delete<Entity>)", hint: "DELETE /:id" }
2047
2223
  ];
2048
2224
  crudOperations = handleCancel(
2049
- await p4__namespace.multiselect({
2225
+ await p15__namespace.multiselect({
2050
2226
  message: "Select standard CRUD use cases to generate:",
2051
2227
  options: crudChoices,
2052
2228
  initialValues: ["create", "read", "list", "update", "delete"],
@@ -2054,13 +2230,13 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2054
2230
  })
2055
2231
  );
2056
2232
  includeTests = handleCancel(
2057
- await p4__namespace.confirm({
2233
+ await p15__namespace.confirm({
2058
2234
  message: "Generate Vitest unit tests for use cases?",
2059
2235
  initialValue: true
2060
2236
  })
2061
2237
  );
2062
2238
  const wantCustom = handleCancel(
2063
- await p4__namespace.confirm({
2239
+ await p15__namespace.confirm({
2064
2240
  message: "Do you want to add any custom use cases to this feature?",
2065
2241
  initialValue: false
2066
2242
  })
@@ -2070,7 +2246,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2070
2246
  const availableIfaces = entityNames.map((e) => `I${e}Repository`);
2071
2247
  while (addMore) {
2072
2248
  const customName = handleCancel(
2073
- await p4__namespace.text({
2249
+ await p15__namespace.text({
2074
2250
  message: "Custom Use Case name:",
2075
2251
  placeholder: "e.g. ResetPassword, VerifyEmail, AssignRole",
2076
2252
  validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
@@ -2080,7 +2256,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2080
2256
  let selectedDeps = [];
2081
2257
  if (availableIfaces.length > 0) {
2082
2258
  selectedDeps = handleCancel(
2083
- await p4__namespace.multiselect({
2259
+ await p15__namespace.multiselect({
2084
2260
  message: `Select domain interfaces to inject into ${customUcName}:`,
2085
2261
  options: availableIfaces.map((i) => ({ value: i, label: i })),
2086
2262
  required: false
@@ -2089,7 +2265,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2089
2265
  }
2090
2266
  let customInputProps = [];
2091
2267
  const hasInput = handleCancel(
2092
- await p4__namespace.confirm({
2268
+ await p15__namespace.confirm({
2093
2269
  message: `Does ${customUcName} accept input arguments?`,
2094
2270
  initialValue: true
2095
2271
  })
@@ -2100,7 +2276,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2100
2276
  }
2101
2277
  let customOutputProps = [];
2102
2278
  const hasCustomOutput = handleCancel(
2103
- await p4__namespace.confirm({
2279
+ await p15__namespace.confirm({
2104
2280
  message: `Does ${customUcName} return custom output data?`,
2105
2281
  initialValue: false
2106
2282
  })
@@ -2110,7 +2286,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2110
2286
  });
2111
2287
  }
2112
2288
  const customIncludeTest = handleCancel(
2113
- await p4__namespace.confirm({
2289
+ await p15__namespace.confirm({
2114
2290
  message: `Generate Vitest unit test for ${customUcName}?`,
2115
2291
  initialValue: true
2116
2292
  })
@@ -2120,14 +2296,14 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2120
2296
  let customHttpPath = `/${toKebabCase(customUcName)}`;
2121
2297
  if (includeExpress) {
2122
2298
  customCreateHttp = handleCancel(
2123
- await p4__namespace.confirm({
2299
+ await p15__namespace.confirm({
2124
2300
  message: `Generate a matching ExpressRoute HTTP handler for ${customUcName}?`,
2125
2301
  initialValue: true
2126
2302
  })
2127
2303
  );
2128
2304
  if (customCreateHttp) {
2129
2305
  customHttpMethod = handleCancel(
2130
- await p4__namespace.select({
2306
+ await p15__namespace.select({
2131
2307
  message: "HTTP method for route:",
2132
2308
  options: [
2133
2309
  { value: "post", label: "POST" },
@@ -2140,7 +2316,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2140
2316
  })
2141
2317
  );
2142
2318
  customHttpPath = handleCancel(
2143
- await p4__namespace.text({
2319
+ await p15__namespace.text({
2144
2320
  message: "Route subpath (relative to feature prefix):",
2145
2321
  initialValue: `/${toKebabCase(customUcName)}`,
2146
2322
  defaultValue: `/${toKebabCase(customUcName)}`
@@ -2161,7 +2337,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2161
2337
  validationFields: customInputProps
2162
2338
  });
2163
2339
  addMore = handleCancel(
2164
- await p4__namespace.confirm({
2340
+ await p15__namespace.confirm({
2165
2341
  message: "Add another custom use case?",
2166
2342
  initialValue: false
2167
2343
  })
@@ -2170,7 +2346,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2170
2346
  }
2171
2347
  }
2172
2348
  includeDiProvider = handleCancel(
2173
- await p4__namespace.confirm({
2349
+ await p15__namespace.confirm({
2174
2350
  message: "Generate / update feature DI provider (diProvider.ts)?",
2175
2351
  initialValue: true
2176
2352
  })
@@ -2182,8 +2358,8 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2182
2358
  entityNames = [defaultEntity];
2183
2359
  }
2184
2360
  }
2185
- const s = p4__namespace.spinner();
2186
- s.start(`Generating feature '${pc13__default.default.cyan(normalizedName)}'...`);
2361
+ const s = p15__namespace.spinner();
2362
+ s.start(`Generating feature '${pc15__default.default.cyan(normalizedName)}'...`);
2187
2363
  const options = {
2188
2364
  name: normalizedName,
2189
2365
  projectRoot,
@@ -2203,9 +2379,207 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2203
2379
  includeDiProvider
2204
2380
  };
2205
2381
  const files = generateFeature(options);
2382
+ if (isRegistry) {
2383
+ const registryPath = path2__default.default.join(targetDir, "registry.json");
2384
+ if (!fs12__default.default.existsSync(registryPath)) {
2385
+ const manifest = {
2386
+ name: toKebabCase(normalizedName),
2387
+ type: "feature",
2388
+ description: flags.description || `${normalizedName} feature module`,
2389
+ version: "1.0.0",
2390
+ dependencies: {
2391
+ npm: includeExpress ? ["@solid-stack/agnos-express", "express"] : [],
2392
+ shared: [],
2393
+ features: []
2394
+ },
2395
+ files: []
2396
+ };
2397
+ const content = JSON.stringify(manifest, null, 2) + "\n";
2398
+ const genFile = writeFileSafely(registryPath, content, {
2399
+ projectRoot,
2400
+ overwrite: options.overwrite,
2401
+ dryRun: options.dryRun
2402
+ });
2403
+ files.push(genFile);
2404
+ }
2405
+ }
2206
2406
  s.stop("Feature scaffolded successfully!");
2207
2407
  printGeneratedFiles(files, options.dryRun);
2208
- p4__namespace.outro(pc13__default.default.green(`Feature '${normalizedName}' ready to use!`));
2408
+ p15__namespace.outro(
2409
+ pc15__default.default.green(
2410
+ `\u2713 Feature '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2411
+ )
2412
+ );
2413
+ }
2414
+ function generateShared(options) {
2415
+ const files = [];
2416
+ const projectRoot = options.projectRoot || findProjectRoot();
2417
+ const rawName = options.name.trim();
2418
+ const pascalName = toPascalCase(rawName);
2419
+ const camelName = toCamelCase(rawName);
2420
+ const kebabName = toKebabCase(rawName);
2421
+ const targetDir = options.targetDir || path2__default.default.join(resolveSharedDir(projectRoot), kebabName);
2422
+ const fileDefs = [];
2423
+ const facadeContent = `import { MakeInjectable, type DepsType } from "@solid-stack/di";
2424
+ import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
2425
+
2426
+ @MakeInjectable
2427
+ export class ${pascalName} {
2428
+ public static deps = { ${camelName}Engine: I${pascalName}Engine };
2429
+ constructor(public readonly deps: DepsType<typeof ${pascalName}.deps>) {}
2430
+ }
2431
+ `;
2432
+ fileDefs.push({
2433
+ absolutePath: path2__default.default.join(targetDir, `${pascalName}.ts`),
2434
+ content: facadeContent
2435
+ });
2436
+ const portContent = `export abstract class I${pascalName}Engine {
2437
+ // Define abstract port contracts for ${pascalName} here
2438
+ }
2439
+ `;
2440
+ fileDefs.push({
2441
+ absolutePath: path2__default.default.join(targetDir, "ports", `I${pascalName}Engine.ts`),
2442
+ content: portContent
2443
+ });
2444
+ const concreteContent = `import { MakeInjectable } from "@solid-stack/di";
2445
+ import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
2446
+
2447
+ @MakeInjectable
2448
+ export class Default${pascalName}Engine implements I${pascalName}Engine {
2449
+ // Default concrete implementation for production
2450
+ }
2451
+ `;
2452
+ fileDefs.push({
2453
+ absolutePath: path2__default.default.join(targetDir, "infrastructure", `Default${pascalName}Engine.ts`),
2454
+ content: concreteContent
2455
+ });
2456
+ if (options.includeStub !== false) {
2457
+ const stubContent = `import { MakeInjectable } from "@solid-stack/di";
2458
+ import { I${pascalName}Engine } from "../ports/I${pascalName}Engine.js";
2459
+
2460
+ @MakeInjectable
2461
+ export class Stub${pascalName}Engine implements I${pascalName}Engine {
2462
+ // In-memory or stub implementation for testing
2463
+ }
2464
+ `;
2465
+ fileDefs.push({
2466
+ absolutePath: path2__default.default.join(targetDir, "infrastructure", `Stub${pascalName}Engine.ts`),
2467
+ content: stubContent
2468
+ });
2469
+ }
2470
+ const providerContent = `import type { Container } from "@solid-stack/di";
2471
+ import { I${pascalName}Engine } from "./ports/I${pascalName}Engine.js";
2472
+ import { Default${pascalName}Engine } from "./infrastructure/Default${pascalName}Engine.js";
2473
+
2474
+ export const diProvider = (c: Container): void => {
2475
+ c.provide(I${pascalName}Engine, Default${pascalName}Engine);
2476
+ };
2477
+
2478
+ export default diProvider;
2479
+ `;
2480
+ fileDefs.push({
2481
+ absolutePath: path2__default.default.join(targetDir, "diProvider.ts"),
2482
+ content: providerContent
2483
+ });
2484
+ if (options.isRegistry) {
2485
+ const registryManifest = {
2486
+ name: kebabName,
2487
+ type: "shared",
2488
+ description: options.description || `${kebabName} shared module`,
2489
+ version: "1.0.0",
2490
+ dependencies: {
2491
+ npm: [],
2492
+ shared: [],
2493
+ features: []
2494
+ },
2495
+ files: []
2496
+ };
2497
+ fileDefs.push({
2498
+ absolutePath: path2__default.default.join(targetDir, "registry.json"),
2499
+ content: JSON.stringify(registryManifest, null, 2) + "\n"
2500
+ });
2501
+ }
2502
+ for (const def of fileDefs) {
2503
+ const genFile = writeFileSafely(def.absolutePath, def.content, {
2504
+ projectRoot,
2505
+ overwrite: options.overwrite,
2506
+ dryRun: options.dryRun
2507
+ });
2508
+ files.push(genFile);
2509
+ }
2510
+ return files;
2511
+ }
2512
+
2513
+ // src/commands/shared.ts
2514
+ async function runSharedCommand(sharedNameArg, flags = {}) {
2515
+ const projectRoot = findProjectRoot();
2516
+ const isRegistry = getEffectiveMode(projectRoot, flags) === "registry";
2517
+ let name = sharedNameArg?.trim();
2518
+ if (!name && flags.yes) {
2519
+ name = "shared";
2520
+ }
2521
+ if (!name) {
2522
+ name = handleCancel(
2523
+ await p15__namespace.text({
2524
+ message: "What is the shared module name?",
2525
+ placeholder: "e.g. hasher, time, uuid, cache, logger",
2526
+ validate: (val) => {
2527
+ if (!val || !val.trim()) return "Shared module name cannot be empty.";
2528
+ return void 0;
2529
+ }
2530
+ })
2531
+ ).trim();
2532
+ }
2533
+ const normalizedName = toKebabCase(name);
2534
+ const baseSharedDir = resolveSharedDir(projectRoot, flags);
2535
+ const defaultDir = path2__default.default.relative(projectRoot, path2__default.default.join(baseSharedDir, normalizedName));
2536
+ let targetDirRel = defaultDir;
2537
+ if (!flags.yes && !flags.target && !flags.dest) {
2538
+ targetDirRel = handleCancel(
2539
+ await p15__namespace.text({
2540
+ message: "Target directory for the shared module:",
2541
+ initialValue: defaultDir,
2542
+ defaultValue: defaultDir
2543
+ })
2544
+ ).trim();
2545
+ }
2546
+ const targetDir = flags.dest ? path2__default.default.resolve(projectRoot, flags.dest) : flags.target ? path2__default.default.resolve(projectRoot, flags.target) : path2__default.default.resolve(projectRoot, targetDirRel);
2547
+ let description = flags.description || "";
2548
+ let includeStub = true;
2549
+ if (!flags.yes) {
2550
+ description = handleCancel(
2551
+ await p15__namespace.text({
2552
+ message: "Module description (optional):",
2553
+ placeholder: "e.g. In-memory caching and Redis port abstraction",
2554
+ initialValue: ""
2555
+ })
2556
+ ).trim();
2557
+ includeStub = handleCancel(
2558
+ await p15__namespace.confirm({
2559
+ message: "Include stub implementation in infrastructure/?",
2560
+ initialValue: true
2561
+ })
2562
+ );
2563
+ }
2564
+ const s = p15__namespace.spinner();
2565
+ s.start(`Generating shared module '${pc15__default.default.cyan(normalizedName)}'...`);
2566
+ const files = generateShared({
2567
+ name: normalizedName,
2568
+ description,
2569
+ projectRoot,
2570
+ targetDir,
2571
+ dryRun: Boolean(flags.dryRun),
2572
+ overwrite: Boolean(flags.overwrite),
2573
+ includeStub,
2574
+ isRegistry
2575
+ });
2576
+ s.stop("Shared module generated successfully!");
2577
+ printGeneratedFiles(files, Boolean(flags.dryRun));
2578
+ p15__namespace.outro(
2579
+ pc15__default.default.green(
2580
+ `\u2713 Shared module '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2581
+ )
2582
+ );
2209
2583
  }
2210
2584
  async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2211
2585
  const projectRoot = findProjectRoot();
@@ -2216,14 +2590,14 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2216
2590
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2217
2591
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2218
2592
  const selected = handleCancel(
2219
- await p4__namespace.select({
2593
+ await p15__namespace.select({
2220
2594
  message: "Select target feature:",
2221
2595
  options: featureOptions
2222
2596
  })
2223
2597
  );
2224
2598
  if (selected === "__custom__") {
2225
2599
  feature = handleCancel(
2226
- await p4__namespace.text({
2600
+ await p15__namespace.text({
2227
2601
  message: "Enter feature name:",
2228
2602
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2229
2603
  })
@@ -2233,7 +2607,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2233
2607
  }
2234
2608
  } else {
2235
2609
  feature = handleCancel(
2236
- await p4__namespace.text({
2610
+ await p15__namespace.text({
2237
2611
  message: "Enter target feature name:",
2238
2612
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2239
2613
  })
@@ -2244,7 +2618,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2244
2618
  let name = useCaseNameArg?.trim();
2245
2619
  if (!name) {
2246
2620
  name = handleCancel(
2247
- await p4__namespace.text({
2621
+ await p15__namespace.text({
2248
2622
  message: "Use Case name:",
2249
2623
  placeholder: "e.g. CreateUser, GetUser, CancelOrder",
2250
2624
  validate: (v) => v && v.trim() ? void 0 : "Use Case name cannot be empty"
@@ -2260,7 +2634,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2260
2634
  label: i.name
2261
2635
  }));
2262
2636
  selectedDeps = handleCancel(
2263
- await p4__namespace.multiselect({
2637
+ await p15__namespace.multiselect({
2264
2638
  message: "Select domain interfaces to inject into use case:",
2265
2639
  options: multiOptions,
2266
2640
  required: false
@@ -2275,7 +2649,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2275
2649
  let httpPath = "/";
2276
2650
  if (!flags.yes) {
2277
2651
  const hasInput = handleCancel(
2278
- await p4__namespace.confirm({
2652
+ await p15__namespace.confirm({
2279
2653
  message: "Does this use case accept input arguments?",
2280
2654
  initialValue: true
2281
2655
  })
@@ -2285,7 +2659,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2285
2659
  });
2286
2660
  }
2287
2661
  const hasCustomOutput = handleCancel(
2288
- await p4__namespace.confirm({
2662
+ await p15__namespace.confirm({
2289
2663
  message: "Does this use case return custom output data?",
2290
2664
  initialValue: false
2291
2665
  })
@@ -2295,20 +2669,20 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2295
2669
  });
2296
2670
  }
2297
2671
  includeTest = handleCancel(
2298
- await p4__namespace.confirm({
2672
+ await p15__namespace.confirm({
2299
2673
  message: "Generate Vitest unit test skeleton (.test.ts)?",
2300
2674
  initialValue: true
2301
2675
  })
2302
2676
  );
2303
2677
  createHttp = handleCancel(
2304
- await p4__namespace.confirm({
2678
+ await p15__namespace.confirm({
2305
2679
  message: "Generate a matching ExpressRoute HTTP handler?",
2306
2680
  initialValue: false
2307
2681
  })
2308
2682
  );
2309
2683
  if (createHttp) {
2310
2684
  httpMethod = handleCancel(
2311
- await p4__namespace.select({
2685
+ await p15__namespace.select({
2312
2686
  message: "HTTP method for route:",
2313
2687
  options: [
2314
2688
  { value: "post", label: "POST" },
@@ -2321,7 +2695,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2321
2695
  })
2322
2696
  );
2323
2697
  httpPath = handleCancel(
2324
- await p4__namespace.text({
2698
+ await p15__namespace.text({
2325
2699
  message: "Route subpath (relative to feature prefix):",
2326
2700
  initialValue: "/",
2327
2701
  defaultValue: "/"
@@ -2329,8 +2703,8 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2329
2703
  ).trim();
2330
2704
  }
2331
2705
  }
2332
- const s = p4__namespace.spinner();
2333
- s.start(`Generating use case '${pc13__default.default.cyan(ucName)}'...`);
2706
+ const s = p15__namespace.spinner();
2707
+ s.start(`Generating use case '${pc15__default.default.cyan(ucName)}'...`);
2334
2708
  const options = {
2335
2709
  name: ucName,
2336
2710
  feature,
@@ -2362,7 +2736,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2362
2736
  }
2363
2737
  s.stop("Use case generated successfully!");
2364
2738
  printGeneratedFiles(files, options.dryRun);
2365
- p4__namespace.outro(pc13__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
2739
+ p15__namespace.outro(pc15__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
2366
2740
  }
2367
2741
  async function runDomainCommand(domainNameArg, flags = {}) {
2368
2742
  const projectRoot = findProjectRoot();
@@ -2373,14 +2747,14 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2373
2747
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2374
2748
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2375
2749
  const selected = handleCancel(
2376
- await p4__namespace.select({
2750
+ await p15__namespace.select({
2377
2751
  message: "Select target feature:",
2378
2752
  options: featureOptions
2379
2753
  })
2380
2754
  );
2381
2755
  if (selected === "__custom__") {
2382
2756
  feature = handleCancel(
2383
- await p4__namespace.text({
2757
+ await p15__namespace.text({
2384
2758
  message: "Enter feature name:",
2385
2759
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2386
2760
  })
@@ -2390,7 +2764,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2390
2764
  }
2391
2765
  } else {
2392
2766
  feature = handleCancel(
2393
- await p4__namespace.text({
2767
+ await p15__namespace.text({
2394
2768
  message: "Enter target feature name:",
2395
2769
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2396
2770
  })
@@ -2401,7 +2775,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2401
2775
  let name = domainNameArg?.trim();
2402
2776
  if (!name) {
2403
2777
  name = handleCancel(
2404
- await p4__namespace.text({
2778
+ await p15__namespace.text({
2405
2779
  message: "Domain model / entity name:",
2406
2780
  placeholder: "e.g. User, Order, Product, Address",
2407
2781
  validate: (v) => v && v.trim() ? void 0 : "Domain name cannot be empty"
@@ -2413,7 +2787,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2413
2787
  let properties = [];
2414
2788
  if (!flags.yes) {
2415
2789
  kind = handleCancel(
2416
- await p4__namespace.select({
2790
+ await p15__namespace.select({
2417
2791
  message: "Select domain model type:",
2418
2792
  options: [
2419
2793
  { value: "interface", label: "TypeScript Interface (pure data structure)" },
@@ -2432,8 +2806,8 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2432
2806
  defaults: kind === "valueObject" ? [{ name: "value", type: "string" }] : defaultProps
2433
2807
  });
2434
2808
  }
2435
- const s = p4__namespace.spinner();
2436
- s.start(`Generating domain model '${pc13__default.default.cyan(entityName)}'...`);
2809
+ const s = p15__namespace.spinner();
2810
+ s.start(`Generating domain model '${pc15__default.default.cyan(entityName)}'...`);
2437
2811
  const options = {
2438
2812
  name: entityName,
2439
2813
  feature,
@@ -2446,7 +2820,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2446
2820
  const file = generateDomain(options);
2447
2821
  s.stop("Domain model generated successfully!");
2448
2822
  printGeneratedFiles([file], options.dryRun);
2449
- p4__namespace.outro(pc13__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2823
+ p15__namespace.outro(pc15__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2450
2824
  }
2451
2825
  async function runInfrastructureCommand(classNameArg, flags = {}) {
2452
2826
  const projectRoot = findProjectRoot();
@@ -2455,7 +2829,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2455
2829
  let feature = flags.feature?.trim();
2456
2830
  if (!flags.yes && !flags.global && !flags.feature) {
2457
2831
  scope = handleCancel(
2458
- await p4__namespace.select({
2832
+ await p15__namespace.select({
2459
2833
  message: "Where should this infrastructure implementation be placed?",
2460
2834
  options: [
2461
2835
  { value: "feature", label: "Feature-level (features/<feature>/infrastructure)" },
@@ -2470,14 +2844,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2470
2844
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2471
2845
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2472
2846
  const selected = handleCancel(
2473
- await p4__namespace.select({
2847
+ await p15__namespace.select({
2474
2848
  message: "Select target feature:",
2475
2849
  options: featureOptions
2476
2850
  })
2477
2851
  );
2478
2852
  if (selected === "__custom__") {
2479
2853
  feature = handleCancel(
2480
- await p4__namespace.text({
2854
+ await p15__namespace.text({
2481
2855
  message: "Enter feature name:",
2482
2856
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2483
2857
  })
@@ -2487,7 +2861,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2487
2861
  }
2488
2862
  } else {
2489
2863
  feature = handleCancel(
2490
- await p4__namespace.text({
2864
+ await p15__namespace.text({
2491
2865
  message: "Enter target feature name:",
2492
2866
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2493
2867
  })
@@ -2500,7 +2874,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2500
2874
  let name = classNameArg?.trim();
2501
2875
  if (!name) {
2502
2876
  name = handleCancel(
2503
- await p4__namespace.text({
2877
+ await p15__namespace.text({
2504
2878
  message: "Infrastructure class name:",
2505
2879
  placeholder: "e.g. StubUserRepository, PostgresUserRepository, S3FileStorage",
2506
2880
  validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
@@ -2519,7 +2893,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2519
2893
  (i) => className.toLowerCase().includes(removeInterfacePrefix(i.name).toLowerCase())
2520
2894
  );
2521
2895
  const ifaceChoice = handleCancel(
2522
- await p4__namespace.select({
2896
+ await p15__namespace.select({
2523
2897
  message: "Does this class implement a domain interface or abstract class?",
2524
2898
  options: [
2525
2899
  ...interfaces.map((i) => ({ value: i.name, label: i.name })),
@@ -2531,7 +2905,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2531
2905
  );
2532
2906
  if (ifaceChoice === "__custom__") {
2533
2907
  implementsInterface = handleCancel(
2534
- await p4__namespace.text({
2908
+ await p15__namespace.text({
2535
2909
  message: "Enter interface or abstract class name:",
2536
2910
  placeholder: "e.g. IUserRepository, BaseStorageAdapter",
2537
2911
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2542,14 +2916,14 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2542
2916
  }
2543
2917
  } else {
2544
2918
  const wantImpl = handleCancel(
2545
- await p4__namespace.confirm({
2919
+ await p15__namespace.confirm({
2546
2920
  message: "Does this class implement a domain interface or abstract class?",
2547
2921
  initialValue: false
2548
2922
  })
2549
2923
  );
2550
2924
  if (wantImpl) {
2551
2925
  implementsInterface = handleCancel(
2552
- await p4__namespace.text({
2926
+ await p15__namespace.text({
2553
2927
  message: "Enter interface or abstract class name:",
2554
2928
  placeholder: "e.g. IUserRepository, BaseStorageAdapter",
2555
2929
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2569,7 +2943,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2569
2943
  const allInterfaces = listDomainInterfaces(projectRoot);
2570
2944
  if (allInterfaces.length > 0) {
2571
2945
  const ifaceChoice = handleCancel(
2572
- await p4__namespace.select({
2946
+ await p15__namespace.select({
2573
2947
  message: "Does this class implement an interface or abstract class?",
2574
2948
  options: [
2575
2949
  ...allInterfaces.map((i) => ({ value: i.name, label: `${i.name} (${i.feature})` })),
@@ -2581,7 +2955,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2581
2955
  );
2582
2956
  if (ifaceChoice === "__custom__") {
2583
2957
  implementsInterface = handleCancel(
2584
- await p4__namespace.text({
2958
+ await p15__namespace.text({
2585
2959
  message: "Enter interface or abstract class name:",
2586
2960
  placeholder: "e.g. IAuthRepository",
2587
2961
  validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
@@ -2594,7 +2968,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2594
2968
  }
2595
2969
  if (!flags.yes && !flags.kind) {
2596
2970
  kind = handleCancel(
2597
- await p4__namespace.select({
2971
+ await p15__namespace.select({
2598
2972
  message: "Select implementation kind:",
2599
2973
  options: [
2600
2974
  { value: "stub", label: "In-Memory Stub (with in-memory store and test helper methods)" },
@@ -2605,7 +2979,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2605
2979
  );
2606
2980
  if (scope === "feature" && feature && implementsInterface) {
2607
2981
  registerInProvider = handleCancel(
2608
- await p4__namespace.confirm({
2982
+ await p15__namespace.confirm({
2609
2983
  message: "Register this implementation in feature diProvider.ts?",
2610
2984
  initialValue: true
2611
2985
  })
@@ -2614,8 +2988,8 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2614
2988
  } else if (flags.yes && scope === "feature" && feature && implementsInterface) {
2615
2989
  registerInProvider = !flags.noProvider;
2616
2990
  }
2617
- const s = p4__namespace.spinner();
2618
- s.start(`Generating infrastructure class '${pc13__default.default.cyan(className)}'...`);
2991
+ const s = p15__namespace.spinner();
2992
+ s.start(`Generating infrastructure class '${pc15__default.default.cyan(className)}'...`);
2619
2993
  const files = [];
2620
2994
  const options = {
2621
2995
  name: className,
@@ -2630,7 +3004,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2630
3004
  const infraFile = generateInfrastructure(options);
2631
3005
  files.push(infraFile);
2632
3006
  if (registerInProvider && scope === "feature" && feature && implementsInterface) {
2633
- const featureDir = path7__default.default.join(resolveFeaturesDir(projectRoot), feature);
3007
+ const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
2634
3008
  const providerFile = updateOrGenerateDiProvider(
2635
3009
  featureDir,
2636
3010
  feature,
@@ -2650,7 +3024,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2650
3024
  }
2651
3025
  s.stop("Infrastructure class generated successfully!");
2652
3026
  printGeneratedFiles(files, options.dryRun);
2653
- p4__namespace.outro(pc13__default.default.green(`Infrastructure class '${className}' created!`));
3027
+ p15__namespace.outro(pc15__default.default.green(`Infrastructure class '${className}' created!`));
2654
3028
  }
2655
3029
  async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2656
3030
  const projectRoot = findProjectRoot();
@@ -2661,14 +3035,14 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2661
3035
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2662
3036
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2663
3037
  const selected = handleCancel(
2664
- await p4__namespace.select({
3038
+ await p15__namespace.select({
2665
3039
  message: "Select target feature:",
2666
3040
  options: featureOptions
2667
3041
  })
2668
3042
  );
2669
3043
  if (selected === "__custom__") {
2670
3044
  feature = handleCancel(
2671
- await p4__namespace.text({
3045
+ await p15__namespace.text({
2672
3046
  message: "Enter feature name:",
2673
3047
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2674
3048
  })
@@ -2678,7 +3052,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2678
3052
  }
2679
3053
  } else {
2680
3054
  feature = handleCancel(
2681
- await p4__namespace.text({
3055
+ await p15__namespace.text({
2682
3056
  message: "Enter target feature name:",
2683
3057
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2684
3058
  })
@@ -2689,7 +3063,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2689
3063
  let name = interfaceNameArg?.trim();
2690
3064
  if (!name) {
2691
3065
  name = handleCancel(
2692
- await p4__namespace.text({
3066
+ await p15__namespace.text({
2693
3067
  message: "Domain interface name:",
2694
3068
  placeholder: "e.g. IUserRepository, IPaymentService",
2695
3069
  validate: (v) => v && v.trim() ? void 0 : "Interface name cannot be empty"
@@ -2707,7 +3081,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2707
3081
  if (!flags.yes) {
2708
3082
  if (existingEntities.length > 0) {
2709
3083
  const selectedEntity = handleCancel(
2710
- await p4__namespace.select({
3084
+ await p15__namespace.select({
2711
3085
  message: "Select domain entity associated with this interface:",
2712
3086
  options: [
2713
3087
  ...existingEntities.map((e) => ({ value: e.name, label: e.name })),
@@ -2718,7 +3092,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2718
3092
  );
2719
3093
  if (selectedEntity === "__custom__") {
2720
3094
  entityName = handleCancel(
2721
- await p4__namespace.text({
3095
+ await p15__namespace.text({
2722
3096
  message: "Enter entity name:",
2723
3097
  defaultValue: removeInterfacePrefix(ifaceName).replace(/Repository$|Service$/, "")
2724
3098
  })
@@ -2728,7 +3102,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2728
3102
  }
2729
3103
  }
2730
3104
  kind = handleCancel(
2731
- await p4__namespace.select({
3105
+ await p15__namespace.select({
2732
3106
  message: "Interface definition style:",
2733
3107
  options: [
2734
3108
  { value: "abstractClass", label: "Abstract Class (Recommended - acts as DI token & type)" },
@@ -2738,7 +3112,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2738
3112
  })
2739
3113
  );
2740
3114
  methodsPreset = handleCancel(
2741
- await p4__namespace.select({
3115
+ await p15__namespace.select({
2742
3116
  message: "Method signature preset:",
2743
3117
  options: [
2744
3118
  { value: "crud", label: "Standard CRUD (GetById, List, Create, Update, Delete)" },
@@ -2748,22 +3122,22 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2748
3122
  })
2749
3123
  );
2750
3124
  generateStub = handleCancel(
2751
- await p4__namespace.confirm({
3125
+ await p15__namespace.confirm({
2752
3126
  message: "Auto-generate Infrastructure Stub implementation?",
2753
3127
  initialValue: true
2754
3128
  })
2755
3129
  );
2756
3130
  if (generateStub) {
2757
3131
  registerInProvider = handleCancel(
2758
- await p4__namespace.confirm({
3132
+ await p15__namespace.confirm({
2759
3133
  message: "Auto-register interface & stub in feature diProvider.ts?",
2760
3134
  initialValue: true
2761
3135
  })
2762
3136
  );
2763
3137
  }
2764
3138
  }
2765
- const s = p4__namespace.spinner();
2766
- s.start(`Generating interface '${pc13__default.default.cyan(ifaceName)}'...`);
3139
+ const s = p15__namespace.spinner();
3140
+ s.start(`Generating interface '${pc15__default.default.cyan(ifaceName)}'...`);
2767
3141
  const files = [];
2768
3142
  const options = {
2769
3143
  name: ifaceName,
@@ -2792,7 +3166,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2792
3166
  files.push(stubFile);
2793
3167
  }
2794
3168
  if (registerInProvider && generateStub) {
2795
- const featureDir = path7__default.default.join(resolveFeaturesDir(projectRoot), feature);
3169
+ const featureDir = path2__default.default.join(resolveFeaturesDir(projectRoot), feature);
2796
3170
  const providerFile = updateOrGenerateDiProvider(
2797
3171
  featureDir,
2798
3172
  feature,
@@ -2812,7 +3186,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2812
3186
  }
2813
3187
  s.stop("Interface generated successfully!");
2814
3188
  printGeneratedFiles(files, options.dryRun);
2815
- p4__namespace.outro(pc13__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
3189
+ p15__namespace.outro(pc15__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
2816
3190
  }
2817
3191
  async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2818
3192
  const projectRoot = findProjectRoot();
@@ -2823,14 +3197,14 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2823
3197
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
2824
3198
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
2825
3199
  const selected = handleCancel(
2826
- await p4__namespace.select({
3200
+ await p15__namespace.select({
2827
3201
  message: "Select target feature:",
2828
3202
  options: featureOptions
2829
3203
  })
2830
3204
  );
2831
3205
  if (selected === "__custom__") {
2832
3206
  feature = handleCancel(
2833
- await p4__namespace.text({
3207
+ await p15__namespace.text({
2834
3208
  message: "Enter feature name:",
2835
3209
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2836
3210
  })
@@ -2840,7 +3214,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2840
3214
  }
2841
3215
  } else {
2842
3216
  feature = handleCancel(
2843
- await p4__namespace.text({
3217
+ await p15__namespace.text({
2844
3218
  message: "Enter target feature name:",
2845
3219
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
2846
3220
  })
@@ -2851,7 +3225,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2851
3225
  let name = handlerNameArg?.trim();
2852
3226
  if (!name) {
2853
3227
  name = handleCancel(
2854
- await p4__namespace.text({
3228
+ await p15__namespace.text({
2855
3229
  message: "HTTP Route Handler class name:",
2856
3230
  placeholder: "e.g. CreateUserHttp, GetUserHttp, HealthHttp",
2857
3231
  validate: (v) => v && v.trim() ? void 0 : "Handler name cannot be empty"
@@ -2870,7 +3244,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2870
3244
  let validationFields = [];
2871
3245
  if (!flags.yes) {
2872
3246
  method = handleCancel(
2873
- await p4__namespace.select({
3247
+ await p15__namespace.select({
2874
3248
  message: "HTTP Method:",
2875
3249
  options: [
2876
3250
  { value: "get", label: "GET" },
@@ -2884,7 +3258,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2884
3258
  );
2885
3259
  const defaultPath = method === "get" ? "/" : "/";
2886
3260
  subPath = handleCancel(
2887
- await p4__namespace.text({
3261
+ await p15__namespace.text({
2888
3262
  message: "Route subpath (relative to feature prefix):",
2889
3263
  initialValue: defaultPath,
2890
3264
  defaultValue: defaultPath
@@ -2895,7 +3269,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2895
3269
  const ucOptions = useCases.map((u) => ({ value: u.name, label: u.name }));
2896
3270
  ucOptions.push({ value: "__none__", label: "None / Standalone Handler" });
2897
3271
  const chosenUc = handleCancel(
2898
- await p4__namespace.select({
3272
+ await p15__namespace.select({
2899
3273
  message: "Connect this handler to a domain Use Case?",
2900
3274
  options: ucOptions,
2901
3275
  initialValue: ucOptions[0]?.value
@@ -2906,7 +3280,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2906
3280
  }
2907
3281
  }
2908
3282
  includeValidation = handleCancel(
2909
- await p4__namespace.confirm({
3283
+ await p15__namespace.confirm({
2910
3284
  message: "Include Zod inbound request validation?",
2911
3285
  initialValue: method !== "delete"
2912
3286
  })
@@ -2914,7 +3288,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2914
3288
  if (includeValidation) {
2915
3289
  const defaultSource = method === "get" ? "query" : "body";
2916
3290
  validationSource = handleCancel(
2917
- await p4__namespace.select({
3291
+ await p15__namespace.select({
2918
3292
  message: "Validation data source:",
2919
3293
  options: [
2920
3294
  { value: "body", label: "req.body (for POST / PUT / PATCH payloads)" },
@@ -2928,8 +3302,8 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2928
3302
  });
2929
3303
  }
2930
3304
  }
2931
- const s = p4__namespace.spinner();
2932
- s.start(`Generating HTTP handler '${pc13__default.default.cyan(className)}'...`);
3305
+ const s = p15__namespace.spinner();
3306
+ s.start(`Generating HTTP handler '${pc15__default.default.cyan(className)}'...`);
2933
3307
  const options = {
2934
3308
  name: className,
2935
3309
  feature,
@@ -2947,7 +3321,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2947
3321
  const files = generateHttpHandler(options);
2948
3322
  s.stop("HTTP handler generated successfully!");
2949
3323
  printGeneratedFiles(files, options.dryRun);
2950
- p4__namespace.outro(pc13__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3324
+ p15__namespace.outro(pc15__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
2951
3325
  }
2952
3326
  function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
2953
3327
  const formattedDeps = dependencies.map((dep) => ({
@@ -2957,7 +3331,7 @@ function generateInjectableCode(className, dependencies = [], methods = [], impl
2957
3331
  const formattedMethods = methods.map((m) => {
2958
3332
  const isAsync = m.async !== false;
2959
3333
  const retType = m.returnType ? isAsync ? `Promise<${m.returnType}>` : m.returnType : isAsync ? "Promise<void>" : "void";
2960
- const params = m.params && m.params.length > 0 ? m.params.map((p15) => `${p15.name}: ${p15.type}`).join(", ") : "";
3334
+ const params = m.params && m.params.length > 0 ? m.params.map((p17) => `${p17.name}: ${p17.type}`).join(", ") : "";
2961
3335
  return {
2962
3336
  name: m.name,
2963
3337
  isAsync,
@@ -2980,21 +3354,21 @@ function generateInjectable(options) {
2980
3354
  const className = toPascalCase(options.name);
2981
3355
  let targetDir = "";
2982
3356
  if (options.customPath) {
2983
- targetDir = path7__default.default.isAbsolute(options.customPath) ? options.customPath : path7__default.default.join(projectRoot, options.customPath);
3357
+ targetDir = path2__default.default.isAbsolute(options.customPath) ? options.customPath : path2__default.default.join(projectRoot, options.customPath);
2984
3358
  } else if (options.location === "feature-service" && options.feature) {
2985
- targetDir = path7__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services");
3359
+ targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services");
2986
3360
  } else if (options.location === "feature-transformer" && options.feature) {
2987
- targetDir = path7__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
3361
+ targetDir = path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "pxExpress", "transformers");
2988
3362
  } else if (options.location === "module") {
2989
- targetDir = path7__default.default.join(projectRoot, "modules", className);
3363
+ targetDir = path2__default.default.join(projectRoot, "modules", className);
2990
3364
  } else {
2991
- targetDir = options.feature ? path7__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path7__default.default.join(projectRoot, "shared", className.toLowerCase());
3365
+ targetDir = options.feature ? path2__default.default.join(resolveFeaturesDir(projectRoot), options.feature, "services") : path2__default.default.join(projectRoot, "shared", className.toLowerCase());
2992
3366
  }
2993
- const filePath = path7__default.default.join(targetDir, `${className}.ts`);
3367
+ const filePath = path2__default.default.join(targetDir, `${className}.ts`);
2994
3368
  let ifaceName;
2995
3369
  if (options.generateInterface) {
2996
3370
  ifaceName = `I${className}`;
2997
- const ifacePath = path7__default.default.join(targetDir, `${ifaceName}.ts`);
3371
+ const ifacePath = path2__default.default.join(targetDir, `${ifaceName}.ts`);
2998
3372
  const ifaceContent = renderTemplate(injectableInterfaceTemplate, { ifaceName });
2999
3373
  files.push(
3000
3374
  writeFileSafely(ifacePath, ifaceContent, {
@@ -3027,7 +3401,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3027
3401
  let name = classNameArg?.trim();
3028
3402
  if (!name) {
3029
3403
  name = handleCancel(
3030
- await p4__namespace.text({
3404
+ await p15__namespace.text({
3031
3405
  message: "Injectable class name:",
3032
3406
  placeholder: "e.g. PasswordHasher, TokenService, UserTransformer",
3033
3407
  validate: (v) => v && v.trim() ? void 0 : "Class name cannot be empty"
@@ -3040,7 +3414,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3040
3414
  let customPath;
3041
3415
  if (!flags.yes) {
3042
3416
  location = handleCancel(
3043
- await p4__namespace.select({
3417
+ await p15__namespace.select({
3044
3418
  message: "Select target architectural location:",
3045
3419
  options: [
3046
3420
  { value: "feature-service", label: "Feature Service (features/<feature>/services/)" },
@@ -3058,14 +3432,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3058
3432
  const featureOptions = existingFeatures.map((f) => ({ value: f, label: f }));
3059
3433
  featureOptions.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom feature name..." });
3060
3434
  const selected = handleCancel(
3061
- await p4__namespace.select({
3435
+ await p15__namespace.select({
3062
3436
  message: "Select target feature:",
3063
3437
  options: featureOptions
3064
3438
  })
3065
3439
  );
3066
3440
  if (selected === "__custom__") {
3067
3441
  feature = handleCancel(
3068
- await p4__namespace.text({
3442
+ await p15__namespace.text({
3069
3443
  message: "Enter feature name:",
3070
3444
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
3071
3445
  })
@@ -3075,7 +3449,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3075
3449
  }
3076
3450
  } else {
3077
3451
  feature = handleCancel(
3078
- await p4__namespace.text({
3452
+ await p15__namespace.text({
3079
3453
  message: "Enter target feature name:",
3080
3454
  validate: (v) => v && v.trim() ? void 0 : "Feature name cannot be empty"
3081
3455
  })
@@ -3085,7 +3459,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3085
3459
  feature = toCamelCase(feature);
3086
3460
  } else if (location === "custom") {
3087
3461
  customPath = handleCancel(
3088
- await p4__namespace.text({
3462
+ await p15__namespace.text({
3089
3463
  message: "Enter destination directory path:",
3090
3464
  placeholder: "e.g. src/services or lib/utils",
3091
3465
  validate: (v) => v && v.trim() ? void 0 : "Path cannot be empty"
@@ -3096,14 +3470,14 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3096
3470
  let generateInterface2 = false;
3097
3471
  if (!flags.yes) {
3098
3472
  generateInterface2 = handleCancel(
3099
- await p4__namespace.confirm({
3473
+ await p15__namespace.confirm({
3100
3474
  message: `Generate abstract class interface (I${className}) as runtime DI token?`,
3101
3475
  initialValue: false
3102
3476
  })
3103
3477
  );
3104
3478
  }
3105
- const s = p4__namespace.spinner();
3106
- s.start(`Generating injectable '${pc13__default.default.cyan(className)}'...`);
3479
+ const s = p15__namespace.spinner();
3480
+ s.start(`Generating injectable '${pc15__default.default.cyan(className)}'...`);
3107
3481
  const options = {
3108
3482
  name: className,
3109
3483
  feature,
@@ -3117,12 +3491,12 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3117
3491
  const files = generateInjectable(options);
3118
3492
  s.stop("Injectable class generated successfully!");
3119
3493
  printGeneratedFiles(files, options.dryRun);
3120
- p4__namespace.outro(pc13__default.default.green(`Injectable '${className}' created successfully!`));
3494
+ p15__namespace.outro(pc15__default.default.green(`Injectable '${className}' created successfully!`));
3121
3495
  }
3122
3496
  async function runInteractiveMenu(flags = {}) {
3123
3497
  printBanner();
3124
3498
  const choice = handleCancel(
3125
- await p4__namespace.select({
3499
+ await p15__namespace.select({
3126
3500
  message: "What would you like to create in your architecture?",
3127
3501
  options: [
3128
3502
  {
@@ -3130,6 +3504,11 @@ async function runInteractiveMenu(flags = {}) {
3130
3504
  label: "Feature",
3131
3505
  hint: "Complete vertical slice (domain, infra, usecase, pxExpress, diProvider)"
3132
3506
  },
3507
+ {
3508
+ value: "shared",
3509
+ label: "Shared Module",
3510
+ hint: "Foundational utility or port with ports, infrastructure & diProvider"
3511
+ },
3133
3512
  {
3134
3513
  value: "usecase",
3135
3514
  label: "Use Case",
@@ -3167,6 +3546,9 @@ async function runInteractiveMenu(flags = {}) {
3167
3546
  case "feature":
3168
3547
  await runFeatureCommand(void 0, flags);
3169
3548
  break;
3549
+ case "shared":
3550
+ await runSharedCommand(void 0, flags);
3551
+ break;
3170
3552
  case "usecase":
3171
3553
  await runUseCaseCommand(void 0, flags);
3172
3554
  break;
@@ -3199,6 +3581,9 @@ async function handleCreate(type, name, flags = {}) {
3199
3581
  case "feature":
3200
3582
  await runFeatureCommand(name, flags);
3201
3583
  break;
3584
+ case "shared":
3585
+ await runSharedCommand(name, flags);
3586
+ break;
3202
3587
  case "usecase":
3203
3588
  await runUseCaseCommand(name, flags);
3204
3589
  break;
@@ -3219,21 +3604,172 @@ async function handleCreate(type, name, flags = {}) {
3219
3604
  await runInjectableCommand(name, flags);
3220
3605
  break;
3221
3606
  default:
3222
- p4__namespace.log.error(
3223
- pc13__default.default.red(
3224
- `Unknown component type: "${type}". Allowed types: feature, usecase, domain, infrastructure, interface, httpHandler, injectable.`
3607
+ p15__namespace.log.error(
3608
+ pc15__default.default.red(
3609
+ `Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
3225
3610
  )
3226
3611
  );
3227
3612
  process.exit(1);
3228
3613
  }
3229
3614
  }
3615
+ var importPrompts = {
3616
+ select: p15__namespace.select,
3617
+ text: p15__namespace.text
3618
+ };
3619
+ function rewriteImports(content, aliasMap) {
3620
+ if (aliasMap.size === 0) return content;
3621
+ let rewritten = content;
3622
+ for (const [oldImport, newImport] of aliasMap.entries()) {
3623
+ const rawOld = oldImport.endsWith("/") ? oldImport.slice(0, -1) : oldImport;
3624
+ const rawNew = newImport.endsWith("/") ? newImport.slice(0, -1) : newImport;
3625
+ const escaped = rawOld.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3626
+ const regex = new RegExp(`${escaped}(?=[\\/'"\`]|$)`, "g");
3627
+ rewritten = rewritten.replace(regex, rawNew);
3628
+ }
3629
+ return rewritten;
3630
+ }
3631
+ function getModuleBaseDir(projectRoot, modType) {
3632
+ const folder = modType === "shared" ? "shared" : "features";
3633
+ return path2__default.default.join(projectRoot, "src", folder);
3634
+ }
3635
+ function getExistingModuleDir(projectRoot, modType, modName) {
3636
+ const folder = modType === "shared" ? "shared" : "features";
3637
+ const inSrc = path2__default.default.join(projectRoot, "src", folder, modName);
3638
+ if (fs12__default.default.existsSync(inSrc)) return inSrc;
3639
+ const inRoot = path2__default.default.join(projectRoot, folder, modName);
3640
+ if (fs12__default.default.existsSync(inRoot)) return inRoot;
3641
+ return null;
3642
+ }
3643
+ function checkModuleExists(projectRoot, modType, modName) {
3644
+ return getExistingModuleDir(projectRoot, modType, modName) !== null;
3645
+ }
3646
+ async function resolveDependency(depType, depName, aliasMap, projectRoot, flags, importModuleRecursive, importedModules, registryBase, index) {
3647
+ const folder = depType === "shared" ? "shared" : "features";
3648
+ const isAlreadyImportedThisRun = importedModules.has(`${depType}:${depName}`);
3649
+ if (isAlreadyImportedThisRun) return;
3650
+ const programmaticAlias = flags.alias?.[depName] || flags.alias?.[`${depType}/${depName}`];
3651
+ if (programmaticAlias) {
3652
+ aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${programmaticAlias}/`);
3653
+ const baseDir = getModuleBaseDir(projectRoot, depType);
3654
+ const displayDest = path2__default.default.relative(
3655
+ projectRoot,
3656
+ path2__default.default.join(baseDir, programmaticAlias)
3657
+ );
3658
+ p15__namespace.log.step(`Importing '${depName}' into '${displayDest}'...`);
3659
+ await importModuleRecursive(depType, depName, programmaticAlias);
3660
+ return;
3661
+ }
3662
+ const existingDir = getExistingModuleDir(projectRoot, depType, depName);
3663
+ if (!existingDir) {
3664
+ p15__namespace.log.info(` \u21B3 Resolving required ${depType} dependency: ${pc15__default.default.cyan(depName)}`);
3665
+ await importModuleRecursive(depType, depName, depName);
3666
+ return;
3667
+ }
3668
+ const entry = (depType === "shared" ? index?.shared : index?.features)?.find(
3669
+ (item) => item.name === depName
3670
+ );
3671
+ let remoteIntegrity = entry?.integrity;
3672
+ if (!remoteIntegrity) {
3673
+ try {
3674
+ const depManifest = await fetchModuleManifest(registryBase, depType, depName);
3675
+ remoteIntegrity = depManifest.integrity;
3676
+ } catch {
3677
+ }
3678
+ }
3679
+ const localHash = computeDirectoryHash(existingDir);
3680
+ if (remoteIntegrity && localHash === remoteIntegrity) {
3681
+ p15__namespace.log.step(`\u2713 ${depType}/${depName} is up to date (reusing existing)`);
3682
+ importedModules.add(`${depType}:${depName}`);
3683
+ return;
3684
+ }
3685
+ if (flags.overwrite) {
3686
+ p15__namespace.log.step(`Overwriting '${depType}/${depName}' with registry version (--overwrite)...`);
3687
+ const baseDir = getModuleBaseDir(projectRoot, depType);
3688
+ const targetDir = path2__default.default.join(baseDir, depName);
3689
+ if (!flags.dryRun) {
3690
+ if (existingDir !== targetDir) {
3691
+ fs12__default.default.rmSync(existingDir, { recursive: true, force: true });
3692
+ }
3693
+ fs12__default.default.rmSync(targetDir, { recursive: true, force: true });
3694
+ }
3695
+ await importModuleRecursive(depType, depName, depName, { overwrite: true });
3696
+ return;
3697
+ }
3698
+ if (flags.yes) {
3699
+ p15__namespace.log.info(`Using existing '${depType}/${depName}'`);
3700
+ importedModules.add(`${depType}:${depName}`);
3701
+ return;
3702
+ }
3703
+ const choice = handleCancel(
3704
+ await importPrompts.select({
3705
+ message: `Local '${depType}/${depName}' differs from the registry version.`,
3706
+ options: [
3707
+ {
3708
+ value: "keep_existing",
3709
+ label: `Keep existing local version (Recommended)`
3710
+ },
3711
+ {
3712
+ value: "rename",
3713
+ label: "Import fresh copy under a new name (alias)"
3714
+ },
3715
+ {
3716
+ value: "overwrite",
3717
+ label: "Overwrite local version with registry version"
3718
+ }
3719
+ ]
3720
+ })
3721
+ );
3722
+ if (choice === "keep_existing" || choice === "use_existing") {
3723
+ p15__namespace.log.step(`Skipping '${depType}/${depName}' (keeping existing)`);
3724
+ importedModules.add(`${depType}:${depName}`);
3725
+ return;
3726
+ }
3727
+ if (choice === "rename") {
3728
+ const newName = handleCancel(
3729
+ await importPrompts.text({
3730
+ message: "What should we name the new copy?",
3731
+ placeholder: `${depName}-copy`,
3732
+ validate(val) {
3733
+ const v = val?.trim();
3734
+ if (!v) return "Name cannot be empty";
3735
+ if (v === depName) return `Name must be different from '${depName}'`;
3736
+ if (!/^[a-zA-Z0-9_-]+$/.test(v)) {
3737
+ return "Name can only contain alphanumeric characters, dashes, and underscores";
3738
+ }
3739
+ if (checkModuleExists(projectRoot, depType, v)) {
3740
+ return `'${v}' already exists in ${folder}, please choose another name`;
3741
+ }
3742
+ }
3743
+ })
3744
+ ).trim();
3745
+ aliasMap.set(`@/${folder}/${depName}/`, `@/${folder}/${newName}/`);
3746
+ const baseDir = getModuleBaseDir(projectRoot, depType);
3747
+ const displayDest = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, newName));
3748
+ p15__namespace.log.step(`Importing '${depName}' into '${displayDest}'...`);
3749
+ await importModuleRecursive(depType, depName, newName);
3750
+ return;
3751
+ }
3752
+ if (choice === "overwrite") {
3753
+ p15__namespace.log.step(`Overwriting '${depType}/${depName}' with registry version...`);
3754
+ const baseDir = getModuleBaseDir(projectRoot, depType);
3755
+ const targetDir = path2__default.default.join(baseDir, depName);
3756
+ if (!flags.dryRun) {
3757
+ if (existingDir !== targetDir) {
3758
+ fs12__default.default.rmSync(existingDir, { recursive: true, force: true });
3759
+ }
3760
+ fs12__default.default.rmSync(targetDir, { recursive: true, force: true });
3761
+ }
3762
+ await importModuleRecursive(depType, depName, depName, { overwrite: true });
3763
+ return;
3764
+ }
3765
+ }
3230
3766
  async function handleImport(typeArg, nameArg, flags = {}) {
3231
3767
  const projectRoot = findProjectRoot();
3232
3768
  let type = typeArg?.trim().toLowerCase();
3233
3769
  if (type === "features") type = "feature";
3234
3770
  if (!type) {
3235
3771
  type = handleCancel(
3236
- await p4__namespace.select({
3772
+ await importPrompts.select({
3237
3773
  message: "What kind of module would you like to import?",
3238
3774
  options: [
3239
3775
  {
@@ -3251,19 +3787,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3251
3787
  );
3252
3788
  }
3253
3789
  if (type !== "shared" && type !== "feature") {
3254
- p4__namespace.log.error(pc13__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3790
+ p15__namespace.log.error(pc15__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3255
3791
  process.exit(1);
3256
3792
  }
3257
3793
  const registryBase = getRegistryBase(projectRoot, flags.registry);
3258
- const s = p4__namespace.spinner();
3259
- s.start(`Connecting to registry at ${pc13__default.default.dim(registryBase)}...`);
3794
+ const s = p15__namespace.spinner();
3795
+ s.start(`Connecting to registry at ${pc15__default.default.dim(registryBase)}...`);
3260
3796
  let index;
3261
3797
  try {
3262
3798
  index = await fetchRegistryIndex(registryBase);
3263
3799
  s.stop("Registry index loaded.");
3264
3800
  } catch (err) {
3265
- s.stop(pc13__default.default.red("Failed to load registry index."));
3266
- p4__namespace.log.error(pc13__default.default.red(err.message || String(err)));
3801
+ s.stop(pc15__default.default.red("Failed to load registry index."));
3802
+ p15__namespace.log.error(pc15__default.default.red(err.message || String(err)));
3267
3803
  process.exit(1);
3268
3804
  }
3269
3805
  const availableList = type === "shared" ? index.shared : index.features;
@@ -3271,11 +3807,11 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3271
3807
  let name = nameArg?.trim();
3272
3808
  if (!name) {
3273
3809
  if (availableList.length === 0) {
3274
- p4__namespace.log.warn(`No ${type} modules available in the registry.`);
3810
+ p15__namespace.log.warn(`No ${type} modules available in the registry.`);
3275
3811
  return;
3276
3812
  }
3277
3813
  name = handleCancel(
3278
- await p4__namespace.select({
3814
+ await importPrompts.select({
3279
3815
  message: `Select a ${type} to import:`,
3280
3816
  options: availableList.map((item) => ({
3281
3817
  value: item.name,
@@ -3287,122 +3823,769 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3287
3823
  }
3288
3824
  if (!candidateNames.includes(name)) {
3289
3825
  const closest = findClosestMatch(name, candidateNames);
3290
- p4__namespace.log.error(pc13__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3826
+ p15__namespace.log.error(pc15__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3291
3827
  if (closest) {
3292
- p4__namespace.log.info(pc13__default.default.yellow(`Did you mean '${pc13__default.default.cyan(closest)}'?`));
3828
+ p15__namespace.log.info(pc15__default.default.yellow(`Did you mean '${pc15__default.default.cyan(closest)}'?`));
3293
3829
  }
3294
3830
  process.exit(1);
3295
3831
  }
3296
3832
  const importedModules = /* @__PURE__ */ new Set();
3297
3833
  const totalWrittenFiles = [];
3298
3834
  const requiredNpmPackages = /* @__PURE__ */ new Set();
3299
- async function importModuleRecursive(modType, modName) {
3300
- const modKey = `${modType}:${modName}`;
3835
+ async function importModuleRecursive(modType, registryName, targetName = registryName, options) {
3836
+ const modKey = `${modType}:${targetName}`;
3301
3837
  if (importedModules.has(modKey)) return;
3302
- p4__namespace.log.step(`Importing ${modType}: ${pc13__default.default.cyan(modName)}...`);
3838
+ p15__namespace.log.step(
3839
+ `Importing ${modType}: ${pc15__default.default.cyan(targetName)}${targetName !== registryName ? pc15__default.default.dim(` (from ${registryName})`) : ""}...`
3840
+ );
3303
3841
  let manifest;
3304
3842
  try {
3305
- manifest = await fetchModuleManifest(registryBase, modType, modName);
3843
+ manifest = await fetchModuleManifest(registryBase, modType, registryName);
3306
3844
  } catch (err) {
3307
- p4__namespace.log.error(pc13__default.default.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
3845
+ p15__namespace.log.error(pc15__default.default.red(`Failed to fetch manifest for ${modType} '${registryName}': ${err.message}`));
3308
3846
  process.exit(1);
3309
3847
  }
3848
+ const aliasMap = /* @__PURE__ */ new Map();
3849
+ if (targetName !== registryName) {
3850
+ const folder2 = modType === "shared" ? "shared" : "features";
3851
+ aliasMap.set(`@/${folder2}/${registryName}/`, `@/${folder2}/${targetName}/`);
3852
+ }
3310
3853
  const sharedDeps = manifest.dependencies?.shared || [];
3311
3854
  for (const dep of sharedDeps) {
3312
- const depPath = path7__default.default.join(projectRoot, "src", "shared", dep);
3313
- if (!fs3__default.default.existsSync(depPath) && !importedModules.has(`shared:${dep}`)) {
3314
- p4__namespace.log.info(` \u21B3 Resolving required shared dependency: ${pc13__default.default.cyan(dep)}`);
3315
- await importModuleRecursive("shared", dep);
3316
- }
3855
+ await resolveDependency(
3856
+ "shared",
3857
+ dep,
3858
+ aliasMap,
3859
+ projectRoot,
3860
+ flags,
3861
+ importModuleRecursive,
3862
+ importedModules,
3863
+ registryBase,
3864
+ index
3865
+ );
3317
3866
  }
3318
3867
  const featureDeps = manifest.dependencies?.features || [];
3319
3868
  for (const dep of featureDeps) {
3320
- const depPath = path7__default.default.join(projectRoot, "src", "features", dep);
3321
- if (!fs3__default.default.existsSync(depPath) && !importedModules.has(`feature:${dep}`)) {
3322
- p4__namespace.log.info(` \u21B3 Resolving required feature dependency: ${pc13__default.default.cyan(dep)}`);
3323
- await importModuleRecursive("feature", dep);
3324
- }
3869
+ await resolveDependency(
3870
+ "feature",
3871
+ dep,
3872
+ aliasMap,
3873
+ projectRoot,
3874
+ flags,
3875
+ importModuleRecursive,
3876
+ importedModules,
3877
+ registryBase,
3878
+ index
3879
+ );
3325
3880
  }
3326
3881
  for (const pkg of manifest.dependencies?.npm || []) {
3327
3882
  requiredNpmPackages.add(pkg);
3328
3883
  }
3329
3884
  const folder = modType === "shared" ? "shared" : "features";
3330
- const targetDir = path7__default.default.join(projectRoot, "src", folder, modName);
3885
+ const targetDir = path2__default.default.join(getModuleBaseDir(projectRoot, modType), targetName);
3886
+ const isOverwrite = Boolean(flags.overwrite || options?.overwrite);
3331
3887
  for (const file of manifest.files || []) {
3332
3888
  if (file === "registry.json") continue;
3333
- const remoteFilePath = `${folder}/${modName}/${file}`;
3334
- const localFilePath = path7__default.default.join(targetDir, file);
3335
- if (fs3__default.default.existsSync(localFilePath) && !flags.overwrite) {
3336
- p4__namespace.log.warn(` \u21B3 Skipping existing file: ${pc13__default.default.dim(path7__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`);
3889
+ const remoteFilePath = `${folder}/${registryName}/${file}`;
3890
+ const localFilePath = path2__default.default.join(targetDir, file);
3891
+ if (fs12__default.default.existsSync(localFilePath) && !isOverwrite) {
3892
+ p15__namespace.log.warn(
3893
+ ` \u21B3 Skipping existing file: ${pc15__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
3894
+ );
3337
3895
  continue;
3338
3896
  }
3339
- const content = await fetchRegistryFile(registryBase, remoteFilePath);
3897
+ let content = await fetchRegistryFile(registryBase, remoteFilePath);
3898
+ content = rewriteImports(content, aliasMap);
3340
3899
  if (!flags.dryRun) {
3341
- fs3__default.default.mkdirSync(path7__default.default.dirname(localFilePath), { recursive: true });
3342
- fs3__default.default.writeFileSync(localFilePath, content, "utf-8");
3900
+ fs12__default.default.mkdirSync(path2__default.default.dirname(localFilePath), { recursive: true });
3901
+ fs12__default.default.writeFileSync(localFilePath, content, "utf-8");
3343
3902
  }
3344
- const displayPath = path7__default.default.relative(projectRoot, localFilePath);
3903
+ const displayPath = path2__default.default.relative(projectRoot, localFilePath);
3345
3904
  totalWrittenFiles.push(displayPath);
3346
- p4__namespace.log.message(` ${pc13__default.default.green("+")} ${displayPath}`);
3905
+ p15__namespace.log.message(` ${pc15__default.default.green("+")} ${displayPath}`);
3347
3906
  }
3348
3907
  importedModules.add(modKey);
3349
3908
  }
3350
- await importModuleRecursive(type, name);
3909
+ await importModuleRecursive(type, name, name);
3351
3910
  const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
3352
3911
  if (missingNpm.length > 0) {
3353
3912
  if (flags.skipInstall || flags.dryRun) {
3354
- p4__namespace.log.info(`Required npm dependencies to install: ${pc13__default.default.cyan(missingNpm.join(", "))}`);
3913
+ p15__namespace.log.info(`Required npm dependencies to install: ${pc15__default.default.cyan(missingNpm.join(", "))}`);
3355
3914
  } else {
3356
3915
  try {
3357
3916
  installNpmPackages(projectRoot, missingNpm);
3358
3917
  } catch (err) {
3359
- p4__namespace.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
3918
+ p15__namespace.log.warn(`Could not automatically install packages (${err.message}). Please run: npm install ${missingNpm.join(" ")}`);
3360
3919
  }
3361
3920
  }
3362
3921
  }
3363
3922
  if (flags.dryRun) {
3364
- p4__namespace.log.info(pc13__default.default.yellow("Dry run complete: No files were written to disk."));
3923
+ p15__namespace.log.info(pc15__default.default.yellow("Dry run complete: No files were written to disk."));
3365
3924
  } else {
3366
- p4__namespace.log.success(
3367
- pc13__default.default.green(
3368
- `Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in src/${type === "shared" ? "shared" : "features"}/${name})`
3925
+ const baseDir = getModuleBaseDir(projectRoot, type);
3926
+ const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, name));
3927
+ p15__namespace.log.success(
3928
+ pc15__default.default.green(
3929
+ `Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
3369
3930
  )
3370
3931
  );
3371
3932
  }
3372
3933
  }
3934
+ function loadMasterIndex(projectRoot) {
3935
+ const indexPath = path2__default.default.join(projectRoot, "index.json");
3936
+ if (!fs12__default.default.existsSync(indexPath)) {
3937
+ const initialIndex = { shared: [], features: [] };
3938
+ fs12__default.default.writeFileSync(indexPath, JSON.stringify(initialIndex, null, 2) + "\n", "utf-8");
3939
+ return initialIndex;
3940
+ }
3941
+ return JSON.parse(fs12__default.default.readFileSync(indexPath, "utf-8"));
3942
+ }
3943
+ function saveMasterIndex(projectRoot, index) {
3944
+ const indexPath = path2__default.default.join(projectRoot, "index.json");
3945
+ index.shared.sort((a, b) => a.name.localeCompare(b.name));
3946
+ index.features.sort((a, b) => a.name.localeCompare(b.name));
3947
+ fs12__default.default.writeFileSync(indexPath, JSON.stringify(index, null, 2) + "\n", "utf-8");
3948
+ }
3949
+ function discoverDiskModules(projectRoot) {
3950
+ const results = [];
3951
+ const sharedDir = resolveSharedDir(projectRoot);
3952
+ if (fs12__default.default.existsSync(sharedDir)) {
3953
+ const entries = fs12__default.default.readdirSync(sharedDir, { withFileTypes: true });
3954
+ for (const entry of entries) {
3955
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
3956
+ results.push({ name: entry.name, type: "shared", dir: path2__default.default.join(sharedDir, entry.name) });
3957
+ }
3958
+ }
3959
+ }
3960
+ const featuresDir = resolveFeaturesDir(projectRoot);
3961
+ if (fs12__default.default.existsSync(featuresDir)) {
3962
+ const entries = fs12__default.default.readdirSync(featuresDir, { withFileTypes: true });
3963
+ for (const entry of entries) {
3964
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
3965
+ results.push({ name: entry.name, type: "feature", dir: path2__default.default.join(featuresDir, entry.name) });
3966
+ }
3967
+ }
3968
+ }
3969
+ return results;
3970
+ }
3971
+ function checkGitStatusForDir(projectRoot, dirPath) {
3972
+ try {
3973
+ const relPath = path2__default.default.relative(projectRoot, dirPath);
3974
+ const output = child_process.execSync(`git status --porcelain -- "${relPath}"`, {
3975
+ cwd: projectRoot,
3976
+ stdio: ["ignore", "pipe", "ignore"],
3977
+ encoding: "utf-8"
3978
+ }).trim();
3979
+ if (!output) {
3980
+ return { hasChanges: false, filesCount: 0 };
3981
+ }
3982
+ const lines = output.split("\n").filter((l) => l.trim().length > 0);
3983
+ return { hasChanges: true, filesCount: lines.length };
3984
+ } catch {
3985
+ return { hasChanges: false, filesCount: 0 };
3986
+ }
3987
+ }
3988
+ function getRegistryDiff(projectRoot) {
3989
+ const index = loadMasterIndex(projectRoot);
3990
+ const diskModules = discoverDiskModules(projectRoot);
3991
+ const diskMap = /* @__PURE__ */ new Map();
3992
+ for (const mod of diskModules) {
3993
+ diskMap.set(`${mod.type}:${mod.name}`, mod);
3994
+ }
3995
+ const allKeys = /* @__PURE__ */ new Set();
3996
+ for (const item of index.shared) {
3997
+ allKeys.add(`shared:${item.name}`);
3998
+ }
3999
+ for (const item of index.features) {
4000
+ allKeys.add(`feature:${item.name}`);
4001
+ }
4002
+ for (const mod of diskModules) {
4003
+ allKeys.add(`${mod.type}:${mod.name}`);
4004
+ }
4005
+ const statuses = [];
4006
+ for (const key of allKeys) {
4007
+ const [typeStr, name] = key.split(":");
4008
+ const type = typeStr;
4009
+ const registeredEntry = (type === "shared" ? index.shared : index.features).find(
4010
+ (e) => e.name === name
4011
+ );
4012
+ const isRegistered = Boolean(registeredEntry);
4013
+ const diskInfo = diskMap.get(key);
4014
+ const existsOnDisk = Boolean(diskInfo && fs12__default.default.existsSync(diskInfo.dir));
4015
+ let hasUncommittedGitChanges = false;
4016
+ let uncommittedFilesCount = 0;
4017
+ let hasIntegrityDrift = false;
4018
+ let localIntegrity;
4019
+ if (existsOnDisk && diskInfo) {
4020
+ const git = checkGitStatusForDir(projectRoot, diskInfo.dir);
4021
+ hasUncommittedGitChanges = git.hasChanges;
4022
+ uncommittedFilesCount = git.filesCount;
4023
+ localIntegrity = computeDirectoryHash(diskInfo.dir);
4024
+ if (isRegistered && registeredEntry?.integrity) {
4025
+ hasIntegrityDrift = localIntegrity !== registeredEntry.integrity;
4026
+ }
4027
+ }
4028
+ statuses.push({
4029
+ name,
4030
+ type,
4031
+ isRegistered,
4032
+ existsOnDisk,
4033
+ hasUncommittedGitChanges,
4034
+ uncommittedFilesCount,
4035
+ hasIntegrityDrift,
4036
+ localIntegrity,
4037
+ registeredIntegrity: registeredEntry?.integrity
4038
+ });
4039
+ }
4040
+ return statuses.sort((a, b) => a.type.localeCompare(b.type) || a.name.localeCompare(b.name));
4041
+ }
4042
+ async function registerModule(typeArg, nameArg, flags = {}) {
4043
+ const projectRoot = findProjectRoot();
4044
+ const index = loadMasterIndex(projectRoot);
4045
+ let type = typeArg?.trim();
4046
+ if (!type) {
4047
+ type = handleCancel(
4048
+ await p15__namespace.select({
4049
+ message: "Module type to register:",
4050
+ options: [
4051
+ { value: "feature", label: "Feature" },
4052
+ { value: "shared", label: "Shared Module" }
4053
+ ]
4054
+ })
4055
+ );
4056
+ }
4057
+ const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
4058
+ const registeredNames = new Set((type === "shared" ? index.shared : index.features).map((m) => m.name));
4059
+ const unregistered = diskModules.filter((m) => !registeredNames.has(m.name));
4060
+ let name = nameArg?.trim();
4061
+ if (!name) {
4062
+ if (unregistered.length > 0) {
4063
+ const opts = unregistered.map((m) => ({ value: m.name, label: m.name, hint: "unregistered on disk" }));
4064
+ opts.push({ value: "__custom__", label: "\u270F\uFE0F Enter custom name...", hint: "" });
4065
+ const choice = handleCancel(
4066
+ await p15__namespace.select({
4067
+ message: `Select a ${type} to register:`,
4068
+ options: opts
4069
+ })
4070
+ );
4071
+ if (choice === "__custom__") {
4072
+ name = handleCancel(
4073
+ await p15__namespace.text({
4074
+ message: `Enter ${type} name:`,
4075
+ validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
4076
+ })
4077
+ ).trim();
4078
+ } else {
4079
+ name = choice;
4080
+ }
4081
+ } else {
4082
+ name = handleCancel(
4083
+ await p15__namespace.text({
4084
+ message: `Enter ${type} name to register:`,
4085
+ validate: (v) => v && v.trim() ? void 0 : "Name cannot be empty"
4086
+ })
4087
+ ).trim();
4088
+ }
4089
+ }
4090
+ const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
4091
+ const modDir = path2__default.default.join(folder, name);
4092
+ if (!fs12__default.default.existsSync(modDir)) {
4093
+ p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
4094
+ process.exit(1);
4095
+ }
4096
+ const files = getAllFiles(modDir);
4097
+ const integrity = computeDirectoryHash(modDir, files);
4098
+ const manifestPath = path2__default.default.join(modDir, "registry.json");
4099
+ let manifest;
4100
+ if (fs12__default.default.existsSync(manifestPath)) {
4101
+ manifest = JSON.parse(fs12__default.default.readFileSync(manifestPath, "utf-8"));
4102
+ } else {
4103
+ manifest = {
4104
+ name,
4105
+ type,
4106
+ description: `${name} ${type} module`,
4107
+ version: "1.0.0",
4108
+ dependencies: {
4109
+ npm: [],
4110
+ shared: [],
4111
+ features: []
4112
+ },
4113
+ files: []
4114
+ };
4115
+ }
4116
+ manifest.name = name;
4117
+ manifest.type = type;
4118
+ manifest.files = files;
4119
+ manifest.integrity = integrity;
4120
+ fs12__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
4121
+ const targetList = type === "shared" ? index.shared : index.features;
4122
+ const existingIdx = targetList.findIndex((item) => item.name === name);
4123
+ if (existingIdx >= 0) {
4124
+ targetList[existingIdx] = {
4125
+ name,
4126
+ description: manifest.description,
4127
+ version: manifest.version,
4128
+ integrity
4129
+ };
4130
+ } else {
4131
+ targetList.push({
4132
+ name,
4133
+ description: manifest.description,
4134
+ version: manifest.version,
4135
+ integrity
4136
+ });
4137
+ }
4138
+ saveMasterIndex(projectRoot, index);
4139
+ p15__namespace.log.success(
4140
+ pc15__default.default.green(
4141
+ `\u2713 Module '${type}/${name}' registered in index.json (${files.length} files, ${integrity.slice(0, 18)}...)`
4142
+ )
4143
+ );
4144
+ }
4145
+ async function commitModule(typeArg, nameArg, flags = {}) {
4146
+ const projectRoot = findProjectRoot();
4147
+ let type = typeArg?.trim();
4148
+ if (!type) {
4149
+ type = handleCancel(
4150
+ await p15__namespace.select({
4151
+ message: "Module type to commit:",
4152
+ options: [
4153
+ { value: "feature", label: "Feature" },
4154
+ { value: "shared", label: "Shared Module" }
4155
+ ]
4156
+ })
4157
+ );
4158
+ }
4159
+ let name = nameArg?.trim();
4160
+ if (!name) {
4161
+ const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
4162
+ const opts = diskModules.map((m) => {
4163
+ const git = checkGitStatusForDir(projectRoot, m.dir);
4164
+ return {
4165
+ value: m.name,
4166
+ label: m.name,
4167
+ hint: git.hasChanges ? `${git.filesCount} uncommitted files` : "clean working tree"
4168
+ };
4169
+ });
4170
+ name = handleCancel(
4171
+ await p15__namespace.select({
4172
+ message: `Select ${type} to commit:`,
4173
+ options: opts
4174
+ })
4175
+ );
4176
+ }
4177
+ const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
4178
+ const modDir = path2__default.default.join(folder, name);
4179
+ if (!fs12__default.default.existsSync(modDir)) {
4180
+ p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
4181
+ process.exit(1);
4182
+ }
4183
+ const relModDir = path2__default.default.relative(projectRoot, modDir);
4184
+ const gitStatus = checkGitStatusForDir(projectRoot, modDir);
4185
+ if (!gitStatus.hasChanges) {
4186
+ p15__namespace.log.warn(pc15__default.default.yellow(`No uncommitted git changes found in '${type}/${name}'.`));
4187
+ return;
4188
+ }
4189
+ let message = flags.message?.trim();
4190
+ if (!message) {
4191
+ message = handleCancel(
4192
+ await p15__namespace.text({
4193
+ message: `Commit message for ${type}/${name}:`,
4194
+ placeholder: `e.g. feat(${name}): update use cases and dependencies`,
4195
+ validate: (v) => v && v.trim() ? void 0 : "Commit message cannot be empty"
4196
+ })
4197
+ ).trim();
4198
+ }
4199
+ const s = p15__namespace.spinner();
4200
+ s.start(`Committing '${type}/${name}' to git repository...`);
4201
+ try {
4202
+ child_process.execSync(`git add "${relModDir}"`, { cwd: projectRoot, stdio: ["ignore", "pipe", "pipe"] });
4203
+ child_process.execSync(`git commit -m "${message.replace(/"/g, '\\"')}" -- "${relModDir}"`, {
4204
+ cwd: projectRoot,
4205
+ stdio: ["ignore", "pipe", "pipe"]
4206
+ });
4207
+ s.stop(`Changes for '${type}/${name}' committed to git.`);
4208
+ p15__namespace.log.success(pc15__default.default.green(`\u2713 Committed '${type}/${name}': "${message}"`));
4209
+ } catch (err) {
4210
+ s.stop(pc15__default.default.red("Git commit failed."));
4211
+ p15__namespace.log.error(pc15__default.default.red(err.message || String(err)));
4212
+ process.exit(1);
4213
+ }
4214
+ }
4215
+ async function registerModuleChanges(typeArg, nameArg, flags = {}) {
4216
+ const projectRoot = findProjectRoot();
4217
+ const index = loadMasterIndex(projectRoot);
4218
+ let type = typeArg?.trim();
4219
+ if (!type) {
4220
+ type = handleCancel(
4221
+ await p15__namespace.select({
4222
+ message: "Module type to update:",
4223
+ options: [
4224
+ { value: "feature", label: "Feature" },
4225
+ { value: "shared", label: "Shared Module" }
4226
+ ]
4227
+ })
4228
+ );
4229
+ }
4230
+ let name = nameArg?.trim();
4231
+ if (!name) {
4232
+ const diskModules = discoverDiskModules(projectRoot).filter((m) => m.type === type);
4233
+ const diffs = getRegistryDiff(projectRoot).filter((d) => d.type === type && d.isRegistered);
4234
+ const opts = diskModules.map((m) => {
4235
+ const d = diffs.find((item) => item.name === m.name);
4236
+ return {
4237
+ value: m.name,
4238
+ label: m.name,
4239
+ hint: d?.hasIntegrityDrift ? "drift detected (needs register)" : "up to date"
4240
+ };
4241
+ });
4242
+ name = handleCancel(
4243
+ await p15__namespace.select({
4244
+ message: `Select ${type} to register changes:`,
4245
+ options: opts
4246
+ })
4247
+ );
4248
+ }
4249
+ const folder = type === "shared" ? resolveSharedDir(projectRoot) : resolveFeaturesDir(projectRoot);
4250
+ const modDir = path2__default.default.join(folder, name);
4251
+ if (!fs12__default.default.existsSync(modDir)) {
4252
+ p15__namespace.log.error(pc15__default.default.red(`Module directory does not exist: ${path2__default.default.relative(projectRoot, modDir)}`));
4253
+ process.exit(1);
4254
+ }
4255
+ const files = getAllFiles(modDir);
4256
+ const integrity = computeDirectoryHash(modDir, files);
4257
+ const manifestPath = path2__default.default.join(modDir, "registry.json");
4258
+ let manifest;
4259
+ if (fs12__default.default.existsSync(manifestPath)) {
4260
+ manifest = JSON.parse(fs12__default.default.readFileSync(manifestPath, "utf-8"));
4261
+ } else {
4262
+ manifest = {
4263
+ name,
4264
+ type,
4265
+ description: `${name} ${type} module`,
4266
+ version: "1.0.0",
4267
+ dependencies: { npm: [], shared: [], features: [] },
4268
+ files: []
4269
+ };
4270
+ }
4271
+ manifest.files = files;
4272
+ manifest.integrity = integrity;
4273
+ if (flags.version) {
4274
+ manifest.version = flags.version;
4275
+ }
4276
+ fs12__default.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
4277
+ const targetList = type === "shared" ? index.shared : index.features;
4278
+ const existingIdx = targetList.findIndex((item) => item.name === name);
4279
+ if (existingIdx >= 0) {
4280
+ targetList[existingIdx] = {
4281
+ name,
4282
+ description: manifest.description,
4283
+ version: manifest.version,
4284
+ integrity
4285
+ };
4286
+ } else {
4287
+ targetList.push({
4288
+ name,
4289
+ description: manifest.description,
4290
+ version: manifest.version,
4291
+ integrity
4292
+ });
4293
+ }
4294
+ saveMasterIndex(projectRoot, index);
4295
+ p15__namespace.log.success(
4296
+ pc15__default.default.green(`\u2713 Registered changes for '${type}/${name}' (new integrity: ${integrity.slice(0, 18)}...)`)
4297
+ );
4298
+ }
4299
+ async function unregisterModule(typeArg, nameArg, flags = {}) {
4300
+ const projectRoot = findProjectRoot();
4301
+ const index = loadMasterIndex(projectRoot);
4302
+ let type = typeArg?.trim();
4303
+ if (!type) {
4304
+ type = handleCancel(
4305
+ await p15__namespace.select({
4306
+ message: "Module type to unregister:",
4307
+ options: [
4308
+ { value: "feature", label: "Feature" },
4309
+ { value: "shared", label: "Shared Module" }
4310
+ ]
4311
+ })
4312
+ );
4313
+ }
4314
+ const registeredList = type === "shared" ? index.shared : index.features;
4315
+ if (registeredList.length === 0) {
4316
+ p15__namespace.log.warn(`No registered ${type} modules found in index.json.`);
4317
+ return;
4318
+ }
4319
+ let name = nameArg?.trim();
4320
+ if (!name) {
4321
+ name = handleCancel(
4322
+ await p15__namespace.select({
4323
+ message: `Select ${type} to unregister from index.json:`,
4324
+ options: registeredList.map((item) => ({
4325
+ value: item.name,
4326
+ label: item.name,
4327
+ hint: item.description
4328
+ }))
4329
+ })
4330
+ );
4331
+ }
4332
+ if (type === "shared") {
4333
+ index.shared = index.shared.filter((item) => item.name !== name);
4334
+ } else {
4335
+ index.features = index.features.filter((item) => item.name !== name);
4336
+ }
4337
+ saveMasterIndex(projectRoot, index);
4338
+ p15__namespace.log.success(
4339
+ pc15__default.default.green(
4340
+ `\u2713 Unregistered module '${type}/${name}' from index.json (source code retained on disk in ${type === "shared" ? "shared" : "features"}/${name})`
4341
+ )
4342
+ );
4343
+ }
4344
+ function diffRegistry(flags = {}) {
4345
+ const projectRoot = findProjectRoot();
4346
+ const diffs = getRegistryDiff(projectRoot);
4347
+ p15__namespace.log.step(pc15__default.default.bold("\u{1F4CB} Mason Registry Diff & Status Summary"));
4348
+ const unregistered = diffs.filter((d) => d.existsOnDisk && !d.isRegistered);
4349
+ const uncommitted = diffs.filter((d) => d.existsOnDisk && d.hasUncommittedGitChanges);
4350
+ const committedDrift = diffs.filter(
4351
+ (d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && d.hasIntegrityDrift
4352
+ );
4353
+ const upToDate = diffs.filter(
4354
+ (d) => d.existsOnDisk && d.isRegistered && !d.hasUncommittedGitChanges && !d.hasIntegrityDrift
4355
+ );
4356
+ const missing = diffs.filter((d) => !d.existsOnDisk && d.isRegistered);
4357
+ if (unregistered.length > 0) {
4358
+ p15__namespace.log.message(pc15__default.default.yellow(`
4359
+ \u{1F4E6} Unregistered Modules (${unregistered.length}):`));
4360
+ for (const mod of unregistered) {
4361
+ p15__namespace.log.message(` ${pc15__default.default.red("\u25CF")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim("(not registered in index.json)")}`);
4362
+ }
4363
+ }
4364
+ if (uncommitted.length > 0) {
4365
+ p15__namespace.log.message(pc15__default.default.yellow(`
4366
+ \u{1F4BE} Uncommitted Changes in Git (${uncommitted.length}):`));
4367
+ for (const mod of uncommitted) {
4368
+ p15__namespace.log.message(
4369
+ ` ${pc15__default.default.yellow("\u25B2")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim(`(${mod.uncommittedFilesCount} modified/untracked files)`)}`
4370
+ );
4371
+ }
4372
+ }
4373
+ if (committedDrift.length > 0) {
4374
+ p15__namespace.log.message(pc15__default.default.cyan(`
4375
+ \u{1F680} Committed But Unregistered Changes (${committedDrift.length}):`));
4376
+ for (const mod of committedDrift) {
4377
+ p15__namespace.log.message(
4378
+ ` ${pc15__default.default.cyan("\u25C6")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim(
4379
+ `local: ${mod.localIntegrity?.slice(0, 16)}... != registered: ${mod.registeredIntegrity?.slice(0, 16)}...`
4380
+ )}`
4381
+ );
4382
+ }
4383
+ }
4384
+ if (missing.length > 0) {
4385
+ p15__namespace.log.message(pc15__default.default.red(`
4386
+ \u26A0\uFE0F Missing on Disk (${missing.length}):`));
4387
+ for (const mod of missing) {
4388
+ p15__namespace.log.message(` ${pc15__default.default.red("\u2715")} ${pc15__default.default.bold(`${mod.type}/${mod.name}`)} ${pc15__default.default.dim("(in index.json, but folder missing)")}`);
4389
+ }
4390
+ }
4391
+ if (upToDate.length > 0) {
4392
+ p15__namespace.log.message(pc15__default.default.green(`
4393
+ \u2713 Up to Date & Registered (${upToDate.length}):`));
4394
+ for (const mod of upToDate) {
4395
+ p15__namespace.log.message(` ${pc15__default.default.green("\u2714")} ${mod.type}/${mod.name} ${pc15__default.default.dim(`(${mod.registeredIntegrity?.slice(0, 18)}...)`)}`);
4396
+ }
4397
+ }
4398
+ p15__namespace.log.message("");
4399
+ }
4400
+ async function handleRegistry(actionArg, typeArg, nameArg, flags = {}) {
4401
+ let action = actionArg?.trim().toLowerCase();
4402
+ if (!action) {
4403
+ action = handleCancel(
4404
+ await p15__namespace.select({
4405
+ message: "\u{1F3DB}\uFE0F Mason Registry: What would you like to do?",
4406
+ options: [
4407
+ {
4408
+ value: "diff",
4409
+ label: "Inspect Diff & Status",
4410
+ hint: "Summary of unregistered, uncommitted, and drifted modules"
4411
+ },
4412
+ {
4413
+ value: "register",
4414
+ label: "Register Module",
4415
+ hint: "Add an existing module to index.json with integrity checksum"
4416
+ },
4417
+ {
4418
+ value: "commit",
4419
+ label: "Commit Changes",
4420
+ hint: "Commit a module's working changes to git in the background"
4421
+ },
4422
+ {
4423
+ value: "register-changes",
4424
+ label: "Register Changes",
4425
+ hint: "Update index.json & registry.json with latest checksums"
4426
+ },
4427
+ {
4428
+ value: "unregister",
4429
+ label: "Unregister Module",
4430
+ hint: "Remove module from index.json (source code stays on disk)"
4431
+ }
4432
+ ]
4433
+ })
4434
+ );
4435
+ }
4436
+ switch (action) {
4437
+ case "diff":
4438
+ case "status":
4439
+ diffRegistry(flags);
4440
+ break;
4441
+ case "register":
4442
+ await registerModule(typeArg, nameArg, flags);
4443
+ break;
4444
+ case "commit":
4445
+ await commitModule(typeArg, nameArg, flags);
4446
+ break;
4447
+ case "register-changes":
4448
+ case "update":
4449
+ await registerModuleChanges(typeArg, nameArg, flags);
4450
+ break;
4451
+ case "unregister":
4452
+ await unregisterModule(typeArg, nameArg, flags);
4453
+ break;
4454
+ default:
4455
+ p15__namespace.log.error(
4456
+ pc15__default.default.red(
4457
+ `Unknown registry action: "${action}". Valid actions: diff, register, commit, register-changes, unregister.`
4458
+ )
4459
+ );
4460
+ process.exit(1);
4461
+ }
4462
+ }
3373
4463
 
3374
4464
  // src/cli.ts
3375
4465
  function createCli() {
3376
4466
  const cli = cac.cac("mason");
3377
- 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) => {
4467
+ 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) => {
3378
4468
  if (args && args.length > 0) {
3379
- p4__namespace.log.error(
3380
- pc13__default.default.red(
4469
+ p15__namespace.log.error(
4470
+ pc15__default.default.red(
3381
4471
  `Unknown command: "${args.join(" ")}". Use --help to list available commands.`
3382
4472
  )
3383
4473
  );
3384
4474
  process.exit(1);
3385
4475
  }
3386
4476
  try {
4477
+ const projectRoot = findProjectRoot();
4478
+ const mode = getEffectiveMode(projectRoot, flags);
4479
+ if (mode === "registry") {
4480
+ const choice2 = handleCancel(
4481
+ await p15__namespace.select({
4482
+ message: "\u{1F3DB}\uFE0F Mason Registry Maintainer: What would you like to do?",
4483
+ options: [
4484
+ {
4485
+ value: "diff",
4486
+ label: "Inspect Diff & Status",
4487
+ hint: "Overview of unregistered, uncommitted, or drifted modules"
4488
+ },
4489
+ {
4490
+ value: "create_feature",
4491
+ label: "Initialize New Feature",
4492
+ hint: "Scaffold a new feature slice under features/"
4493
+ },
4494
+ {
4495
+ value: "create_shared",
4496
+ label: "Initialize New Shared Module",
4497
+ hint: "Scaffold a new shared utility/port under shared/"
4498
+ },
4499
+ {
4500
+ value: "create_usecase",
4501
+ label: "Create New Use Case",
4502
+ hint: "Add a new use case to an existing feature"
4503
+ },
4504
+ {
4505
+ value: "register",
4506
+ label: "Register Module",
4507
+ hint: "Add a module to index.json with integrity checksum"
4508
+ },
4509
+ {
4510
+ value: "commit",
4511
+ label: "Commit Module Changes",
4512
+ hint: "Commit working changes in a module to git"
4513
+ },
4514
+ {
4515
+ value: "register_changes",
4516
+ label: "Register Changes",
4517
+ hint: "Recompute checksums and update index.json"
4518
+ },
4519
+ {
4520
+ value: "unregister",
4521
+ label: "Unregister Module",
4522
+ hint: "Remove from index.json but keep source on disk"
4523
+ },
4524
+ {
4525
+ value: "import",
4526
+ label: "Import from Remote Registry",
4527
+ hint: "Download a module from upstream"
4528
+ }
4529
+ ]
4530
+ })
4531
+ );
4532
+ switch (choice2) {
4533
+ case "diff":
4534
+ await handleRegistry("diff", void 0, void 0, flags);
4535
+ break;
4536
+ case "create_feature":
4537
+ await handleCreate("feature", void 0, flags);
4538
+ break;
4539
+ case "create_shared":
4540
+ await handleCreate("shared", void 0, flags);
4541
+ break;
4542
+ case "create_usecase":
4543
+ await handleCreate("usecase", void 0, flags);
4544
+ break;
4545
+ case "register":
4546
+ await handleRegistry("register", void 0, void 0, flags);
4547
+ break;
4548
+ case "commit":
4549
+ await handleRegistry("commit", void 0, void 0, flags);
4550
+ break;
4551
+ case "register_changes":
4552
+ await handleRegistry("register-changes", void 0, void 0, flags);
4553
+ break;
4554
+ case "unregister":
4555
+ await handleRegistry("unregister", void 0, void 0, flags);
4556
+ break;
4557
+ case "import":
4558
+ await handleImport(void 0, void 0, flags);
4559
+ break;
4560
+ }
4561
+ return;
4562
+ }
3387
4563
  const choice = handleCancel(
3388
- await p4__namespace.select({
4564
+ await p15__namespace.select({
3389
4565
  message: "\u{1F9F1} Mason: What would you like to do?",
3390
4566
  options: [
3391
4567
  {
3392
4568
  value: "create",
3393
4569
  label: "Create local component",
3394
- hint: "Scaffold a new feature, use case, domain entity, interface, etc."
4570
+ hint: "Scaffold a new feature, shared module, use case, domain entity, etc."
3395
4571
  },
3396
4572
  {
3397
4573
  value: "import",
3398
4574
  label: "Import from registry",
3399
4575
  hint: "Download and link pre-built shared modules or features from the registry"
4576
+ },
4577
+ {
4578
+ value: "registry",
4579
+ label: "Registry management",
4580
+ hint: "Inspect diff, register modules, or commit module changes"
3400
4581
  }
3401
4582
  ]
3402
4583
  })
3403
4584
  );
3404
4585
  if (choice === "create") {
3405
4586
  await handleCreate(void 0, void 0, flags);
4587
+ } else if (choice === "registry") {
4588
+ await handleRegistry(void 0, void 0, void 0, flags);
3406
4589
  } else {
3407
4590
  await handleImport(void 0, void 0, flags);
3408
4591
  }
@@ -3412,8 +4595,8 @@ function createCli() {
3412
4595
  });
3413
4596
  cli.command(
3414
4597
  "create [type] [name]",
3415
- "Scaffold local Clean Architecture components (feature, usecase, domain, etc.)"
3416
- ).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 = {}) => {
4598
+ "Scaffold local Clean Architecture components (feature, shared, usecase, domain, etc.)"
4599
+ ).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 = {}) => {
3417
4600
  try {
3418
4601
  await handleCreate(type, name, flags);
3419
4602
  } catch (err) {
@@ -3430,8 +4613,28 @@ function createCli() {
3430
4613
  handleCliError(err);
3431
4614
  }
3432
4615
  });
4616
+ cli.command(
4617
+ "registry [action] [type] [name]",
4618
+ "Registry & version management (diff, register, commit, register-changes, unregister)"
4619
+ ).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(
4620
+ async (action, type, name, flags = {}) => {
4621
+ try {
4622
+ await handleRegistry(action, type, name, flags);
4623
+ } catch (err) {
4624
+ handleCliError(err);
4625
+ }
4626
+ }
4627
+ );
4628
+ cli.command("diff", "Inspect registry status and diff (alias for 'mason registry diff')").allowUnknownOptions().action(async (flags = {}) => {
4629
+ try {
4630
+ await handleRegistry("diff", void 0, void 0, flags);
4631
+ } catch (err) {
4632
+ handleCliError(err);
4633
+ }
4634
+ });
3433
4635
  const createSubtypes = [
3434
4636
  "feature",
4637
+ "shared",
3435
4638
  "usecase",
3436
4639
  "domain",
3437
4640
  "infrastructure",
@@ -3452,7 +4655,7 @@ function createCli() {
3452
4655
  });
3453
4656
  }
3454
4657
  cli.help();
3455
- cli.version("1.0.10");
4658
+ cli.version("1.0.13");
3456
4659
  return cli;
3457
4660
  }
3458
4661
  function handleCliError(err) {
@@ -3460,7 +4663,7 @@ function handleCliError(err) {
3460
4663
  process.exit(0);
3461
4664
  }
3462
4665
  const message = err instanceof Error ? err.message : String(err);
3463
- p4__namespace.log.error(pc13__default.default.red(`Error: ${message}`));
4666
+ p15__namespace.log.error(pc15__default.default.red(`Error: ${message}`));
3464
4667
  process.exit(1);
3465
4668
  }
3466
4669
  async function runCli(argv = process.argv) {
@@ -3470,6 +4673,7 @@ async function runCli(argv = process.argv) {
3470
4673
 
3471
4674
  exports.DEFAULT_REGISTRY_BASE = DEFAULT_REGISTRY_BASE;
3472
4675
  exports.PromptCancelledError = PromptCancelledError;
4676
+ exports.computeDirectoryHash = computeDirectoryHash;
3473
4677
  exports.concreteInfraTemplate = concreteInfraTemplate;
3474
4678
  exports.createCli = createCli;
3475
4679
  exports.detectPackageManager = detectPackageManager;
@@ -3484,6 +4688,7 @@ exports.fetchRegistryFile = fetchRegistryFile;
3484
4688
  exports.fetchRegistryIndex = fetchRegistryIndex;
3485
4689
  exports.findClosestMatch = findClosestMatch;
3486
4690
  exports.findInterfaceFile = findInterfaceFile;
4691
+ exports.findLocalRegistry = findLocalRegistry;
3487
4692
  exports.findProjectRoot = findProjectRoot;
3488
4693
  exports.generateConcreteInfraCode = generateConcreteInfraCode;
3489
4694
  exports.generateDomain = generateDomain;
@@ -3496,16 +4701,22 @@ exports.generateInjectable = generateInjectable;
3496
4701
  exports.generateInjectableCode = generateInjectableCode;
3497
4702
  exports.generateInterface = generateInterface;
3498
4703
  exports.generateInterfaceCode = generateInterfaceCode;
4704
+ exports.generateShared = generateShared;
3499
4705
  exports.generateStubCode = generateStubCode;
3500
4706
  exports.generateUseCase = generateUseCase;
3501
4707
  exports.generateUseCaseCode = generateUseCaseCode;
3502
4708
  exports.generateUseCaseTestCode = generateUseCaseTestCode;
4709
+ exports.getAllFiles = getAllFiles;
4710
+ exports.getConfiguredFeaturesDir = getConfiguredFeaturesDir;
4711
+ exports.getConfiguredSharedDir = getConfiguredSharedDir;
4712
+ exports.getEffectiveMode = getEffectiveMode;
3503
4713
  exports.getMissingNpmPackages = getMissingNpmPackages;
3504
4714
  exports.getRegistryBase = getRegistryBase;
3505
4715
  exports.getTemplatesDir = getTemplatesDir;
3506
4716
  exports.handleCancel = handleCancel;
3507
4717
  exports.handleCreate = handleCreate;
3508
4718
  exports.handleImport = handleImport;
4719
+ exports.handleRegistry = handleRegistry;
3509
4720
  exports.hbs = hbs;
3510
4721
  exports.httpHandlerTemplate = httpHandlerTemplate;
3511
4722
  exports.injectableInterfaceTemplate = injectableInterfaceTemplate;
@@ -3517,9 +4728,12 @@ exports.levenshteinDistance = levenshteinDistance;
3517
4728
  exports.listDomainEntities = listDomainEntities;
3518
4729
  exports.listDomainInterfaces = listDomainInterfaces;
3519
4730
  exports.listExistingFeatures = listExistingFeatures;
4731
+ exports.listExistingShared = listExistingShared;
3520
4732
  exports.listUseCases = listUseCases;
3521
4733
  exports.loadInterfaceHierarchy = loadInterfaceHierarchy;
4734
+ exports.loadMasonConfig = loadMasonConfig;
3522
4735
  exports.loadTemplate = loadTemplate;
4736
+ exports.normalizeRegistryUrl = normalizeRegistryUrl;
3523
4737
  exports.parseInterfaceMethodsAndImports = parseInterfaceMethodsAndImports;
3524
4738
  exports.printBanner = printBanner;
3525
4739
  exports.printGeneratedFiles = printGeneratedFiles;
@@ -3528,7 +4742,9 @@ exports.removeInterfacePrefix = removeInterfacePrefix;
3528
4742
  exports.renderTemplate = renderTemplate;
3529
4743
  exports.renderTemplateFile = renderTemplateFile;
3530
4744
  exports.resolveFeaturesDir = resolveFeaturesDir;
4745
+ exports.resolveSharedDir = resolveSharedDir;
3531
4746
  exports.runCli = runCli;
4747
+ exports.runSharedCommand = runSharedCommand;
3532
4748
  exports.stubInfraTemplate = stubInfraTemplate;
3533
4749
  exports.synthesizeCrudMethods = synthesizeCrudMethods;
3534
4750
  exports.toCamelCase = toCamelCase;