@solid-stack/mason 1.0.22 → 1.0.24

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/bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { cac } from 'cac';
3
3
  import * as p14 from '@clack/prompts';
4
- import pc15 from 'picocolors';
4
+ import pc14 from 'picocolors';
5
5
  import fs9 from 'fs';
6
6
  import path2 from 'path';
7
7
  import { fileURLToPath } from 'url';
@@ -47,17 +47,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
47
47
  function printGeneratedFiles(files, isDryRun = false) {
48
48
  console.log("");
49
49
  if (isDryRun) {
50
- p14.log.warn(pc15.yellow(pc15.bold("[DRY RUN PREVIEW] No files were written to disk.")));
50
+ p14.log.warn(pc14.yellow(pc14.bold("[DRY RUN PREVIEW] No files were written to disk.")));
51
51
  }
52
52
  for (const f of files) {
53
53
  if (f.action === "created") {
54
- p14.log.success(`${pc15.bold(f.relativePath)} ${pc15.dim("(created)")}`);
54
+ p14.log.success(`${pc14.bold(f.relativePath)} ${pc14.dim("(created)")}`);
55
55
  } else if (f.action === "overwritten") {
56
- p14.log.warn(`${pc15.bold(f.relativePath)} ${pc15.dim("(overwritten)")}`);
56
+ p14.log.warn(`${pc14.bold(f.relativePath)} ${pc14.dim("(overwritten)")}`);
57
57
  } else if (f.action === "skipped") {
58
- p14.log.info(`${pc15.dim(f.relativePath)} ${pc15.dim("(skipped - already exists, pass -o to overwrite)")}`);
58
+ p14.log.info(`${pc14.dim(f.relativePath)} ${pc14.dim("(skipped - already exists, pass -o to overwrite)")}`);
59
59
  } else {
60
- p14.log.info(`${pc15.bold(f.relativePath)} ${pc15.dim("(preview)")}`);
60
+ p14.log.info(`${pc14.bold(f.relativePath)} ${pc14.dim("(preview)")}`);
61
61
  }
62
62
  }
63
63
  }
@@ -1744,13 +1744,13 @@ function handleCancel(result) {
1744
1744
  }
1745
1745
  function printBanner() {
1746
1746
  console.log("");
1747
- p14.intro(pc15.bgCyan(pc15.black(" @solid-stack/mason ")));
1747
+ p14.intro(pc14.bgCyan(pc14.black(" @solid-stack/mason ")));
1748
1748
  }
1749
1749
  async function promptProperties(options = {}) {
1750
1750
  const properties = options.defaults ? [...options.defaults] : [];
1751
1751
  if (properties.length > 0) {
1752
1752
  const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
1753
- p14.log.info(pc15.dim(`Pre-configured fields: ${summary}`));
1753
+ p14.log.info(pc14.dim(`Pre-configured fields: ${summary}`));
1754
1754
  const addMore = handleCancel(
1755
1755
  await p14.confirm({
1756
1756
  message: "Would you like to add custom fields/properties?",
@@ -1761,7 +1761,7 @@ async function promptProperties(options = {}) {
1761
1761
  return properties;
1762
1762
  }
1763
1763
  }
1764
- p14.log.info(pc15.cyan("Enter properties (leave name blank when finished):"));
1764
+ p14.log.info(pc14.cyan("Enter properties (leave name blank when finished):"));
1765
1765
  while (true) {
1766
1766
  const name = handleCancel(
1767
1767
  await p14.text({
@@ -1809,7 +1809,7 @@ async function promptProperties(options = {}) {
1809
1809
  optional
1810
1810
  });
1811
1811
  p14.log.success(
1812
- pc15.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
1812
+ pc14.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
1813
1813
  );
1814
1814
  }
1815
1815
  return properties;
@@ -2069,7 +2069,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2069
2069
  }
2070
2070
  }
2071
2071
  const s = p14.spinner();
2072
- s.start(`Generating feature '${pc15.cyan(normalizedName)}'...`);
2072
+ s.start(`Generating feature '${pc14.cyan(normalizedName)}'...`);
2073
2073
  const options = {
2074
2074
  name: normalizedName,
2075
2075
  projectRoot,
@@ -2116,7 +2116,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
2116
2116
  s.stop("Feature scaffolded successfully!");
2117
2117
  printGeneratedFiles(files, options.dryRun);
2118
2118
  p14.outro(
2119
- pc15.green(
2119
+ pc14.green(
2120
2120
  `\u2713 Feature '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2121
2121
  )
2122
2122
  );
@@ -2272,7 +2272,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
2272
2272
  );
2273
2273
  }
2274
2274
  const s = p14.spinner();
2275
- s.start(`Generating shared module '${pc15.cyan(normalizedName)}'...`);
2275
+ s.start(`Generating shared module '${pc14.cyan(normalizedName)}'...`);
2276
2276
  const files = generateShared({
2277
2277
  name: normalizedName,
2278
2278
  description,
@@ -2286,7 +2286,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
2286
2286
  s.stop("Shared module generated successfully!");
2287
2287
  printGeneratedFiles(files, Boolean(flags.dryRun));
2288
2288
  p14.outro(
2289
- pc15.green(
2289
+ pc14.green(
2290
2290
  `\u2713 Shared module '${normalizedName}' initialized in ${path2.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
2291
2291
  )
2292
2292
  );
@@ -2414,7 +2414,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2414
2414
  }
2415
2415
  }
2416
2416
  const s = p14.spinner();
2417
- s.start(`Generating use case '${pc15.cyan(ucName)}'...`);
2417
+ s.start(`Generating use case '${pc14.cyan(ucName)}'...`);
2418
2418
  const options = {
2419
2419
  name: ucName,
2420
2420
  feature,
@@ -2446,7 +2446,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
2446
2446
  }
2447
2447
  s.stop("Use case generated successfully!");
2448
2448
  printGeneratedFiles(files, options.dryRun);
2449
- p14.outro(pc15.green(`Use case '${ucName}' created in feature '${feature}'!`));
2449
+ p14.outro(pc14.green(`Use case '${ucName}' created in feature '${feature}'!`));
2450
2450
  }
2451
2451
  async function runDomainCommand(domainNameArg, flags = {}) {
2452
2452
  const projectRoot = findProjectRoot();
@@ -2517,7 +2517,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2517
2517
  });
2518
2518
  }
2519
2519
  const s = p14.spinner();
2520
- s.start(`Generating domain model '${pc15.cyan(entityName)}'...`);
2520
+ s.start(`Generating domain model '${pc14.cyan(entityName)}'...`);
2521
2521
  const options = {
2522
2522
  name: entityName,
2523
2523
  feature,
@@ -2530,7 +2530,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
2530
2530
  const file = generateDomain(options);
2531
2531
  s.stop("Domain model generated successfully!");
2532
2532
  printGeneratedFiles([file], options.dryRun);
2533
- p14.outro(pc15.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2533
+ p14.outro(pc14.green(`Domain model '${entityName}' created in feature '${feature}'!`));
2534
2534
  }
2535
2535
  async function runInfrastructureCommand(classNameArg, flags = {}) {
2536
2536
  const projectRoot = findProjectRoot();
@@ -2699,7 +2699,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2699
2699
  registerInProvider = !flags.noProvider;
2700
2700
  }
2701
2701
  const s = p14.spinner();
2702
- s.start(`Generating infrastructure class '${pc15.cyan(className)}'...`);
2702
+ s.start(`Generating infrastructure class '${pc14.cyan(className)}'...`);
2703
2703
  const files = [];
2704
2704
  const options = {
2705
2705
  name: className,
@@ -2734,7 +2734,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
2734
2734
  }
2735
2735
  s.stop("Infrastructure class generated successfully!");
2736
2736
  printGeneratedFiles(files, options.dryRun);
2737
- p14.outro(pc15.green(`Infrastructure class '${className}' created!`));
2737
+ p14.outro(pc14.green(`Infrastructure class '${className}' created!`));
2738
2738
  }
2739
2739
  async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2740
2740
  const projectRoot = findProjectRoot();
@@ -2847,7 +2847,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2847
2847
  }
2848
2848
  }
2849
2849
  const s = p14.spinner();
2850
- s.start(`Generating interface '${pc15.cyan(ifaceName)}'...`);
2850
+ s.start(`Generating interface '${pc14.cyan(ifaceName)}'...`);
2851
2851
  const files = [];
2852
2852
  const options = {
2853
2853
  name: ifaceName,
@@ -2896,7 +2896,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
2896
2896
  }
2897
2897
  s.stop("Interface generated successfully!");
2898
2898
  printGeneratedFiles(files, options.dryRun);
2899
- p14.outro(pc15.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
2899
+ p14.outro(pc14.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
2900
2900
  }
2901
2901
  async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
2902
2902
  const projectRoot = findProjectRoot();
@@ -3013,7 +3013,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
3013
3013
  }
3014
3014
  }
3015
3015
  const s = p14.spinner();
3016
- s.start(`Generating HTTP handler '${pc15.cyan(className)}'...`);
3016
+ s.start(`Generating HTTP handler '${pc14.cyan(className)}'...`);
3017
3017
  const options = {
3018
3018
  name: className,
3019
3019
  feature,
@@ -3031,7 +3031,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
3031
3031
  const files = generateHttpHandler(options);
3032
3032
  s.stop("HTTP handler generated successfully!");
3033
3033
  printGeneratedFiles(files, options.dryRun);
3034
- p14.outro(pc15.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3034
+ p14.outro(pc14.green(`HTTP handler '${className}' created in feature '${feature}'!`));
3035
3035
  }
3036
3036
  function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
3037
3037
  const formattedDeps = dependencies.map((dep) => ({
@@ -3187,7 +3187,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3187
3187
  );
3188
3188
  }
3189
3189
  const s = p14.spinner();
3190
- s.start(`Generating injectable '${pc15.cyan(className)}'...`);
3190
+ s.start(`Generating injectable '${pc14.cyan(className)}'...`);
3191
3191
  const options = {
3192
3192
  name: className,
3193
3193
  feature,
@@ -3201,7 +3201,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
3201
3201
  const files = generateInjectable(options);
3202
3202
  s.stop("Injectable class generated successfully!");
3203
3203
  printGeneratedFiles(files, options.dryRun);
3204
- p14.outro(pc15.green(`Injectable '${className}' created successfully!`));
3204
+ p14.outro(pc14.green(`Injectable '${className}' created successfully!`));
3205
3205
  }
3206
3206
  async function runInteractiveMenu(flags = {}) {
3207
3207
  printBanner();
@@ -3315,7 +3315,7 @@ async function handleCreate(type, name, flags = {}) {
3315
3315
  break;
3316
3316
  default:
3317
3317
  p14.log.error(
3318
- pc15.red(
3318
+ pc14.red(
3319
3319
  `Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
3320
3320
  )
3321
3321
  );
@@ -3551,7 +3551,7 @@ function installNpmPackages(projectRoot, packages, pm) {
3551
3551
  installCmd = `npm install ${packages.join(" ")}`;
3552
3552
  break;
3553
3553
  }
3554
- p14.log.info(`Installing missing dependencies with ${manager}: ${pc15.cyan(packages.join(", "))}`);
3554
+ p14.log.info(`Installing missing dependencies with ${manager}: ${pc14.cyan(packages.join(", "))}`);
3555
3555
  execSync(installCmd, {
3556
3556
  cwd: projectRoot,
3557
3557
  stdio: "inherit"
@@ -3707,19 +3707,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3707
3707
  );
3708
3708
  }
3709
3709
  if (type !== "shared" && type !== "feature") {
3710
- p14.log.error(pc15.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3710
+ p14.log.error(pc14.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
3711
3711
  process.exit(1);
3712
3712
  }
3713
3713
  const registryBase = getRegistryBase(projectRoot, flags.registry);
3714
3714
  const s = p14.spinner();
3715
- s.start(`Connecting to registry at ${pc15.dim(registryBase)}...`);
3715
+ s.start(`Connecting to registry at ${pc14.dim(registryBase)}...`);
3716
3716
  let index;
3717
3717
  try {
3718
3718
  index = await fetchRegistryIndex(registryBase);
3719
3719
  s.stop("Registry index loaded.");
3720
3720
  } catch (err) {
3721
- s.stop(pc15.red("Failed to load registry index."));
3722
- p14.log.error(pc15.red(err.message || String(err)));
3721
+ s.stop(pc14.red("Failed to load registry index."));
3722
+ p14.log.error(pc14.red(err.message || String(err)));
3723
3723
  process.exit(1);
3724
3724
  }
3725
3725
  const availableList = type === "shared" ? index.shared : index.features;
@@ -3743,9 +3743,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3743
3743
  }
3744
3744
  if (!candidateNames.includes(name)) {
3745
3745
  const closest = findClosestMatch(name, candidateNames);
3746
- p14.log.error(pc15.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3746
+ p14.log.error(pc14.red(`\u274C Module '${name}' not found in the ${type} registry.`));
3747
3747
  if (closest) {
3748
- p14.log.info(pc15.yellow(`Did you mean '${pc15.cyan(closest)}'?`));
3748
+ p14.log.info(pc14.yellow(`Did you mean '${pc14.cyan(closest)}'?`));
3749
3749
  }
3750
3750
  process.exit(1);
3751
3751
  }
@@ -3757,7 +3757,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3757
3757
  try {
3758
3758
  manifest = await fetchModuleManifest(registryBase, modType, modName);
3759
3759
  } catch (err) {
3760
- p14.log.error(pc15.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
3760
+ p14.log.error(pc14.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
3761
3761
  process.exit(1);
3762
3762
  }
3763
3763
  nodes.set(key, {
@@ -3797,7 +3797,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3797
3797
  buildTopologicalOrder(rootKey);
3798
3798
  const resolutions = /* @__PURE__ */ new Map();
3799
3799
  const globalAliasMap = /* @__PURE__ */ new Map();
3800
- p14.log.step(`Importing ${type}: ${pc15.cyan(name)}...`);
3800
+ p14.log.step(`Importing ${type}: ${pc14.cyan(name)}...`);
3801
3801
  for (const key of orderedKeys) {
3802
3802
  const node = nodes.get(key);
3803
3803
  const { type: modType, name: registryName, manifest } = node;
@@ -3830,7 +3830,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3830
3830
  if (programmaticPointTo) {
3831
3831
  if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
3832
3832
  p14.log.error(
3833
- pc15.red(
3833
+ pc14.red(
3834
3834
  `Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
3835
3835
  )
3836
3836
  );
@@ -3866,7 +3866,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
3866
3866
  }
3867
3867
  const existingDir = getExistingModuleDir(projectRoot, modType, registryName);
3868
3868
  if (!existingDir) {
3869
- p14.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc15.cyan(registryName)}`);
3869
+ p14.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc14.cyan(registryName)}`);
3870
3870
  resolutions.set(key, {
3871
3871
  type: modType,
3872
3872
  registryName,
@@ -4097,7 +4097,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4097
4097
  const isOverwrite = Boolean(flags.overwrite || res.action === "overwrite");
4098
4098
  if (key !== rootKey) {
4099
4099
  p14.log.step(
4100
- `Importing ${res.type}: ${pc15.cyan(res.targetName)}${res.targetName !== res.registryName ? pc15.dim(` (from ${res.registryName})`) : ""}...`
4100
+ `Importing ${res.type}: ${pc14.cyan(res.targetName)}${res.targetName !== res.registryName ? pc14.dim(` (from ${res.registryName})`) : ""}...`
4101
4101
  );
4102
4102
  }
4103
4103
  if (res.action === "overwrite" && !flags.dryRun) {
@@ -4116,7 +4116,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4116
4116
  const localFilePath = path2.join(targetDir, file);
4117
4117
  if (fs9.existsSync(localFilePath) && !isOverwrite) {
4118
4118
  p14.log.warn(
4119
- ` \u21B3 Skipping existing file: ${pc15.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
4119
+ ` \u21B3 Skipping existing file: ${pc14.dim(path2.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
4120
4120
  );
4121
4121
  continue;
4122
4122
  }
@@ -4128,13 +4128,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4128
4128
  }
4129
4129
  const displayPath = path2.relative(projectRoot, localFilePath);
4130
4130
  totalWrittenFiles.push(displayPath);
4131
- p14.log.message(` ${pc15.green("+")} ${displayPath}`);
4131
+ p14.log.message(` ${pc14.green("+")} ${displayPath}`);
4132
4132
  }
4133
4133
  }
4134
4134
  const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
4135
4135
  if (missingNpm.length > 0) {
4136
4136
  if (flags.skipInstall || flags.dryRun) {
4137
- p14.log.info(`Required npm dependencies to install: ${pc15.cyan(missingNpm.join(", "))}`);
4137
+ p14.log.info(`Required npm dependencies to install: ${pc14.cyan(missingNpm.join(", "))}`);
4138
4138
  } else {
4139
4139
  try {
4140
4140
  installNpmPackages(projectRoot, missingNpm);
@@ -4148,13 +4148,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
4148
4148
  }
4149
4149
  }
4150
4150
  if (flags.dryRun) {
4151
- p14.log.info(pc15.yellow("Dry run complete: No files were written to disk."));
4151
+ p14.log.info(pc14.yellow("Dry run complete: No files were written to disk."));
4152
4152
  } else {
4153
4153
  const rootRes = resolutions.get(rootKey);
4154
4154
  const baseDir = getModuleBaseDir(projectRoot, type);
4155
4155
  const displayTarget = path2.relative(projectRoot, path2.join(baseDir, rootRes.targetName));
4156
4156
  p14.log.success(
4157
- pc15.green(
4157
+ pc14.green(
4158
4158
  `Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
4159
4159
  )
4160
4160
  );
@@ -4403,6 +4403,350 @@ function discoverAllFeatures(projectRoot) {
4403
4403
  dir: path2.join(featuresBase, e.name)
4404
4404
  })).sort((a, b) => a.name.localeCompare(b.name));
4405
4405
  }
4406
+ function extractClassMethods(content, className) {
4407
+ let clean = "";
4408
+ let inLineComment = false;
4409
+ let inBlockComment = false;
4410
+ let inString = null;
4411
+ let escaped = false;
4412
+ for (let i = 0; i < content.length; i++) {
4413
+ const c = content[i];
4414
+ const next = content[i + 1];
4415
+ if (inLineComment) {
4416
+ if (c === "\n") {
4417
+ inLineComment = false;
4418
+ clean += "\n";
4419
+ } else {
4420
+ clean += " ";
4421
+ }
4422
+ continue;
4423
+ }
4424
+ if (inBlockComment) {
4425
+ if (c === "*" && next === "/") {
4426
+ inBlockComment = false;
4427
+ clean += " ";
4428
+ i++;
4429
+ } else {
4430
+ clean += c === "\n" ? "\n" : " ";
4431
+ }
4432
+ continue;
4433
+ }
4434
+ if (inString) {
4435
+ clean += c === "\n" ? "\n" : " ";
4436
+ if (escaped) {
4437
+ escaped = false;
4438
+ } else if (c === "\\") {
4439
+ escaped = true;
4440
+ } else if (c === inString) {
4441
+ inString = null;
4442
+ }
4443
+ continue;
4444
+ }
4445
+ if (c === "/" && next === "/") {
4446
+ inLineComment = true;
4447
+ clean += " ";
4448
+ i++;
4449
+ continue;
4450
+ }
4451
+ if (c === "/" && next === "*") {
4452
+ inBlockComment = true;
4453
+ clean += " ";
4454
+ i++;
4455
+ continue;
4456
+ }
4457
+ if (c === '"' || c === "'" || c === "`") {
4458
+ inString = c;
4459
+ clean += " ";
4460
+ continue;
4461
+ }
4462
+ clean += c;
4463
+ }
4464
+ const classDeclRegex = new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b[^{]*\\{`);
4465
+ const match = classDeclRegex.exec(clean);
4466
+ if (!match) return null;
4467
+ const classBodyStart = match.index + match[0].length - 1;
4468
+ const methods = [];
4469
+ let depth = 1;
4470
+ let parenDepth = 0;
4471
+ let atMemberStart = true;
4472
+ const methodRegex = /^\s*(?:@[a-zA-Z0-9_$]+(?:\([^)]*\))?\s+)*(?:(public|private|protected|static)\s+)*(?:(async)\s+)?(?:(get|set)\s+)?(#?[a-zA-Z0-9_$]+)\s*(?:<[^>]+>)?\s*\(/;
4473
+ const arrowRegex = /^\s*(?:(public|private|protected|static)\s+)?(#?[a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\(/;
4474
+ for (let i = classBodyStart + 1; i < clean.length && depth > 0; i++) {
4475
+ const c = clean[i];
4476
+ if (c === "{") {
4477
+ depth++;
4478
+ atMemberStart = false;
4479
+ continue;
4480
+ }
4481
+ if (c === "}") {
4482
+ depth--;
4483
+ if (depth === 1) {
4484
+ atMemberStart = true;
4485
+ }
4486
+ continue;
4487
+ }
4488
+ if (depth === 1) {
4489
+ if (c === ";") {
4490
+ atMemberStart = true;
4491
+ parenDepth = 0;
4492
+ continue;
4493
+ }
4494
+ if (c === "(") {
4495
+ parenDepth++;
4496
+ } else if (c === ")") {
4497
+ parenDepth = Math.max(0, parenDepth - 1);
4498
+ }
4499
+ if (atMemberStart && parenDepth === 0) {
4500
+ if (/\s/.test(c)) continue;
4501
+ const slice = clean.slice(i);
4502
+ const methodMatch = slice.match(methodRegex);
4503
+ if (methodMatch) {
4504
+ const fullMatch = methodMatch[0];
4505
+ const beforeParen = fullMatch.slice(0, fullMatch.lastIndexOf("(")).trim();
4506
+ const tokens = beforeParen.split(/\s+/).filter(Boolean);
4507
+ const rawName = tokens[tokens.length - 1] || "";
4508
+ const methodName = rawName.replace(/<.*$/, "");
4509
+ const controlKeywords = ["if", "for", "while", "switch", "catch", "with"];
4510
+ if (!controlKeywords.includes(methodName)) {
4511
+ const isPrivate = tokens.includes("private") || tokens.includes("protected") || methodName.startsWith("#");
4512
+ const isAsync = tokens.includes("async");
4513
+ const isPublic = !isPrivate;
4514
+ const lineNum = content.slice(0, i).split("\n").length;
4515
+ const lineCode = content.split("\n")[lineNum - 1]?.trim() || "";
4516
+ methods.push({
4517
+ name: methodName,
4518
+ isAsync,
4519
+ isPublic,
4520
+ line: lineNum,
4521
+ code: lineCode
4522
+ });
4523
+ i += fullMatch.length - 1;
4524
+ parenDepth = 1;
4525
+ atMemberStart = false;
4526
+ continue;
4527
+ }
4528
+ }
4529
+ const arrowMatch = slice.match(arrowRegex);
4530
+ if (arrowMatch) {
4531
+ const fullMatch = arrowMatch[0];
4532
+ const beforeEquals = fullMatch.split("=")[0].trim();
4533
+ const tokens = beforeEquals.split(/\s+/).filter(Boolean);
4534
+ const rawName = tokens[tokens.length - 1] || "";
4535
+ const isPrivate = tokens.includes("private") || tokens.includes("protected") || rawName.startsWith("#");
4536
+ const isAsync = fullMatch.includes("async");
4537
+ const isPublic = !isPrivate;
4538
+ const lineNum = content.slice(0, i).split("\n").length;
4539
+ const lineCode = content.split("\n")[lineNum - 1]?.trim() || "";
4540
+ methods.push({
4541
+ name: rawName,
4542
+ isAsync,
4543
+ isPublic,
4544
+ line: lineNum,
4545
+ code: lineCode
4546
+ });
4547
+ i += fullMatch.length - 1;
4548
+ parenDepth = 1;
4549
+ atMemberStart = false;
4550
+ continue;
4551
+ }
4552
+ atMemberStart = false;
4553
+ }
4554
+ }
4555
+ }
4556
+ return methods;
4557
+ }
4558
+ function checkUseCaseShape(featureName, filePath, relativePath, content, addViolation) {
4559
+ const fileName = path2.basename(filePath);
4560
+ const lines = content.split(/\r?\n/);
4561
+ const classMatch = content.match(/(?:export\s+(?:default\s+)?)?class\s+([A-Za-z0-9_]+)/);
4562
+ if (!classMatch) {
4563
+ addViolation({
4564
+ feature: featureName,
4565
+ layer: "useCases",
4566
+ file: filePath,
4567
+ relativePath,
4568
+ line: 1,
4569
+ category: "Use Case Shape Violation",
4570
+ code: lines[0]?.trim() || "",
4571
+ ruleId: "U_SHAPE_CLASS",
4572
+ severity: "error",
4573
+ advice: `Use case file '${fileName}' must define and export a class.`,
4574
+ fixExample: {
4575
+ bad: "// Missing class definition",
4576
+ good: `export class ${fileName.replace(/\.(ts|js)$/, "")} { ... }`
4577
+ }
4578
+ });
4579
+ return;
4580
+ }
4581
+ const className = classMatch[1];
4582
+ let classLine = 1;
4583
+ for (let i = 0; i < lines.length; i++) {
4584
+ if (new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b`).test(lines[i])) {
4585
+ classLine = i + 1;
4586
+ break;
4587
+ }
4588
+ }
4589
+ if (!/@MakeInjectable\b/.test(content)) {
4590
+ addViolation({
4591
+ feature: featureName,
4592
+ layer: "useCases",
4593
+ file: filePath,
4594
+ relativePath,
4595
+ line: classLine,
4596
+ category: "Use Case Shape Violation",
4597
+ code: lines[classLine - 1]?.trim() || `class ${className}`,
4598
+ ruleId: "U_SHAPE_DECORATOR",
4599
+ severity: "error",
4600
+ advice: `Use case class '${className}' in '${fileName}' must be decorated with @MakeInjectable.`,
4601
+ fixExample: {
4602
+ bad: lines[classLine - 1]?.trim() || `class ${className}`,
4603
+ good: `@MakeInjectable
4604
+ export class ${className} { ... }`
4605
+ }
4606
+ });
4607
+ }
4608
+ const hasAsyncExecute = /(?:public\s+)?async\s+execute\s*(?:<[^>]+>)?\s*\(/.test(content);
4609
+ if (!hasAsyncExecute) {
4610
+ const hasSyncExecute = /(?:public\s+)?execute\s*(?:<[^>]+>)?\s*\(/.test(content);
4611
+ addViolation({
4612
+ feature: featureName,
4613
+ layer: "useCases",
4614
+ file: filePath,
4615
+ relativePath,
4616
+ line: classLine,
4617
+ category: "Use Case Shape Violation",
4618
+ code: lines[classLine - 1]?.trim() || `class ${className}`,
4619
+ ruleId: "U_SHAPE_EXECUTE",
4620
+ severity: "error",
4621
+ advice: hasSyncExecute ? `Use case class '${className}' in '${fileName}' must have an 'async execute' method (found synchronous 'execute').` : `Use case class '${className}' in '${fileName}' must implement an 'async execute(...)' method.`,
4622
+ fixExample: {
4623
+ bad: hasSyncExecute ? "execute(props: ...) { ... }" : `class ${className} { ... }`,
4624
+ good: "async execute(props: ...): Promise<...> { ... }"
4625
+ }
4626
+ });
4627
+ }
4628
+ const classMethods = extractClassMethods(content, className);
4629
+ if (classMethods) {
4630
+ const extraPublicMethods = classMethods.filter(
4631
+ (m) => m.isPublic && m.name !== "constructor" && m.name !== "execute"
4632
+ );
4633
+ for (const m of extraPublicMethods) {
4634
+ addViolation({
4635
+ feature: featureName,
4636
+ layer: "useCases",
4637
+ file: filePath,
4638
+ relativePath,
4639
+ line: m.line,
4640
+ category: "Use Case Shape Violation",
4641
+ code: m.code || `public ${m.name}(...)`,
4642
+ ruleId: "U_SHAPE_ONLY_EXECUTE",
4643
+ severity: "error",
4644
+ advice: `Use case class '${className}' in '${fileName}' has disallowed public method '${m.name}'. The only allowed public methods are constructor and async execute. Extract extra methods into their own use cases, or make them private.`,
4645
+ fixExample: {
4646
+ bad: m.code || `${m.name}(...) { ... }`,
4647
+ good: `// Extract '${m.name}' into its own dedicated Use Case, or make it private if it is internal helper logic:
4648
+ private ${m.name}(...) { ... }`
4649
+ }
4650
+ });
4651
+ }
4652
+ }
4653
+ }
4654
+ function checkServiceShape(featureName, filePath, relativePath, content, addViolation) {
4655
+ const fileName = path2.basename(filePath);
4656
+ const lines = content.split(/\r?\n/);
4657
+ const classMatch = content.match(/(?:export\s+(?:default\s+)?)?class\s+([A-Za-z0-9_]+)/);
4658
+ if (!classMatch) {
4659
+ addViolation({
4660
+ feature: featureName,
4661
+ layer: "services",
4662
+ file: filePath,
4663
+ relativePath,
4664
+ line: 1,
4665
+ category: "Service Shape Violation",
4666
+ code: lines[0]?.trim() || "",
4667
+ ruleId: "S_SHAPE_CLASS",
4668
+ severity: "error",
4669
+ advice: `Service file '${fileName}' must define and export a class.`,
4670
+ fixExample: {
4671
+ bad: "// Missing class definition",
4672
+ good: `export class ${fileName.replace(/\.(ts|js)$/, "")} { ... }`
4673
+ }
4674
+ });
4675
+ return;
4676
+ }
4677
+ const className = classMatch[1];
4678
+ let classLine = 1;
4679
+ for (let i = 0; i < lines.length; i++) {
4680
+ if (new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b`).test(lines[i])) {
4681
+ classLine = i + 1;
4682
+ break;
4683
+ }
4684
+ }
4685
+ if (!/@MakeInjectable\b/.test(content)) {
4686
+ addViolation({
4687
+ feature: featureName,
4688
+ layer: "services",
4689
+ file: filePath,
4690
+ relativePath,
4691
+ line: classLine,
4692
+ category: "Service Shape Violation",
4693
+ code: lines[classLine - 1]?.trim() || `class ${className}`,
4694
+ ruleId: "S_SHAPE_DECORATOR",
4695
+ severity: "error",
4696
+ advice: `Service class '${className}' in '${fileName}' must be decorated with @MakeInjectable.`,
4697
+ fixExample: {
4698
+ bad: lines[classLine - 1]?.trim() || `class ${className}`,
4699
+ good: `@MakeInjectable
4700
+ export class ${className} { ... }`
4701
+ }
4702
+ });
4703
+ }
4704
+ const hasAsyncExecute = /(?:public\s+)?async\s+execute\s*(?:<[^>]+>)?\s*\(/.test(content);
4705
+ if (!hasAsyncExecute) {
4706
+ const hasSyncExecute = /(?:public\s+)?execute\s*(?:<[^>]+>)?\s*\(/.test(content);
4707
+ addViolation({
4708
+ feature: featureName,
4709
+ layer: "services",
4710
+ file: filePath,
4711
+ relativePath,
4712
+ line: classLine,
4713
+ category: "Service Shape Violation",
4714
+ code: lines[classLine - 1]?.trim() || `class ${className}`,
4715
+ ruleId: "S_SHAPE_EXECUTE",
4716
+ severity: "error",
4717
+ advice: hasSyncExecute ? `Service class '${className}' in '${fileName}' must have an 'async execute' method (found synchronous 'execute').` : `Service class '${className}' in '${fileName}' must implement an 'async execute(...)' method.`,
4718
+ fixExample: {
4719
+ bad: hasSyncExecute ? "execute(props: ...) { ... }" : `class ${className} { ... }`,
4720
+ good: "async execute(props: ...): Promise<...> { ... }"
4721
+ }
4722
+ });
4723
+ }
4724
+ const classMethods = extractClassMethods(content, className);
4725
+ if (classMethods) {
4726
+ const extraPublicMethods = classMethods.filter(
4727
+ (m) => m.isPublic && m.name !== "constructor" && m.name !== "execute"
4728
+ );
4729
+ for (const m of extraPublicMethods) {
4730
+ addViolation({
4731
+ feature: featureName,
4732
+ layer: "services",
4733
+ file: filePath,
4734
+ relativePath,
4735
+ line: m.line,
4736
+ category: "Service Shape Violation",
4737
+ code: m.code || `public ${m.name}(...)`,
4738
+ ruleId: "S_SHAPE_ONLY_EXECUTE",
4739
+ severity: "error",
4740
+ advice: `Service class '${className}' in '${fileName}' has disallowed public method '${m.name}'. The only allowed public methods are constructor and async execute. If a service has multiple public methods, those should be on their own services instead of being cramped in a single service.`,
4741
+ fixExample: {
4742
+ bad: m.code || `${m.name}(...) { ... }`,
4743
+ good: `// Extract '${m.name}' into its own dedicated Service, or make it private if it is internal helper logic:
4744
+ private ${m.name}(...) { ... }`
4745
+ }
4746
+ });
4747
+ }
4748
+ }
4749
+ }
4406
4750
  function lintCodebase(options = {}) {
4407
4751
  const startTime = Date.now();
4408
4752
  const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
@@ -4420,13 +4764,9 @@ function lintCodebase(options = {}) {
4420
4764
  featureResult.passed = false;
4421
4765
  }
4422
4766
  if (failFast) {
4423
- const errorMsg = `[FAIL FAST] Dependency rule violation detected:
4424
- Feature: ${v.feature}
4425
- Category: ${v.category}
4426
- Rule: ${v.ruleId}
4427
- Location: ${v.relativePath}:${v.line}
4428
- Code: ${v.code}
4429
- Explanation: ${v.advice}`;
4767
+ const errorMsg = `Dependency rule violation detected in ${v.relativePath}:${v.line}:
4768
+ [${v.category}] ${v.advice}
4769
+ Code: ${v.code}`;
4430
4770
  throw new DependencyRuleError(errorMsg, [v], true);
4431
4771
  }
4432
4772
  };
@@ -4450,6 +4790,14 @@ function lintCodebase(options = {}) {
4450
4790
  if (!layer) continue;
4451
4791
  const content = fs9.readFileSync(file, "utf-8");
4452
4792
  const statements = extractImportStatements(content);
4793
+ const baseName = path2.basename(file);
4794
+ const isBarrel = baseName === "index.ts" || baseName === "index.js";
4795
+ if (layer === "useCases" && !isBarrel) {
4796
+ checkUseCaseShape(feat.name, file, relPath, content, addViolation);
4797
+ }
4798
+ if (layer === "services" && !isBarrel) {
4799
+ checkServiceShape(feat.name, file, relPath, content, addViolation);
4800
+ }
4453
4801
  if (layer === "domain") {
4454
4802
  const lines = content.split(/\r?\n/);
4455
4803
  for (let i = 0; i < lines.length; i++) {
@@ -4860,6 +5208,24 @@ function lintCodebase(options = {}) {
4860
5208
  }
4861
5209
  });
4862
5210
  }
5211
+ if (target.targetLayer === "services") {
5212
+ addViolation({
5213
+ feature: feat.name,
5214
+ layer,
5215
+ file,
5216
+ relativePath: relPath,
5217
+ line: stmt.line,
5218
+ category: "Presentation Layer Violation",
5219
+ code: stmt.rawCode,
5220
+ ruleId: "P4_DIRECT_SERVICE",
5221
+ severity: "error",
5222
+ advice: "Presentation handlers must not depend on services directly. They must use use cases, and use cases will then depend on the service.",
5223
+ fixExample: {
5224
+ bad: stmt.rawCode,
5225
+ good: "// Inject a Use Case via static deps instead of a Service."
5226
+ }
5227
+ });
5228
+ }
4863
5229
  }
4864
5230
  }
4865
5231
  }
@@ -4914,87 +5280,23 @@ ${compiledDetails}
4914
5280
  }
4915
5281
 
4916
5282
  // src/commands/lint.ts
4917
- function printLintBanner(mode) {
4918
- console.log("");
4919
- const title = " Mason Clean Architecture Dependency Linter ";
4920
- const line = "\u2501".repeat(title.length + 2);
4921
- console.log(pc15.cyan(` \u250F${line}\u2513`));
4922
- console.log(` ${pc15.cyan("\u2503")} ${pc15.bgCyan(pc15.black(title))} ${pc15.cyan("\u2503")}`);
4923
- console.log(pc15.cyan(` \u2517${line}\u251B`));
4924
- const modeText = mode === "failFast" ? `${pc15.yellow("Fail-Fast")} ${pc15.dim("(default: stops at first error)")}` : `${pc15.magenta("Complete")} ${pc15.dim("(--complete: compiles all errors)")}`;
4925
- console.log(` Mode: ${modeText}`);
4926
- console.log("");
4927
- }
4928
- function formatViolation(v, showFixHint = true) {
5283
+ function formatViolation(v) {
4929
5284
  const lines = [];
4930
- const categoryBadge = pc15.red(pc15.bold(`[${v.category}]`));
4931
- const location = pc15.white(`${v.relativePath}:${pc15.yellow(String(v.line))}`);
4932
- lines.push(` ${categoryBadge} ${location}`);
4933
- lines.push(` ${pc15.dim("Rule ID:")} ${pc15.yellow(v.ruleId)}`);
4934
- lines.push(` ${pc15.dim("Code:")} ${pc15.magenta(v.code)}`);
4935
- lines.push(` ${pc15.dim("Advice:")} ${pc15.cyan(v.advice)}`);
4936
- if (showFixHint && v.fixExample) {
4937
- lines.push(` ${pc15.dim("Example:")}`);
4938
- lines.push(` ${pc15.red(`- ${v.fixExample.bad}`)}`);
4939
- lines.push(` ${pc15.green(`+ ${v.fixExample.good}`)}`);
4940
- }
4941
- return lines.join("\n");
4942
- }
4943
- function formatFeatureResult(res, options = {}) {
4944
- const lines = [];
4945
- if (res.passed) {
4946
- lines.push(
4947
- ` ${pc15.green("\u2714")} ${pc15.bold(res.featureName)} ${pc15.dim(
4948
- `(${res.scannedFilesCount} files checked - clean)`
4949
- )}`
4950
- );
4951
- return lines.join("\n");
4952
- }
4953
- const countBadge = pc15.red(
4954
- `(${res.violations.length} violation${res.violations.length > 1 ? "s" : ""})`
5285
+ lines.push(pc14.underline(pc14.cyan(v.relativePath)) + pc14.dim(":") + pc14.yellow(String(v.line)));
5286
+ const gutter = `${String(v.line).padStart(4, " ")} | `;
5287
+ lines.push(pc14.dim(gutter) + v.code);
5288
+ const categoryShort = v.category.replace(" Violation", "");
5289
+ lines.push(
5290
+ " " + pc14.red(pc14.bold("\u2716")) + " " + pc14.red(`[${categoryShort}]`) + " " + v.advice
4955
5291
  );
4956
- lines.push(`
4957
- ${pc15.red("\u2716")} ${pc15.bold(pc15.white(res.featureName))} ${countBadge}`);
4958
- lines.push(` ${pc15.dim("Path:")} ${res.featureDir}`);
4959
- lines.push("");
4960
- for (const v of res.violations) {
4961
- lines.push(formatViolation(v, options.fixHint !== false));
4962
- lines.push("");
5292
+ if (v.fixExample?.good) {
5293
+ lines.push(" " + pc14.dim("\u2192") + " " + pc14.dim(v.fixExample.good));
4963
5294
  }
4964
5295
  return lines.join("\n");
4965
5296
  }
4966
- function printTerminalReport(summary, options = {}) {
4967
- for (const res of summary.results) {
4968
- const formatted = formatFeatureResult(res, options);
4969
- if (formatted.trim()) {
4970
- console.log(formatted);
4971
- }
4972
- }
4973
- console.log("\u2501".repeat(70));
4974
- const statusIcon = summary.totalViolations === 0 ? pc15.green("\u2714 PASS") : pc15.red("\u2716 FAIL");
4975
- console.log(
4976
- ` ${statusIcon} ${pc15.bold("Dependency Rules Audit Result")} ${pc15.dim(
4977
- `(${summary.durationMs}ms)`
4978
- )}`
4979
- );
4980
- console.log(
4981
- ` ${pc15.bold("Features:")} ${pc15.cyan(String(summary.totalFeatures))} total | ${pc15.green(
4982
- String(summary.cleanFeatures)
4983
- )} clean | ${summary.violatedFeatures > 0 ? pc15.red(String(summary.violatedFeatures)) : pc15.green("0")} with violations`
4984
- );
4985
- console.log(
4986
- ` ${pc15.bold("Violations:")} ${summary.totalViolations > 0 ? pc15.red(pc15.bold(String(summary.totalViolations))) : pc15.green("0")} rule violations found`
4987
- );
4988
- console.log("\u2501".repeat(70));
4989
- console.log("");
4990
- }
4991
5297
  async function handleLint(flags = {}) {
4992
5298
  const complete = Boolean(flags.complete);
4993
- const mode = complete ? "complete" : "failFast";
4994
5299
  const quiet = Boolean(flags.quiet);
4995
- if (!quiet) {
4996
- printLintBanner(mode);
4997
- }
4998
5300
  const options = {
4999
5301
  complete,
5000
5302
  quiet,
@@ -5005,14 +5307,23 @@ async function handleLint(flags = {}) {
5005
5307
  };
5006
5308
  try {
5007
5309
  const summary = lintCodebase(options);
5008
- if (!quiet) {
5009
- printTerminalReport(summary, options);
5010
- }
5011
5310
  if (summary.totalViolations > 0) {
5311
+ if (!quiet) {
5312
+ console.log("");
5313
+ for (const v of summary.allViolations) {
5314
+ console.log(formatViolation(v));
5315
+ console.log("");
5316
+ }
5317
+ console.log(
5318
+ pc14.red(
5319
+ `\u2716 Dependency rules check failed with ${summary.totalViolations} error${summary.totalViolations > 1 ? "s" : ""} across ${summary.violatedFeatures} feature${summary.violatedFeatures > 1 ? "s" : ""} (${summary.totalFeatures} features scanned).`
5320
+ )
5321
+ );
5322
+ console.log("");
5323
+ }
5012
5324
  const errorHeader = `Dependency rule audit failed with ${summary.totalViolations} violation(s) across ${summary.violatedFeatures} feature(s):`;
5013
5325
  const compiledDetails = summary.allViolations.map(
5014
5326
  (v, i) => `${i + 1}. [${v.category}] ${v.relativePath}:${v.line}
5015
- Rule: ${v.ruleId}
5016
5327
  Code: ${v.code}
5017
5328
  Advice: ${v.advice}`
5018
5329
  ).join("\n\n");
@@ -5023,20 +5334,22 @@ ${compiledDetails}
5023
5334
  }
5024
5335
  if (!quiet) {
5025
5336
  console.log(
5026
- pc15.green(`\u2714 All dependency rules are being followed! (${summary.totalFeatures} features checked)`)
5337
+ pc14.green(
5338
+ `\u2714 Dependency rules verified across ${summary.totalFeatures} feature${summary.totalFeatures === 1 ? "" : "s"}.`
5339
+ )
5027
5340
  );
5028
- console.log("");
5029
5341
  }
5030
5342
  return summary;
5031
5343
  } catch (err) {
5032
5344
  if (err instanceof DependencyRuleError && err.isFailFast) {
5033
5345
  if (!quiet) {
5034
- console.log(pc15.red(`
5035
- \u2716 [FAIL FAST] Terminating on first detected dependency rule violation:`));
5346
+ console.log("");
5036
5347
  const v = err.violations[0];
5037
5348
  if (v) {
5038
- console.log(formatViolation(v, options.fixHint !== false));
5349
+ console.log(formatViolation(v));
5350
+ console.log("");
5039
5351
  }
5352
+ console.log(pc14.red(`\u2716 Dependency rules check failed (1 error).`));
5040
5353
  console.log("");
5041
5354
  }
5042
5355
  }
@@ -5050,7 +5363,7 @@ function createCli() {
5050
5363
  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").option("--point-to <mapping>", "Point a dependency to an existing local module (e.g. --point-to time:mytime)").option("--alias <mapping>", "Import a dependency fresh under an alias name (e.g. --alias time:time-mason)").option("--tests", "Include test files (use --no-tests to exclude)").action(async (args, flags) => {
5051
5364
  if (args && args.length > 0) {
5052
5365
  p14.log.error(
5053
- pc15.red(
5366
+ pc14.red(
5054
5367
  `Unknown command: "${args.join(" ")}". Use --help to list available commands.`
5055
5368
  )
5056
5369
  );
@@ -5169,6 +5482,12 @@ function createCli() {
5169
5482
  targetDir: target || flags.target || flags.dest
5170
5483
  });
5171
5484
  } catch (err) {
5485
+ if (err instanceof DependencyRuleError) {
5486
+ if (process.env.VITEST) {
5487
+ throw err;
5488
+ }
5489
+ process.exit(1);
5490
+ }
5172
5491
  handleCliError(err);
5173
5492
  }
5174
5493
  });
@@ -5203,7 +5522,7 @@ function handleCliError(err) {
5203
5522
  process.exit(0);
5204
5523
  }
5205
5524
  const message = err instanceof Error ? err.message : String(err);
5206
- p14.log.error(pc15.red(`Error: ${message}`));
5525
+ p14.log.error(pc14.red(`Error: ${message}`));
5207
5526
  if (process.env.VITEST) {
5208
5527
  throw err;
5209
5528
  }