@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/index.cjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
var fs4 = require('fs');
|
|
4
4
|
var path2 = require('path');
|
|
5
5
|
var p14 = require('@clack/prompts');
|
|
6
|
-
var
|
|
6
|
+
var pc14 = require('picocolors');
|
|
7
7
|
var url = require('url');
|
|
8
8
|
var child_process = require('child_process');
|
|
9
9
|
var crypto = require('crypto');
|
|
@@ -34,7 +34,7 @@ function _interopNamespace(e) {
|
|
|
34
34
|
var fs4__default = /*#__PURE__*/_interopDefault(fs4);
|
|
35
35
|
var path2__default = /*#__PURE__*/_interopDefault(path2);
|
|
36
36
|
var p14__namespace = /*#__PURE__*/_interopNamespace(p14);
|
|
37
|
-
var
|
|
37
|
+
var pc14__default = /*#__PURE__*/_interopDefault(pc14);
|
|
38
38
|
var Handlebars__default = /*#__PURE__*/_interopDefault(Handlebars);
|
|
39
39
|
|
|
40
40
|
// src/utils/formatting.ts
|
|
@@ -141,17 +141,17 @@ function writeFileSafely(absolutePath, content, options = {}) {
|
|
|
141
141
|
function printGeneratedFiles(files, isDryRun = false) {
|
|
142
142
|
console.log("");
|
|
143
143
|
if (isDryRun) {
|
|
144
|
-
p14__namespace.log.warn(
|
|
144
|
+
p14__namespace.log.warn(pc14__default.default.yellow(pc14__default.default.bold("[DRY RUN PREVIEW] No files were written to disk.")));
|
|
145
145
|
}
|
|
146
146
|
for (const f of files) {
|
|
147
147
|
if (f.action === "created") {
|
|
148
|
-
p14__namespace.log.success(`${
|
|
148
|
+
p14__namespace.log.success(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(created)")}`);
|
|
149
149
|
} else if (f.action === "overwritten") {
|
|
150
|
-
p14__namespace.log.warn(`${
|
|
150
|
+
p14__namespace.log.warn(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(overwritten)")}`);
|
|
151
151
|
} else if (f.action === "skipped") {
|
|
152
|
-
p14__namespace.log.info(`${
|
|
152
|
+
p14__namespace.log.info(`${pc14__default.default.dim(f.relativePath)} ${pc14__default.default.dim("(skipped - already exists, pass -o to overwrite)")}`);
|
|
153
153
|
} else {
|
|
154
|
-
p14__namespace.log.info(`${
|
|
154
|
+
p14__namespace.log.info(`${pc14__default.default.bold(f.relativePath)} ${pc14__default.default.dim("(preview)")}`);
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
}
|
|
@@ -361,13 +361,13 @@ function handleCancel(result) {
|
|
|
361
361
|
}
|
|
362
362
|
function printBanner() {
|
|
363
363
|
console.log("");
|
|
364
|
-
p14__namespace.intro(
|
|
364
|
+
p14__namespace.intro(pc14__default.default.bgCyan(pc14__default.default.black(" @solid-stack/mason ")));
|
|
365
365
|
}
|
|
366
366
|
async function promptProperties(options = {}) {
|
|
367
367
|
const properties = options.defaults ? [...options.defaults] : [];
|
|
368
368
|
if (properties.length > 0) {
|
|
369
369
|
const summary = properties.map((p16) => `${p16.name}: ${p16.type}${p16.optional ? "?" : ""}`).join(", ");
|
|
370
|
-
p14__namespace.log.info(
|
|
370
|
+
p14__namespace.log.info(pc14__default.default.dim(`Pre-configured fields: ${summary}`));
|
|
371
371
|
const addMore = handleCancel(
|
|
372
372
|
await p14__namespace.confirm({
|
|
373
373
|
message: "Would you like to add custom fields/properties?",
|
|
@@ -378,7 +378,7 @@ async function promptProperties(options = {}) {
|
|
|
378
378
|
return properties;
|
|
379
379
|
}
|
|
380
380
|
}
|
|
381
|
-
p14__namespace.log.info(
|
|
381
|
+
p14__namespace.log.info(pc14__default.default.cyan("Enter properties (leave name blank when finished):"));
|
|
382
382
|
while (true) {
|
|
383
383
|
const name = handleCancel(
|
|
384
384
|
await p14__namespace.text({
|
|
@@ -426,7 +426,7 @@ async function promptProperties(options = {}) {
|
|
|
426
426
|
optional
|
|
427
427
|
});
|
|
428
428
|
p14__namespace.log.success(
|
|
429
|
-
|
|
429
|
+
pc14__default.default.green(`Added: ${name.trim()}${optional ? "?" : ""}: ${finalType.trim()}`)
|
|
430
430
|
);
|
|
431
431
|
}
|
|
432
432
|
return properties;
|
|
@@ -660,7 +660,7 @@ function installNpmPackages(projectRoot, packages, pm) {
|
|
|
660
660
|
installCmd = `npm install ${packages.join(" ")}`;
|
|
661
661
|
break;
|
|
662
662
|
}
|
|
663
|
-
p14__namespace.log.info(`Installing missing dependencies with ${manager}: ${
|
|
663
|
+
p14__namespace.log.info(`Installing missing dependencies with ${manager}: ${pc14__default.default.cyan(packages.join(", "))}`);
|
|
664
664
|
child_process.execSync(installCmd, {
|
|
665
665
|
cwd: projectRoot,
|
|
666
666
|
stdio: "inherit"
|
|
@@ -2377,7 +2377,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2377
2377
|
}
|
|
2378
2378
|
}
|
|
2379
2379
|
const s = p14__namespace.spinner();
|
|
2380
|
-
s.start(`Generating feature '${
|
|
2380
|
+
s.start(`Generating feature '${pc14__default.default.cyan(normalizedName)}'...`);
|
|
2381
2381
|
const options = {
|
|
2382
2382
|
name: normalizedName,
|
|
2383
2383
|
projectRoot,
|
|
@@ -2424,7 +2424,7 @@ async function runFeatureCommand(featureNameArg, flags = {}) {
|
|
|
2424
2424
|
s.stop("Feature scaffolded successfully!");
|
|
2425
2425
|
printGeneratedFiles(files, options.dryRun);
|
|
2426
2426
|
p14__namespace.outro(
|
|
2427
|
-
|
|
2427
|
+
pc14__default.default.green(
|
|
2428
2428
|
`\u2713 Feature '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2429
2429
|
)
|
|
2430
2430
|
);
|
|
@@ -2580,7 +2580,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2580
2580
|
);
|
|
2581
2581
|
}
|
|
2582
2582
|
const s = p14__namespace.spinner();
|
|
2583
|
-
s.start(`Generating shared module '${
|
|
2583
|
+
s.start(`Generating shared module '${pc14__default.default.cyan(normalizedName)}'...`);
|
|
2584
2584
|
const files = generateShared({
|
|
2585
2585
|
name: normalizedName,
|
|
2586
2586
|
description,
|
|
@@ -2594,7 +2594,7 @@ async function runSharedCommand(sharedNameArg, flags = {}) {
|
|
|
2594
2594
|
s.stop("Shared module generated successfully!");
|
|
2595
2595
|
printGeneratedFiles(files, Boolean(flags.dryRun));
|
|
2596
2596
|
p14__namespace.outro(
|
|
2597
|
-
|
|
2597
|
+
pc14__default.default.green(
|
|
2598
2598
|
`\u2713 Shared module '${normalizedName}' initialized in ${path2__default.default.relative(projectRoot, targetDir)} (uncommitted, unregistered)`
|
|
2599
2599
|
)
|
|
2600
2600
|
);
|
|
@@ -2722,7 +2722,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2722
2722
|
}
|
|
2723
2723
|
}
|
|
2724
2724
|
const s = p14__namespace.spinner();
|
|
2725
|
-
s.start(`Generating use case '${
|
|
2725
|
+
s.start(`Generating use case '${pc14__default.default.cyan(ucName)}'...`);
|
|
2726
2726
|
const options = {
|
|
2727
2727
|
name: ucName,
|
|
2728
2728
|
feature,
|
|
@@ -2754,7 +2754,7 @@ async function runUseCaseCommand(useCaseNameArg, flags = {}) {
|
|
|
2754
2754
|
}
|
|
2755
2755
|
s.stop("Use case generated successfully!");
|
|
2756
2756
|
printGeneratedFiles(files, options.dryRun);
|
|
2757
|
-
p14__namespace.outro(
|
|
2757
|
+
p14__namespace.outro(pc14__default.default.green(`Use case '${ucName}' created in feature '${feature}'!`));
|
|
2758
2758
|
}
|
|
2759
2759
|
async function runDomainCommand(domainNameArg, flags = {}) {
|
|
2760
2760
|
const projectRoot = findProjectRoot();
|
|
@@ -2825,7 +2825,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2825
2825
|
});
|
|
2826
2826
|
}
|
|
2827
2827
|
const s = p14__namespace.spinner();
|
|
2828
|
-
s.start(`Generating domain model '${
|
|
2828
|
+
s.start(`Generating domain model '${pc14__default.default.cyan(entityName)}'...`);
|
|
2829
2829
|
const options = {
|
|
2830
2830
|
name: entityName,
|
|
2831
2831
|
feature,
|
|
@@ -2838,7 +2838,7 @@ async function runDomainCommand(domainNameArg, flags = {}) {
|
|
|
2838
2838
|
const file = generateDomain(options);
|
|
2839
2839
|
s.stop("Domain model generated successfully!");
|
|
2840
2840
|
printGeneratedFiles([file], options.dryRun);
|
|
2841
|
-
p14__namespace.outro(
|
|
2841
|
+
p14__namespace.outro(pc14__default.default.green(`Domain model '${entityName}' created in feature '${feature}'!`));
|
|
2842
2842
|
}
|
|
2843
2843
|
async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
2844
2844
|
const projectRoot = findProjectRoot();
|
|
@@ -3007,7 +3007,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
3007
3007
|
registerInProvider = !flags.noProvider;
|
|
3008
3008
|
}
|
|
3009
3009
|
const s = p14__namespace.spinner();
|
|
3010
|
-
s.start(`Generating infrastructure class '${
|
|
3010
|
+
s.start(`Generating infrastructure class '${pc14__default.default.cyan(className)}'...`);
|
|
3011
3011
|
const files = [];
|
|
3012
3012
|
const options = {
|
|
3013
3013
|
name: className,
|
|
@@ -3042,7 +3042,7 @@ async function runInfrastructureCommand(classNameArg, flags = {}) {
|
|
|
3042
3042
|
}
|
|
3043
3043
|
s.stop("Infrastructure class generated successfully!");
|
|
3044
3044
|
printGeneratedFiles(files, options.dryRun);
|
|
3045
|
-
p14__namespace.outro(
|
|
3045
|
+
p14__namespace.outro(pc14__default.default.green(`Infrastructure class '${className}' created!`));
|
|
3046
3046
|
}
|
|
3047
3047
|
async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
3048
3048
|
const projectRoot = findProjectRoot();
|
|
@@ -3155,7 +3155,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3155
3155
|
}
|
|
3156
3156
|
}
|
|
3157
3157
|
const s = p14__namespace.spinner();
|
|
3158
|
-
s.start(`Generating interface '${
|
|
3158
|
+
s.start(`Generating interface '${pc14__default.default.cyan(ifaceName)}'...`);
|
|
3159
3159
|
const files = [];
|
|
3160
3160
|
const options = {
|
|
3161
3161
|
name: ifaceName,
|
|
@@ -3204,7 +3204,7 @@ async function runInterfaceCommand(interfaceNameArg, flags = {}) {
|
|
|
3204
3204
|
}
|
|
3205
3205
|
s.stop("Interface generated successfully!");
|
|
3206
3206
|
printGeneratedFiles(files, options.dryRun);
|
|
3207
|
-
p14__namespace.outro(
|
|
3207
|
+
p14__namespace.outro(pc14__default.default.green(`Interface '${ifaceName}' created in feature '${feature}'!`));
|
|
3208
3208
|
}
|
|
3209
3209
|
async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
3210
3210
|
const projectRoot = findProjectRoot();
|
|
@@ -3321,7 +3321,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3321
3321
|
}
|
|
3322
3322
|
}
|
|
3323
3323
|
const s = p14__namespace.spinner();
|
|
3324
|
-
s.start(`Generating HTTP handler '${
|
|
3324
|
+
s.start(`Generating HTTP handler '${pc14__default.default.cyan(className)}'...`);
|
|
3325
3325
|
const options = {
|
|
3326
3326
|
name: className,
|
|
3327
3327
|
feature,
|
|
@@ -3339,7 +3339,7 @@ async function runHttpHandlerCommand(handlerNameArg, flags = {}) {
|
|
|
3339
3339
|
const files = generateHttpHandler(options);
|
|
3340
3340
|
s.stop("HTTP handler generated successfully!");
|
|
3341
3341
|
printGeneratedFiles(files, options.dryRun);
|
|
3342
|
-
p14__namespace.outro(
|
|
3342
|
+
p14__namespace.outro(pc14__default.default.green(`HTTP handler '${className}' created in feature '${feature}'!`));
|
|
3343
3343
|
}
|
|
3344
3344
|
function generateInjectableCode(className, dependencies = [], methods = [], implementsInterface) {
|
|
3345
3345
|
const formattedDeps = dependencies.map((dep) => ({
|
|
@@ -3495,7 +3495,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3495
3495
|
);
|
|
3496
3496
|
}
|
|
3497
3497
|
const s = p14__namespace.spinner();
|
|
3498
|
-
s.start(`Generating injectable '${
|
|
3498
|
+
s.start(`Generating injectable '${pc14__default.default.cyan(className)}'...`);
|
|
3499
3499
|
const options = {
|
|
3500
3500
|
name: className,
|
|
3501
3501
|
feature,
|
|
@@ -3509,7 +3509,7 @@ async function runInjectableCommand(classNameArg, flags = {}) {
|
|
|
3509
3509
|
const files = generateInjectable(options);
|
|
3510
3510
|
s.stop("Injectable class generated successfully!");
|
|
3511
3511
|
printGeneratedFiles(files, options.dryRun);
|
|
3512
|
-
p14__namespace.outro(
|
|
3512
|
+
p14__namespace.outro(pc14__default.default.green(`Injectable '${className}' created successfully!`));
|
|
3513
3513
|
}
|
|
3514
3514
|
async function runInteractiveMenu(flags = {}) {
|
|
3515
3515
|
printBanner();
|
|
@@ -3623,7 +3623,7 @@ async function handleCreate(type, name, flags = {}) {
|
|
|
3623
3623
|
break;
|
|
3624
3624
|
default:
|
|
3625
3625
|
p14__namespace.log.error(
|
|
3626
|
-
|
|
3626
|
+
pc14__default.default.red(
|
|
3627
3627
|
`Unknown component type: "${type}". Allowed types: feature, shared, usecase, domain, infrastructure, interface, httpHandler, injectable.`
|
|
3628
3628
|
)
|
|
3629
3629
|
);
|
|
@@ -3743,19 +3743,19 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3743
3743
|
);
|
|
3744
3744
|
}
|
|
3745
3745
|
if (type !== "shared" && type !== "feature") {
|
|
3746
|
-
p14__namespace.log.error(
|
|
3746
|
+
p14__namespace.log.error(pc14__default.default.red(`Invalid module type: "${typeArg}". Must be "shared" or "feature".`));
|
|
3747
3747
|
process.exit(1);
|
|
3748
3748
|
}
|
|
3749
3749
|
const registryBase = getRegistryBase(projectRoot, flags.registry);
|
|
3750
3750
|
const s = p14__namespace.spinner();
|
|
3751
|
-
s.start(`Connecting to registry at ${
|
|
3751
|
+
s.start(`Connecting to registry at ${pc14__default.default.dim(registryBase)}...`);
|
|
3752
3752
|
let index;
|
|
3753
3753
|
try {
|
|
3754
3754
|
index = await fetchRegistryIndex(registryBase);
|
|
3755
3755
|
s.stop("Registry index loaded.");
|
|
3756
3756
|
} catch (err) {
|
|
3757
|
-
s.stop(
|
|
3758
|
-
p14__namespace.log.error(
|
|
3757
|
+
s.stop(pc14__default.default.red("Failed to load registry index."));
|
|
3758
|
+
p14__namespace.log.error(pc14__default.default.red(err.message || String(err)));
|
|
3759
3759
|
process.exit(1);
|
|
3760
3760
|
}
|
|
3761
3761
|
const availableList = type === "shared" ? index.shared : index.features;
|
|
@@ -3779,9 +3779,9 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3779
3779
|
}
|
|
3780
3780
|
if (!candidateNames.includes(name)) {
|
|
3781
3781
|
const closest = findClosestMatch(name, candidateNames);
|
|
3782
|
-
p14__namespace.log.error(
|
|
3782
|
+
p14__namespace.log.error(pc14__default.default.red(`\u274C Module '${name}' not found in the ${type} registry.`));
|
|
3783
3783
|
if (closest) {
|
|
3784
|
-
p14__namespace.log.info(
|
|
3784
|
+
p14__namespace.log.info(pc14__default.default.yellow(`Did you mean '${pc14__default.default.cyan(closest)}'?`));
|
|
3785
3785
|
}
|
|
3786
3786
|
process.exit(1);
|
|
3787
3787
|
}
|
|
@@ -3793,7 +3793,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3793
3793
|
try {
|
|
3794
3794
|
manifest = await fetchModuleManifest(registryBase, modType, modName);
|
|
3795
3795
|
} catch (err) {
|
|
3796
|
-
p14__namespace.log.error(
|
|
3796
|
+
p14__namespace.log.error(pc14__default.default.red(`Failed to fetch manifest for ${modType} '${modName}': ${err.message}`));
|
|
3797
3797
|
process.exit(1);
|
|
3798
3798
|
}
|
|
3799
3799
|
nodes.set(key, {
|
|
@@ -3833,7 +3833,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3833
3833
|
buildTopologicalOrder(rootKey);
|
|
3834
3834
|
const resolutions = /* @__PURE__ */ new Map();
|
|
3835
3835
|
const globalAliasMap = /* @__PURE__ */ new Map();
|
|
3836
|
-
p14__namespace.log.step(`Importing ${type}: ${
|
|
3836
|
+
p14__namespace.log.step(`Importing ${type}: ${pc14__default.default.cyan(name)}...`);
|
|
3837
3837
|
for (const key of orderedKeys) {
|
|
3838
3838
|
const node = nodes.get(key);
|
|
3839
3839
|
const { type: modType, name: registryName, manifest } = node;
|
|
@@ -3866,7 +3866,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3866
3866
|
if (programmaticPointTo) {
|
|
3867
3867
|
if (!checkModuleExists(projectRoot, modType, programmaticPointTo)) {
|
|
3868
3868
|
p14__namespace.log.error(
|
|
3869
|
-
|
|
3869
|
+
pc14__default.default.red(
|
|
3870
3870
|
`Module '${programmaticPointTo}' specified in --point-to does not exist in ${folder}.`
|
|
3871
3871
|
)
|
|
3872
3872
|
);
|
|
@@ -3902,7 +3902,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
3902
3902
|
}
|
|
3903
3903
|
const existingDir = getExistingModuleDir(projectRoot, modType, registryName);
|
|
3904
3904
|
if (!existingDir) {
|
|
3905
|
-
p14__namespace.log.info(` \u21B3 Resolving required ${modType} dependency: ${
|
|
3905
|
+
p14__namespace.log.info(` \u21B3 Resolving required ${modType} dependency: ${pc14__default.default.cyan(registryName)}`);
|
|
3906
3906
|
resolutions.set(key, {
|
|
3907
3907
|
type: modType,
|
|
3908
3908
|
registryName,
|
|
@@ -4133,7 +4133,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4133
4133
|
const isOverwrite = Boolean(flags.overwrite || res.action === "overwrite");
|
|
4134
4134
|
if (key !== rootKey) {
|
|
4135
4135
|
p14__namespace.log.step(
|
|
4136
|
-
`Importing ${res.type}: ${
|
|
4136
|
+
`Importing ${res.type}: ${pc14__default.default.cyan(res.targetName)}${res.targetName !== res.registryName ? pc14__default.default.dim(` (from ${res.registryName})`) : ""}...`
|
|
4137
4137
|
);
|
|
4138
4138
|
}
|
|
4139
4139
|
if (res.action === "overwrite" && !flags.dryRun) {
|
|
@@ -4152,7 +4152,7 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4152
4152
|
const localFilePath = path2__default.default.join(targetDir, file);
|
|
4153
4153
|
if (fs4__default.default.existsSync(localFilePath) && !isOverwrite) {
|
|
4154
4154
|
p14__namespace.log.warn(
|
|
4155
|
-
` \u21B3 Skipping existing file: ${
|
|
4155
|
+
` \u21B3 Skipping existing file: ${pc14__default.default.dim(path2__default.default.relative(projectRoot, localFilePath))} (use --overwrite to replace)`
|
|
4156
4156
|
);
|
|
4157
4157
|
continue;
|
|
4158
4158
|
}
|
|
@@ -4164,13 +4164,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4164
4164
|
}
|
|
4165
4165
|
const displayPath = path2__default.default.relative(projectRoot, localFilePath);
|
|
4166
4166
|
totalWrittenFiles.push(displayPath);
|
|
4167
|
-
p14__namespace.log.message(` ${
|
|
4167
|
+
p14__namespace.log.message(` ${pc14__default.default.green("+")} ${displayPath}`);
|
|
4168
4168
|
}
|
|
4169
4169
|
}
|
|
4170
4170
|
const missingNpm = getMissingNpmPackages(projectRoot, Array.from(requiredNpmPackages));
|
|
4171
4171
|
if (missingNpm.length > 0) {
|
|
4172
4172
|
if (flags.skipInstall || flags.dryRun) {
|
|
4173
|
-
p14__namespace.log.info(`Required npm dependencies to install: ${
|
|
4173
|
+
p14__namespace.log.info(`Required npm dependencies to install: ${pc14__default.default.cyan(missingNpm.join(", "))}`);
|
|
4174
4174
|
} else {
|
|
4175
4175
|
try {
|
|
4176
4176
|
installNpmPackages(projectRoot, missingNpm);
|
|
@@ -4184,13 +4184,13 @@ async function handleImport(typeArg, nameArg, flags = {}) {
|
|
|
4184
4184
|
}
|
|
4185
4185
|
}
|
|
4186
4186
|
if (flags.dryRun) {
|
|
4187
|
-
p14__namespace.log.info(
|
|
4187
|
+
p14__namespace.log.info(pc14__default.default.yellow("Dry run complete: No files were written to disk."));
|
|
4188
4188
|
} else {
|
|
4189
4189
|
const rootRes = resolutions.get(rootKey);
|
|
4190
4190
|
const baseDir = getModuleBaseDir(projectRoot, type);
|
|
4191
4191
|
const displayTarget = path2__default.default.relative(projectRoot, path2__default.default.join(baseDir, rootRes.targetName));
|
|
4192
4192
|
p14__namespace.log.success(
|
|
4193
|
-
|
|
4193
|
+
pc14__default.default.green(
|
|
4194
4194
|
`Successfully imported ${type} '${name}' (${totalWrittenFiles.length} file${totalWrittenFiles.length === 1 ? "" : "s"} placed in ${displayTarget})`
|
|
4195
4195
|
)
|
|
4196
4196
|
);
|
|
@@ -4439,6 +4439,350 @@ function discoverAllFeatures(projectRoot) {
|
|
|
4439
4439
|
dir: path2__default.default.join(featuresBase, e.name)
|
|
4440
4440
|
})).sort((a, b) => a.name.localeCompare(b.name));
|
|
4441
4441
|
}
|
|
4442
|
+
function extractClassMethods(content, className) {
|
|
4443
|
+
let clean = "";
|
|
4444
|
+
let inLineComment = false;
|
|
4445
|
+
let inBlockComment = false;
|
|
4446
|
+
let inString = null;
|
|
4447
|
+
let escaped = false;
|
|
4448
|
+
for (let i = 0; i < content.length; i++) {
|
|
4449
|
+
const c = content[i];
|
|
4450
|
+
const next = content[i + 1];
|
|
4451
|
+
if (inLineComment) {
|
|
4452
|
+
if (c === "\n") {
|
|
4453
|
+
inLineComment = false;
|
|
4454
|
+
clean += "\n";
|
|
4455
|
+
} else {
|
|
4456
|
+
clean += " ";
|
|
4457
|
+
}
|
|
4458
|
+
continue;
|
|
4459
|
+
}
|
|
4460
|
+
if (inBlockComment) {
|
|
4461
|
+
if (c === "*" && next === "/") {
|
|
4462
|
+
inBlockComment = false;
|
|
4463
|
+
clean += " ";
|
|
4464
|
+
i++;
|
|
4465
|
+
} else {
|
|
4466
|
+
clean += c === "\n" ? "\n" : " ";
|
|
4467
|
+
}
|
|
4468
|
+
continue;
|
|
4469
|
+
}
|
|
4470
|
+
if (inString) {
|
|
4471
|
+
clean += c === "\n" ? "\n" : " ";
|
|
4472
|
+
if (escaped) {
|
|
4473
|
+
escaped = false;
|
|
4474
|
+
} else if (c === "\\") {
|
|
4475
|
+
escaped = true;
|
|
4476
|
+
} else if (c === inString) {
|
|
4477
|
+
inString = null;
|
|
4478
|
+
}
|
|
4479
|
+
continue;
|
|
4480
|
+
}
|
|
4481
|
+
if (c === "/" && next === "/") {
|
|
4482
|
+
inLineComment = true;
|
|
4483
|
+
clean += " ";
|
|
4484
|
+
i++;
|
|
4485
|
+
continue;
|
|
4486
|
+
}
|
|
4487
|
+
if (c === "/" && next === "*") {
|
|
4488
|
+
inBlockComment = true;
|
|
4489
|
+
clean += " ";
|
|
4490
|
+
i++;
|
|
4491
|
+
continue;
|
|
4492
|
+
}
|
|
4493
|
+
if (c === '"' || c === "'" || c === "`") {
|
|
4494
|
+
inString = c;
|
|
4495
|
+
clean += " ";
|
|
4496
|
+
continue;
|
|
4497
|
+
}
|
|
4498
|
+
clean += c;
|
|
4499
|
+
}
|
|
4500
|
+
const classDeclRegex = new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b[^{]*\\{`);
|
|
4501
|
+
const match = classDeclRegex.exec(clean);
|
|
4502
|
+
if (!match) return null;
|
|
4503
|
+
const classBodyStart = match.index + match[0].length - 1;
|
|
4504
|
+
const methods = [];
|
|
4505
|
+
let depth = 1;
|
|
4506
|
+
let parenDepth = 0;
|
|
4507
|
+
let atMemberStart = true;
|
|
4508
|
+
const methodRegex = /^\s*(?:@[a-zA-Z0-9_$]+(?:\([^)]*\))?\s+)*(?:(public|private|protected|static)\s+)*(?:(async)\s+)?(?:(get|set)\s+)?(#?[a-zA-Z0-9_$]+)\s*(?:<[^>]+>)?\s*\(/;
|
|
4509
|
+
const arrowRegex = /^\s*(?:(public|private|protected|static)\s+)?(#?[a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\(/;
|
|
4510
|
+
for (let i = classBodyStart + 1; i < clean.length && depth > 0; i++) {
|
|
4511
|
+
const c = clean[i];
|
|
4512
|
+
if (c === "{") {
|
|
4513
|
+
depth++;
|
|
4514
|
+
atMemberStart = false;
|
|
4515
|
+
continue;
|
|
4516
|
+
}
|
|
4517
|
+
if (c === "}") {
|
|
4518
|
+
depth--;
|
|
4519
|
+
if (depth === 1) {
|
|
4520
|
+
atMemberStart = true;
|
|
4521
|
+
}
|
|
4522
|
+
continue;
|
|
4523
|
+
}
|
|
4524
|
+
if (depth === 1) {
|
|
4525
|
+
if (c === ";") {
|
|
4526
|
+
atMemberStart = true;
|
|
4527
|
+
parenDepth = 0;
|
|
4528
|
+
continue;
|
|
4529
|
+
}
|
|
4530
|
+
if (c === "(") {
|
|
4531
|
+
parenDepth++;
|
|
4532
|
+
} else if (c === ")") {
|
|
4533
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
4534
|
+
}
|
|
4535
|
+
if (atMemberStart && parenDepth === 0) {
|
|
4536
|
+
if (/\s/.test(c)) continue;
|
|
4537
|
+
const slice = clean.slice(i);
|
|
4538
|
+
const methodMatch = slice.match(methodRegex);
|
|
4539
|
+
if (methodMatch) {
|
|
4540
|
+
const fullMatch = methodMatch[0];
|
|
4541
|
+
const beforeParen = fullMatch.slice(0, fullMatch.lastIndexOf("(")).trim();
|
|
4542
|
+
const tokens = beforeParen.split(/\s+/).filter(Boolean);
|
|
4543
|
+
const rawName = tokens[tokens.length - 1] || "";
|
|
4544
|
+
const methodName = rawName.replace(/<.*$/, "");
|
|
4545
|
+
const controlKeywords = ["if", "for", "while", "switch", "catch", "with"];
|
|
4546
|
+
if (!controlKeywords.includes(methodName)) {
|
|
4547
|
+
const isPrivate = tokens.includes("private") || tokens.includes("protected") || methodName.startsWith("#");
|
|
4548
|
+
const isAsync = tokens.includes("async");
|
|
4549
|
+
const isPublic = !isPrivate;
|
|
4550
|
+
const lineNum = content.slice(0, i).split("\n").length;
|
|
4551
|
+
const lineCode = content.split("\n")[lineNum - 1]?.trim() || "";
|
|
4552
|
+
methods.push({
|
|
4553
|
+
name: methodName,
|
|
4554
|
+
isAsync,
|
|
4555
|
+
isPublic,
|
|
4556
|
+
line: lineNum,
|
|
4557
|
+
code: lineCode
|
|
4558
|
+
});
|
|
4559
|
+
i += fullMatch.length - 1;
|
|
4560
|
+
parenDepth = 1;
|
|
4561
|
+
atMemberStart = false;
|
|
4562
|
+
continue;
|
|
4563
|
+
}
|
|
4564
|
+
}
|
|
4565
|
+
const arrowMatch = slice.match(arrowRegex);
|
|
4566
|
+
if (arrowMatch) {
|
|
4567
|
+
const fullMatch = arrowMatch[0];
|
|
4568
|
+
const beforeEquals = fullMatch.split("=")[0].trim();
|
|
4569
|
+
const tokens = beforeEquals.split(/\s+/).filter(Boolean);
|
|
4570
|
+
const rawName = tokens[tokens.length - 1] || "";
|
|
4571
|
+
const isPrivate = tokens.includes("private") || tokens.includes("protected") || rawName.startsWith("#");
|
|
4572
|
+
const isAsync = fullMatch.includes("async");
|
|
4573
|
+
const isPublic = !isPrivate;
|
|
4574
|
+
const lineNum = content.slice(0, i).split("\n").length;
|
|
4575
|
+
const lineCode = content.split("\n")[lineNum - 1]?.trim() || "";
|
|
4576
|
+
methods.push({
|
|
4577
|
+
name: rawName,
|
|
4578
|
+
isAsync,
|
|
4579
|
+
isPublic,
|
|
4580
|
+
line: lineNum,
|
|
4581
|
+
code: lineCode
|
|
4582
|
+
});
|
|
4583
|
+
i += fullMatch.length - 1;
|
|
4584
|
+
parenDepth = 1;
|
|
4585
|
+
atMemberStart = false;
|
|
4586
|
+
continue;
|
|
4587
|
+
}
|
|
4588
|
+
atMemberStart = false;
|
|
4589
|
+
}
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
return methods;
|
|
4593
|
+
}
|
|
4594
|
+
function checkUseCaseShape(featureName, filePath, relativePath, content, addViolation) {
|
|
4595
|
+
const fileName = path2__default.default.basename(filePath);
|
|
4596
|
+
const lines = content.split(/\r?\n/);
|
|
4597
|
+
const classMatch = content.match(/(?:export\s+(?:default\s+)?)?class\s+([A-Za-z0-9_]+)/);
|
|
4598
|
+
if (!classMatch) {
|
|
4599
|
+
addViolation({
|
|
4600
|
+
feature: featureName,
|
|
4601
|
+
layer: "useCases",
|
|
4602
|
+
file: filePath,
|
|
4603
|
+
relativePath,
|
|
4604
|
+
line: 1,
|
|
4605
|
+
category: "Use Case Shape Violation",
|
|
4606
|
+
code: lines[0]?.trim() || "",
|
|
4607
|
+
ruleId: "U_SHAPE_CLASS",
|
|
4608
|
+
severity: "error",
|
|
4609
|
+
advice: `Use case file '${fileName}' must define and export a class.`,
|
|
4610
|
+
fixExample: {
|
|
4611
|
+
bad: "// Missing class definition",
|
|
4612
|
+
good: `export class ${fileName.replace(/\.(ts|js)$/, "")} { ... }`
|
|
4613
|
+
}
|
|
4614
|
+
});
|
|
4615
|
+
return;
|
|
4616
|
+
}
|
|
4617
|
+
const className = classMatch[1];
|
|
4618
|
+
let classLine = 1;
|
|
4619
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4620
|
+
if (new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b`).test(lines[i])) {
|
|
4621
|
+
classLine = i + 1;
|
|
4622
|
+
break;
|
|
4623
|
+
}
|
|
4624
|
+
}
|
|
4625
|
+
if (!/@MakeInjectable\b/.test(content)) {
|
|
4626
|
+
addViolation({
|
|
4627
|
+
feature: featureName,
|
|
4628
|
+
layer: "useCases",
|
|
4629
|
+
file: filePath,
|
|
4630
|
+
relativePath,
|
|
4631
|
+
line: classLine,
|
|
4632
|
+
category: "Use Case Shape Violation",
|
|
4633
|
+
code: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4634
|
+
ruleId: "U_SHAPE_DECORATOR",
|
|
4635
|
+
severity: "error",
|
|
4636
|
+
advice: `Use case class '${className}' in '${fileName}' must be decorated with @MakeInjectable.`,
|
|
4637
|
+
fixExample: {
|
|
4638
|
+
bad: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4639
|
+
good: `@MakeInjectable
|
|
4640
|
+
export class ${className} { ... }`
|
|
4641
|
+
}
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
const hasAsyncExecute = /(?:public\s+)?async\s+execute\s*(?:<[^>]+>)?\s*\(/.test(content);
|
|
4645
|
+
if (!hasAsyncExecute) {
|
|
4646
|
+
const hasSyncExecute = /(?:public\s+)?execute\s*(?:<[^>]+>)?\s*\(/.test(content);
|
|
4647
|
+
addViolation({
|
|
4648
|
+
feature: featureName,
|
|
4649
|
+
layer: "useCases",
|
|
4650
|
+
file: filePath,
|
|
4651
|
+
relativePath,
|
|
4652
|
+
line: classLine,
|
|
4653
|
+
category: "Use Case Shape Violation",
|
|
4654
|
+
code: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4655
|
+
ruleId: "U_SHAPE_EXECUTE",
|
|
4656
|
+
severity: "error",
|
|
4657
|
+
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.`,
|
|
4658
|
+
fixExample: {
|
|
4659
|
+
bad: hasSyncExecute ? "execute(props: ...) { ... }" : `class ${className} { ... }`,
|
|
4660
|
+
good: "async execute(props: ...): Promise<...> { ... }"
|
|
4661
|
+
}
|
|
4662
|
+
});
|
|
4663
|
+
}
|
|
4664
|
+
const classMethods = extractClassMethods(content, className);
|
|
4665
|
+
if (classMethods) {
|
|
4666
|
+
const extraPublicMethods = classMethods.filter(
|
|
4667
|
+
(m) => m.isPublic && m.name !== "constructor" && m.name !== "execute"
|
|
4668
|
+
);
|
|
4669
|
+
for (const m of extraPublicMethods) {
|
|
4670
|
+
addViolation({
|
|
4671
|
+
feature: featureName,
|
|
4672
|
+
layer: "useCases",
|
|
4673
|
+
file: filePath,
|
|
4674
|
+
relativePath,
|
|
4675
|
+
line: m.line,
|
|
4676
|
+
category: "Use Case Shape Violation",
|
|
4677
|
+
code: m.code || `public ${m.name}(...)`,
|
|
4678
|
+
ruleId: "U_SHAPE_ONLY_EXECUTE",
|
|
4679
|
+
severity: "error",
|
|
4680
|
+
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.`,
|
|
4681
|
+
fixExample: {
|
|
4682
|
+
bad: m.code || `${m.name}(...) { ... }`,
|
|
4683
|
+
good: `// Extract '${m.name}' into its own dedicated Use Case, or make it private if it is internal helper logic:
|
|
4684
|
+
private ${m.name}(...) { ... }`
|
|
4685
|
+
}
|
|
4686
|
+
});
|
|
4687
|
+
}
|
|
4688
|
+
}
|
|
4689
|
+
}
|
|
4690
|
+
function checkServiceShape(featureName, filePath, relativePath, content, addViolation) {
|
|
4691
|
+
const fileName = path2__default.default.basename(filePath);
|
|
4692
|
+
const lines = content.split(/\r?\n/);
|
|
4693
|
+
const classMatch = content.match(/(?:export\s+(?:default\s+)?)?class\s+([A-Za-z0-9_]+)/);
|
|
4694
|
+
if (!classMatch) {
|
|
4695
|
+
addViolation({
|
|
4696
|
+
feature: featureName,
|
|
4697
|
+
layer: "services",
|
|
4698
|
+
file: filePath,
|
|
4699
|
+
relativePath,
|
|
4700
|
+
line: 1,
|
|
4701
|
+
category: "Service Shape Violation",
|
|
4702
|
+
code: lines[0]?.trim() || "",
|
|
4703
|
+
ruleId: "S_SHAPE_CLASS",
|
|
4704
|
+
severity: "error",
|
|
4705
|
+
advice: `Service file '${fileName}' must define and export a class.`,
|
|
4706
|
+
fixExample: {
|
|
4707
|
+
bad: "// Missing class definition",
|
|
4708
|
+
good: `export class ${fileName.replace(/\.(ts|js)$/, "")} { ... }`
|
|
4709
|
+
}
|
|
4710
|
+
});
|
|
4711
|
+
return;
|
|
4712
|
+
}
|
|
4713
|
+
const className = classMatch[1];
|
|
4714
|
+
let classLine = 1;
|
|
4715
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4716
|
+
if (new RegExp(`(?:export\\s+(?:default\\s+)?)?class\\s+${className}\\b`).test(lines[i])) {
|
|
4717
|
+
classLine = i + 1;
|
|
4718
|
+
break;
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
if (!/@MakeInjectable\b/.test(content)) {
|
|
4722
|
+
addViolation({
|
|
4723
|
+
feature: featureName,
|
|
4724
|
+
layer: "services",
|
|
4725
|
+
file: filePath,
|
|
4726
|
+
relativePath,
|
|
4727
|
+
line: classLine,
|
|
4728
|
+
category: "Service Shape Violation",
|
|
4729
|
+
code: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4730
|
+
ruleId: "S_SHAPE_DECORATOR",
|
|
4731
|
+
severity: "error",
|
|
4732
|
+
advice: `Service class '${className}' in '${fileName}' must be decorated with @MakeInjectable.`,
|
|
4733
|
+
fixExample: {
|
|
4734
|
+
bad: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4735
|
+
good: `@MakeInjectable
|
|
4736
|
+
export class ${className} { ... }`
|
|
4737
|
+
}
|
|
4738
|
+
});
|
|
4739
|
+
}
|
|
4740
|
+
const hasAsyncExecute = /(?:public\s+)?async\s+execute\s*(?:<[^>]+>)?\s*\(/.test(content);
|
|
4741
|
+
if (!hasAsyncExecute) {
|
|
4742
|
+
const hasSyncExecute = /(?:public\s+)?execute\s*(?:<[^>]+>)?\s*\(/.test(content);
|
|
4743
|
+
addViolation({
|
|
4744
|
+
feature: featureName,
|
|
4745
|
+
layer: "services",
|
|
4746
|
+
file: filePath,
|
|
4747
|
+
relativePath,
|
|
4748
|
+
line: classLine,
|
|
4749
|
+
category: "Service Shape Violation",
|
|
4750
|
+
code: lines[classLine - 1]?.trim() || `class ${className}`,
|
|
4751
|
+
ruleId: "S_SHAPE_EXECUTE",
|
|
4752
|
+
severity: "error",
|
|
4753
|
+
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.`,
|
|
4754
|
+
fixExample: {
|
|
4755
|
+
bad: hasSyncExecute ? "execute(props: ...) { ... }" : `class ${className} { ... }`,
|
|
4756
|
+
good: "async execute(props: ...): Promise<...> { ... }"
|
|
4757
|
+
}
|
|
4758
|
+
});
|
|
4759
|
+
}
|
|
4760
|
+
const classMethods = extractClassMethods(content, className);
|
|
4761
|
+
if (classMethods) {
|
|
4762
|
+
const extraPublicMethods = classMethods.filter(
|
|
4763
|
+
(m) => m.isPublic && m.name !== "constructor" && m.name !== "execute"
|
|
4764
|
+
);
|
|
4765
|
+
for (const m of extraPublicMethods) {
|
|
4766
|
+
addViolation({
|
|
4767
|
+
feature: featureName,
|
|
4768
|
+
layer: "services",
|
|
4769
|
+
file: filePath,
|
|
4770
|
+
relativePath,
|
|
4771
|
+
line: m.line,
|
|
4772
|
+
category: "Service Shape Violation",
|
|
4773
|
+
code: m.code || `public ${m.name}(...)`,
|
|
4774
|
+
ruleId: "S_SHAPE_ONLY_EXECUTE",
|
|
4775
|
+
severity: "error",
|
|
4776
|
+
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.`,
|
|
4777
|
+
fixExample: {
|
|
4778
|
+
bad: m.code || `${m.name}(...) { ... }`,
|
|
4779
|
+
good: `// Extract '${m.name}' into its own dedicated Service, or make it private if it is internal helper logic:
|
|
4780
|
+
private ${m.name}(...) { ... }`
|
|
4781
|
+
}
|
|
4782
|
+
});
|
|
4783
|
+
}
|
|
4784
|
+
}
|
|
4785
|
+
}
|
|
4442
4786
|
function lintCodebase(options = {}) {
|
|
4443
4787
|
const startTime = Date.now();
|
|
4444
4788
|
const projectRoot = options.projectRoot || findProjectRoot(options.targetDir || process.cwd());
|
|
@@ -4456,13 +4800,9 @@ function lintCodebase(options = {}) {
|
|
|
4456
4800
|
featureResult.passed = false;
|
|
4457
4801
|
}
|
|
4458
4802
|
if (failFast) {
|
|
4459
|
-
const errorMsg = `
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
Rule: ${v.ruleId}
|
|
4463
|
-
Location: ${v.relativePath}:${v.line}
|
|
4464
|
-
Code: ${v.code}
|
|
4465
|
-
Explanation: ${v.advice}`;
|
|
4803
|
+
const errorMsg = `Dependency rule violation detected in ${v.relativePath}:${v.line}:
|
|
4804
|
+
[${v.category}] ${v.advice}
|
|
4805
|
+
Code: ${v.code}`;
|
|
4466
4806
|
throw new DependencyRuleError(errorMsg, [v], true);
|
|
4467
4807
|
}
|
|
4468
4808
|
};
|
|
@@ -4486,6 +4826,14 @@ function lintCodebase(options = {}) {
|
|
|
4486
4826
|
if (!layer) continue;
|
|
4487
4827
|
const content = fs4__default.default.readFileSync(file, "utf-8");
|
|
4488
4828
|
const statements = extractImportStatements(content);
|
|
4829
|
+
const baseName = path2__default.default.basename(file);
|
|
4830
|
+
const isBarrel = baseName === "index.ts" || baseName === "index.js";
|
|
4831
|
+
if (layer === "useCases" && !isBarrel) {
|
|
4832
|
+
checkUseCaseShape(feat.name, file, relPath, content, addViolation);
|
|
4833
|
+
}
|
|
4834
|
+
if (layer === "services" && !isBarrel) {
|
|
4835
|
+
checkServiceShape(feat.name, file, relPath, content, addViolation);
|
|
4836
|
+
}
|
|
4489
4837
|
if (layer === "domain") {
|
|
4490
4838
|
const lines = content.split(/\r?\n/);
|
|
4491
4839
|
for (let i = 0; i < lines.length; i++) {
|
|
@@ -4896,6 +5244,24 @@ function lintCodebase(options = {}) {
|
|
|
4896
5244
|
}
|
|
4897
5245
|
});
|
|
4898
5246
|
}
|
|
5247
|
+
if (target.targetLayer === "services") {
|
|
5248
|
+
addViolation({
|
|
5249
|
+
feature: feat.name,
|
|
5250
|
+
layer,
|
|
5251
|
+
file,
|
|
5252
|
+
relativePath: relPath,
|
|
5253
|
+
line: stmt.line,
|
|
5254
|
+
category: "Presentation Layer Violation",
|
|
5255
|
+
code: stmt.rawCode,
|
|
5256
|
+
ruleId: "P4_DIRECT_SERVICE",
|
|
5257
|
+
severity: "error",
|
|
5258
|
+
advice: "Presentation handlers must not depend on services directly. They must use use cases, and use cases will then depend on the service.",
|
|
5259
|
+
fixExample: {
|
|
5260
|
+
bad: stmt.rawCode,
|
|
5261
|
+
good: "// Inject a Use Case via static deps instead of a Service."
|
|
5262
|
+
}
|
|
5263
|
+
});
|
|
5264
|
+
}
|
|
4899
5265
|
}
|
|
4900
5266
|
}
|
|
4901
5267
|
}
|
|
@@ -4950,87 +5316,23 @@ ${compiledDetails}
|
|
|
4950
5316
|
}
|
|
4951
5317
|
|
|
4952
5318
|
// src/commands/lint.ts
|
|
4953
|
-
function
|
|
4954
|
-
console.log("");
|
|
4955
|
-
const title = " Mason Clean Architecture Dependency Linter ";
|
|
4956
|
-
const line = "\u2501".repeat(title.length + 2);
|
|
4957
|
-
console.log(pc15__default.default.cyan(` \u250F${line}\u2513`));
|
|
4958
|
-
console.log(` ${pc15__default.default.cyan("\u2503")} ${pc15__default.default.bgCyan(pc15__default.default.black(title))} ${pc15__default.default.cyan("\u2503")}`);
|
|
4959
|
-
console.log(pc15__default.default.cyan(` \u2517${line}\u251B`));
|
|
4960
|
-
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)")}`;
|
|
4961
|
-
console.log(` Mode: ${modeText}`);
|
|
4962
|
-
console.log("");
|
|
4963
|
-
}
|
|
4964
|
-
function formatViolation(v, showFixHint = true) {
|
|
5319
|
+
function formatViolation(v) {
|
|
4965
5320
|
const lines = [];
|
|
4966
|
-
|
|
4967
|
-
const
|
|
4968
|
-
lines.push(
|
|
4969
|
-
|
|
4970
|
-
lines.push(
|
|
4971
|
-
|
|
4972
|
-
if (showFixHint && v.fixExample) {
|
|
4973
|
-
lines.push(` ${pc15__default.default.dim("Example:")}`);
|
|
4974
|
-
lines.push(` ${pc15__default.default.red(`- ${v.fixExample.bad}`)}`);
|
|
4975
|
-
lines.push(` ${pc15__default.default.green(`+ ${v.fixExample.good}`)}`);
|
|
4976
|
-
}
|
|
4977
|
-
return lines.join("\n");
|
|
4978
|
-
}
|
|
4979
|
-
function formatFeatureResult(res, options = {}) {
|
|
4980
|
-
const lines = [];
|
|
4981
|
-
if (res.passed) {
|
|
4982
|
-
lines.push(
|
|
4983
|
-
` ${pc15__default.default.green("\u2714")} ${pc15__default.default.bold(res.featureName)} ${pc15__default.default.dim(
|
|
4984
|
-
`(${res.scannedFilesCount} files checked - clean)`
|
|
4985
|
-
)}`
|
|
4986
|
-
);
|
|
4987
|
-
return lines.join("\n");
|
|
4988
|
-
}
|
|
4989
|
-
const countBadge = pc15__default.default.red(
|
|
4990
|
-
`(${res.violations.length} violation${res.violations.length > 1 ? "s" : ""})`
|
|
5321
|
+
lines.push(pc14__default.default.underline(pc14__default.default.cyan(v.relativePath)) + pc14__default.default.dim(":") + pc14__default.default.yellow(String(v.line)));
|
|
5322
|
+
const gutter = `${String(v.line).padStart(4, " ")} | `;
|
|
5323
|
+
lines.push(pc14__default.default.dim(gutter) + v.code);
|
|
5324
|
+
const categoryShort = v.category.replace(" Violation", "");
|
|
5325
|
+
lines.push(
|
|
5326
|
+
" " + pc14__default.default.red(pc14__default.default.bold("\u2716")) + " " + pc14__default.default.red(`[${categoryShort}]`) + " " + v.advice
|
|
4991
5327
|
);
|
|
4992
|
-
|
|
4993
|
-
|
|
4994
|
-
lines.push(` ${pc15__default.default.dim("Path:")} ${res.featureDir}`);
|
|
4995
|
-
lines.push("");
|
|
4996
|
-
for (const v of res.violations) {
|
|
4997
|
-
lines.push(formatViolation(v, options.fixHint !== false));
|
|
4998
|
-
lines.push("");
|
|
5328
|
+
if (v.fixExample?.good) {
|
|
5329
|
+
lines.push(" " + pc14__default.default.dim("\u2192") + " " + pc14__default.default.dim(v.fixExample.good));
|
|
4999
5330
|
}
|
|
5000
5331
|
return lines.join("\n");
|
|
5001
5332
|
}
|
|
5002
|
-
function printTerminalReport(summary, options = {}) {
|
|
5003
|
-
for (const res of summary.results) {
|
|
5004
|
-
const formatted = formatFeatureResult(res, options);
|
|
5005
|
-
if (formatted.trim()) {
|
|
5006
|
-
console.log(formatted);
|
|
5007
|
-
}
|
|
5008
|
-
}
|
|
5009
|
-
console.log("\u2501".repeat(70));
|
|
5010
|
-
const statusIcon = summary.totalViolations === 0 ? pc15__default.default.green("\u2714 PASS") : pc15__default.default.red("\u2716 FAIL");
|
|
5011
|
-
console.log(
|
|
5012
|
-
` ${statusIcon} ${pc15__default.default.bold("Dependency Rules Audit Result")} ${pc15__default.default.dim(
|
|
5013
|
-
`(${summary.durationMs}ms)`
|
|
5014
|
-
)}`
|
|
5015
|
-
);
|
|
5016
|
-
console.log(
|
|
5017
|
-
` ${pc15__default.default.bold("Features:")} ${pc15__default.default.cyan(String(summary.totalFeatures))} total | ${pc15__default.default.green(
|
|
5018
|
-
String(summary.cleanFeatures)
|
|
5019
|
-
)} clean | ${summary.violatedFeatures > 0 ? pc15__default.default.red(String(summary.violatedFeatures)) : pc15__default.default.green("0")} with violations`
|
|
5020
|
-
);
|
|
5021
|
-
console.log(
|
|
5022
|
-
` ${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`
|
|
5023
|
-
);
|
|
5024
|
-
console.log("\u2501".repeat(70));
|
|
5025
|
-
console.log("");
|
|
5026
|
-
}
|
|
5027
5333
|
async function handleLint(flags = {}) {
|
|
5028
5334
|
const complete = Boolean(flags.complete);
|
|
5029
|
-
const mode = complete ? "complete" : "failFast";
|
|
5030
5335
|
const quiet = Boolean(flags.quiet);
|
|
5031
|
-
if (!quiet) {
|
|
5032
|
-
printLintBanner(mode);
|
|
5033
|
-
}
|
|
5034
5336
|
const options = {
|
|
5035
5337
|
complete,
|
|
5036
5338
|
quiet,
|
|
@@ -5041,14 +5343,23 @@ async function handleLint(flags = {}) {
|
|
|
5041
5343
|
};
|
|
5042
5344
|
try {
|
|
5043
5345
|
const summary = lintCodebase(options);
|
|
5044
|
-
if (!quiet) {
|
|
5045
|
-
printTerminalReport(summary, options);
|
|
5046
|
-
}
|
|
5047
5346
|
if (summary.totalViolations > 0) {
|
|
5347
|
+
if (!quiet) {
|
|
5348
|
+
console.log("");
|
|
5349
|
+
for (const v of summary.allViolations) {
|
|
5350
|
+
console.log(formatViolation(v));
|
|
5351
|
+
console.log("");
|
|
5352
|
+
}
|
|
5353
|
+
console.log(
|
|
5354
|
+
pc14__default.default.red(
|
|
5355
|
+
`\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).`
|
|
5356
|
+
)
|
|
5357
|
+
);
|
|
5358
|
+
console.log("");
|
|
5359
|
+
}
|
|
5048
5360
|
const errorHeader = `Dependency rule audit failed with ${summary.totalViolations} violation(s) across ${summary.violatedFeatures} feature(s):`;
|
|
5049
5361
|
const compiledDetails = summary.allViolations.map(
|
|
5050
5362
|
(v, i) => `${i + 1}. [${v.category}] ${v.relativePath}:${v.line}
|
|
5051
|
-
Rule: ${v.ruleId}
|
|
5052
5363
|
Code: ${v.code}
|
|
5053
5364
|
Advice: ${v.advice}`
|
|
5054
5365
|
).join("\n\n");
|
|
@@ -5059,20 +5370,22 @@ ${compiledDetails}
|
|
|
5059
5370
|
}
|
|
5060
5371
|
if (!quiet) {
|
|
5061
5372
|
console.log(
|
|
5062
|
-
|
|
5373
|
+
pc14__default.default.green(
|
|
5374
|
+
`\u2714 Dependency rules verified across ${summary.totalFeatures} feature${summary.totalFeatures === 1 ? "" : "s"}.`
|
|
5375
|
+
)
|
|
5063
5376
|
);
|
|
5064
|
-
console.log("");
|
|
5065
5377
|
}
|
|
5066
5378
|
return summary;
|
|
5067
5379
|
} catch (err) {
|
|
5068
5380
|
if (err instanceof DependencyRuleError && err.isFailFast) {
|
|
5069
5381
|
if (!quiet) {
|
|
5070
|
-
console.log(
|
|
5071
|
-
\u2716 [FAIL FAST] Terminating on first detected dependency rule violation:`));
|
|
5382
|
+
console.log("");
|
|
5072
5383
|
const v = err.violations[0];
|
|
5073
5384
|
if (v) {
|
|
5074
|
-
console.log(formatViolation(v
|
|
5385
|
+
console.log(formatViolation(v));
|
|
5386
|
+
console.log("");
|
|
5075
5387
|
}
|
|
5388
|
+
console.log(pc14__default.default.red(`\u2716 Dependency rules check failed (1 error).`));
|
|
5076
5389
|
console.log("");
|
|
5077
5390
|
}
|
|
5078
5391
|
}
|
|
@@ -5086,7 +5399,7 @@ function createCli() {
|
|
|
5086
5399
|
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) => {
|
|
5087
5400
|
if (args && args.length > 0) {
|
|
5088
5401
|
p14__namespace.log.error(
|
|
5089
|
-
|
|
5402
|
+
pc14__default.default.red(
|
|
5090
5403
|
`Unknown command: "${args.join(" ")}". Use --help to list available commands.`
|
|
5091
5404
|
)
|
|
5092
5405
|
);
|
|
@@ -5205,6 +5518,12 @@ function createCli() {
|
|
|
5205
5518
|
targetDir: target || flags.target || flags.dest
|
|
5206
5519
|
});
|
|
5207
5520
|
} catch (err) {
|
|
5521
|
+
if (err instanceof DependencyRuleError) {
|
|
5522
|
+
if (process.env.VITEST) {
|
|
5523
|
+
throw err;
|
|
5524
|
+
}
|
|
5525
|
+
process.exit(1);
|
|
5526
|
+
}
|
|
5208
5527
|
handleCliError(err);
|
|
5209
5528
|
}
|
|
5210
5529
|
});
|
|
@@ -5239,7 +5558,7 @@ function handleCliError(err) {
|
|
|
5239
5558
|
process.exit(0);
|
|
5240
5559
|
}
|
|
5241
5560
|
const message = err instanceof Error ? err.message : String(err);
|
|
5242
|
-
p14__namespace.log.error(
|
|
5561
|
+
p14__namespace.log.error(pc14__default.default.red(`Error: ${message}`));
|
|
5243
5562
|
if (process.env.VITEST) {
|
|
5244
5563
|
throw err;
|
|
5245
5564
|
}
|