@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.cjs +170 -31
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +170 -31
- package/dist/bin.js.map +1 -1
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/core/lintEngine.d.ts +9 -0
- package/dist/core/lintEngine.d.ts.map +1 -1
- package/dist/index.cjs +170 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +170 -31
- package/dist/index.js.map +1 -1
- package/dist/types/lint.d.ts +2 -0
- package/dist/types/lint.d.ts.map +1 -1
- package/dist/utils/config.d.ts +1 -1
- package/dist/utils/config.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -5648,42 +5648,108 @@ private ${m.name}(...) { ... }`
|
|
|
5648
5648
|
}
|
|
5649
5649
|
}
|
|
5650
5650
|
}
|
|
5651
|
+
function resolveRegistryModuleError(modDir, modName) {
|
|
5652
|
+
const errorsDir = path26__default.default.join(modDir, "errors");
|
|
5653
|
+
if (!fs17__default.default.existsSync(errorsDir) || !fs17__default.default.statSync(errorsDir).isDirectory()) {
|
|
5654
|
+
return { errorsDir, hasErrorsDir: false };
|
|
5655
|
+
}
|
|
5656
|
+
const entries = fs17__default.default.readdirSync(errorsDir);
|
|
5657
|
+
const errorFiles = entries.filter((f) => f.endsWith(".ts") || f.endsWith(".js"));
|
|
5658
|
+
const nameNormalized = modName.toLowerCase().replace(/[-_]/g, "");
|
|
5659
|
+
const singularNormalized = toSingular(modName).toLowerCase().replace(/[-_]/g, "");
|
|
5660
|
+
let baseErrorFileName = errorFiles.find((f) => {
|
|
5661
|
+
const baseNoExt = f.replace(/\.(ts|js)$/, "").toLowerCase().replace(/[-_]/g, "");
|
|
5662
|
+
return baseNoExt === `${nameNormalized}error` || baseNoExt === `${singularNormalized}error` || baseNoExt === `${nameNormalized}errors` || baseNoExt === `${singularNormalized}errors`;
|
|
5663
|
+
});
|
|
5664
|
+
if (!baseErrorFileName) {
|
|
5665
|
+
return { errorsDir, hasErrorsDir: true };
|
|
5666
|
+
}
|
|
5667
|
+
const baseErrorFileAbs = path26__default.default.join(errorsDir, baseErrorFileName);
|
|
5668
|
+
const content = fs17__default.default.readFileSync(baseErrorFileAbs, "utf-8");
|
|
5669
|
+
const classRegex = /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_]+))?/g;
|
|
5670
|
+
let match;
|
|
5671
|
+
const classes = [];
|
|
5672
|
+
while ((match = classRegex.exec(content)) !== null) {
|
|
5673
|
+
classes.push({
|
|
5674
|
+
name: match[1],
|
|
5675
|
+
extendsName: match[2],
|
|
5676
|
+
raw: match[0]
|
|
5677
|
+
});
|
|
5678
|
+
}
|
|
5679
|
+
const expectedNames = [
|
|
5680
|
+
`${toPascalCase(modName)}Error`,
|
|
5681
|
+
`${toPascalCase(toSingular(modName))}Error`,
|
|
5682
|
+
`${toPascalCase(modName)}Errors`,
|
|
5683
|
+
`${toPascalCase(toSingular(modName))}Errors`
|
|
5684
|
+
].map((n) => n.toLowerCase());
|
|
5685
|
+
const targetClass = classes.find((c) => expectedNames.includes(c.name.toLowerCase())) || classes.find((c) => c.raw.includes("export")) || classes[0];
|
|
5686
|
+
return {
|
|
5687
|
+
errorsDir,
|
|
5688
|
+
hasErrorsDir: true,
|
|
5689
|
+
baseErrorFileAbs,
|
|
5690
|
+
baseErrorClassName: targetClass ? targetClass.name : void 0,
|
|
5691
|
+
baseErrorExtendsName: targetClass ? targetClass.extendsName : void 0,
|
|
5692
|
+
baseErrorRawClassCode: targetClass ? targetClass.raw : void 0
|
|
5693
|
+
};
|
|
5694
|
+
}
|
|
5651
5695
|
function lintCodebase(options = {}) {
|
|
5652
5696
|
const startTime = Date.now();
|
|
5653
5697
|
const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
|
|
5654
5698
|
const failFast = !options.complete;
|
|
5699
|
+
const effectiveMode = options.mode || getEffectiveMode(projectRoot, options.registry !== void 0 ? { registry: options.registry } : void 0);
|
|
5700
|
+
const isRegistryMode = effectiveMode === "registry";
|
|
5655
5701
|
const { featuresBase, sharedBase, errorsBase, baseErrorAbs } = resolveLintEnvironment(projectRoot);
|
|
5656
|
-
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5702
|
+
if (!isRegistryMode) {
|
|
5703
|
+
const missingFoundations = [];
|
|
5704
|
+
if (!fs17__default.default.existsSync(featuresBase)) missingFoundations.push("features/");
|
|
5705
|
+
if (!fs17__default.default.existsSync(sharedBase)) missingFoundations.push("shared/");
|
|
5706
|
+
if (!fs17__default.default.existsSync(errorsBase)) missingFoundations.push("errors/");
|
|
5707
|
+
if (missingFoundations.length > 0) {
|
|
5708
|
+
throw new DependencyRuleError(
|
|
5709
|
+
`Project Structure Violation: Missing required foundational directories: ${missingFoundations.join(", ")}. Every architecture must contain 'features/', 'shared/', and 'errors/'.`,
|
|
5710
|
+
[],
|
|
5711
|
+
true
|
|
5712
|
+
);
|
|
5713
|
+
}
|
|
5714
|
+
if (!fs17__default.default.existsSync(baseErrorAbs)) {
|
|
5715
|
+
throw new DependencyRuleError(
|
|
5716
|
+
`Project Structure Violation: Base error class not found at '${path26__default.default.relative(projectRoot, baseErrorAbs).replace(/\\/g, "/")}'. Configure paths.baseError in mason.config.json.`,
|
|
5717
|
+
[],
|
|
5718
|
+
true
|
|
5719
|
+
);
|
|
5720
|
+
}
|
|
5721
|
+
} else {
|
|
5722
|
+
if (!fs17__default.default.existsSync(featuresBase) && !fs17__default.default.existsSync(sharedBase)) {
|
|
5723
|
+
throw new DependencyRuleError(
|
|
5724
|
+
`Project Structure Violation: Missing required foundational directories: features/ or shared/. A registry must contain 'features/' or 'shared/'.`,
|
|
5725
|
+
[],
|
|
5726
|
+
true
|
|
5727
|
+
);
|
|
5728
|
+
}
|
|
5666
5729
|
}
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5730
|
+
let baseErrorClassName = "AppError";
|
|
5731
|
+
if (!isRegistryMode && fs17__default.default.existsSync(baseErrorAbs)) {
|
|
5732
|
+
const baseErrorContent = fs17__default.default.readFileSync(baseErrorAbs, "utf-8");
|
|
5733
|
+
const baseClassMatch = baseErrorContent.match(
|
|
5734
|
+
/(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/
|
|
5672
5735
|
);
|
|
5736
|
+
baseErrorClassName = baseClassMatch ? baseClassMatch[1] : "AppError";
|
|
5673
5737
|
}
|
|
5674
|
-
const baseErrorContent = fs17__default.default.readFileSync(baseErrorAbs, "utf-8");
|
|
5675
|
-
const baseClassMatch = baseErrorContent.match(
|
|
5676
|
-
/(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)/
|
|
5677
|
-
);
|
|
5678
|
-
const baseErrorClassName = baseClassMatch ? baseClassMatch[1] : "AppError";
|
|
5679
5738
|
const features = discoverAllFeatures(featuresBase, [sharedBase, errorsBase]);
|
|
5680
5739
|
const sharedModules = discoverAllShared(sharedBase, [featuresBase, errorsBase]);
|
|
5681
|
-
const errorModule = { name: "global-errors", dir: errorsBase, type: "error" };
|
|
5682
5740
|
const allModules = [
|
|
5683
5741
|
...features.map((f) => ({ ...f, type: "feature" })),
|
|
5684
|
-
...sharedModules.map((s) => ({ ...s, type: "shared" }))
|
|
5685
|
-
errorModule
|
|
5742
|
+
...sharedModules.map((s) => ({ ...s, type: "shared" }))
|
|
5686
5743
|
];
|
|
5744
|
+
if (!isRegistryMode) {
|
|
5745
|
+
const errorModule = { name: "global-errors", dir: errorsBase, type: "error" };
|
|
5746
|
+
allModules.push(errorModule);
|
|
5747
|
+
} else if (fs17__default.default.existsSync(errorsBase)) {
|
|
5748
|
+
const errorFiles = scanDirFiles(errorsBase, projectRoot);
|
|
5749
|
+
if (errorFiles.length > 0) {
|
|
5750
|
+
allModules.push({ name: "global-errors", dir: errorsBase, type: "error" });
|
|
5751
|
+
}
|
|
5752
|
+
}
|
|
5687
5753
|
const graph = new FeatureDependencyGraph();
|
|
5688
5754
|
const allViolations = [];
|
|
5689
5755
|
const results = [];
|
|
@@ -5717,6 +5783,77 @@ Code: ${v.code}`;
|
|
|
5717
5783
|
}
|
|
5718
5784
|
for (const mod of allModules) {
|
|
5719
5785
|
const modResult = results.find((r) => r.featureName === mod.name);
|
|
5786
|
+
let modBaseErrorClass = baseErrorClassName;
|
|
5787
|
+
let modBaseErrorAbs = !isRegistryMode ? baseErrorAbs : void 0;
|
|
5788
|
+
if (isRegistryMode && (mod.type === "feature" || mod.type === "shared")) {
|
|
5789
|
+
const errorInfo = resolveRegistryModuleError(mod.dir, mod.name);
|
|
5790
|
+
if (!errorInfo.hasErrorsDir) {
|
|
5791
|
+
addViolation({
|
|
5792
|
+
feature: mod.name,
|
|
5793
|
+
layer: "domain",
|
|
5794
|
+
file: mod.dir,
|
|
5795
|
+
relativePath: path26__default.default.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/"),
|
|
5796
|
+
line: 1,
|
|
5797
|
+
category: "Project Structure Violation",
|
|
5798
|
+
code: `Missing errors/ directory in registry ${mod.type} '${mod.name}'`,
|
|
5799
|
+
ruleId: "REG_ERRORS_DIR_REQUIRED",
|
|
5800
|
+
severity: "error",
|
|
5801
|
+
advice: `Registry ${mod.type} '${mod.name}' must implement its own 'errors/' directory at '${path26__default.default.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/")}'.`,
|
|
5802
|
+
fixExample: {
|
|
5803
|
+
bad: "// Missing errors directory",
|
|
5804
|
+
good: `mkdir -p ${path26__default.default.relative(projectRoot, errorInfo.errorsDir).replace(/\\/g, "/")}`
|
|
5805
|
+
}
|
|
5806
|
+
});
|
|
5807
|
+
} else if (!errorInfo.baseErrorFileAbs) {
|
|
5808
|
+
const expectedFileName = `${toPascalCase(mod.name)}Error.ts`;
|
|
5809
|
+
const expectedRel = path26__default.default.relative(projectRoot, path26__default.default.join(errorInfo.errorsDir, expectedFileName)).replace(/\\/g, "/");
|
|
5810
|
+
addViolation({
|
|
5811
|
+
feature: mod.name,
|
|
5812
|
+
layer: "domain",
|
|
5813
|
+
file: errorInfo.errorsDir,
|
|
5814
|
+
relativePath: expectedRel,
|
|
5815
|
+
line: 1,
|
|
5816
|
+
category: "Project Structure Violation",
|
|
5817
|
+
code: `Missing base error file in '${mod.name}/errors'`,
|
|
5818
|
+
ruleId: "REG_BASE_ERROR_REQUIRED",
|
|
5819
|
+
severity: "error",
|
|
5820
|
+
advice: `Registry ${mod.type} '${mod.name}' must have a base error file at '${expectedRel}'.`,
|
|
5821
|
+
fixExample: {
|
|
5822
|
+
bad: "// Missing base error file",
|
|
5823
|
+
good: `export class ${toPascalCase(mod.name)}Error extends Error {
|
|
5824
|
+
constructor(message: string) {
|
|
5825
|
+
super(message);
|
|
5826
|
+
}
|
|
5827
|
+
}`
|
|
5828
|
+
}
|
|
5829
|
+
});
|
|
5830
|
+
} else {
|
|
5831
|
+
modBaseErrorAbs = errorInfo.baseErrorFileAbs;
|
|
5832
|
+
modBaseErrorClass = errorInfo.baseErrorClassName || `${toPascalCase(mod.name)}Error`;
|
|
5833
|
+
if (!errorInfo.baseErrorClassName || errorInfo.baseErrorExtendsName !== "Error") {
|
|
5834
|
+
addViolation({
|
|
5835
|
+
feature: mod.name,
|
|
5836
|
+
layer: "domain",
|
|
5837
|
+
file: errorInfo.baseErrorFileAbs,
|
|
5838
|
+
relativePath: path26__default.default.relative(projectRoot, errorInfo.baseErrorFileAbs).replace(/\\/g, "/"),
|
|
5839
|
+
line: 1,
|
|
5840
|
+
category: "Error Inheritance Violation",
|
|
5841
|
+
code: errorInfo.baseErrorRawClassCode || `class ${modBaseErrorClass}`,
|
|
5842
|
+
ruleId: "REG_BASE_ERROR_MUST_EXTEND_ERROR",
|
|
5843
|
+
severity: "error",
|
|
5844
|
+
advice: `Registry ${mod.type} base error class '${modBaseErrorClass}' in '${mod.name}' must extend native 'Error'.`,
|
|
5845
|
+
fixExample: {
|
|
5846
|
+
bad: errorInfo.baseErrorRawClassCode || `class ${modBaseErrorClass}`,
|
|
5847
|
+
good: `export class ${modBaseErrorClass} extends Error {
|
|
5848
|
+
constructor(message: string) {
|
|
5849
|
+
super(message);
|
|
5850
|
+
}
|
|
5851
|
+
}`
|
|
5852
|
+
}
|
|
5853
|
+
});
|
|
5854
|
+
}
|
|
5855
|
+
}
|
|
5856
|
+
}
|
|
5720
5857
|
const allFiles = scanDirFiles(mod.dir, projectRoot);
|
|
5721
5858
|
modResult.scannedFilesCount = allFiles.length;
|
|
5722
5859
|
for (const file of allFiles) {
|
|
@@ -5744,15 +5881,15 @@ Code: ${v.code}`;
|
|
|
5744
5881
|
code: lineCode,
|
|
5745
5882
|
ruleId: "E3_PLAIN_ERROR_THROW",
|
|
5746
5883
|
severity: "error",
|
|
5747
|
-
advice: `Throwing plain 'Error' is prohibited. You must throw '${
|
|
5884
|
+
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.`,
|
|
5748
5885
|
fixExample: {
|
|
5749
5886
|
bad: lineCode,
|
|
5750
|
-
good: `throw new ${
|
|
5887
|
+
good: `throw new ${modBaseErrorClass}("Something went wrong");`
|
|
5751
5888
|
}
|
|
5752
5889
|
});
|
|
5753
5890
|
}
|
|
5754
5891
|
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");
|
|
5755
|
-
const isBaseErrorFile = path26__default.default.resolve(file) === path26__default.default.resolve(baseErrorAbs);
|
|
5892
|
+
const isBaseErrorFile = modBaseErrorAbs ? path26__default.default.resolve(file) === path26__default.default.resolve(modBaseErrorAbs) : !isRegistryMode && path26__default.default.resolve(file) === path26__default.default.resolve(baseErrorAbs);
|
|
5756
5893
|
if (isErrorFile && !isBaseErrorFile) {
|
|
5757
5894
|
const classRegex = /(?:export\s+(?:default\s+)?)?(?:abstract\s+)?class\s+([A-Za-z0-9_]+)(?:<[^>]+>)?(?:\s+extends\s+([A-Za-z0-9_]+))?/g;
|
|
5758
5895
|
let classMatch;
|
|
@@ -5774,10 +5911,10 @@ Code: ${v.code}`;
|
|
|
5774
5911
|
code: lineCode,
|
|
5775
5912
|
ruleId: "E4_MISSING_INHERITANCE",
|
|
5776
5913
|
severity: "error",
|
|
5777
|
-
advice: `Error class '${className}' must extend '${
|
|
5914
|
+
advice: isRegistryMode ? `Error class '${className}' in registry ${mod.type} '${mod.name}' must extend '${modBaseErrorClass}'.` : `Error class '${className}' must extend '${modBaseErrorClass}'.`,
|
|
5778
5915
|
fixExample: {
|
|
5779
5916
|
bad: lineCode,
|
|
5780
|
-
good: `export class ${className} extends ${
|
|
5917
|
+
good: `export class ${className} extends ${modBaseErrorClass} { ... }`
|
|
5781
5918
|
}
|
|
5782
5919
|
});
|
|
5783
5920
|
} else if (extendsName === "Error") {
|
|
@@ -5791,10 +5928,10 @@ Code: ${v.code}`;
|
|
|
5791
5928
|
code: lineCode,
|
|
5792
5929
|
ruleId: "E5_NATIVE_ERROR_INHERITANCE",
|
|
5793
5930
|
severity: "error",
|
|
5794
|
-
advice: `Error class '${className}' extends native 'Error'. It must extend your configured base error class ('${
|
|
5931
|
+
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}').`,
|
|
5795
5932
|
fixExample: {
|
|
5796
5933
|
bad: lineCode,
|
|
5797
|
-
good: `export class ${className} extends ${
|
|
5934
|
+
good: `export class ${className} extends ${modBaseErrorClass} { ... }`
|
|
5798
5935
|
}
|
|
5799
5936
|
});
|
|
5800
5937
|
}
|
|
@@ -6554,7 +6691,9 @@ async function handleLint(flags = {}) {
|
|
|
6554
6691
|
targetDir: flags.targetDir || flags.target,
|
|
6555
6692
|
projectRoot: flags.projectRoot,
|
|
6556
6693
|
fixHint: flags.fixHint !== false,
|
|
6557
|
-
throwOnError: false
|
|
6694
|
+
throwOnError: false,
|
|
6695
|
+
mode: flags.mode || (flags.registry ? "registry" : void 0),
|
|
6696
|
+
registry: Boolean(flags.registry)
|
|
6558
6697
|
};
|
|
6559
6698
|
try {
|
|
6560
6699
|
const summary = lintCodebase(options);
|