@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/bin.cjs +184 -3
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +184 -3
- package/dist/bin.js.map +1 -1
- package/dist/core/lintEngine.d.ts +1 -0
- package/dist/core/lintEngine.d.ts.map +1 -1
- package/dist/index.cjs +185 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +185 -4
- package/dist/index.js.map +1 -1
- package/dist/types/lint.d.ts +5 -2
- package/dist/types/lint.d.ts.map +1 -1
- package/dist/utils/config.d.ts +5 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -218,6 +218,16 @@ function getConfiguredSharedDir(projectRoot, flags) {
|
|
|
218
218
|
}
|
|
219
219
|
return path2__default.default.join(projectRoot, "shared");
|
|
220
220
|
}
|
|
221
|
+
function loadLintConfig(projectRoot) {
|
|
222
|
+
const configPath = path2__default.default.join(projectRoot, "mason-lint.config.json");
|
|
223
|
+
if (fs14__default.default.existsSync(configPath)) {
|
|
224
|
+
try {
|
|
225
|
+
return JSON.parse(fs14__default.default.readFileSync(configPath, "utf-8"));
|
|
226
|
+
} catch {
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return { baseErrorPath: "src/errors/AppError.ts" };
|
|
230
|
+
}
|
|
221
231
|
|
|
222
232
|
// src/utils/project.ts
|
|
223
233
|
function findProjectRoot(startDir = process.cwd()) {
|
|
@@ -4937,6 +4947,15 @@ function classifyFile(fullPath, projectRoot) {
|
|
|
4937
4947
|
}
|
|
4938
4948
|
return { moduleType: "shared", moduleName, feature: moduleName, layer };
|
|
4939
4949
|
}
|
|
4950
|
+
const errorMatch = rel.match(/(?:^|\/)(?:src\/)?errors(?:\/(.*))?$/);
|
|
4951
|
+
if (errorMatch) {
|
|
4952
|
+
return {
|
|
4953
|
+
moduleType: "error",
|
|
4954
|
+
moduleName: "global-errors",
|
|
4955
|
+
feature: "global-errors",
|
|
4956
|
+
layer: "domain"
|
|
4957
|
+
};
|
|
4958
|
+
}
|
|
4940
4959
|
return {};
|
|
4941
4960
|
}
|
|
4942
4961
|
function classifyTarget(specifier, sourceFileAbs, projectRoot) {
|
|
@@ -4962,15 +4981,28 @@ function classifyTarget(specifier, sourceFileAbs, projectRoot) {
|
|
|
4962
4981
|
resolvedRel = specifier.replace(/^[@~]\//, "src/");
|
|
4963
4982
|
} else if (specifier === "@configs" || specifier.startsWith("@configs/")) {
|
|
4964
4983
|
resolvedRel = "src/" + specifier.slice(1);
|
|
4984
|
+
} else if (specifier.startsWith("@/errors/") || specifier.startsWith("~/errors/") || specifier === "@/errors" || specifier === "~/errors") {
|
|
4985
|
+
resolvedRel = specifier.replace(/^[@~]\//, "src/");
|
|
4986
|
+
} else if (specifier === "@errors" || specifier.startsWith("@errors/")) {
|
|
4987
|
+
resolvedRel = "src/" + specifier.slice(1);
|
|
4965
4988
|
} else if (specifier.startsWith("@/") || specifier.startsWith("~/")) {
|
|
4966
4989
|
resolvedRel = "src/" + specifier.slice(2);
|
|
4967
|
-
} else if (specifier.startsWith("features/") || specifier.startsWith("shared/") || specifier.startsWith("configs/") || specifier === "configs") {
|
|
4990
|
+
} else if (specifier.startsWith("features/") || specifier.startsWith("shared/") || specifier.startsWith("configs/") || specifier === "configs" || specifier.startsWith("errors/") || specifier === "errors") {
|
|
4968
4991
|
resolvedRel = specifier;
|
|
4969
4992
|
}
|
|
4970
4993
|
if (!resolvedRel) {
|
|
4971
4994
|
return { isExternal: true, isDiFramework: false };
|
|
4972
4995
|
}
|
|
4973
4996
|
const isGlobalConfig = resolvedRel.startsWith("src/configs/") || resolvedRel === "src/configs" || resolvedRel.startsWith("configs/") || resolvedRel === "configs";
|
|
4997
|
+
const isGlobalError = resolvedRel.startsWith("src/errors/") || resolvedRel === "src/errors" || resolvedRel.startsWith("errors/") || resolvedRel === "errors";
|
|
4998
|
+
if (isGlobalError) {
|
|
4999
|
+
return {
|
|
5000
|
+
isExternal: false,
|
|
5001
|
+
isDiFramework: false,
|
|
5002
|
+
isGlobalError: true,
|
|
5003
|
+
isTargetError: true
|
|
5004
|
+
};
|
|
5005
|
+
}
|
|
4974
5006
|
const featMatch = resolvedRel.match(/(?:^|\/)(?:src\/)?features\/([^/]+)\/([^/]+)(?:\/(.*))?$/);
|
|
4975
5007
|
if (featMatch && featMatch[1] && featMatch[2]) {
|
|
4976
5008
|
const targetFeature = featMatch[1];
|
|
@@ -5511,11 +5543,45 @@ function lintCodebase(options = {}) {
|
|
|
5511
5543
|
const startTime = Date.now();
|
|
5512
5544
|
const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
|
|
5513
5545
|
const failFast = !options.complete;
|
|
5546
|
+
const lintConfig = loadLintConfig(projectRoot);
|
|
5547
|
+
const baseErrorRelative = lintConfig.baseErrorPath || "src/errors/AppError.ts";
|
|
5548
|
+
const baseErrorAbs = path2__default.default.resolve(projectRoot, baseErrorRelative);
|
|
5549
|
+
const featuresBase = resolveFeaturesDir(projectRoot);
|
|
5550
|
+
const sharedBase = resolveSharedDir(projectRoot);
|
|
5551
|
+
let errorsBase = path2__default.default.join(projectRoot, "src/errors");
|
|
5552
|
+
if (!fs14__default.default.existsSync(errorsBase) && fs14__default.default.existsSync(path2__default.default.join(projectRoot, "errors"))) {
|
|
5553
|
+
errorsBase = path2__default.default.join(projectRoot, "errors");
|
|
5554
|
+
}
|
|
5555
|
+
const missingFoundations = [];
|
|
5556
|
+
if (!fs14__default.default.existsSync(featuresBase)) missingFoundations.push("features/");
|
|
5557
|
+
if (!fs14__default.default.existsSync(sharedBase)) missingFoundations.push("shared/");
|
|
5558
|
+
if (!fs14__default.default.existsSync(errorsBase)) missingFoundations.push("errors/");
|
|
5559
|
+
if (missingFoundations.length > 0) {
|
|
5560
|
+
throw new DependencyRuleError(
|
|
5561
|
+
`Project Structure Violation: Missing required foundational directories: ${missingFoundations.join(", ")}. Every project must contain 'features/', 'shared/', and 'errors/'.`,
|
|
5562
|
+
[],
|
|
5563
|
+
true
|
|
5564
|
+
);
|
|
5565
|
+
}
|
|
5566
|
+
if (!fs14__default.default.existsSync(baseErrorAbs)) {
|
|
5567
|
+
throw new DependencyRuleError(
|
|
5568
|
+
`Project Structure Violation: Base error class not found at '${baseErrorRelative}'. Create this file or update 'baseErrorPath' in mason-lint.config.json.`,
|
|
5569
|
+
[],
|
|
5570
|
+
true
|
|
5571
|
+
);
|
|
5572
|
+
}
|
|
5573
|
+
const baseErrorContent = fs14__default.default.readFileSync(baseErrorAbs, "utf-8");
|
|
5574
|
+
const baseClassMatch = baseErrorContent.match(
|
|
5575
|
+
/(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/
|
|
5576
|
+
);
|
|
5577
|
+
const baseErrorClassName = baseClassMatch ? baseClassMatch[1] : "AppError";
|
|
5514
5578
|
const features = discoverAllFeatures(projectRoot);
|
|
5515
5579
|
const sharedModules = discoverAllShared(projectRoot);
|
|
5580
|
+
const errorModule = { name: "global-errors", dir: errorsBase, type: "error" };
|
|
5516
5581
|
const allModules = [
|
|
5517
5582
|
...features.map((f) => ({ ...f, type: "feature" })),
|
|
5518
|
-
...sharedModules.map((s) => ({ ...s, type: "shared" }))
|
|
5583
|
+
...sharedModules.map((s) => ({ ...s, type: "shared" })),
|
|
5584
|
+
errorModule
|
|
5519
5585
|
];
|
|
5520
5586
|
const graph = new FeatureDependencyGraph();
|
|
5521
5587
|
const allViolations = [];
|
|
@@ -5545,7 +5611,7 @@ Code: ${v.code}`;
|
|
|
5545
5611
|
scannedFilesCount: 0,
|
|
5546
5612
|
passed: true,
|
|
5547
5613
|
violations: [],
|
|
5548
|
-
layersChecked: mod.type === "feature" ? ["domain", "useCases", "services", "infrastructure", "presentation"] : ["domain", "ports", "infrastructure", "root"]
|
|
5614
|
+
layersChecked: mod.type === "feature" ? ["domain", "useCases", "services", "infrastructure", "presentation"] : mod.type === "shared" ? ["domain", "ports", "infrastructure", "root"] : ["domain"]
|
|
5549
5615
|
});
|
|
5550
5616
|
}
|
|
5551
5617
|
for (const mod of allModules) {
|
|
@@ -5560,6 +5626,121 @@ Code: ${v.code}`;
|
|
|
5560
5626
|
const statements = extractImportStatements(content);
|
|
5561
5627
|
const baseName = path2__default.default.basename(file);
|
|
5562
5628
|
const isBarrel = baseName === "index.ts" || baseName === "index.js";
|
|
5629
|
+
const throwErrorRegex = /\bthrow\s+(?:new\s+)?Error\s*\(/g;
|
|
5630
|
+
let throwMatch;
|
|
5631
|
+
while ((throwMatch = throwErrorRegex.exec(content)) !== null) {
|
|
5632
|
+
const charIndex = throwMatch.index;
|
|
5633
|
+
const lineNum = content.substring(0, charIndex).split("\n").length;
|
|
5634
|
+
const lineCode = content.split(/\r?\n/)[lineNum - 1]?.trim() || throwMatch[0];
|
|
5635
|
+
if (lineCode.startsWith("//")) continue;
|
|
5636
|
+
addViolation({
|
|
5637
|
+
feature: moduleName,
|
|
5638
|
+
layer,
|
|
5639
|
+
file,
|
|
5640
|
+
relativePath: relPath,
|
|
5641
|
+
line: lineNum,
|
|
5642
|
+
category: "Plain Error Throw Violation",
|
|
5643
|
+
code: lineCode,
|
|
5644
|
+
ruleId: "E3_PLAIN_ERROR_THROW",
|
|
5645
|
+
severity: "error",
|
|
5646
|
+
advice: `Throwing plain 'Error' is prohibited. You must throw '${baseErrorClassName}' or a domain-specific error class that inherits from it.`,
|
|
5647
|
+
fixExample: {
|
|
5648
|
+
bad: lineCode,
|
|
5649
|
+
good: `throw new ${baseErrorClassName}("Something went wrong");`
|
|
5650
|
+
}
|
|
5651
|
+
});
|
|
5652
|
+
}
|
|
5653
|
+
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");
|
|
5654
|
+
const isBaseErrorFile = path2__default.default.resolve(file) === path2__default.default.resolve(baseErrorAbs);
|
|
5655
|
+
if (isErrorFile && !isBaseErrorFile) {
|
|
5656
|
+
const classRegex = /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_]+))?/g;
|
|
5657
|
+
let classMatch;
|
|
5658
|
+
while ((classMatch = classRegex.exec(content)) !== null) {
|
|
5659
|
+
const className = classMatch[1];
|
|
5660
|
+
const extendsName = classMatch[2];
|
|
5661
|
+
const charIndex = classMatch.index;
|
|
5662
|
+
const lineNum = content.substring(0, charIndex).split("\n").length;
|
|
5663
|
+
const lineCode = content.split(/\r?\n/)[lineNum - 1]?.trim() || classMatch[0];
|
|
5664
|
+
if (lineCode.startsWith("//")) continue;
|
|
5665
|
+
if (!extendsName) {
|
|
5666
|
+
addViolation({
|
|
5667
|
+
feature: moduleName,
|
|
5668
|
+
layer,
|
|
5669
|
+
file,
|
|
5670
|
+
relativePath: relPath,
|
|
5671
|
+
line: lineNum,
|
|
5672
|
+
category: "Error Inheritance Violation",
|
|
5673
|
+
code: lineCode,
|
|
5674
|
+
ruleId: "E4_MISSING_INHERITANCE",
|
|
5675
|
+
severity: "error",
|
|
5676
|
+
advice: `Error class '${className}' must extend '${baseErrorClassName}' (or another class that derives from it).`,
|
|
5677
|
+
fixExample: {
|
|
5678
|
+
bad: lineCode,
|
|
5679
|
+
good: `export class ${className} extends ${baseErrorClassName} { ... }`
|
|
5680
|
+
}
|
|
5681
|
+
});
|
|
5682
|
+
} else if (extendsName === "Error") {
|
|
5683
|
+
addViolation({
|
|
5684
|
+
feature: moduleName,
|
|
5685
|
+
layer,
|
|
5686
|
+
file,
|
|
5687
|
+
relativePath: relPath,
|
|
5688
|
+
line: lineNum,
|
|
5689
|
+
category: "Error Inheritance Violation",
|
|
5690
|
+
code: lineCode,
|
|
5691
|
+
ruleId: "E5_NATIVE_ERROR_INHERITANCE",
|
|
5692
|
+
severity: "error",
|
|
5693
|
+
advice: `Error class '${className}' extends the native 'Error'. It must extend your base error class ('${baseErrorClassName}').`,
|
|
5694
|
+
fixExample: {
|
|
5695
|
+
bad: lineCode,
|
|
5696
|
+
good: `export class ${className} extends ${baseErrorClassName} { ... }`
|
|
5697
|
+
}
|
|
5698
|
+
});
|
|
5699
|
+
}
|
|
5700
|
+
}
|
|
5701
|
+
}
|
|
5702
|
+
if (moduleType === "error") {
|
|
5703
|
+
for (const stmt of statements) {
|
|
5704
|
+
const target = classifyTarget(stmt.specifier, file, projectRoot);
|
|
5705
|
+
if (target.targetFeature) {
|
|
5706
|
+
addViolation({
|
|
5707
|
+
feature: "global-errors",
|
|
5708
|
+
layer: "domain",
|
|
5709
|
+
file,
|
|
5710
|
+
relativePath: relPath,
|
|
5711
|
+
line: stmt.line,
|
|
5712
|
+
category: "Global Error Purity Violation",
|
|
5713
|
+
code: stmt.rawCode,
|
|
5714
|
+
ruleId: "E1_FEATURE_DEPENDENCY",
|
|
5715
|
+
severity: "error",
|
|
5716
|
+
advice: `Global errors must not depend on features ('${target.targetFeature}'). Global errors represent pure domain or system states.`,
|
|
5717
|
+
fixExample: {
|
|
5718
|
+
bad: stmt.rawCode,
|
|
5719
|
+
good: "// Global errors must not reference specific features. Keep error types pure."
|
|
5720
|
+
}
|
|
5721
|
+
});
|
|
5722
|
+
}
|
|
5723
|
+
if (target.targetSharedModule && !target.isSharedDomain) {
|
|
5724
|
+
addViolation({
|
|
5725
|
+
feature: "global-errors",
|
|
5726
|
+
layer: "domain",
|
|
5727
|
+
file,
|
|
5728
|
+
relativePath: relPath,
|
|
5729
|
+
line: stmt.line,
|
|
5730
|
+
category: "Global Error Purity Violation",
|
|
5731
|
+
code: stmt.rawCode,
|
|
5732
|
+
ruleId: "E2_SHARED_NON_DOMAIN",
|
|
5733
|
+
severity: "error",
|
|
5734
|
+
advice: `Global errors can only depend on shared domain elements (e.g., value objects). They cannot depend on shared ports or infrastructure.`,
|
|
5735
|
+
fixExample: {
|
|
5736
|
+
bad: stmt.rawCode,
|
|
5737
|
+
good: "// Depend only on pure domain types from shared modules."
|
|
5738
|
+
}
|
|
5739
|
+
});
|
|
5740
|
+
}
|
|
5741
|
+
}
|
|
5742
|
+
continue;
|
|
5743
|
+
}
|
|
5563
5744
|
if (moduleType === "shared") {
|
|
5564
5745
|
if (layer === "domain") {
|
|
5565
5746
|
const lines = content.split(/\r?\n/);
|