@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.cjs +455 -136
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +454 -135
- package/dist/bin.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/commands/lint.d.ts +2 -5
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/core/lintEngine.d.ts +10 -0
- package/dist/core/lintEngine.d.ts.map +1 -1
- package/dist/index.cjs +455 -136
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +454 -135
- package/dist/index.js.map +1 -1
- package/dist/types/lint.d.ts +1 -1
- package/dist/types/lint.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/bin.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
var cac = require('cac');
|
|
5
5
|
var p14 = require('@clack/prompts');
|
|
6
|
-
var
|
|
6
|
+
var pc14 = require('picocolors');
|
|
7
7
|
var fs9 = require('fs');
|
|
8
8
|
var path2 = require('path');
|
|
9
9
|
var url = require('url');
|
|
@@ -33,7 +33,7 @@ function _interopNamespace(e) {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
var p14__namespace = /*#__PURE__*/_interopNamespace(p14);
|
|
36
|
-
var
|
|
36
|
+
var pc14__default = /*#__PURE__*/_interopDefault(pc14);
|
|
37
37
|
var fs9__default = /*#__PURE__*/_interopDefault(fs9);
|
|
38
38
|
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
39
39
|
var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
|
|
@@ -76,17 +76,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
76
76
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
77
77
|
console.log("");
|
|
78
78
|
if (isDryRun) {
|
|
79
|
-
p14__namespace.log.warn(
|
|
79
|
+
p14__namespace.log.warn(pc14__default.default.yellow(pc14__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
80
80
|
}
|
|
81
81
|
for (const f of files) {
|
|
82
82
|
if (f.action === "created") {
|
|
83
|
-
p14__namespace.log.success(`${
|
|
83
|
+
p14__namespace.log.success(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(created)")}`);
|
|
84
84
|
} else if (f.action === "overwritten") {
|
|
85
|
-
p14__namespace.log.warn(`${
|
|
85
|
+
p14__namespace.log.warn(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(overwritten)")}`);
|
|
86
86
|
} else if (f.action === "skipped") {
|
|
87
|
-
p14__namespace.log.info(`${
|
|
87
|
+
p14__namespace.log.info(`${pc14__default.default.dim(f.relativePath)} ${pc14__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
88
88
|
} else {
|
|
89
|
-
p14__namespace.log.info(`${
|
|
89
|
+
p14__namespace.log.info(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(preview)")}`);
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
}
|
|
@@ -1773,13 +1773,13 @@ function handleCancel(result) {
|
|
|
1773
1773
|
}
|
|
1774
1774
|
function printBanner() {
|
|
1775
1775
|
console.log("");
|
|
1776
|
-
p14__namespace.intro(
|
|
1776
|
+
p14__namespace.intro(pc14__default.default.bgCyan(pc14__default.default.black(" @solid-stack/mason ")));
|
|
1777
1777
|
}
|
|
1778
1778
|
async function promptProperties(options = {}) {
|
|
1779
1779
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
1780
1780
|
if (properties.length > 0) {
|
|
1781
1781
|
const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
|
|
1782
|
-
p14__namespace.log.info(
|
|
1782
|
+
p14__namespace.log.info(pc14__default.default.dim(`Pre-configured fields: ${summary}`));
|
|
1783
1783
|
const addMore = handleCancel(
|
|
1784
1784
|
await p14__namespace.confirm({
|
|
1785
1785
|
message: "Would you like to add custom fields/properties?",
|
|
@@ -1790,7 +1790,7 @@ async function promptProperties(options = {}) {
|
|
|
1790
1790
|
return properties;
|
|
1791
1791
|
}
|
|
1792
1792
|
}
|
|
1793
|
-
p14__namespace.log.info(
|
|
1793
|
+
p14__namespace.log.info(pc14__default.default.cyan("Enter properties (leave name blank when finished):"));
|
|
1794
1794
|
while (true) {
|
|
1795
1795
|
const name = handleCancel(
|
|
1796
1796
|
await p14__namespace.text({
|
|
@@ -1838,7 +1838,7 @@ async function promptProperties(options = {}) {
|
|
|
1838
1838
|
optional
|
|
1839
1839
|
});
|
|
1840
1840
|
p14__namespace.log.success(
|
|
1841
|
-
|
|
1841
|
+
pc14__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
1842
1842
|
);
|
|
1843
1843
|
}
|
|
1844
1844
|
return properties;
|
|
@@ -2098,7 +2098,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2098
2098
|
}
|
|
2099
2099
|
}
|
|
2100
2100
|
const s = p14__namespace.spinner();
|
|
2101
|
-
s.start(`Generating feature '${
|
|
2101
|
+
s.start(`Generating feature '${pc14__default.default.cyan(normalizedName)}'...`);
|
|
2102
2102
|
const options = {
|
|
2103
2103
|
name: normalizedName,
|
|
2104
2104
|
projectRoot,
|
|
@@ -2145,7 +2145,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2145
2145
|
s.stop("Feature scaffolded successfully!");
|
|
2146
2146
|
printGeneratedFiles(files, options.dryRun);
|
|
2147
2147
|
p14__namespace.outro(
|
|
2148
|
-
|
|
2148
|
+
pc14__default.default.green(
|
|
2149
2149
|
`\u2713 Feature '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2150
2150
|
)
|
|
2151
2151
|
);
|
|
@@ -2301,7 +2301,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2301
2301
|
);
|
|
2302
2302
|
}
|
|
2303
2303
|
const s = p14__namespace.spinner();
|
|
2304
|
-
s.start(`Generating shared module '${
|
|
2304
|
+
s.start(`Generating shared module '${pc14__default.default.cyan(normalizedName)}'...`);
|
|
2305
2305
|
const files = generateShared({
|
|
2306
2306
|
name: normalizedName,
|
|
2307
2307
|
description,
|
|
@@ -2315,7 +2315,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2315
2315
|
s.stop("Shared module generated successfully!");
|
|
2316
2316
|
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2317
2317
|
p14__namespace.outro(
|
|
2318
|
-
|
|
2318
|
+
pc14__default.default.green(
|
|
2319
2319
|
`\u2713 Shared module '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2320
2320
|
)
|
|
2321
2321
|
);
|
|
@@ -2443,7 +2443,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2443
2443
|
}
|
|
2444
2444
|
}
|
|
2445
2445
|
const s = p14__namespace.spinner();
|
|
2446
|
-
s.start(`Generating use case '${
|
|
2446
|
+
s.start(`Generating use case '${pc14__default.default.cyan(ucName)}'...`);
|
|
2447
2447
|
const options = {
|
|
2448
2448
|
name: ucName,
|
|
2449
2449
|
feature,
|
|
@@ -2475,7 +2475,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2475
2475
|
}
|
|
2476
2476
|
s.stop("Use case generated successfully!");
|
|
2477
2477
|
printGeneratedFiles(files, options.dryRun);
|
|
2478
|
-
p14__namespace.outro(
|
|
2478
|
+
p14__namespace.outro(pc14__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2479
2479
|
}
|
|
2480
2480
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2481
2481
|
const projectRoot = findProjectRoot();
|
|
@@ -2546,7 +2546,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2546
2546
|
});
|
|
2547
2547
|
}
|
|
2548
2548
|
const s = p14__namespace.spinner();
|
|
2549
|
-
s.start(`Generating domain model '${
|
|
2549
|
+
s.start(`Generating domain model '${pc14__default.default.cyan(entityName)}'...`);
|
|
2550
2550
|
const options = {
|
|
2551
2551
|
name: entityName,
|
|
2552
2552
|
feature,
|
|
@@ -2559,7 +2559,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2559
2559
|
const file = generateDomain(options);
|
|
2560
2560
|
s.stop("Domain model generated successfully!");
|
|
2561
2561
|
printGeneratedFiles([file], options.dryRun);
|
|
2562
|
-
p14__namespace.outro(
|
|
2562
|
+
p14__namespace.outro(pc14__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2563
2563
|
}
|
|
2564
2564
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2565
2565
|
const projectRoot = findProjectRoot();
|
|
@@ -2728,7 +2728,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2728
2728
|
registerInProvider = !flags.noProvider;
|
|
2729
2729
|
}
|
|
2730
2730
|
const s = p14__namespace.spinner();
|
|
2731
|
-
s.start(`Generating infrastructure class '${
|
|
2731
|
+
s.start(`Generating infrastructure class '${pc14__default.default.cyan(className)}'...`);
|
|
2732
2732
|
const files = [];
|
|
2733
2733
|
const options = {
|
|
2734
2734
|
name: className,
|
|
@@ -2763,7 +2763,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
2763
2763
|
}
|
|
2764
2764
|
s.stop("Infrastructure class generated successfully!");
|
|
2765
2765
|
printGeneratedFiles(files, options.dryRun);
|
|
2766
|
-
p14__namespace.outro(
|
|
2766
|
+
p14__namespace.outro(pc14__default.default.green(`Infrastructure class '${className}' created!`));
|
|
2767
2767
|
}
|
|
2768
2768
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
2769
2769
|
const projectRoot = findProjectRoot();
|
|
@@ -2876,7 +2876,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2876
2876
|
}
|
|
2877
2877
|
}
|
|
2878
2878
|
const s = p14__namespace.spinner();
|
|
2879
|
-
s.start(`Generating interface '${
|
|
2879
|
+
s.start(`Generating interface '${pc14__default.default.cyan(ifaceName)}'...`);
|
|
2880
2880
|
const files = [];
|
|
2881
2881
|
const options = {
|
|
2882
2882
|
name: ifaceName,
|
|
@@ -2925,7 +2925,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
2925
2925
|
}
|
|
2926
2926
|
s.stop("Interface generated successfully!");
|
|
2927
2927
|
printGeneratedFiles(files, options.dryRun);
|
|
2928
|
-
p14__namespace.outro(
|
|
2928
|
+
p14__namespace.outro(pc14__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
2929
2929
|
}
|
|
2930
2930
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
2931
2931
|
const projectRoot = findProjectRoot();
|
|
@@ -3042,7 +3042,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3042
3042
|
}
|
|
3043
3043
|
}
|
|
3044
3044
|
const s = p14__namespace.spinner();
|
|
3045
|
-
s.start(`Generating HTTP handler '${
|
|
3045
|
+
s.start(`Generating HTTP handler '${pc14__default.default.cyan(className)}'...`);
|
|
3046
3046
|
const options = {
|
|
3047
3047
|
name: className,
|
|
3048
3048
|
feature,
|
|
@@ -3060,7 +3060,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3060
3060
|
const files = generateHttpHandler(options);
|
|
3061
3061
|
s.stop("HTTP handler generated successfully!");
|
|
3062
3062
|
printGeneratedFiles(files, options.dryRun);
|
|
3063
|
-
p14__namespace.outro(
|
|
3063
|
+
p14__namespace.outro(pc14__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
3064
3064
|
}
|
|
3065
3065
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
3066
3066
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -3216,7 +3216,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3216
3216
|
);
|
|
3217
3217
|
}
|
|
3218
3218
|
const s = p14__namespace.spinner();
|
|
3219
|
-
s.start(`Generating injectable '${
|
|
3219
|
+
s.start(`Generating injectable '${pc14__default.default.cyan(className)}'...`);
|
|
3220
3220
|
const options = {
|
|
3221
3221
|
name: className,
|
|
3222
3222
|
feature,
|
|
@@ -3230,7 +3230,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3230
3230
|
const files = generateInjectable(options);
|
|
3231
3231
|
s.stop("Injectable class generated successfully!");
|
|
3232
3232
|
printGeneratedFiles(files, options.dryRun);
|
|
3233
|
-
p14__namespace.outro(
|
|
3233
|
+
p14__namespace.outro(pc14__default.default.green(`Injectable '${className}' created successfully!`));
|
|
3234
3234
|
}
|
|
3235
3235
|
async function runInteractiveMenu(flags = {}) {
|
|
3236
3236
|
printBanner();
|
|
@@ -3344,7 +3344,7 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3344
3344
|
break;
|
|
3345
3345
|
default:
|
|
3346
3346
|
p14__namespace.log.error(
|
|
3347
|
-
|
|
3347
|
+
pc14__default.default.red(
|
|
3348
3348
|
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3349
3349
|
)
|
|
3350
3350
|
);
|
|
@@ -3580,7 +3580,7 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
3580
3580
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
3581
3581
|
break;
|
|
3582
3582
|
}
|
|
3583
|
-
p14__namespace.log.info(`Installing missing dependencies with ${manager}: ${
|
|
3583
|
+
p14__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc14__default.default.cyan(packages.join(", "))}`);
|
|
3584
3584
|
child_process.execSync(installCmd, {
|
|
3585
3585
|
cwd: projectRoot,
|
|
3586
3586
|
stdio: "inherit"
|
|
@@ -3736,19 +3736,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3736
3736
|
);
|
|
3737
3737
|
}
|
|
3738
3738
|
if (type !== "shared" && type !== "feature") {
|
|
3739
|
-
p14__namespace.log.error(
|
|
3739
|
+
p14__namespace.log.error(pc14__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3740
3740
|
process.exit(1);
|
|
3741
3741
|
}
|
|
3742
3742
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3743
3743
|
const s = p14__namespace.spinner();
|
|
3744
|
-
s.start(`Connecting to registry at ${
|
|
3744
|
+
s.start(`Connecting to registry at ${pc14__default.default.dim(registryBase)}...`);
|
|
3745
3745
|
let index;
|
|
3746
3746
|
try {
|
|
3747
3747
|
index = await fetchRegistryIndex(registryBase);
|
|
3748
3748
|
s.stop("Registry index loaded.");
|
|
3749
3749
|
} catch (err) {
|
|
3750
|
-
s.stop(
|
|
3751
|
-
p14__namespace.log.error(
|
|
3750
|
+
s.stop(pc14__default.default.red("Failed to load registry index."));
|
|
3751
|
+
p14__namespace.log.error(pc14__default.default.red(err.message || String(err)));
|
|
3752
3752
|
process.exit(1);
|
|
3753
3753
|
}
|
|
3754
3754
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3772,9 +3772,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3772
3772
|
}
|
|
3773
3773
|
if (!candidateNames.includes(name)) {
|
|
3774
3774
|
const closest = findClosestMatch(name, candidateNames);
|
|
3775
|
-
p14__namespace.log.error(
|
|
3775
|
+
p14__namespace.log.error(pc14__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3776
3776
|
if (closest) {
|
|
3777
|
-
p14__namespace.log.info(
|
|
3777
|
+
p14__namespace.log.info(pc14__default.default.yellow(`Did you mean '${pc14__default.default.cyan(closest)}'?`));
|
|
3778
3778
|
}
|
|
3779
3779
|
process.exit(1);
|
|
3780
3780
|
}
|
|
@@ -3786,7 +3786,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3786
3786
|
try {
|
|
3787
3787
|
manifest = await fetchModuleManifest(registryBase, modType, modName);
|
|
3788
3788
|
} catch (err) {
|
|
3789
|
-
p14__namespace.log.error(
|
|
3789
|
+
p14__namespace.log.error(pc14__default.default.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
|
|
3790
3790
|
process.exit(1);
|
|
3791
3791
|
}
|
|
3792
3792
|
nodes.set(key, {
|
|
@@ -3826,7 +3826,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3826
3826
|
buildTopologicalOrder(rootKey);
|
|
3827
3827
|
const resolutions = /* @__PURE__ */ new Map();
|
|
3828
3828
|
const globalAliasMap = /* @__PURE__ */ new Map();
|
|
3829
|
-
p14__namespace.log.step(`Importing ${type}: ${
|
|
3829
|
+
p14__namespace.log.step(`Importing ${type}: ${pc14__default.default.cyan(name)}...`);
|
|
3830
3830
|
for (const key of orderedKeys) {
|
|
3831
3831
|
const node = nodes.get(key);
|
|
3832
3832
|
const { type: modType, name: registryName, manifest } = node;
|
|
@@ -3859,7 +3859,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3859
3859
|
if (programmaticPointTo) {
|
|
3860
3860
|
if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
|
|
3861
3861
|
p14__namespace.log.error(
|
|
3862
|
-
|
|
3862
|
+
pc14__default.default.red(
|
|
3863
3863
|
`Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
|
|
3864
3864
|
)
|
|
3865
3865
|
);
|
|
@@ -3895,7 +3895,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3895
3895
|
}
|
|
3896
3896
|
const existingDir = getExistingModuleDir(projectRoot, modType, registryName);
|
|
3897
3897
|
if (!existingDir) {
|
|
3898
|
-
p14__namespace.log.info(` \u21B3 Resolving required ${modType} dependency: ${
|
|
3898
|
+
p14__namespace.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc14__default.default.cyan(registryName)}`);
|
|
3899
3899
|
resolutions.set(key, {
|
|
3900
3900
|
type: modType,
|
|
3901
3901
|
registryName,
|
|
@@ -4126,7 +4126,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4126
4126
|
const isOverwrite = Boolean(flags.overwrite || res.action === "overwrite");
|
|
4127
4127
|
if (key !== rootKey) {
|
|
4128
4128
|
p14__namespace.log.step(
|
|
4129
|
-
`Importing ${res.type}: ${
|
|
4129
|
+
`Importing ${res.type}: ${pc14__default.default.cyan(res.targetName)}${res.targetName !== res.registryName ? pc14__default.default.dim(` (from ${res.registryName})`) : ""}...`
|
|
4130
4130
|
);
|
|
4131
4131
|
}
|
|
4132
4132
|
if (res.action === "overwrite" && !flags.dryRun) {
|
|
@@ -4145,7 +4145,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4145
4145
|
const localFilePath = path2__default.default.join(targetDir, file);
|
|
4146
4146
|
if (fs9__default.default.existsSync(localFilePath) && !isOverwrite) {
|
|
4147
4147
|
p14__namespace.log.warn(
|
|
4148
|
-
` \u21B3 Skipping existing file: ${
|
|
4148
|
+
` \u21B3 Skipping existing file: ${pc14__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
4149
4149
|
);
|
|
4150
4150
|
continue;
|
|
4151
4151
|
}
|
|
@@ -4157,13 +4157,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4157
4157
|
}
|
|
4158
4158
|
const displayPath = path2__default.default.relative(projectRoot, localFilePath);
|
|
4159
4159
|
totalWrittenFiles.push(displayPath);
|
|
4160
|
-
p14__namespace.log.message(` ${
|
|
4160
|
+
p14__namespace.log.message(` ${pc14__default.default.green("+")} ${displayPath}`);
|
|
4161
4161
|
}
|
|
4162
4162
|
}
|
|
4163
4163
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
4164
4164
|
if (missingNpm.length > 0) {
|
|
4165
4165
|
if (flags.skipInstall || flags.dryRun) {
|
|
4166
|
-
p14__namespace.log.info(`Required npm dependencies to install: ${
|
|
4166
|
+
p14__namespace.log.info(`Required npm dependencies to install: ${pc14__default.default.cyan(missingNpm.join(", "))}`);
|
|
4167
4167
|
} else {
|
|
4168
4168
|
try {
|
|
4169
4169
|
installNpmPackages(projectRoot, missingNpm);
|
|
@@ -4177,13 +4177,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4177
4177
|
}
|
|
4178
4178
|
}
|
|
4179
4179
|
if (flags.dryRun) {
|
|
4180
|
-
p14__namespace.log.info(
|
|
4180
|
+
p14__namespace.log.info(pc14__default.default.yellow("Dry run complete: No files were written to disk."));
|
|
4181
4181
|
} else {
|
|
4182
4182
|
const rootRes = resolutions.get(rootKey);
|
|
4183
4183
|
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
4184
4184
|
const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, rootRes.targetName));
|
|
4185
4185
|
p14__namespace.log.success(
|
|
4186
|
-
|
|
4186
|
+
pc14__default.default.green(
|
|
4187
4187
|
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
4188
4188
|
)
|
|
4189
4189
|
);
|
|
@@ -4432,6 +4432,350 @@ function discoverAllFeatures(projectRoot) {
|
|
|
4432
4432
|
dir: path2__default.default.join(featuresBase, e.name)
|
|
4433
4433
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
4434
4434
|
}
|
|
4435
|
+
function extractClassMethods(content, className) {
|
|
4436
|
+
let clean = "";
|
|
4437
|
+
let inLineComment = false;
|
|
4438
|
+
let inBlockComment = false;
|
|
4439
|
+
let inString = null;
|
|
4440
|
+
let escaped = false;
|
|
4441
|
+
for (let i = 0; i < content.length; i++) {
|
|
4442
|
+
const c = content[i];
|
|
4443
|
+
const next = content[i + 1];
|
|
4444
|
+
if (inLineComment) {
|
|
4445
|
+
if (c === "\n") {
|
|
4446
|
+
inLineComment = false;
|
|
4447
|
+
clean += "\n";
|
|
4448
|
+
} else {
|
|
4449
|
+
clean += " ";
|
|
4450
|
+
}
|
|
4451
|
+
continue;
|
|
4452
|
+
}
|
|
4453
|
+
if (inBlockComment) {
|
|
4454
|
+
if (c === "*" && next === "/") {
|
|
4455
|
+
inBlockComment = false;
|
|
4456
|
+
clean += " ";
|
|
4457
|
+
i++;
|
|
4458
|
+
} else {
|
|
4459
|
+
clean += c === "\n" ? "\n" : " ";
|
|
4460
|
+
}
|
|
4461
|
+
continue;
|
|
4462
|
+
}
|
|
4463
|
+
if (inString) {
|
|
4464
|
+
clean += c === "\n" ? "\n" : " ";
|
|
4465
|
+
if (escaped) {
|
|
4466
|
+
escaped = false;
|
|
4467
|
+
} else if (c === "\\") {
|
|
4468
|
+
escaped = true;
|
|
4469
|
+
} else if (c === inString) {
|
|
4470
|
+
inString = null;
|
|
4471
|
+
}
|
|
4472
|
+
continue;
|
|
4473
|
+
}
|
|
4474
|
+
if (c === "/" && next === "/") {
|
|
4475
|
+
inLineComment = true;
|
|
4476
|
+
clean += " ";
|
|
4477
|
+
i++;
|
|
4478
|
+
continue;
|
|
4479
|
+
}
|
|
4480
|
+
if (c === "/" && next === "*") {
|
|
4481
|
+
inBlockComment = true;
|
|
4482
|
+
clean += " ";
|
|
4483
|
+
i++;
|
|
4484
|
+
continue;
|
|
4485
|
+
}
|
|
4486
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
4487
|
+
inString = c;
|
|
4488
|
+
clean += " ";
|
|
4489
|
+
continue;
|
|
4490
|
+
}
|
|
4491
|
+
clean += c;
|
|
4492
|
+
}
|
|
4493
|
+
const classDeclRegex = new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b[^{]*\\{`);
|
|
4494
|
+
const match = classDeclRegex.exec(clean);
|
|
4495
|
+
if (!match) return null;
|
|
4496
|
+
const classBodyStart = match.index + match[0].length - 1;
|
|
4497
|
+
const methods = [];
|
|
4498
|
+
let depth = 1;
|
|
4499
|
+
let parenDepth = 0;
|
|
4500
|
+
let atMemberStart = true;
|
|
4501
|
+
const methodRegex = /^\s*(?:@[a-zA-Z0-9_$]+(?:\([^)]*\))?\s+)*(?:(public|private|protected|static)\s+)*(?:(async)\s+)?(?:(get|set)\s+)?(#?[a-zA-Z0-9_$]+)\s*(?:<[^>]+>)?\s*\(/;
|
|
4502
|
+
const arrowRegex = /^\s*(?:(public|private|protected|static)\s+)?(#?[a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\(/;
|
|
4503
|
+
for (let i = classBodyStart + 1; i < clean.length && depth > 0; i++) {
|
|
4504
|
+
const c = clean[i];
|
|
4505
|
+
if (c === "{") {
|
|
4506
|
+
depth++;
|
|
4507
|
+
atMemberStart = false;
|
|
4508
|
+
continue;
|
|
4509
|
+
}
|
|
4510
|
+
if (c === "}") {
|
|
4511
|
+
depth--;
|
|
4512
|
+
if (depth === 1) {
|
|
4513
|
+
atMemberStart = true;
|
|
4514
|
+
}
|
|
4515
|
+
continue;
|
|
4516
|
+
}
|
|
4517
|
+
if (depth === 1) {
|
|
4518
|
+
if (c === ";") {
|
|
4519
|
+
atMemberStart = true;
|
|
4520
|
+
parenDepth = 0;
|
|
4521
|
+
continue;
|
|
4522
|
+
}
|
|
4523
|
+
if (c === "(") {
|
|
4524
|
+
parenDepth++;
|
|
4525
|
+
} else if (c === ")") {
|
|
4526
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
4527
|
+
}
|
|
4528
|
+
if (atMemberStart && parenDepth === 0) {
|
|
4529
|
+
if (/\s/.test(c)) continue;
|
|
4530
|
+
const slice = clean.slice(i);
|
|
4531
|
+
const methodMatch = slice.match(methodRegex);
|
|
4532
|
+
if (methodMatch) {
|
|
4533
|
+
const fullMatch = methodMatch[0];
|
|
4534
|
+
const beforeParen = fullMatch.slice(0, fullMatch.lastIndexOf("(")).trim();
|
|
4535
|
+
const tokens = beforeParen.split(/\s+/).filter(Boolean);
|
|
4536
|
+
const rawName = tokens[tokens.length - 1] || "";
|
|
4537
|
+
const methodName = rawName.replace(/<.*$/, "");
|
|
4538
|
+
const controlKeywords = ["if", "for", "while", "switch", "catch", "with"];
|
|
4539
|
+
if (!controlKeywords.includes(methodName)) {
|
|
4540
|
+
const isPrivate = tokens.includes("private") || tokens.includes("protected") || methodName.startsWith("#");
|
|
4541
|
+
const isAsync = tokens.includes("async");
|
|
4542
|
+
const isPublic = !isPrivate;
|
|
4543
|
+
const lineNum = content.slice(0, i).split("\n").length;
|
|
4544
|
+
const lineCode = content.split("\n")[lineNum - 1]?.trim() || "";
|
|
4545
|
+
methods.push({
|
|
4546
|
+
name: methodName,
|
|
4547
|
+
isAsync,
|
|
4548
|
+
isPublic,
|
|
4549
|
+
line: lineNum,
|
|
4550
|
+
code: lineCode
|
|
4551
|
+
});
|
|
4552
|
+
i += fullMatch.length - 1;
|
|
4553
|
+
parenDepth = 1;
|
|
4554
|
+
atMemberStart = false;
|
|
4555
|
+
continue;
|
|
4556
|
+
}
|
|
4557
|
+
}
|
|
4558
|
+
const arrowMatch = slice.match(arrowRegex);
|
|
4559
|
+
if (arrowMatch) {
|
|
4560
|
+
const fullMatch = arrowMatch[0];
|
|
4561
|
+
const beforeEquals = fullMatch.split("=")[0].trim();
|
|
4562
|
+
const tokens = beforeEquals.split(/\s+/).filter(Boolean);
|
|
4563
|
+
const rawName = tokens[tokens.length - 1] || "";
|
|
4564
|
+
const isPrivate = tokens.includes("private") || tokens.includes("protected") || rawName.startsWith("#");
|
|
4565
|
+
const isAsync = fullMatch.includes("async");
|
|
4566
|
+
const isPublic = !isPrivate;
|
|
4567
|
+
const lineNum = content.slice(0, i).split("\n").length;
|
|
4568
|
+
const lineCode = content.split("\n")[lineNum - 1]?.trim() || "";
|
|
4569
|
+
methods.push({
|
|
4570
|
+
name: rawName,
|
|
4571
|
+
isAsync,
|
|
4572
|
+
isPublic,
|
|
4573
|
+
line: lineNum,
|
|
4574
|
+
code: lineCode
|
|
4575
|
+
});
|
|
4576
|
+
i += fullMatch.length - 1;
|
|
4577
|
+
parenDepth = 1;
|
|
4578
|
+
atMemberStart = false;
|
|
4579
|
+
continue;
|
|
4580
|
+
}
|
|
4581
|
+
atMemberStart = false;
|
|
4582
|
+
}
|
|
4583
|
+
}
|
|
4584
|
+
}
|
|
4585
|
+
return methods;
|
|
4586
|
+
}
|
|
4587
|
+
function checkUseCaseShape(featureName, filePath, relativePath, content, addViolation) {
|
|
4588
|
+
const fileName = path2__default.default.basename(filePath);
|
|
4589
|
+
const lines = content.split(/\r?\n/);
|
|
4590
|
+
const classMatch = content.match(/(?:export\s+(?:default\s+)?)?class\s+([A-Za-z0-9_]+)/);
|
|
4591
|
+
if (!classMatch) {
|
|
4592
|
+
addViolation({
|
|
4593
|
+
feature: featureName,
|
|
4594
|
+
layer: "useCases",
|
|
4595
|
+
file: filePath,
|
|
4596
|
+
relativePath,
|
|
4597
|
+
line: 1,
|
|
4598
|
+
category: "Use Case Shape Violation",
|
|
4599
|
+
code: lines[0]?.trim() || "",
|
|
4600
|
+
ruleId: "U_SHAPE_CLASS",
|
|
4601
|
+
severity: "error",
|
|
4602
|
+
advice: `Use case file '${fileName}' must define and export a class.`,
|
|
4603
|
+
fixExample: {
|
|
4604
|
+
bad: "// Missing class definition",
|
|
4605
|
+
good: `export class ${fileName.replace(/\.(ts|js)$/, "")} { ... }`
|
|
4606
|
+
}
|
|
4607
|
+
});
|
|
4608
|
+
return;
|
|
4609
|
+
}
|
|
4610
|
+
const className = classMatch[1];
|
|
4611
|
+
let classLine = 1;
|
|
4612
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4613
|
+
if (new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b`).test(lines[i])) {
|
|
4614
|
+
classLine = i + 1;
|
|
4615
|
+
break;
|
|
4616
|
+
}
|
|
4617
|
+
}
|
|
4618
|
+
if (!/@MakeInjectable\b/.test(content)) {
|
|
4619
|
+
addViolation({
|
|
4620
|
+
feature: featureName,
|
|
4621
|
+
layer: "useCases",
|
|
4622
|
+
file: filePath,
|
|
4623
|
+
relativePath,
|
|
4624
|
+
line: classLine,
|
|
4625
|
+
category: "Use Case Shape Violation",
|
|
4626
|
+
code: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4627
|
+
ruleId: "U_SHAPE_DECORATOR",
|
|
4628
|
+
severity: "error",
|
|
4629
|
+
advice: `Use case class '${className}' in '${fileName}' must be decorated with @MakeInjectable.`,
|
|
4630
|
+
fixExample: {
|
|
4631
|
+
bad: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4632
|
+
good: `@MakeInjectable
|
|
4633
|
+
export class ${className} { ... }`
|
|
4634
|
+
}
|
|
4635
|
+
});
|
|
4636
|
+
}
|
|
4637
|
+
const hasAsyncExecute = /(?:public\s+)?async\s+execute\s*(?:<[^>]+>)?\s*\(/.test(content);
|
|
4638
|
+
if (!hasAsyncExecute) {
|
|
4639
|
+
const hasSyncExecute = /(?:public\s+)?execute\s*(?:<[^>]+>)?\s*\(/.test(content);
|
|
4640
|
+
addViolation({
|
|
4641
|
+
feature: featureName,
|
|
4642
|
+
layer: "useCases",
|
|
4643
|
+
file: filePath,
|
|
4644
|
+
relativePath,
|
|
4645
|
+
line: classLine,
|
|
4646
|
+
category: "Use Case Shape Violation",
|
|
4647
|
+
code: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4648
|
+
ruleId: "U_SHAPE_EXECUTE",
|
|
4649
|
+
severity: "error",
|
|
4650
|
+
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.`,
|
|
4651
|
+
fixExample: {
|
|
4652
|
+
bad: hasSyncExecute ? "execute(props: ...) { ... }" : `class ${className} { ... }`,
|
|
4653
|
+
good: "async execute(props: ...): Promise<...> { ... }"
|
|
4654
|
+
}
|
|
4655
|
+
});
|
|
4656
|
+
}
|
|
4657
|
+
const classMethods = extractClassMethods(content, className);
|
|
4658
|
+
if (classMethods) {
|
|
4659
|
+
const extraPublicMethods = classMethods.filter(
|
|
4660
|
+
(m) => m.isPublic && m.name !== "constructor" && m.name !== "execute"
|
|
4661
|
+
);
|
|
4662
|
+
for (const m of extraPublicMethods) {
|
|
4663
|
+
addViolation({
|
|
4664
|
+
feature: featureName,
|
|
4665
|
+
layer: "useCases",
|
|
4666
|
+
file: filePath,
|
|
4667
|
+
relativePath,
|
|
4668
|
+
line: m.line,
|
|
4669
|
+
category: "Use Case Shape Violation",
|
|
4670
|
+
code: m.code || `public ${m.name}(...)`,
|
|
4671
|
+
ruleId: "U_SHAPE_ONLY_EXECUTE",
|
|
4672
|
+
severity: "error",
|
|
4673
|
+
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.`,
|
|
4674
|
+
fixExample: {
|
|
4675
|
+
bad: m.code || `${m.name}(...) { ... }`,
|
|
4676
|
+
good: `// Extract '${m.name}' into its own dedicated Use Case, or make it private if it is internal helper logic:
|
|
4677
|
+
private ${m.name}(...) { ... }`
|
|
4678
|
+
}
|
|
4679
|
+
});
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
function checkServiceShape(featureName, filePath, relativePath, content, addViolation) {
|
|
4684
|
+
const fileName = path2__default.default.basename(filePath);
|
|
4685
|
+
const lines = content.split(/\r?\n/);
|
|
4686
|
+
const classMatch = content.match(/(?:export\s+(?:default\s+)?)?class\s+([A-Za-z0-9_]+)/);
|
|
4687
|
+
if (!classMatch) {
|
|
4688
|
+
addViolation({
|
|
4689
|
+
feature: featureName,
|
|
4690
|
+
layer: "services",
|
|
4691
|
+
file: filePath,
|
|
4692
|
+
relativePath,
|
|
4693
|
+
line: 1,
|
|
4694
|
+
category: "Service Shape Violation",
|
|
4695
|
+
code: lines[0]?.trim() || "",
|
|
4696
|
+
ruleId: "S_SHAPE_CLASS",
|
|
4697
|
+
severity: "error",
|
|
4698
|
+
advice: `Service file '${fileName}' must define and export a class.`,
|
|
4699
|
+
fixExample: {
|
|
4700
|
+
bad: "// Missing class definition",
|
|
4701
|
+
good: `export class ${fileName.replace(/\.(ts|js)$/, "")} { ... }`
|
|
4702
|
+
}
|
|
4703
|
+
});
|
|
4704
|
+
return;
|
|
4705
|
+
}
|
|
4706
|
+
const className = classMatch[1];
|
|
4707
|
+
let classLine = 1;
|
|
4708
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4709
|
+
if (new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b`).test(lines[i])) {
|
|
4710
|
+
classLine = i + 1;
|
|
4711
|
+
break;
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4714
|
+
if (!/@MakeInjectable\b/.test(content)) {
|
|
4715
|
+
addViolation({
|
|
4716
|
+
feature: featureName,
|
|
4717
|
+
layer: "services",
|
|
4718
|
+
file: filePath,
|
|
4719
|
+
relativePath,
|
|
4720
|
+
line: classLine,
|
|
4721
|
+
category: "Service Shape Violation",
|
|
4722
|
+
code: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4723
|
+
ruleId: "S_SHAPE_DECORATOR",
|
|
4724
|
+
severity: "error",
|
|
4725
|
+
advice: `Service class '${className}' in '${fileName}' must be decorated with @MakeInjectable.`,
|
|
4726
|
+
fixExample: {
|
|
4727
|
+
bad: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4728
|
+
good: `@MakeInjectable
|
|
4729
|
+
export class ${className} { ... }`
|
|
4730
|
+
}
|
|
4731
|
+
});
|
|
4732
|
+
}
|
|
4733
|
+
const hasAsyncExecute = /(?:public\s+)?async\s+execute\s*(?:<[^>]+>)?\s*\(/.test(content);
|
|
4734
|
+
if (!hasAsyncExecute) {
|
|
4735
|
+
const hasSyncExecute = /(?:public\s+)?execute\s*(?:<[^>]+>)?\s*\(/.test(content);
|
|
4736
|
+
addViolation({
|
|
4737
|
+
feature: featureName,
|
|
4738
|
+
layer: "services",
|
|
4739
|
+
file: filePath,
|
|
4740
|
+
relativePath,
|
|
4741
|
+
line: classLine,
|
|
4742
|
+
category: "Service Shape Violation",
|
|
4743
|
+
code: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4744
|
+
ruleId: "S_SHAPE_EXECUTE",
|
|
4745
|
+
severity: "error",
|
|
4746
|
+
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.`,
|
|
4747
|
+
fixExample: {
|
|
4748
|
+
bad: hasSyncExecute ? "execute(props: ...) { ... }" : `class ${className} { ... }`,
|
|
4749
|
+
good: "async execute(props: ...): Promise<...> { ... }"
|
|
4750
|
+
}
|
|
4751
|
+
});
|
|
4752
|
+
}
|
|
4753
|
+
const classMethods = extractClassMethods(content, className);
|
|
4754
|
+
if (classMethods) {
|
|
4755
|
+
const extraPublicMethods = classMethods.filter(
|
|
4756
|
+
(m) => m.isPublic && m.name !== "constructor" && m.name !== "execute"
|
|
4757
|
+
);
|
|
4758
|
+
for (const m of extraPublicMethods) {
|
|
4759
|
+
addViolation({
|
|
4760
|
+
feature: featureName,
|
|
4761
|
+
layer: "services",
|
|
4762
|
+
file: filePath,
|
|
4763
|
+
relativePath,
|
|
4764
|
+
line: m.line,
|
|
4765
|
+
category: "Service Shape Violation",
|
|
4766
|
+
code: m.code || `public ${m.name}(...)`,
|
|
4767
|
+
ruleId: "S_SHAPE_ONLY_EXECUTE",
|
|
4768
|
+
severity: "error",
|
|
4769
|
+
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.`,
|
|
4770
|
+
fixExample: {
|
|
4771
|
+
bad: m.code || `${m.name}(...) { ... }`,
|
|
4772
|
+
good: `// Extract '${m.name}' into its own dedicated Service, or make it private if it is internal helper logic:
|
|
4773
|
+
private ${m.name}(...) { ... }`
|
|
4774
|
+
}
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
}
|
|
4778
|
+
}
|
|
4435
4779
|
function lintCodebase(options = {}) {
|
|
4436
4780
|
const startTime = Date.now();
|
|
4437
4781
|
const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
|
|
@@ -4449,13 +4793,9 @@ function lintCodebase(options = {}) {
|
|
|
4449
4793
|
featureResult.passed = false;
|
|
4450
4794
|
}
|
|
4451
4795
|
if (failFast) {
|
|
4452
|
-
const errorMsg = `
|
|
4453
|
-
|
|
4454
|
-
|
|
4455
|
-
Rule: ${v.ruleId}
|
|
4456
|
-
Location: ${v.relativePath}:${v.line}
|
|
4457
|
-
Code: ${v.code}
|
|
4458
|
-
Explanation: ${v.advice}`;
|
|
4796
|
+
const errorMsg = `Dependency rule violation detected in ${v.relativePath}:${v.line}:
|
|
4797
|
+
[${v.category}] ${v.advice}
|
|
4798
|
+
Code: ${v.code}`;
|
|
4459
4799
|
throw new DependencyRuleError(errorMsg, [v], true);
|
|
4460
4800
|
}
|
|
4461
4801
|
};
|
|
@@ -4479,6 +4819,14 @@ function lintCodebase(options = {}) {
|
|
|
4479
4819
|
if (!layer) continue;
|
|
4480
4820
|
const content = fs9__default.default.readFileSync(file, "utf-8");
|
|
4481
4821
|
const statements = extractImportStatements(content);
|
|
4822
|
+
const baseName = path2__default.default.basename(file);
|
|
4823
|
+
const isBarrel = baseName === "index.ts" || baseName === "index.js";
|
|
4824
|
+
if (layer === "useCases" && !isBarrel) {
|
|
4825
|
+
checkUseCaseShape(feat.name, file, relPath, content, addViolation);
|
|
4826
|
+
}
|
|
4827
|
+
if (layer === "services" && !isBarrel) {
|
|
4828
|
+
checkServiceShape(feat.name, file, relPath, content, addViolation);
|
|
4829
|
+
}
|
|
4482
4830
|
if (layer === "domain") {
|
|
4483
4831
|
const lines = content.split(/\r?\n/);
|
|
4484
4832
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -4889,6 +5237,24 @@ function lintCodebase(options = {}) {
|
|
|
4889
5237
|
}
|
|
4890
5238
|
});
|
|
4891
5239
|
}
|
|
5240
|
+
if (target.targetLayer === "services") {
|
|
5241
|
+
addViolation({
|
|
5242
|
+
feature: feat.name,
|
|
5243
|
+
layer,
|
|
5244
|
+
file,
|
|
5245
|
+
relativePath: relPath,
|
|
5246
|
+
line: stmt.line,
|
|
5247
|
+
category: "Presentation Layer Violation",
|
|
5248
|
+
code: stmt.rawCode,
|
|
5249
|
+
ruleId: "P4_DIRECT_SERVICE",
|
|
5250
|
+
severity: "error",
|
|
5251
|
+
advice: "Presentation handlers must not depend on services directly. They must use use cases, and use cases will then depend on the service.",
|
|
5252
|
+
fixExample: {
|
|
5253
|
+
bad: stmt.rawCode,
|
|
5254
|
+
good: "// Inject a Use Case via static deps instead of a Service."
|
|
5255
|
+
}
|
|
5256
|
+
});
|
|
5257
|
+
}
|
|
4892
5258
|
}
|
|
4893
5259
|
}
|
|
4894
5260
|
}
|
|
@@ -4943,87 +5309,23 @@ ${compiledDetails}
|
|
|
4943
5309
|
}
|
|
4944
5310
|
|
|
4945
5311
|
// src/commands/lint.ts
|
|
4946
|
-
function
|
|
4947
|
-
console.log("");
|
|
4948
|
-
const title = " Mason Clean Architecture Dependency Linter ";
|
|
4949
|
-
const line = "\u2501".repeat(title.length + 2);
|
|
4950
|
-
console.log(pc15__default.default.cyan(` \u250F${line}\u2513`));
|
|
4951
|
-
console.log(` ${pc15__default.default.cyan("\u2503")} ${pc15__default.default.bgCyan(pc15__default.default.black(title))} ${pc15__default.default.cyan("\u2503")}`);
|
|
4952
|
-
console.log(pc15__default.default.cyan(` \u2517${line}\u251B`));
|
|
4953
|
-
const modeText = mode === "failFast" ? `${pc15__default.default.yellow("Fail-Fast")} ${pc15__default.default.dim("(default: stops at first error)")}` : `${pc15__default.default.magenta("Complete")} ${pc15__default.default.dim("(--complete: compiles all errors)")}`;
|
|
4954
|
-
console.log(` Mode: ${modeText}`);
|
|
4955
|
-
console.log("");
|
|
4956
|
-
}
|
|
4957
|
-
function formatViolation(v, showFixHint = true) {
|
|
5312
|
+
function formatViolation(v) {
|
|
4958
5313
|
const lines = [];
|
|
4959
|
-
|
|
4960
|
-
const
|
|
4961
|
-
lines.push(
|
|
4962
|
-
|
|
4963
|
-
lines.push(
|
|
4964
|
-
|
|
4965
|
-
if (showFixHint && v.fixExample) {
|
|
4966
|
-
lines.push(` ${pc15__default.default.dim("Example:")}`);
|
|
4967
|
-
lines.push(` ${pc15__default.default.red(`- ${v.fixExample.bad}`)}`);
|
|
4968
|
-
lines.push(` ${pc15__default.default.green(`+ ${v.fixExample.good}`)}`);
|
|
4969
|
-
}
|
|
4970
|
-
return lines.join("\n");
|
|
4971
|
-
}
|
|
4972
|
-
function formatFeatureResult(res, options = {}) {
|
|
4973
|
-
const lines = [];
|
|
4974
|
-
if (res.passed) {
|
|
4975
|
-
lines.push(
|
|
4976
|
-
` ${pc15__default.default.green("\u2714")} ${pc15__default.default.bold(res.featureName)} ${pc15__default.default.dim(
|
|
4977
|
-
`(${res.scannedFilesCount} files checked - clean)`
|
|
4978
|
-
)}`
|
|
4979
|
-
);
|
|
4980
|
-
return lines.join("\n");
|
|
4981
|
-
}
|
|
4982
|
-
const countBadge = pc15__default.default.red(
|
|
4983
|
-
`(${res.violations.length} violation${res.violations.length > 1 ? "s" : ""})`
|
|
5314
|
+
lines.push(pc14__default.default.underline(pc14__default.default.cyan(v.relativePath)) + pc14__default.default.dim(":") + pc14__default.default.yellow(String(v.line)));
|
|
5315
|
+
const gutter = `${String(v.line).padStart(4, " ")} | `;
|
|
5316
|
+
lines.push(pc14__default.default.dim(gutter) + v.code);
|
|
5317
|
+
const categoryShort = v.category.replace(" Violation", "");
|
|
5318
|
+
lines.push(
|
|
5319
|
+
" " + pc14__default.default.red(pc14__default.default.bold("\u2716")) + " " + pc14__default.default.red(`[${categoryShort}]`) + " " + v.advice
|
|
4984
5320
|
);
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
lines.push(` ${pc15__default.default.dim("Path:")} ${res.featureDir}`);
|
|
4988
|
-
lines.push("");
|
|
4989
|
-
for (const v of res.violations) {
|
|
4990
|
-
lines.push(formatViolation(v, options.fixHint !== false));
|
|
4991
|
-
lines.push("");
|
|
5321
|
+
if (v.fixExample?.good) {
|
|
5322
|
+
lines.push(" " + pc14__default.default.dim("\u2192") + " " + pc14__default.default.dim(v.fixExample.good));
|
|
4992
5323
|
}
|
|
4993
5324
|
return lines.join("\n");
|
|
4994
5325
|
}
|
|
4995
|
-
function printTerminalReport(summary, options = {}) {
|
|
4996
|
-
for (const res of summary.results) {
|
|
4997
|
-
const formatted = formatFeatureResult(res, options);
|
|
4998
|
-
if (formatted.trim()) {
|
|
4999
|
-
console.log(formatted);
|
|
5000
|
-
}
|
|
5001
|
-
}
|
|
5002
|
-
console.log("\u2501".repeat(70));
|
|
5003
|
-
const statusIcon = summary.totalViolations === 0 ? pc15__default.default.green("\u2714 PASS") : pc15__default.default.red("\u2716 FAIL");
|
|
5004
|
-
console.log(
|
|
5005
|
-
` ${statusIcon} ${pc15__default.default.bold("Dependency Rules Audit Result")} ${pc15__default.default.dim(
|
|
5006
|
-
`(${summary.durationMs}ms)`
|
|
5007
|
-
)}`
|
|
5008
|
-
);
|
|
5009
|
-
console.log(
|
|
5010
|
-
` ${pc15__default.default.bold("Features:")} ${pc15__default.default.cyan(String(summary.totalFeatures))} total | ${pc15__default.default.green(
|
|
5011
|
-
String(summary.cleanFeatures)
|
|
5012
|
-
)} clean | ${summary.violatedFeatures > 0 ? pc15__default.default.red(String(summary.violatedFeatures)) : pc15__default.default.green("0")} with violations`
|
|
5013
|
-
);
|
|
5014
|
-
console.log(
|
|
5015
|
-
` ${pc15__default.default.bold("Violations:")} ${summary.totalViolations > 0 ? pc15__default.default.red(pc15__default.default.bold(String(summary.totalViolations))) : pc15__default.default.green("0")} rule violations found`
|
|
5016
|
-
);
|
|
5017
|
-
console.log("\u2501".repeat(70));
|
|
5018
|
-
console.log("");
|
|
5019
|
-
}
|
|
5020
5326
|
async function handleLint(flags = {}) {
|
|
5021
5327
|
const complete = Boolean(flags.complete);
|
|
5022
|
-
const mode = complete ? "complete" : "failFast";
|
|
5023
5328
|
const quiet = Boolean(flags.quiet);
|
|
5024
|
-
if (!quiet) {
|
|
5025
|
-
printLintBanner(mode);
|
|
5026
|
-
}
|
|
5027
5329
|
const options = {
|
|
5028
5330
|
complete,
|
|
5029
5331
|
quiet,
|
|
@@ -5034,14 +5336,23 @@ async function handleLint(flags = {}) {
|
|
|
5034
5336
|
};
|
|
5035
5337
|
try {
|
|
5036
5338
|
const summary = lintCodebase(options);
|
|
5037
|
-
if (!quiet) {
|
|
5038
|
-
printTerminalReport(summary, options);
|
|
5039
|
-
}
|
|
5040
5339
|
if (summary.totalViolations > 0) {
|
|
5340
|
+
if (!quiet) {
|
|
5341
|
+
console.log("");
|
|
5342
|
+
for (const v of summary.allViolations) {
|
|
5343
|
+
console.log(formatViolation(v));
|
|
5344
|
+
console.log("");
|
|
5345
|
+
}
|
|
5346
|
+
console.log(
|
|
5347
|
+
pc14__default.default.red(
|
|
5348
|
+
`\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).`
|
|
5349
|
+
)
|
|
5350
|
+
);
|
|
5351
|
+
console.log("");
|
|
5352
|
+
}
|
|
5041
5353
|
const errorHeader = `Dependency rule audit failed with ${summary.totalViolations} violation(s) across ${summary.violatedFeatures} feature(s):`;
|
|
5042
5354
|
const compiledDetails = summary.allViolations.map(
|
|
5043
5355
|
(v, i) => `${i + 1}. [${v.category}] ${v.relativePath}:${v.line}
|
|
5044
|
-
Rule: ${v.ruleId}
|
|
5045
5356
|
Code: ${v.code}
|
|
5046
5357
|
Advice: ${v.advice}`
|
|
5047
5358
|
).join("\n\n");
|
|
@@ -5052,20 +5363,22 @@ ${compiledDetails}
|
|
|
5052
5363
|
}
|
|
5053
5364
|
if (!quiet) {
|
|
5054
5365
|
console.log(
|
|
5055
|
-
|
|
5366
|
+
pc14__default.default.green(
|
|
5367
|
+
`\u2714 Dependency rules verified across ${summary.totalFeatures} feature${summary.totalFeatures === 1 ? "" : "s"}.`
|
|
5368
|
+
)
|
|
5056
5369
|
);
|
|
5057
|
-
console.log("");
|
|
5058
5370
|
}
|
|
5059
5371
|
return summary;
|
|
5060
5372
|
} catch (err) {
|
|
5061
5373
|
if (err instanceof DependencyRuleError && err.isFailFast) {
|
|
5062
5374
|
if (!quiet) {
|
|
5063
|
-
console.log(
|
|
5064
|
-
\u2716 [FAIL FAST] Terminating on first detected dependency rule violation:`));
|
|
5375
|
+
console.log("");
|
|
5065
5376
|
const v = err.violations[0];
|
|
5066
5377
|
if (v) {
|
|
5067
|
-
console.log(formatViolation(v
|
|
5378
|
+
console.log(formatViolation(v));
|
|
5379
|
+
console.log("");
|
|
5068
5380
|
}
|
|
5381
|
+
console.log(pc14__default.default.red(`\u2716 Dependency rules check failed (1 error).`));
|
|
5069
5382
|
console.log("");
|
|
5070
5383
|
}
|
|
5071
5384
|
}
|
|
@@ -5079,7 +5392,7 @@ function createCli() {
|
|
|
5079
5392
|
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) => {
|
|
5080
5393
|
if (args && args.length > 0) {
|
|
5081
5394
|
p14__namespace.log.error(
|
|
5082
|
-
|
|
5395
|
+
pc14__default.default.red(
|
|
5083
5396
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
5084
5397
|
)
|
|
5085
5398
|
);
|
|
@@ -5198,6 +5511,12 @@ function createCli() {
|
|
|
5198
5511
|
targetDir: target || flags.target || flags.dest
|
|
5199
5512
|
});
|
|
5200
5513
|
} catch (err) {
|
|
5514
|
+
if (err instanceof DependencyRuleError) {
|
|
5515
|
+
if (process.env.VITEST) {
|
|
5516
|
+
throw err;
|
|
5517
|
+
}
|
|
5518
|
+
process.exit(1);
|
|
5519
|
+
}
|
|
5201
5520
|
handleCliError(err);
|
|
5202
5521
|
}
|
|
5203
5522
|
});
|
|
@@ -5232,7 +5551,7 @@ function handleCliError(err) {
|
|
|
5232
5551
|
process.exit(0);
|
|
5233
5552
|
}
|
|
5234
5553
|
const message = err instanceof Error ? err.message : String(err);
|
|
5235
|
-
p14__namespace.log.error(
|
|
5554
|
+
p14__namespace.log.error(pc14__default.default.red(`Error: ${message}`));
|
|
5236
5555
|
if (process.env.VITEST) {
|
|
5237
5556
|
throw err;
|
|
5238
5557
|
}
|