@solid-stack/mason 1.0.32 → 1.0.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -190,6 +190,16 @@ function getConfiguredSharedDir(projectRoot, flags) {
190
190
  }
191
191
  return path2.join(projectRoot, "shared");
192
192
  }
193
+ function loadLintConfig(projectRoot) {
194
+ const configPath = path2.join(projectRoot, "mason-lint.config.json");
195
+ if (fs4.existsSync(configPath)) {
196
+ try {
197
+ return JSON.parse(fs4.readFileSync(configPath, "utf-8"));
198
+ } catch {
199
+ }
200
+ }
201
+ return { baseErrorPath: "src/errors/AppError.ts" };
202
+ }
193
203
 
194
204
  // src/utils/project.ts
195
205
  function findProjectRoot(startDir = process.cwd()) {
@@ -4917,6 +4927,15 @@ function classifyFile(fullPath, projectRoot) {
4917
4927
  }
4918
4928
  return { moduleType: "shared", moduleName, feature: moduleName, layer };
4919
4929
  }
4930
+ const errorMatch = rel.match(/(?:^|\/)(?:src\/)?errors(?:\/(.*))?$/);
4931
+ if (errorMatch) {
4932
+ return {
4933
+ moduleType: "error",
4934
+ moduleName: "global-errors",
4935
+ feature: "global-errors",
4936
+ layer: "domain"
4937
+ };
4938
+ }
4920
4939
  return {};
4921
4940
  }
4922
4941
  function classifyTarget(specifier, sourceFileAbs, projectRoot) {
@@ -4942,15 +4961,28 @@ function classifyTarget(specifier, sourceFileAbs, projectRoot) {
4942
4961
  resolvedRel = specifier.replace(/^[@~]\//, "src/");
4943
4962
  } else if (specifier === "@configs" || specifier.startsWith("@configs/")) {
4944
4963
  resolvedRel = "src/" + specifier.slice(1);
4964
+ } else if (specifier.startsWith("@/errors/") || specifier.startsWith("~/errors/") || specifier === "@/errors" || specifier === "~/errors") {
4965
+ resolvedRel = specifier.replace(/^[@~]\//, "src/");
4966
+ } else if (specifier === "@errors" || specifier.startsWith("@errors/")) {
4967
+ resolvedRel = "src/" + specifier.slice(1);
4945
4968
  } else if (specifier.startsWith("@/") || specifier.startsWith("~/")) {
4946
4969
  resolvedRel = "src/" + specifier.slice(2);
4947
- } else if (specifier.startsWith("features/") || specifier.startsWith("shared/") || specifier.startsWith("configs/") || specifier === "configs") {
4970
+ } else if (specifier.startsWith("features/") || specifier.startsWith("shared/") || specifier.startsWith("configs/") || specifier === "configs" || specifier.startsWith("errors/") || specifier === "errors") {
4948
4971
  resolvedRel = specifier;
4949
4972
  }
4950
4973
  if (!resolvedRel) {
4951
4974
  return { isExternal: true, isDiFramework: false };
4952
4975
  }
4953
4976
  const isGlobalConfig = resolvedRel.startsWith("src/configs/") || resolvedRel === "src/configs" || resolvedRel.startsWith("configs/") || resolvedRel === "configs";
4977
+ const isGlobalError = resolvedRel.startsWith("src/errors/") || resolvedRel === "src/errors" || resolvedRel.startsWith("errors/") || resolvedRel === "errors";
4978
+ if (isGlobalError) {
4979
+ return {
4980
+ isExternal: false,
4981
+ isDiFramework: false,
4982
+ isGlobalError: true,
4983
+ isTargetError: true
4984
+ };
4985
+ }
4954
4986
  const featMatch = resolvedRel.match(/(?:^|\/)(?:src\/)?features\/([^/]+)\/([^/]+)(?:\/(.*))?$/);
4955
4987
  if (featMatch && featMatch[1] && featMatch[2]) {
4956
4988
  const targetFeature = featMatch[1];
@@ -5491,11 +5523,45 @@ function lintCodebase(options = {}) {
5491
5523
  const startTime = Date.now();
5492
5524
  const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
5493
5525
  const failFast = !options.complete;
5526
+ const lintConfig = loadLintConfig(projectRoot);
5527
+ const baseErrorRelative = lintConfig.baseErrorPath || "src/errors/AppError.ts";
5528
+ const baseErrorAbs = path2.resolve(projectRoot, baseErrorRelative);
5529
+ const featuresBase = resolveFeaturesDir(projectRoot);
5530
+ const sharedBase = resolveSharedDir(projectRoot);
5531
+ let errorsBase = path2.join(projectRoot, "src/errors");
5532
+ if (!fs4.existsSync(errorsBase) && fs4.existsSync(path2.join(projectRoot, "errors"))) {
5533
+ errorsBase = path2.join(projectRoot, "errors");
5534
+ }
5535
+ const missingFoundations = [];
5536
+ if (!fs4.existsSync(featuresBase)) missingFoundations.push("features/");
5537
+ if (!fs4.existsSync(sharedBase)) missingFoundations.push("shared/");
5538
+ if (!fs4.existsSync(errorsBase)) missingFoundations.push("errors/");
5539
+ if (missingFoundations.length > 0) {
5540
+ throw new DependencyRuleError(
5541
+ `Project Structure Violation: Missing required foundational directories: ${missingFoundations.join(", ")}. Every project must contain 'features/', 'shared/', and 'errors/'.`,
5542
+ [],
5543
+ true
5544
+ );
5545
+ }
5546
+ if (!fs4.existsSync(baseErrorAbs)) {
5547
+ throw new DependencyRuleError(
5548
+ `Project Structure Violation: Base error class not found at '${baseErrorRelative}'. Create this file or update 'baseErrorPath' in mason-lint.config.json.`,
5549
+ [],
5550
+ true
5551
+ );
5552
+ }
5553
+ const baseErrorContent = fs4.readFileSync(baseErrorAbs, "utf-8");
5554
+ const baseClassMatch = baseErrorContent.match(
5555
+ /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/
5556
+ );
5557
+ const baseErrorClassName = baseClassMatch ? baseClassMatch[1] : "AppError";
5494
5558
  const features = discoverAllFeatures(projectRoot);
5495
5559
  const sharedModules = discoverAllShared(projectRoot);
5560
+ const errorModule = { name: "global-errors", dir: errorsBase, type: "error" };
5496
5561
  const allModules = [
5497
5562
  ...features.map((f) => ({ ...f, type: "feature" })),
5498
- ...sharedModules.map((s) => ({ ...s, type: "shared" }))
5563
+ ...sharedModules.map((s) => ({ ...s, type: "shared" })),
5564
+ errorModule
5499
5565
  ];
5500
5566
  const graph = new FeatureDependencyGraph();
5501
5567
  const allViolations = [];
@@ -5525,7 +5591,7 @@ Code: ${v.code}`;
5525
5591
  scannedFilesCount: 0,
5526
5592
  passed: true,
5527
5593
  violations: [],
5528
- layersChecked: mod.type === "feature" ? ["domain", "useCases", "services", "infrastructure", "presentation"] : ["domain", "ports", "infrastructure", "root"]
5594
+ layersChecked: mod.type === "feature" ? ["domain", "useCases", "services", "infrastructure", "presentation"] : mod.type === "shared" ? ["domain", "ports", "infrastructure", "root"] : ["domain"]
5529
5595
  });
5530
5596
  }
5531
5597
  for (const mod of allModules) {
@@ -5540,6 +5606,121 @@ Code: ${v.code}`;
5540
5606
  const statements = extractImportStatements(content);
5541
5607
  const baseName = path2.basename(file);
5542
5608
  const isBarrel = baseName === "index.ts" || baseName === "index.js";
5609
+ const throwErrorRegex = /\bthrow\s+(?:new\s+)?Error\s*\(/g;
5610
+ let throwMatch;
5611
+ while ((throwMatch = throwErrorRegex.exec(content)) !== null) {
5612
+ const charIndex = throwMatch.index;
5613
+ const lineNum = content.substring(0, charIndex).split("\n").length;
5614
+ const lineCode = content.split(/\r?\n/)[lineNum - 1]?.trim() || throwMatch[0];
5615
+ if (lineCode.startsWith("//")) continue;
5616
+ addViolation({
5617
+ feature: moduleName,
5618
+ layer,
5619
+ file,
5620
+ relativePath: relPath,
5621
+ line: lineNum,
5622
+ category: "Plain Error Throw Violation",
5623
+ code: lineCode,
5624
+ ruleId: "E3_PLAIN_ERROR_THROW",
5625
+ severity: "error",
5626
+ advice: `Throwing plain 'Error' is prohibited. You must throw '${baseErrorClassName}' or a domain-specific error class that inherits from it.`,
5627
+ fixExample: {
5628
+ bad: lineCode,
5629
+ good: `throw new ${baseErrorClassName}("Something went wrong");`
5630
+ }
5631
+ });
5632
+ }
5633
+ 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");
5634
+ const isBaseErrorFile = path2.resolve(file) === path2.resolve(baseErrorAbs);
5635
+ if (isErrorFile && !isBaseErrorFile) {
5636
+ const classRegex = /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_]+))?/g;
5637
+ let classMatch;
5638
+ while ((classMatch = classRegex.exec(content)) !== null) {
5639
+ const className = classMatch[1];
5640
+ const extendsName = classMatch[2];
5641
+ const charIndex = classMatch.index;
5642
+ const lineNum = content.substring(0, charIndex).split("\n").length;
5643
+ const lineCode = content.split(/\r?\n/)[lineNum - 1]?.trim() || classMatch[0];
5644
+ if (lineCode.startsWith("//")) continue;
5645
+ if (!extendsName) {
5646
+ addViolation({
5647
+ feature: moduleName,
5648
+ layer,
5649
+ file,
5650
+ relativePath: relPath,
5651
+ line: lineNum,
5652
+ category: "Error Inheritance Violation",
5653
+ code: lineCode,
5654
+ ruleId: "E4_MISSING_INHERITANCE",
5655
+ severity: "error",
5656
+ advice: `Error class '${className}' must extend '${baseErrorClassName}' (or another class that derives from it).`,
5657
+ fixExample: {
5658
+ bad: lineCode,
5659
+ good: `export class ${className} extends ${baseErrorClassName} { ... }`
5660
+ }
5661
+ });
5662
+ } else if (extendsName === "Error") {
5663
+ addViolation({
5664
+ feature: moduleName,
5665
+ layer,
5666
+ file,
5667
+ relativePath: relPath,
5668
+ line: lineNum,
5669
+ category: "Error Inheritance Violation",
5670
+ code: lineCode,
5671
+ ruleId: "E5_NATIVE_ERROR_INHERITANCE",
5672
+ severity: "error",
5673
+ advice: `Error class '${className}' extends the native 'Error'. It must extend your base error class ('${baseErrorClassName}').`,
5674
+ fixExample: {
5675
+ bad: lineCode,
5676
+ good: `export class ${className} extends ${baseErrorClassName} { ... }`
5677
+ }
5678
+ });
5679
+ }
5680
+ }
5681
+ }
5682
+ if (moduleType === "error") {
5683
+ for (const stmt of statements) {
5684
+ const target = classifyTarget(stmt.specifier, file, projectRoot);
5685
+ if (target.targetFeature) {
5686
+ addViolation({
5687
+ feature: "global-errors",
5688
+ layer: "domain",
5689
+ file,
5690
+ relativePath: relPath,
5691
+ line: stmt.line,
5692
+ category: "Global Error Purity Violation",
5693
+ code: stmt.rawCode,
5694
+ ruleId: "E1_FEATURE_DEPENDENCY",
5695
+ severity: "error",
5696
+ advice: `Global errors must not depend on features ('${target.targetFeature}'). Global errors represent pure domain or system states.`,
5697
+ fixExample: {
5698
+ bad: stmt.rawCode,
5699
+ good: "// Global errors must not reference specific features. Keep error types pure."
5700
+ }
5701
+ });
5702
+ }
5703
+ if (target.targetSharedModule && !target.isSharedDomain) {
5704
+ addViolation({
5705
+ feature: "global-errors",
5706
+ layer: "domain",
5707
+ file,
5708
+ relativePath: relPath,
5709
+ line: stmt.line,
5710
+ category: "Global Error Purity Violation",
5711
+ code: stmt.rawCode,
5712
+ ruleId: "E2_SHARED_NON_DOMAIN",
5713
+ severity: "error",
5714
+ advice: `Global errors can only depend on shared domain elements (e.g., value objects). They cannot depend on shared ports or infrastructure.`,
5715
+ fixExample: {
5716
+ bad: stmt.rawCode,
5717
+ good: "// Depend only on pure domain types from shared modules."
5718
+ }
5719
+ });
5720
+ }
5721
+ }
5722
+ continue;
5723
+ }
5543
5724
  if (moduleType === "shared") {
5544
5725
  if (layer === "domain") {
5545
5726
  const lines = content.split(/\r?\n/);
@@ -6562,6 +6743,6 @@ async function runCli(argv = process.argv) {
6562
6743
  cli.parse(argv, { run: true });
6563
6744
  }
6564
6745
 
6565
- export { DEFAULT_REGISTRY_BASE, DependencyRuleError, EnvEditor, FeatureDependencyGraph, PromptCancelledError, computeDirectoryHash, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, discoverEnvironments, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateShared, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getAllFiles, getConfiguredFeaturesDir, getConfiguredSharedDir, getEffectiveMode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleEnv, handleImport, handleInit, handleLint, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, lintCodebase, listDomainEntities, listDomainInterfaces, listExistingFeatures, listExistingShared, listUseCases, loadInterfaceHierarchy, loadMasonConfig, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveEnvFilename, resolveFeaturesDir, resolveSharedDir, runCli, runEnvAdd, runEnvAudit, runEnvCommand, runEnvDelete, runEnvExamplesCommand, runEnvInit, runEnvInitExamples, runEnvMenu, runEnvUpdate, runSharedCommand, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
6746
+ export { DEFAULT_REGISTRY_BASE, DependencyRuleError, EnvEditor, FeatureDependencyGraph, PromptCancelledError, computeDirectoryHash, concreteInfraTemplate, createCli, detectPackageManager, diProviderTemplate, discoverEnvironments, domainClassTemplate, domainInterfaceTemplate, domainValueObjectTemplate, ensureInterfacePrefix, expressIndexTemplate, fetchModuleManifest, fetchRegistryFile, fetchRegistryIndex, findClosestMatch, findInterfaceFile, findLocalRegistry, findProjectRoot, generateConcreteInfraCode, generateDomain, generateDomainEntityCode, generateFeature, generateHttpHandler, generateHttpHandlerCode, generateInfrastructure, generateInjectable, generateInjectableCode, generateInterface, generateInterfaceCode, generateShared, generateStubCode, generateUseCase, generateUseCaseCode, generateUseCaseTestCode, getAllFiles, getConfiguredFeaturesDir, getConfiguredSharedDir, getEffectiveMode, getMissingNpmPackages, getRegistryBase, getTemplatesDir, handleCancel, handleCreate, handleEnv, handleImport, handleInit, handleLint, hbs, httpHandlerTemplate, injectableInterfaceTemplate, injectableTemplate, installNpmPackages, interfaceTemplate, isValidIdentifier, levenshteinDistance, lintCodebase, listDomainEntities, listDomainInterfaces, listExistingFeatures, listExistingShared, listUseCases, loadInterfaceHierarchy, loadLintConfig, loadMasonConfig, loadTemplate, normalizeRegistryUrl, parseInterfaceMethodsAndImports, printBanner, printGeneratedFiles, promptProperties, removeInterfacePrefix, renderTemplate, renderTemplateFile, resolveEnvFilename, resolveFeaturesDir, resolveSharedDir, runCli, runEnvAdd, runEnvAudit, runEnvCommand, runEnvDelete, runEnvExamplesCommand, runEnvInit, runEnvInitExamples, runEnvMenu, runEnvUpdate, runSharedCommand, stubInfraTemplate, synthesizeCrudMethods, toCamelCase, toKebabCase, toPascalCase, toPlural, toSingular, toStubName, updateOrGenerateDiProvider, useCaseTemplate, useCaseTestTemplate, writeFileSafely };
6566
6747
  //# sourceMappingURL=index.js.map
6567
6748
  //# sourceMappingURL=index.js.map