@solid-stack/mason 1.0.35 → 1.0.36

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
@@ -5619,42 +5619,108 @@ private ${m.name}(...) { ... }`
5619
5619
  }
5620
5620
  }
5621
5621
  }
5622
+ function resolveRegistryModuleError(modDir, modName) {
5623
+ const errorsDir = path26.join(modDir, "errors");
5624
+ if (!fs17.existsSync(errorsDir) || !fs17.statSync(errorsDir).isDirectory()) {
5625
+ return { errorsDir, hasErrorsDir: false };
5626
+ }
5627
+ const entries = fs17.readdirSync(errorsDir);
5628
+ const errorFiles = entries.filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
5629
+ const nameNormalized = modName.toLowerCase().replace(/[-_]/g, "");
5630
+ const singularNormalized = toSingular(modName).toLowerCase().replace(/[-_]/g, "");
5631
+ let baseErrorFileName = errorFiles.find((f) => {
5632
+ const baseNoExt = f.replace(/\.(ts|js)$/, "").toLowerCase().replace(/[-_]/g, "");
5633
+ return baseNoExt === `${nameNormalized}error` || baseNoExt === `${singularNormalized}error` || baseNoExt === `${nameNormalized}errors` || baseNoExt === `${singularNormalized}errors`;
5634
+ });
5635
+ if (!baseErrorFileName) {
5636
+ return { errorsDir, hasErrorsDir: true };
5637
+ }
5638
+ const baseErrorFileAbs = path26.join(errorsDir, baseErrorFileName);
5639
+ const content = fs17.readFileSync(baseErrorFileAbs, "utf-8");
5640
+ const classRegex = /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_]+))?/g;
5641
+ let match;
5642
+ const classes = [];
5643
+ while ((match = classRegex.exec(content)) !== null) {
5644
+ classes.push({
5645
+ name: match[1],
5646
+ extendsName: match[2],
5647
+ raw: match[0]
5648
+ });
5649
+ }
5650
+ const expectedNames = [
5651
+ `${toPascalCase(modName)}Error`,
5652
+ `${toPascalCase(toSingular(modName))}Error`,
5653
+ `${toPascalCase(modName)}Errors`,
5654
+ `${toPascalCase(toSingular(modName))}Errors`
5655
+ ].map((n) => n.toLowerCase());
5656
+ const targetClass = classes.find((c) => expectedNames.includes(c.name.toLowerCase())) || classes.find((c) => c.raw.includes("export")) || classes[0];
5657
+ return {
5658
+ errorsDir,
5659
+ hasErrorsDir: true,
5660
+ baseErrorFileAbs,
5661
+ baseErrorClassName: targetClass ? targetClass.name : void 0,
5662
+ baseErrorExtendsName: targetClass ? targetClass.extendsName : void 0,
5663
+ baseErrorRawClassCode: targetClass ? targetClass.raw : void 0
5664
+ };
5665
+ }
5622
5666
  function lintCodebase(options = {}) {
5623
5667
  const startTime = Date.now();
5624
5668
  const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
5625
5669
  const failFast = !options.complete;
5670
+ const effectiveMode = options.mode || getEffectiveMode(projectRoot, options.registry !== void 0 ? { registry: options.registry } : void 0);
5671
+ const isRegistryMode = effectiveMode === "registry";
5626
5672
  const { featuresBase, sharedBase, errorsBase, baseErrorAbs } = resolveLintEnvironment(projectRoot);
5627
- const missingFoundations = [];
5628
- if (!fs17.existsSync(featuresBase)) missingFoundations.push("features/");
5629
- if (!fs17.existsSync(sharedBase)) missingFoundations.push("shared/");
5630
- if (!fs17.existsSync(errorsBase)) missingFoundations.push("errors/");
5631
- if (missingFoundations.length > 0) {
5632
- throw new DependencyRuleError(
5633
- `Project Structure Violation: Missing required foundational directories: ${missingFoundations.join(", ")}. Every architecture must contain 'features/', 'shared/', and 'errors/'.`,
5634
- [],
5635
- true
5636
- );
5673
+ if (!isRegistryMode) {
5674
+ const missingFoundations = [];
5675
+ if (!fs17.existsSync(featuresBase)) missingFoundations.push("features/");
5676
+ if (!fs17.existsSync(sharedBase)) missingFoundations.push("shared/");
5677
+ if (!fs17.existsSync(errorsBase)) missingFoundations.push("errors/");
5678
+ if (missingFoundations.length > 0) {
5679
+ throw new DependencyRuleError(
5680
+ `Project Structure Violation: Missing required foundational directories: ${missingFoundations.join(", ")}. Every architecture must contain 'features/', 'shared/', and 'errors/'.`,
5681
+ [],
5682
+ true
5683
+ );
5684
+ }
5685
+ if (!fs17.existsSync(baseErrorAbs)) {
5686
+ throw new DependencyRuleError(
5687
+ `Project Structure Violation: Base error class not found at '${path26.relative(projectRoot, baseErrorAbs).replace(/\\/g, "/")}'. Configure paths.baseError in mason.config.json.`,
5688
+ [],
5689
+ true
5690
+ );
5691
+ }
5692
+ } else {
5693
+ if (!fs17.existsSync(featuresBase) && !fs17.existsSync(sharedBase)) {
5694
+ throw new DependencyRuleError(
5695
+ `Project Structure Violation: Missing required foundational directories: features/ or shared/. A registry must contain 'features/' or 'shared/'.`,
5696
+ [],
5697
+ true
5698
+ );
5699
+ }
5637
5700
  }
5638
- if (!fs17.existsSync(baseErrorAbs)) {
5639
- throw new DependencyRuleError(
5640
- `Project Structure Violation: Base error class not found at '${path26.relative(projectRoot, baseErrorAbs).replace(/\\/g, "/")}'. Configure paths.baseError in mason.config.json.`,
5641
- [],
5642
- true
5701
+ let baseErrorClassName = "AppError";
5702
+ if (!isRegistryMode && fs17.existsSync(baseErrorAbs)) {
5703
+ const baseErrorContent = fs17.readFileSync(baseErrorAbs, "utf-8");
5704
+ const baseClassMatch = baseErrorContent.match(
5705
+ /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/
5643
5706
  );
5707
+ baseErrorClassName = baseClassMatch ? baseClassMatch[1] : "AppError";
5644
5708
  }
5645
- const baseErrorContent = fs17.readFileSync(baseErrorAbs, "utf-8");
5646
- const baseClassMatch = baseErrorContent.match(
5647
- /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/
5648
- );
5649
- const baseErrorClassName = baseClassMatch ? baseClassMatch[1] : "AppError";
5650
5709
  const features = discoverAllFeatures(featuresBase, [sharedBase, errorsBase]);
5651
5710
  const sharedModules = discoverAllShared(sharedBase, [featuresBase, errorsBase]);
5652
- const errorModule = { name: "global-errors", dir: errorsBase, type: "error" };
5653
5711
  const allModules = [
5654
5712
  ...features.map((f) => ({ ...f, type: "feature" })),
5655
- ...sharedModules.map((s) => ({ ...s, type: "shared" })),
5656
- errorModule
5713
+ ...sharedModules.map((s) => ({ ...s, type: "shared" }))
5657
5714
  ];
5715
+ if (!isRegistryMode) {
5716
+ const errorModule = { name: "global-errors", dir: errorsBase, type: "error" };
5717
+ allModules.push(errorModule);
5718
+ } else if (fs17.existsSync(errorsBase)) {
5719
+ const errorFiles = scanDirFiles(errorsBase, projectRoot);
5720
+ if (errorFiles.length > 0) {
5721
+ allModules.push({ name: "global-errors", dir: errorsBase, type: "error" });
5722
+ }
5723
+ }
5658
5724
  const graph = new FeatureDependencyGraph();
5659
5725
  const allViolations = [];
5660
5726
  const results = [];
@@ -5688,6 +5754,77 @@ Code: ${v.code}`;
5688
5754
  }
5689
5755
  for (const mod of allModules) {
5690
5756
  const modResult = results.find((r) => r.featureName === mod.name);
5757
+ let modBaseErrorClass = baseErrorClassName;
5758
+ let modBaseErrorAbs = !isRegistryMode ? baseErrorAbs : void 0;
5759
+ if (isRegistryMode && (mod.type === "feature" || mod.type === "shared")) {
5760
+ const errorInfo = resolveRegistryModuleError(mod.dir, mod.name);
5761
+ if (!errorInfo.hasErrorsDir) {
5762
+ addViolation({
5763
+ feature: mod.name,
5764
+ layer: "domain",
5765
+ file: mod.dir,
5766
+ relativePath: path26.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/"),
5767
+ line: 1,
5768
+ category: "Project Structure Violation",
5769
+ code: `Missing errors/ directory in registry ${mod.type} '${mod.name}'`,
5770
+ ruleId: "REG_ERRORS_DIR_REQUIRED",
5771
+ severity: "error",
5772
+ advice: `Registry ${mod.type} '${mod.name}' must implement its own 'errors/' directory at '${path26.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/")}'.`,
5773
+ fixExample: {
5774
+ bad: "// Missing errors directory",
5775
+ good: `mkdir -p ${path26.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/")}`
5776
+ }
5777
+ });
5778
+ } else if (!errorInfo.baseErrorFileAbs) {
5779
+ const expectedFileName = `${toPascalCase(mod.name)}Error.ts`;
5780
+ const expectedRel = path26.relative(projectRoot, path26.join(errorInfo.errorsDir, expectedFileName)).replace(/\\/g, "/");
5781
+ addViolation({
5782
+ feature: mod.name,
5783
+ layer: "domain",
5784
+ file: errorInfo.errorsDir,
5785
+ relativePath: expectedRel,
5786
+ line: 1,
5787
+ category: "Project Structure Violation",
5788
+ code: `Missing base error file in '${mod.name}/errors'`,
5789
+ ruleId: "REG_BASE_ERROR_REQUIRED",
5790
+ severity: "error",
5791
+ advice: `Registry ${mod.type} '${mod.name}' must have a base error file at '${expectedRel}'.`,
5792
+ fixExample: {
5793
+ bad: "// Missing base error file",
5794
+ good: `export class ${toPascalCase(mod.name)}Error extends Error {
5795
+ constructor(message: string) {
5796
+ super(message);
5797
+ }
5798
+ }`
5799
+ }
5800
+ });
5801
+ } else {
5802
+ modBaseErrorAbs = errorInfo.baseErrorFileAbs;
5803
+ modBaseErrorClass = errorInfo.baseErrorClassName || `${toPascalCase(mod.name)}Error`;
5804
+ if (!errorInfo.baseErrorClassName || errorInfo.baseErrorExtendsName !== "Error") {
5805
+ addViolation({
5806
+ feature: mod.name,
5807
+ layer: "domain",
5808
+ file: errorInfo.baseErrorFileAbs,
5809
+ relativePath: path26.relative(projectRoot, errorInfo.baseErrorFileAbs).replace(/\\/g, "/"),
5810
+ line: 1,
5811
+ category: "Error Inheritance Violation",
5812
+ code: errorInfo.baseErrorRawClassCode || `class ${modBaseErrorClass}`,
5813
+ ruleId: "REG_BASE_ERROR_MUST_EXTEND_ERROR",
5814
+ severity: "error",
5815
+ advice: `Registry ${mod.type} base error class '${modBaseErrorClass}' in '${mod.name}' must extend native 'Error'.`,
5816
+ fixExample: {
5817
+ bad: errorInfo.baseErrorRawClassCode || `class ${modBaseErrorClass}`,
5818
+ good: `export class ${modBaseErrorClass} extends Error {
5819
+ constructor(message: string) {
5820
+ super(message);
5821
+ }
5822
+ }`
5823
+ }
5824
+ });
5825
+ }
5826
+ }
5827
+ }
5691
5828
  const allFiles = scanDirFiles(mod.dir, projectRoot);
5692
5829
  modResult.scannedFilesCount = allFiles.length;
5693
5830
  for (const file of allFiles) {
@@ -5715,15 +5852,15 @@ Code: ${v.code}`;
5715
5852
  code: lineCode,
5716
5853
  ruleId: "E3_PLAIN_ERROR_THROW",
5717
5854
  severity: "error",
5718
- advice: `Throwing plain 'Error' is prohibited. You must throw '${baseErrorClassName}' or a custom error class that inherits from it.`,
5855
+ advice: isRegistryMode ? `Throwing plain 'Error' is prohibited in registry ${mod.type} '${mod.name}'. You must throw '${modBaseErrorClass}' or a class that inherits from it.` : `Throwing plain 'Error' is prohibited. You must throw '${modBaseErrorClass}' or a custom error class that inherits from it.`,
5719
5856
  fixExample: {
5720
5857
  bad: lineCode,
5721
- good: `throw new ${baseErrorClassName}("Something went wrong");`
5858
+ good: `throw new ${modBaseErrorClass}("Something went wrong");`
5722
5859
  }
5723
5860
  });
5724
5861
  }
5725
5862
  const isErrorFile = relPath.includes("/errors/") || relPath.startsWith("errors/") || relPath.startsWith("src/errors/") || moduleType === "error" || baseName.endsWith("Error.ts") || baseName.endsWith("Errors.ts") || baseName.endsWith("Error.js") || baseName.endsWith("Errors.js");
5726
- const isBaseErrorFile = path26.resolve(file) === path26.resolve(baseErrorAbs);
5863
+ const isBaseErrorFile = modBaseErrorAbs ? path26.resolve(file) === path26.resolve(modBaseErrorAbs) : !isRegistryMode && path26.resolve(file) === path26.resolve(baseErrorAbs);
5727
5864
  if (isErrorFile && !isBaseErrorFile) {
5728
5865
  const classRegex = /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_]+))?/g;
5729
5866
  let classMatch;
@@ -5745,10 +5882,10 @@ Code: ${v.code}`;
5745
5882
  code: lineCode,
5746
5883
  ruleId: "E4_MISSING_INHERITANCE",
5747
5884
  severity: "error",
5748
- advice: `Error class '${className}' must extend '${baseErrorClassName}'.`,
5885
+ advice: isRegistryMode ? `Error class '${className}' in registry ${mod.type} '${mod.name}' must extend '${modBaseErrorClass}'.` : `Error class '${className}' must extend '${modBaseErrorClass}'.`,
5749
5886
  fixExample: {
5750
5887
  bad: lineCode,
5751
- good: `export class ${className} extends ${baseErrorClassName} { ... }`
5888
+ good: `export class ${className} extends ${modBaseErrorClass} { ... }`
5752
5889
  }
5753
5890
  });
5754
5891
  } else if (extendsName === "Error") {
@@ -5762,10 +5899,10 @@ Code: ${v.code}`;
5762
5899
  code: lineCode,
5763
5900
  ruleId: "E5_NATIVE_ERROR_INHERITANCE",
5764
5901
  severity: "error",
5765
- advice: `Error class '${className}' extends native 'Error'. It must extend your configured base error class ('${baseErrorClassName}').`,
5902
+ advice: isRegistryMode ? `Error class '${className}' extends native 'Error'. In registry ${mod.type} '${mod.name}', it must extend your base error class ('${modBaseErrorClass}').` : `Error class '${className}' extends native 'Error'. It must extend your configured base error class ('${modBaseErrorClass}').`,
5766
5903
  fixExample: {
5767
5904
  bad: lineCode,
5768
- good: `export class ${className} extends ${baseErrorClassName} { ... }`
5905
+ good: `export class ${className} extends ${modBaseErrorClass} { ... }`
5769
5906
  }
5770
5907
  });
5771
5908
  }
@@ -6525,7 +6662,9 @@ async function handleLint(flags = {}) {
6525
6662
  targetDir: flags.targetDir || flags.target,
6526
6663
  projectRoot: flags.projectRoot,
6527
6664
  fixHint: flags.fixHint !== false,
6528
- throwOnError: false
6665
+ throwOnError: false,
6666
+ mode: flags.mode || (flags.registry ? "registry" : void 0),
6667
+ registry: Boolean(flags.registry)
6529
6668
  };
6530
6669
  try {
6531
6670
  const summary = lintCodebase(options);