@webpieces/code-rules 0.3.251 → 0.3.253
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/package.json +2 -2
- package/src/index.d.ts +1 -0
- package/src/index.js +3 -1
- package/src/index.js.map +1 -1
- package/src/tag-rule.d.ts +51 -0
- package/src/tag-rule.js +175 -0
- package/src/tag-rule.js.map +1 -0
- package/src/validate-code.js +2 -0
- package/src/validate-code.js.map +1 -1
- package/src/validate-framework-tag.d.ts +8 -22
- package/src/validate-framework-tag.js +10 -150
- package/src/validate-framework-tag.js.map +1 -1
- package/src/validate-role-tag.d.ts +27 -0
- package/src/validate-role-tag.js +59 -0
- package/src/validate-role-tag.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webpieces/code-rules",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.253",
|
|
4
4
|
"description": "Standalone code validation rules extracted from architecture-validators, no Nx dependency required",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"directory": "packages/tooling/code-rules"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@webpieces/rules-config": "0.3.
|
|
23
|
+
"@webpieces/rules-config": "0.3.253"
|
|
24
24
|
},
|
|
25
25
|
"publishConfig": {
|
|
26
26
|
"access": "public"
|
package/src/index.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ export { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver'
|
|
|
13
13
|
export { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';
|
|
14
14
|
export { EnforceControllerNamingValidator } from './validate-enforce-controller-naming';
|
|
15
15
|
export { FrameworkTagValidator } from './validate-framework-tag';
|
|
16
|
+
export { RoleTagValidator } from './validate-role-tag';
|
|
16
17
|
export { PrismaValidateDtosValidator } from './validate-dtos';
|
|
17
18
|
export { PrismaConverterValidator } from './validate-prisma-converters';
|
|
18
19
|
export { default as validateCode } from './validate-code';
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.validateCode = exports.PrismaConverterValidator = exports.PrismaValidateDtosValidator = exports.FrameworkTagValidator = exports.EnforceControllerNamingValidator = exports.NoSymbolDiTokensValidator = exports.NoDirectApiResolverValidator = exports.NoDestructureValidator = exports.NoUnmanagedExceptionsValidator = exports.CatchErrorPatternValidator = exports.NoInlineTypeLiteralsValidator = exports.RequireReturnTypeValidator = exports.MaxFileLinesValidator = exports.runNewMethods = exports.runModifiedMethods = exports.MaxMethodLinesValidator = exports.NoImplicitAnyValidator = exports.NoAnyUnknownValidator = exports.CodeValidator = void 0;
|
|
3
|
+
exports.validateCode = exports.PrismaConverterValidator = exports.PrismaValidateDtosValidator = exports.RoleTagValidator = exports.FrameworkTagValidator = exports.EnforceControllerNamingValidator = exports.NoSymbolDiTokensValidator = exports.NoDirectApiResolverValidator = exports.NoDestructureValidator = exports.NoUnmanagedExceptionsValidator = exports.CatchErrorPatternValidator = exports.NoInlineTypeLiteralsValidator = exports.RequireReturnTypeValidator = exports.MaxFileLinesValidator = exports.runNewMethods = exports.runModifiedMethods = exports.MaxMethodLinesValidator = exports.NoImplicitAnyValidator = exports.NoAnyUnknownValidator = exports.CodeValidator = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
var code_validator_1 = require("./code-validator");
|
|
6
6
|
Object.defineProperty(exports, "CodeValidator", { enumerable: true, get: function () { return code_validator_1.CodeValidator; } });
|
|
@@ -33,6 +33,8 @@ var validate_enforce_controller_naming_1 = require("./validate-enforce-controlle
|
|
|
33
33
|
Object.defineProperty(exports, "EnforceControllerNamingValidator", { enumerable: true, get: function () { return validate_enforce_controller_naming_1.EnforceControllerNamingValidator; } });
|
|
34
34
|
var validate_framework_tag_1 = require("./validate-framework-tag");
|
|
35
35
|
Object.defineProperty(exports, "FrameworkTagValidator", { enumerable: true, get: function () { return validate_framework_tag_1.FrameworkTagValidator; } });
|
|
36
|
+
var validate_role_tag_1 = require("./validate-role-tag");
|
|
37
|
+
Object.defineProperty(exports, "RoleTagValidator", { enumerable: true, get: function () { return validate_role_tag_1.RoleTagValidator; } });
|
|
36
38
|
var validate_dtos_1 = require("./validate-dtos");
|
|
37
39
|
Object.defineProperty(exports, "PrismaValidateDtosValidator", { enumerable: true, get: function () { return validate_dtos_1.PrismaValidateDtosValidator; } });
|
|
38
40
|
var validate_prisma_converters_1 = require("./validate-prisma-converters");
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,mDAAiE;AAAxD,+GAAA,aAAa,OAAA;AAEtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,uEAAoE;AAA3D,kIAAA,sBAAsB,OAAA;AAC/B,yEAA0F;AAAjF,oIAAA,uBAAuB,OAAA;AAAE,+HAAA,kBAAkB,OAAA;AACpD,+DAAuD;AAA9C,qHAAA,aAAa,OAAA;AACtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,iEAAqE;AAA5D,mIAAA,0BAA0B,OAAA;AACnC,uEAA2E;AAAlE,yIAAA,6BAA6B,OAAA;AACtC,+EAA4E;AAAnE,0IAAA,0BAA0B,OAAA;AACnC,uFAAoF;AAA3E,kJAAA,8BAA8B,OAAA;AACvC,qEAAmE;AAA1D,iIAAA,sBAAsB,OAAA;AAC/B,qFAAiF;AAAxE,+IAAA,4BAA4B,OAAA;AACrC,+EAA2E;AAAlE,yIAAA,yBAAyB,OAAA;AAClC,2FAAwF;AAA/E,sJAAA,gCAAgC,OAAA;AACzC,mEAAiE;AAAxD,+HAAA,qBAAqB,OAAA;AAC9B,iDAA8D;AAArD,4HAAA,2BAA2B,OAAA;AACpC,2EAAwE;AAA/D,sIAAA,wBAAwB,OAAA;AACjC,iDAA0D;AAAjD,sIAAA,OAAO,OAAgB","sourcesContent":["export { CodeValidator, ExecutorResult } from './code-validator';\n\nexport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nexport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nexport { MaxMethodLinesValidator, runModifiedMethods } from './validate-modified-methods';\nexport { runNewMethods } from './validate-new-methods';\nexport { MaxFileLinesValidator } from './validate-modified-files';\nexport { RequireReturnTypeValidator } from './validate-return-types';\nexport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nexport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nexport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nexport { NoDestructureValidator } from './validate-no-destructure';\nexport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nexport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nexport { EnforceControllerNamingValidator } from './validate-enforce-controller-naming';\nexport { FrameworkTagValidator } from './validate-framework-tag';\nexport { PrismaValidateDtosValidator } from './validate-dtos';\nexport { PrismaConverterValidator } from './validate-prisma-converters';\nexport { default as validateCode } from './validate-code';\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/index.ts"],"names":[],"mappings":";;;;AAAA,mDAAiE;AAAxD,+GAAA,aAAa,OAAA;AAEtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,uEAAoE;AAA3D,kIAAA,sBAAsB,OAAA;AAC/B,yEAA0F;AAAjF,oIAAA,uBAAuB,OAAA;AAAE,+HAAA,kBAAkB,OAAA;AACpD,+DAAuD;AAA9C,qHAAA,aAAa,OAAA;AACtB,qEAAkE;AAAzD,gIAAA,qBAAqB,OAAA;AAC9B,iEAAqE;AAA5D,mIAAA,0BAA0B,OAAA;AACnC,uEAA2E;AAAlE,yIAAA,6BAA6B,OAAA;AACtC,+EAA4E;AAAnE,0IAAA,0BAA0B,OAAA;AACnC,uFAAoF;AAA3E,kJAAA,8BAA8B,OAAA;AACvC,qEAAmE;AAA1D,iIAAA,sBAAsB,OAAA;AAC/B,qFAAiF;AAAxE,+IAAA,4BAA4B,OAAA;AACrC,+EAA2E;AAAlE,yIAAA,yBAAyB,OAAA;AAClC,2FAAwF;AAA/E,sJAAA,gCAAgC,OAAA;AACzC,mEAAiE;AAAxD,+HAAA,qBAAqB,OAAA;AAC9B,yDAAuD;AAA9C,qHAAA,gBAAgB,OAAA;AACzB,iDAA8D;AAArD,4HAAA,2BAA2B,OAAA;AACpC,2EAAwE;AAA/D,sIAAA,wBAAwB,OAAA;AACjC,iDAA0D;AAAjD,sIAAA,OAAO,OAAgB","sourcesContent":["export { CodeValidator, ExecutorResult } from './code-validator';\n\nexport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nexport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nexport { MaxMethodLinesValidator, runModifiedMethods } from './validate-modified-methods';\nexport { runNewMethods } from './validate-new-methods';\nexport { MaxFileLinesValidator } from './validate-modified-files';\nexport { RequireReturnTypeValidator } from './validate-return-types';\nexport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nexport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nexport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nexport { NoDestructureValidator } from './validate-no-destructure';\nexport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nexport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nexport { EnforceControllerNamingValidator } from './validate-enforce-controller-naming';\nexport { FrameworkTagValidator } from './validate-framework-tag';\nexport { RoleTagValidator } from './validate-role-tag';\nexport { PrismaValidateDtosValidator } from './validate-dtos';\nexport { PrismaConverterValidator } from './validate-prisma-converters';\nexport { default as validateCode } from './validate-code';\n"]}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared "every modified project must carry a `<prefix>` nx tag" rule engine.
|
|
3
|
+
*
|
|
4
|
+
* Both `framework-tag` (prefix `framework:`) and `role-tag` (prefix `role:`) are
|
|
5
|
+
* the same rule with different prefix/known-values/messages, so the
|
|
6
|
+
* project-walking + diff-scoping + mode logic lives here once and each concrete
|
|
7
|
+
* rule supplies only a {@link TagRuleSpec}.
|
|
8
|
+
*/
|
|
9
|
+
import { ProjectMode } from '@webpieces/rules-config';
|
|
10
|
+
import { ExecutorResult } from './code-validator';
|
|
11
|
+
/** A project (identified by its project.json) missing the required tag. */
|
|
12
|
+
export declare class MissingTagProject {
|
|
13
|
+
readonly name: string;
|
|
14
|
+
readonly projectJsonPath: string;
|
|
15
|
+
constructor(name: string, projectJsonPath: string);
|
|
16
|
+
}
|
|
17
|
+
/** The knobs and messages that make a concrete tag rule out of the shared engine. */
|
|
18
|
+
export declare class TagRuleSpec {
|
|
19
|
+
/** Tag prefix, e.g. `framework:` or `role:`. */
|
|
20
|
+
readonly tagPrefix: string;
|
|
21
|
+
/** Rule id, e.g. `framework-tag` / `role-tag` (used in skip/validating logs). */
|
|
22
|
+
readonly ruleName: string;
|
|
23
|
+
/** Human label for the "Validating X" banner, e.g. `Framework Tag`. */
|
|
24
|
+
readonly ruleLabel: string;
|
|
25
|
+
/** Default allowed values when config.knownTypes is empty. */
|
|
26
|
+
readonly defaultKnownTypes: string[];
|
|
27
|
+
/** Print the violation report for the missing-tag projects. */
|
|
28
|
+
readonly report: (untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode) => void;
|
|
29
|
+
constructor(
|
|
30
|
+
/** Tag prefix, e.g. `framework:` or `role:`. */
|
|
31
|
+
tagPrefix: string,
|
|
32
|
+
/** Rule id, e.g. `framework-tag` / `role-tag` (used in skip/validating logs). */
|
|
33
|
+
ruleName: string,
|
|
34
|
+
/** Human label for the "Validating X" banner, e.g. `Framework Tag`. */
|
|
35
|
+
ruleLabel: string,
|
|
36
|
+
/** Default allowed values when config.knownTypes is empty. */
|
|
37
|
+
defaultKnownTypes: string[],
|
|
38
|
+
/** Print the violation report for the missing-tag projects. */
|
|
39
|
+
report: (untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode) => void);
|
|
40
|
+
}
|
|
41
|
+
/** The config fields the shared engine reads (shared by every tag rule config). */
|
|
42
|
+
export interface TagRuleConfig {
|
|
43
|
+
mode?: ProjectMode;
|
|
44
|
+
knownTypes?: string[];
|
|
45
|
+
ignoreModifiedUntilEpoch?: number;
|
|
46
|
+
ignoreRuleWhileOnBranch?: string;
|
|
47
|
+
}
|
|
48
|
+
/** Every owning project of a changed file that is missing a `<tagPrefix>` tag. */
|
|
49
|
+
export declare function findProjectsMissingTag(workspaceRoot: string, changedFiles: string[], tagPrefix: string): MissingTagProject[];
|
|
50
|
+
/** Run a concrete tag rule (framework-tag / role-tag) against the git diff. */
|
|
51
|
+
export declare function runTagValidator(options: TagRuleConfig, workspaceRoot: string, spec: TagRuleSpec): Promise<ExecutorResult>;
|
package/src/tag-rule.js
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared "every modified project must carry a `<prefix>` nx tag" rule engine.
|
|
4
|
+
*
|
|
5
|
+
* Both `framework-tag` (prefix `framework:`) and `role-tag` (prefix `role:`) are
|
|
6
|
+
* the same rule with different prefix/known-values/messages, so the
|
|
7
|
+
* project-walking + diff-scoping + mode logic lives here once and each concrete
|
|
8
|
+
* rule supplies only a {@link TagRuleSpec}.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.TagRuleSpec = exports.MissingTagProject = void 0;
|
|
12
|
+
exports.findProjectsMissingTag = findProjectsMissingTag;
|
|
13
|
+
exports.runTagValidator = runTagValidator;
|
|
14
|
+
const tslib_1 = require("tslib");
|
|
15
|
+
const fs = tslib_1.__importStar(require("fs"));
|
|
16
|
+
const path = tslib_1.__importStar(require("path"));
|
|
17
|
+
const rules_config_1 = require("@webpieces/rules-config");
|
|
18
|
+
const resolve_mode_1 = require("./resolve-mode");
|
|
19
|
+
/** A project (identified by its project.json) missing the required tag. */
|
|
20
|
+
class MissingTagProject {
|
|
21
|
+
name;
|
|
22
|
+
projectJsonPath;
|
|
23
|
+
constructor(name, projectJsonPath) {
|
|
24
|
+
this.name = name;
|
|
25
|
+
this.projectJsonPath = projectJsonPath;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.MissingTagProject = MissingTagProject;
|
|
29
|
+
/** The knobs and messages that make a concrete tag rule out of the shared engine. */
|
|
30
|
+
class TagRuleSpec {
|
|
31
|
+
tagPrefix;
|
|
32
|
+
ruleName;
|
|
33
|
+
ruleLabel;
|
|
34
|
+
defaultKnownTypes;
|
|
35
|
+
report;
|
|
36
|
+
constructor(
|
|
37
|
+
/** Tag prefix, e.g. `framework:` or `role:`. */
|
|
38
|
+
tagPrefix,
|
|
39
|
+
/** Rule id, e.g. `framework-tag` / `role-tag` (used in skip/validating logs). */
|
|
40
|
+
ruleName,
|
|
41
|
+
/** Human label for the "Validating X" banner, e.g. `Framework Tag`. */
|
|
42
|
+
ruleLabel,
|
|
43
|
+
/** Default allowed values when config.knownTypes is empty. */
|
|
44
|
+
defaultKnownTypes,
|
|
45
|
+
/** Print the violation report for the missing-tag projects. */
|
|
46
|
+
report) {
|
|
47
|
+
this.tagPrefix = tagPrefix;
|
|
48
|
+
this.ruleName = ruleName;
|
|
49
|
+
this.ruleLabel = ruleLabel;
|
|
50
|
+
this.defaultKnownTypes = defaultKnownTypes;
|
|
51
|
+
this.report = report;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.TagRuleSpec = TagRuleSpec;
|
|
55
|
+
class ProjectJson {
|
|
56
|
+
name;
|
|
57
|
+
tags;
|
|
58
|
+
constructor(name, tags) {
|
|
59
|
+
this.name = name;
|
|
60
|
+
this.tags = tags;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse `name` + `tags` from a project.json. Returns an empty ProjectJson when
|
|
65
|
+
* unreadable/unparseable — config validation owns malformed-json reporting; this
|
|
66
|
+
* rule just nudges for a tag and must never crash the build.
|
|
67
|
+
*/
|
|
68
|
+
function readProjectJson(projectJsonPath, workspaceRoot) {
|
|
69
|
+
const fullPath = path.join(workspaceRoot, projectJsonPath);
|
|
70
|
+
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
71
|
+
try {
|
|
72
|
+
const parsed = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
|
|
73
|
+
const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : null;
|
|
74
|
+
const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((tag) => typeof tag === 'string') : [];
|
|
75
|
+
return new ProjectJson(name, tags);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const error = (0, rules_config_1.toError)(err);
|
|
79
|
+
void error; // swallow — malformed project.json is not this rule's concern
|
|
80
|
+
return new ProjectJson(null, []);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Walk up from a changed file to the nearest ancestor directory containing a
|
|
85
|
+
* project.json (the file's owning nx project). Returns the repo-relative
|
|
86
|
+
* project.json path, or null when the file belongs to no project.
|
|
87
|
+
*/
|
|
88
|
+
function findOwningProjectJson(changedFile, workspaceRoot) {
|
|
89
|
+
let dir = path.dirname(changedFile);
|
|
90
|
+
while (true) {
|
|
91
|
+
const candidate = path.join(dir, 'project.json');
|
|
92
|
+
if (fs.existsSync(path.join(workspaceRoot, candidate))) {
|
|
93
|
+
return candidate;
|
|
94
|
+
}
|
|
95
|
+
const parent = path.dirname(dir);
|
|
96
|
+
if (parent === dir || dir === '.' || dir === '') {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
dir = parent;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function hasTag(tags, tagPrefix) {
|
|
103
|
+
return tags.some((tag) => tag.startsWith(tagPrefix) && tag.slice(tagPrefix.length).trim().length > 0);
|
|
104
|
+
}
|
|
105
|
+
/** Every owning project of a changed file that is missing a `<tagPrefix>` tag. */
|
|
106
|
+
function findProjectsMissingTag(workspaceRoot, changedFiles, tagPrefix) {
|
|
107
|
+
const projectJsonPaths = new Set();
|
|
108
|
+
for (const file of changedFiles) {
|
|
109
|
+
const owning = findOwningProjectJson(file, workspaceRoot);
|
|
110
|
+
if (owning !== null) {
|
|
111
|
+
projectJsonPaths.add(owning);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
const missing = [];
|
|
115
|
+
for (const projectJsonPath of Array.from(projectJsonPaths).sort()) {
|
|
116
|
+
const projectJson = readProjectJson(projectJsonPath, workspaceRoot);
|
|
117
|
+
if (!hasTag(projectJson.tags, tagPrefix)) {
|
|
118
|
+
const name = projectJson.name ?? path.dirname(projectJsonPath);
|
|
119
|
+
missing.push(new MissingTagProject(name, projectJsonPath));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return missing;
|
|
123
|
+
}
|
|
124
|
+
function resolveMode(normalMode, epoch, branchPattern, ruleName) {
|
|
125
|
+
if (normalMode === 'OFF') {
|
|
126
|
+
return normalMode;
|
|
127
|
+
}
|
|
128
|
+
const skip = (0, resolve_mode_1.shouldSkipRule)(epoch, branchPattern);
|
|
129
|
+
if (skip.skip) {
|
|
130
|
+
console.log(`\n⏭️ Skipping ${ruleName} validation (${skip.reason})`);
|
|
131
|
+
console.log('');
|
|
132
|
+
return 'OFF';
|
|
133
|
+
}
|
|
134
|
+
return normalMode;
|
|
135
|
+
}
|
|
136
|
+
/** Run a concrete tag rule (framework-tag / role-tag) against the git diff. */
|
|
137
|
+
async function runTagValidator(options, workspaceRoot, spec) {
|
|
138
|
+
const mode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch, spec.ruleName);
|
|
139
|
+
const knownTypes = options.knownTypes && options.knownTypes.length > 0 ? options.knownTypes : spec.defaultKnownTypes;
|
|
140
|
+
if (mode === 'OFF') {
|
|
141
|
+
console.log(`\n⏭️ Skipping ${spec.ruleName} validation (mode: OFF)`);
|
|
142
|
+
console.log('');
|
|
143
|
+
return { success: true };
|
|
144
|
+
}
|
|
145
|
+
console.log(`\n📏 Validating ${spec.ruleLabel}\n`);
|
|
146
|
+
console.log(` Mode: ${mode}`);
|
|
147
|
+
let base = process.env['NX_BASE'];
|
|
148
|
+
const head = process.env['NX_HEAD'];
|
|
149
|
+
if (!base) {
|
|
150
|
+
base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
|
|
151
|
+
if (!base) {
|
|
152
|
+
console.log(`\n⏭️ Skipping ${spec.ruleName} validation (could not detect base branch)`);
|
|
153
|
+
console.log('');
|
|
154
|
+
return { success: true };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
console.log(` Base: ${base}`);
|
|
158
|
+
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
|
|
159
|
+
console.log('');
|
|
160
|
+
// Project-level rule: ANY changed file (not just .ts) makes its owning
|
|
161
|
+
// project require a tag, so include non-code files too.
|
|
162
|
+
const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head, { tsOnly: false });
|
|
163
|
+
if (changedFiles.length === 0) {
|
|
164
|
+
console.log('✅ No changed files in any project');
|
|
165
|
+
return { success: true };
|
|
166
|
+
}
|
|
167
|
+
const missing = findProjectsMissingTag(workspaceRoot, changedFiles, spec.tagPrefix);
|
|
168
|
+
if (missing.length === 0) {
|
|
169
|
+
console.log(`✅ Every modified project declares a ${spec.tagPrefix.replace(/:$/, '')} tag`);
|
|
170
|
+
return { success: true };
|
|
171
|
+
}
|
|
172
|
+
spec.report(missing, knownTypes, mode);
|
|
173
|
+
return { success: false };
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=tag-rule.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tag-rule.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/tag-rule.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Shared \"every modified project must carry a `<prefix>` nx tag\" rule engine.\n *\n * Both `framework-tag` (prefix `framework:`) and `role-tag` (prefix `role:`) are\n * the same rule with different prefix/known-values/messages, so the\n * project-walking + diff-scoping + mode logic lives here once and each concrete\n * rule supplies only a {@link TagRuleSpec}.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ProjectMode, detectBase, getChangedFiles, toError } from '@webpieces/rules-config';\nimport { ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\n/** A project (identified by its project.json) missing the required tag. */\nexport class MissingTagProject {\n constructor(\n public readonly name: string,\n public readonly projectJsonPath: string\n ) {}\n}\n\n/** The knobs and messages that make a concrete tag rule out of the shared engine. */\nexport class TagRuleSpec {\n constructor(\n /** Tag prefix, e.g. `framework:` or `role:`. */\n public readonly tagPrefix: string,\n /** Rule id, e.g. `framework-tag` / `role-tag` (used in skip/validating logs). */\n public readonly ruleName: string,\n /** Human label for the \"Validating X\" banner, e.g. `Framework Tag`. */\n public readonly ruleLabel: string,\n /** Default allowed values when config.knownTypes is empty. */\n public readonly defaultKnownTypes: string[],\n /** Print the violation report for the missing-tag projects. */\n public readonly report: (untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode) => void\n ) {}\n}\n\n/** The config fields the shared engine reads (shared by every tag rule config). */\nexport interface TagRuleConfig {\n mode?: ProjectMode;\n knownTypes?: string[];\n ignoreModifiedUntilEpoch?: number;\n ignoreRuleWhileOnBranch?: string;\n}\n\ntype RawProjectJson = { name?: string; tags?: string[] };\n\nclass ProjectJson {\n constructor(\n public readonly name: string | null,\n public readonly tags: string[]\n ) {}\n}\n\n/**\n * Parse `name` + `tags` from a project.json. Returns an empty ProjectJson when\n * unreadable/unparseable — config validation owns malformed-json reporting; this\n * rule just nudges for a tag and must never crash the build.\n */\nfunction readProjectJson(projectJsonPath: string, workspaceRoot: string): ProjectJson {\n const fullPath = path.join(workspaceRoot, projectJsonPath);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const parsed: RawProjectJson = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));\n const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : null;\n const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((tag: string) => typeof tag === 'string') : [];\n return new ProjectJson(name, tags);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — malformed project.json is not this rule's concern\n return new ProjectJson(null, []);\n }\n}\n\n/**\n * Walk up from a changed file to the nearest ancestor directory containing a\n * project.json (the file's owning nx project). Returns the repo-relative\n * project.json path, or null when the file belongs to no project.\n */\nfunction findOwningProjectJson(changedFile: string, workspaceRoot: string): string | null {\n let dir = path.dirname(changedFile);\n while (true) {\n const candidate = path.join(dir, 'project.json');\n if (fs.existsSync(path.join(workspaceRoot, candidate))) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir || dir === '.' || dir === '') {\n return null;\n }\n dir = parent;\n }\n}\n\nfunction hasTag(tags: string[], tagPrefix: string): boolean {\n return tags.some((tag: string) => tag.startsWith(tagPrefix) && tag.slice(tagPrefix.length).trim().length > 0);\n}\n\n/** Every owning project of a changed file that is missing a `<tagPrefix>` tag. */\nexport function findProjectsMissingTag(\n workspaceRoot: string,\n changedFiles: string[],\n tagPrefix: string\n): MissingTagProject[] {\n const projectJsonPaths = new Set<string>();\n for (const file of changedFiles) {\n const owning = findOwningProjectJson(file, workspaceRoot);\n if (owning !== null) {\n projectJsonPaths.add(owning);\n }\n }\n\n const missing: MissingTagProject[] = [];\n for (const projectJsonPath of Array.from(projectJsonPaths).sort()) {\n const projectJson = readProjectJson(projectJsonPath, workspaceRoot);\n if (!hasTag(projectJson.tags, tagPrefix)) {\n const name = projectJson.name ?? path.dirname(projectJsonPath);\n missing.push(new MissingTagProject(name, projectJsonPath));\n }\n }\n return missing;\n}\n\nfunction resolveMode(\n normalMode: ProjectMode,\n epoch: number | undefined,\n branchPattern: string | undefined,\n ruleName: string\n): ProjectMode {\n if (normalMode === 'OFF') {\n return normalMode;\n }\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping ${ruleName} validation (${skip.reason})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\n/** Run a concrete tag rule (framework-tag / role-tag) against the git diff. */\nexport async function runTagValidator(\n options: TagRuleConfig,\n workspaceRoot: string,\n spec: TagRuleSpec\n): Promise<ExecutorResult> {\n const mode: ProjectMode = resolveMode(\n options.mode ?? 'OFF',\n options.ignoreModifiedUntilEpoch,\n options.ignoreRuleWhileOnBranch,\n spec.ruleName\n );\n const knownTypes = options.knownTypes && options.knownTypes.length > 0 ? options.knownTypes : spec.defaultKnownTypes;\n\n if (mode === 'OFF') {\n console.log(`\\n⏭️ Skipping ${spec.ruleName} validation (mode: OFF)`);\n console.log('');\n return { success: true };\n }\n\n console.log(`\\n📏 Validating ${spec.ruleLabel}\\n`);\n console.log(` Mode: ${mode}`);\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log(`\\n⏭️ Skipping ${spec.ruleName} validation (could not detect base branch)`);\n console.log('');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\n // Project-level rule: ANY changed file (not just .ts) makes its owning\n // project require a tag, so include non-code files too.\n const changedFiles = getChangedFiles(workspaceRoot, base, head, { tsOnly: false });\n if (changedFiles.length === 0) {\n console.log('✅ No changed files in any project');\n return { success: true };\n }\n\n const missing = findProjectsMissingTag(workspaceRoot, changedFiles, spec.tagPrefix);\n if (missing.length === 0) {\n console.log(`✅ Every modified project declares a ${spec.tagPrefix.replace(/:$/, '')} tag`);\n return { success: true };\n }\n\n spec.report(missing, knownTypes, mode);\n return { success: false };\n}\n"]}
|
package/src/validate-code.js
CHANGED
|
@@ -18,6 +18,7 @@ const validate_no_direct_api_resolver_1 = require("./validate-no-direct-api-reso
|
|
|
18
18
|
const validate_no_symbol_di_tokens_1 = require("./validate-no-symbol-di-tokens");
|
|
19
19
|
const validate_enforce_controller_naming_1 = require("./validate-enforce-controller-naming");
|
|
20
20
|
const validate_framework_tag_1 = require("./validate-framework-tag");
|
|
21
|
+
const validate_role_tag_1 = require("./validate-role-tag");
|
|
21
22
|
const validate_match_rules_1 = require("./validate-match-rules");
|
|
22
23
|
/**
|
|
23
24
|
* Build every code validator from the typed WebpiecesRulesConfig. Each validator is
|
|
@@ -43,6 +44,7 @@ function buildValidators(config, matchRules) {
|
|
|
43
44
|
new validate_no_symbol_di_tokens_1.NoSymbolDiTokensValidator(config['no-symbol-di-tokens'] ?? new rules_config_1.NoSymbolDiTokensConfig()),
|
|
44
45
|
new validate_enforce_controller_naming_1.EnforceControllerNamingValidator(config['enforce-controller-naming'] ?? new rules_config_1.EnforceControllerNamingConfig()),
|
|
45
46
|
new validate_framework_tag_1.FrameworkTagValidator(config['framework-tag'] ?? new rules_config_1.FrameworkTagConfig()),
|
|
47
|
+
new validate_role_tag_1.RoleTagValidator(config['role-tag'] ?? new rules_config_1.RoleTagConfig()),
|
|
46
48
|
// One validator per client-authored match-rules entry (each with its own name/mode/epoch).
|
|
47
49
|
...matchRules.map((mr) => new validate_match_rules_1.MatchRulesValidator(mr)),
|
|
48
50
|
];
|
package/src/validate-code.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-code.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-code.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"validate-code.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-code.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n loadAndValidate,\n WebpiecesRulesConfig,\n BaseRuleConfig,\n MaxMethodLinesConfig,\n MaxFileLinesConfig,\n RequireReturnTypeConfig,\n NoInlineTypeLiteralsConfig,\n NoAnyUnknownConfig,\n NoImplicitAnyConfig,\n PrismaValidateDtosConfig,\n PrismaConverterConfig,\n NoDestructureConfig,\n CatchErrorPatternConfig,\n NoUnmanagedExceptionsConfig,\n AngularNoDirectApiInResolverConfig,\n NoSymbolDiTokensConfig,\n EnforceControllerNamingConfig,\n FrameworkTagConfig,\n RoleTagConfig,\n} from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { runValidators } from './rule-reporter';\nimport { MaxMethodLinesValidator } from './validate-modified-methods';\nimport { MaxFileLinesValidator } from './validate-modified-files';\nimport { RequireReturnTypeValidator } from './validate-return-types';\nimport { NoInlineTypeLiteralsValidator } from './validate-no-inline-types';\nimport { NoAnyUnknownValidator } from './validate-no-any-unknown';\nimport { NoImplicitAnyValidator } from './validate-no-implicit-any';\nimport { PrismaValidateDtosValidator } from './validate-dtos';\nimport { PrismaConverterValidator } from './validate-prisma-converters';\nimport { NoDestructureValidator } from './validate-no-destructure';\nimport { CatchErrorPatternValidator } from './validate-catch-error-pattern';\nimport { NoUnmanagedExceptionsValidator } from './validate-no-unmanaged-exceptions';\nimport { NoDirectApiResolverValidator } from './validate-no-direct-api-resolver';\nimport { NoSymbolDiTokensValidator } from './validate-no-symbol-di-tokens';\nimport { EnforceControllerNamingValidator } from './validate-enforce-controller-naming';\nimport { FrameworkTagValidator } from './validate-framework-tag';\nimport { RoleTagValidator } from './validate-role-tag';\nimport { MatchRulesValidator } from './validate-match-rules';\nimport { MatchRuleConfig } from '@webpieces/rules-config';\n\nexport { ExecutorResult } from './code-validator';\n\n/**\n * Build every code validator from the typed WebpiecesRulesConfig. Each validator is\n * constructed with its own `*Config` (the genuinely-consumed config class), so the\n * config types are exercised at compile time. Missing rule keys fall back to an empty\n * config instance — `loadAndValidate` already validates that configured rules\n * carry an explicit `mode`, so in practice every key is present.\n */\nfunction buildValidators(\n config: WebpiecesRulesConfig,\n matchRules: readonly MatchRuleConfig[],\n): CodeValidator<BaseRuleConfig>[] {\n return [\n new MaxMethodLinesValidator(config['max-method-lines'] ?? new MaxMethodLinesConfig()),\n new MaxFileLinesValidator(config['max-file-lines'] ?? new MaxFileLinesConfig()),\n new RequireReturnTypeValidator(config['require-return-type'] ?? new RequireReturnTypeConfig()),\n new NoInlineTypeLiteralsValidator(config['no-inline-type-literals'] ?? new NoInlineTypeLiteralsConfig()),\n new NoAnyUnknownValidator(config['no-any-unknown'] ?? new NoAnyUnknownConfig()),\n new NoImplicitAnyValidator(config['no-implicit-any'] ?? new NoImplicitAnyConfig()),\n new PrismaValidateDtosValidator(config['prisma-validate-dtos'] ?? new PrismaValidateDtosConfig()),\n new PrismaConverterValidator(config['prisma-converter'] ?? new PrismaConverterConfig()),\n new NoDestructureValidator(config['no-destructure'] ?? new NoDestructureConfig()),\n new CatchErrorPatternValidator(config['catch-error-pattern'] ?? new CatchErrorPatternConfig()),\n new NoUnmanagedExceptionsValidator(config['no-unmanaged-exceptions'] ?? new NoUnmanagedExceptionsConfig()),\n new NoDirectApiResolverValidator(config['angular-no-direct-api-in-resolver'] ?? new AngularNoDirectApiInResolverConfig()),\n new NoSymbolDiTokensValidator(config['no-symbol-di-tokens'] ?? new NoSymbolDiTokensConfig()),\n new EnforceControllerNamingValidator(config['enforce-controller-naming'] ?? new EnforceControllerNamingConfig()),\n new FrameworkTagValidator(config['framework-tag'] ?? new FrameworkTagConfig()),\n new RoleTagValidator(config['role-tag'] ?? new RoleTagConfig()),\n // One validator per client-authored match-rules entry (each with its own name/mode/epoch).\n ...matchRules.map((mr: MatchRuleConfig) => new MatchRulesValidator(mr)),\n ];\n}\n\n/**\n * Run all configured code validators against the workspace.\n *\n * Owns config loading: callers just pass the workspace root. Config comes from\n * webpieces.config.json (loaded via @webpieces/rules-config so ai-hooks and this\n * executor agree on every rule's enabled/mode/options). A validator runs only when\n * `shouldRun()` is true (i.e. its mode is not OFF and no branch/epoch escape hatch matches).\n */\nexport default async function runValidator(workspaceRoot: string): Promise<ExecutorResult> {\n const loaded = loadAndValidate(workspaceRoot);\n if (loaded.configPath === null) {\n console.error('\\n❌ No webpieces.config.json found at workspace root (or any ancestor).\\n');\n return { success: false };\n }\n\n console.log(`\\n📄 Loaded config: ${loaded.configPath}`);\n\n const validators = buildValidators(loaded.rulesConfig, loaded.matchRules);\n const active = validators.filter((v: CodeValidator<BaseRuleConfig>) => v.shouldRun());\n\n if (active.length === 0) {\n console.log('\\n⏭️ Skipping all code validations (all modes: OFF)\\n');\n return { success: true };\n }\n\n console.log('\\n📏 Running Code Validations\\n');\n console.log(` Active rules: ${active.map((v: CodeValidator<BaseRuleConfig>) => v.name).join(', ')}`);\n console.log('');\n\n // Per-validator isolation lives in runValidators: a bug in one validator no longer aborts the\n // rest, and a thrown RuleFailError is reported like any other failure.\n const result = await runValidators(active, workspaceRoot);\n const allSuccess = result.success;\n\n console.log(allSuccess ? '\\n✅ All code validations passed\\n' : '\\n❌ Some code validations failed\\n');\n return { success: allSuccess };\n}\n"]}
|
|
@@ -12,40 +12,26 @@
|
|
|
12
12
|
* `library-types-match-client` rule, which uses it to keep an express project
|
|
13
13
|
* from depending on an angular-only library (and vice-versa).
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* A project.json with no `framework:` tag whose source was modified:
|
|
19
|
-
* { "name": "my-lib", "tags": [] }
|
|
20
|
-
*
|
|
21
|
-
* ============================================================================
|
|
22
|
-
* ALLOWED
|
|
23
|
-
* ============================================================================
|
|
24
|
-
* { "name": "my-lib", "tags": ["framework:all"] }
|
|
15
|
+
* The project-walking + diff-scoping + mode logic is shared with the `role-tag`
|
|
16
|
+
* rule in `tag-rule.ts`; this file supplies only the framework-specific prefix,
|
|
17
|
+
* known values, and violation message.
|
|
25
18
|
*
|
|
26
19
|
* ============================================================================
|
|
27
20
|
* MODES (PROJECT-LEVEL)
|
|
28
21
|
* ============================================================================
|
|
29
22
|
* - OFF: Skip validation entirely.
|
|
30
23
|
* - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY
|
|
31
|
-
* changed file (not just .ts, and not line-scoped)
|
|
32
|
-
* check is inherently project-level. nx `affected` already
|
|
33
|
-
* narrows execution to the changed projects.
|
|
24
|
+
* changed file (not just .ts, and not line-scoped).
|
|
34
25
|
*/
|
|
35
26
|
import { FrameworkTagConfig } from '@webpieces/rules-config';
|
|
36
27
|
import { CodeValidator, ExecutorResult } from './code-validator';
|
|
28
|
+
import { MissingTagProject } from './tag-rule';
|
|
37
29
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
30
|
+
* Back-compat export used by tests: the framework-tag flavor of the shared
|
|
31
|
+
* missing-tag scan.
|
|
40
32
|
*/
|
|
41
|
-
declare
|
|
42
|
-
readonly name: string;
|
|
43
|
-
readonly projectJsonPath: string;
|
|
44
|
-
constructor(name: string, projectJsonPath: string);
|
|
45
|
-
}
|
|
46
|
-
export declare function findUntaggedProjects(workspaceRoot: string, changedFiles: string[]): UntaggedProject[];
|
|
33
|
+
export declare function findUntaggedProjects(workspaceRoot: string, changedFiles: string[]): MissingTagProject[];
|
|
47
34
|
export declare class FrameworkTagValidator extends CodeValidator<FrameworkTagConfig> {
|
|
48
35
|
constructor(config: FrameworkTagConfig);
|
|
49
36
|
run(workspaceRoot: string): Promise<ExecutorResult>;
|
|
50
37
|
}
|
|
51
|
-
export {};
|
|
@@ -13,121 +13,30 @@
|
|
|
13
13
|
* `library-types-match-client` rule, which uses it to keep an express project
|
|
14
14
|
* from depending on an angular-only library (and vice-versa).
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* A project.json with no `framework:` tag whose source was modified:
|
|
20
|
-
* { "name": "my-lib", "tags": [] }
|
|
21
|
-
*
|
|
22
|
-
* ============================================================================
|
|
23
|
-
* ALLOWED
|
|
24
|
-
* ============================================================================
|
|
25
|
-
* { "name": "my-lib", "tags": ["framework:all"] }
|
|
16
|
+
* The project-walking + diff-scoping + mode logic is shared with the `role-tag`
|
|
17
|
+
* rule in `tag-rule.ts`; this file supplies only the framework-specific prefix,
|
|
18
|
+
* known values, and violation message.
|
|
26
19
|
*
|
|
27
20
|
* ============================================================================
|
|
28
21
|
* MODES (PROJECT-LEVEL)
|
|
29
22
|
* ============================================================================
|
|
30
23
|
* - OFF: Skip validation entirely.
|
|
31
24
|
* - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY
|
|
32
|
-
* changed file (not just .ts, and not line-scoped)
|
|
33
|
-
* check is inherently project-level. nx `affected` already
|
|
34
|
-
* narrows execution to the changed projects.
|
|
25
|
+
* changed file (not just .ts, and not line-scoped).
|
|
35
26
|
*/
|
|
36
27
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
28
|
exports.FrameworkTagValidator = void 0;
|
|
38
29
|
exports.findUntaggedProjects = findUntaggedProjects;
|
|
39
|
-
const tslib_1 = require("tslib");
|
|
40
|
-
const fs = tslib_1.__importStar(require("fs"));
|
|
41
|
-
const path = tslib_1.__importStar(require("path"));
|
|
42
|
-
const rules_config_1 = require("@webpieces/rules-config");
|
|
43
30
|
const code_validator_1 = require("./code-validator");
|
|
44
|
-
const
|
|
31
|
+
const tag_rule_1 = require("./tag-rule");
|
|
45
32
|
const FRAMEWORK_TAG_PREFIX = 'framework:';
|
|
46
33
|
const DEFAULT_KNOWN_TYPES = ['angular', 'react', 'express', 'all'];
|
|
47
34
|
/**
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*/
|
|
51
|
-
class UntaggedProject {
|
|
52
|
-
name;
|
|
53
|
-
projectJsonPath;
|
|
54
|
-
constructor(name, projectJsonPath) {
|
|
55
|
-
this.name = name;
|
|
56
|
-
this.projectJsonPath = projectJsonPath;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* The parts of a project.json this rule reads.
|
|
61
|
-
*/
|
|
62
|
-
class ProjectJson {
|
|
63
|
-
name;
|
|
64
|
-
tags;
|
|
65
|
-
constructor(name, tags) {
|
|
66
|
-
this.name = name;
|
|
67
|
-
this.tags = tags;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Parse the `name` + `tags` from a project.json. Returns an empty ProjectJson
|
|
72
|
-
* (no name, no tags) when the file is unreadable/unparseable — config
|
|
73
|
-
* validation owns malformed-json reporting; this rule just nudges for a tag and
|
|
74
|
-
* must never crash the build.
|
|
35
|
+
* Back-compat export used by tests: the framework-tag flavor of the shared
|
|
36
|
+
* missing-tag scan.
|
|
75
37
|
*/
|
|
76
|
-
function readProjectJson(projectJsonPath, workspaceRoot) {
|
|
77
|
-
const fullPath = path.join(workspaceRoot, projectJsonPath);
|
|
78
|
-
// eslint-disable-next-line @webpieces/no-unmanaged-exceptions
|
|
79
|
-
try {
|
|
80
|
-
const parsed = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
|
|
81
|
-
const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : null;
|
|
82
|
-
const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((tag) => typeof tag === 'string') : [];
|
|
83
|
-
return new ProjectJson(name, tags);
|
|
84
|
-
}
|
|
85
|
-
catch (err) {
|
|
86
|
-
const error = (0, rules_config_1.toError)(err);
|
|
87
|
-
void error; // swallow — malformed project.json is not this rule's concern
|
|
88
|
-
return new ProjectJson(null, []);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* Walk up from a changed file to the nearest ancestor directory containing a
|
|
93
|
-
* project.json (the file's owning nx project). Returns the repo-relative
|
|
94
|
-
* project.json path, or null when the file belongs to no project (e.g. a
|
|
95
|
-
* workspace-root config file).
|
|
96
|
-
*/
|
|
97
|
-
function findOwningProjectJson(changedFile, workspaceRoot) {
|
|
98
|
-
let dir = path.dirname(changedFile);
|
|
99
|
-
while (true) {
|
|
100
|
-
const candidate = path.join(dir, 'project.json');
|
|
101
|
-
if (fs.existsSync(path.join(workspaceRoot, candidate))) {
|
|
102
|
-
return candidate;
|
|
103
|
-
}
|
|
104
|
-
const parent = path.dirname(dir);
|
|
105
|
-
if (parent === dir || dir === '.' || dir === '') {
|
|
106
|
-
return null;
|
|
107
|
-
}
|
|
108
|
-
dir = parent;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
function hasFrameworkTag(tags) {
|
|
112
|
-
return tags.some((tag) => tag.startsWith(FRAMEWORK_TAG_PREFIX) && tag.slice(FRAMEWORK_TAG_PREFIX.length).trim().length > 0);
|
|
113
|
-
}
|
|
114
38
|
function findUntaggedProjects(workspaceRoot, changedFiles) {
|
|
115
|
-
|
|
116
|
-
for (const file of changedFiles) {
|
|
117
|
-
const owning = findOwningProjectJson(file, workspaceRoot);
|
|
118
|
-
if (owning !== null) {
|
|
119
|
-
projectJsonPaths.add(owning);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
const untagged = [];
|
|
123
|
-
for (const projectJsonPath of Array.from(projectJsonPaths).sort()) {
|
|
124
|
-
const projectJson = readProjectJson(projectJsonPath, workspaceRoot);
|
|
125
|
-
if (!hasFrameworkTag(projectJson.tags)) {
|
|
126
|
-
const name = projectJson.name ?? path.dirname(projectJsonPath);
|
|
127
|
-
untagged.push(new UntaggedProject(name, projectJsonPath));
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
return untagged;
|
|
39
|
+
return (0, tag_rule_1.findProjectsMissingTag)(workspaceRoot, changedFiles, FRAMEWORK_TAG_PREFIX);
|
|
131
40
|
}
|
|
132
41
|
function reportViolations(untagged, knownTypes, mode) {
|
|
133
42
|
const suggestion = knownTypes.map((t) => `${FRAMEWORK_TAG_PREFIX}${t}`).join(' | ');
|
|
@@ -146,62 +55,13 @@ function reportViolations(untagged, knownTypes, mode) {
|
|
|
146
55
|
console.error(` Current mode: ${mode}`);
|
|
147
56
|
console.error('');
|
|
148
57
|
}
|
|
149
|
-
|
|
150
|
-
if (normalMode === 'OFF') {
|
|
151
|
-
return normalMode;
|
|
152
|
-
}
|
|
153
|
-
const skip = (0, resolve_mode_1.shouldSkipRule)(epoch, branchPattern);
|
|
154
|
-
if (skip.skip) {
|
|
155
|
-
console.log(`\n⏭️ Skipping framework-tag validation (${skip.reason})`);
|
|
156
|
-
console.log('');
|
|
157
|
-
return 'OFF';
|
|
158
|
-
}
|
|
159
|
-
return normalMode;
|
|
160
|
-
}
|
|
161
|
-
async function runValidatorImpl(options, workspaceRoot) {
|
|
162
|
-
const mode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch);
|
|
163
|
-
const knownTypes = options.knownTypes && options.knownTypes.length > 0 ? options.knownTypes : DEFAULT_KNOWN_TYPES;
|
|
164
|
-
if (mode === 'OFF') {
|
|
165
|
-
console.log('\n⏭️ Skipping framework-tag validation (mode: OFF)');
|
|
166
|
-
console.log('');
|
|
167
|
-
return { success: true };
|
|
168
|
-
}
|
|
169
|
-
console.log('\n📏 Validating Framework Tag\n');
|
|
170
|
-
console.log(` Mode: ${mode}`);
|
|
171
|
-
let base = process.env['NX_BASE'];
|
|
172
|
-
const head = process.env['NX_HEAD'];
|
|
173
|
-
if (!base) {
|
|
174
|
-
base = (0, rules_config_1.detectBase)(workspaceRoot) ?? undefined;
|
|
175
|
-
if (!base) {
|
|
176
|
-
console.log('\n⏭️ Skipping framework-tag validation (could not detect base branch)');
|
|
177
|
-
console.log('');
|
|
178
|
-
return { success: true };
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
console.log(` Base: ${base}`);
|
|
182
|
-
console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);
|
|
183
|
-
console.log('');
|
|
184
|
-
// Project-level rule: ANY changed file (not just .ts) makes its owning
|
|
185
|
-
// project require a tag, so include non-code files too.
|
|
186
|
-
const changedFiles = (0, rules_config_1.getChangedFiles)(workspaceRoot, base, head, { tsOnly: false });
|
|
187
|
-
if (changedFiles.length === 0) {
|
|
188
|
-
console.log('✅ No changed files in any project');
|
|
189
|
-
return { success: true };
|
|
190
|
-
}
|
|
191
|
-
const untagged = findUntaggedProjects(workspaceRoot, changedFiles);
|
|
192
|
-
if (untagged.length === 0) {
|
|
193
|
-
console.log('✅ Every modified project declares a framework tag');
|
|
194
|
-
return { success: true };
|
|
195
|
-
}
|
|
196
|
-
reportViolations(untagged, knownTypes, mode);
|
|
197
|
-
return { success: false };
|
|
198
|
-
}
|
|
58
|
+
const FRAMEWORK_TAG_SPEC = new tag_rule_1.TagRuleSpec(FRAMEWORK_TAG_PREFIX, 'framework-tag', 'Framework Tag', DEFAULT_KNOWN_TYPES, reportViolations);
|
|
199
59
|
class FrameworkTagValidator extends code_validator_1.CodeValidator {
|
|
200
60
|
constructor(config) {
|
|
201
61
|
super(config, 'framework-tag');
|
|
202
62
|
}
|
|
203
63
|
async run(workspaceRoot) {
|
|
204
|
-
return
|
|
64
|
+
return (0, tag_rule_1.runTagValidator)(this.config, workspaceRoot, FRAMEWORK_TAG_SPEC);
|
|
205
65
|
}
|
|
206
66
|
}
|
|
207
67
|
exports.FrameworkTagValidator = FrameworkTagValidator;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validate-framework-tag.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-framework-tag.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Validate Framework Tag Executor\n *\n * Every project that a changed source file belongs to must carry a\n * `framework:<value>` nx tag in its project.json. That tag is the project's\n * \"libType\" — which client side it targets:\n *\n * framework:angular | framework:react | framework:express | framework:all\n *\n * (\"all\" = a library usable by any side.) It is the source of truth for the\n * `framework` field written into architecture/dependencies.json and for the\n * `library-types-match-client` rule, which uses it to keep an express project\n * from depending on an angular-only library (and vice-versa).\n *\n * ============================================================================\n * VIOLATION (BAD)\n * ============================================================================\n * A project.json with no `framework:` tag whose source was modified:\n * { \"name\": \"my-lib\", \"tags\": [] }\n *\n * ============================================================================\n * ALLOWED\n * ============================================================================\n * { \"name\": \"my-lib\", \"tags\": [\"framework:all\"] }\n *\n * ============================================================================\n * MODES (PROJECT-LEVEL)\n * ============================================================================\n * - OFF: Skip validation entirely.\n * - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY\n * changed file (not just .ts, and not line-scoped) — the\n * check is inherently project-level. nx `affected` already\n * narrows execution to the changed projects.\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport { ProjectMode, FrameworkTagConfig, detectBase, getChangedFiles, toError } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { shouldSkipRule } from './resolve-mode';\n\nconst FRAMEWORK_TAG_PREFIX = 'framework:';\nconst DEFAULT_KNOWN_TYPES = ['angular', 'react', 'express', 'all'];\n\n/**\n * A project (identified by its project.json) that owns at least one changed\n * file but is missing a `framework:` tag.\n */\nclass UntaggedProject {\n constructor(\n public readonly name: string,\n public readonly projectJsonPath: string\n ) {}\n}\n\n/**\n * Raw shape parsed out of a project.json (only the fields this rule reads).\n */\ntype RawProjectJson = { name?: string; tags?: string[] };\n\n/**\n * The parts of a project.json this rule reads.\n */\nclass ProjectJson {\n constructor(\n public readonly name: string | null,\n public readonly tags: string[]\n ) {}\n}\n\n/**\n * Parse the `name` + `tags` from a project.json. Returns an empty ProjectJson\n * (no name, no tags) when the file is unreadable/unparseable — config\n * validation owns malformed-json reporting; this rule just nudges for a tag and\n * must never crash the build.\n */\nfunction readProjectJson(projectJsonPath: string, workspaceRoot: string): ProjectJson {\n const fullPath = path.join(workspaceRoot, projectJsonPath);\n // eslint-disable-next-line @webpieces/no-unmanaged-exceptions\n try {\n const parsed: RawProjectJson = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));\n const name = typeof parsed.name === 'string' && parsed.name.length > 0 ? parsed.name : null;\n const tags = Array.isArray(parsed.tags) ? parsed.tags.filter((tag: string) => typeof tag === 'string') : [];\n return new ProjectJson(name, tags);\n } catch (err: unknown) {\n const error = toError(err);\n void error; // swallow — malformed project.json is not this rule's concern\n return new ProjectJson(null, []);\n }\n}\n\n/**\n * Walk up from a changed file to the nearest ancestor directory containing a\n * project.json (the file's owning nx project). Returns the repo-relative\n * project.json path, or null when the file belongs to no project (e.g. a\n * workspace-root config file).\n */\nfunction findOwningProjectJson(changedFile: string, workspaceRoot: string): string | null {\n let dir = path.dirname(changedFile);\n while (true) {\n const candidate = path.join(dir, 'project.json');\n if (fs.existsSync(path.join(workspaceRoot, candidate))) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir || dir === '.' || dir === '') {\n return null;\n }\n dir = parent;\n }\n}\n\nfunction hasFrameworkTag(tags: string[]): boolean {\n return tags.some((tag: string) => tag.startsWith(FRAMEWORK_TAG_PREFIX) && tag.slice(FRAMEWORK_TAG_PREFIX.length).trim().length > 0);\n}\n\nexport function findUntaggedProjects(workspaceRoot: string, changedFiles: string[]): UntaggedProject[] {\n const projectJsonPaths = new Set<string>();\n for (const file of changedFiles) {\n const owning = findOwningProjectJson(file, workspaceRoot);\n if (owning !== null) {\n projectJsonPaths.add(owning);\n }\n }\n\n const untagged: UntaggedProject[] = [];\n for (const projectJsonPath of Array.from(projectJsonPaths).sort()) {\n const projectJson = readProjectJson(projectJsonPath, workspaceRoot);\n if (!hasFrameworkTag(projectJson.tags)) {\n const name = projectJson.name ?? path.dirname(projectJsonPath);\n untagged.push(new UntaggedProject(name, projectJsonPath));\n }\n }\n return untagged;\n}\n\nfunction reportViolations(untagged: UntaggedProject[], knownTypes: string[], mode: ProjectMode): void {\n const suggestion = knownTypes.map((t: string) => `${FRAMEWORK_TAG_PREFIX}${t}`).join(' | ');\n console.error('');\n console.error('❌ Every modified project must declare which client side it targets (a framework tag)!');\n console.error('');\n console.error(` Add ONE of these to the project's project.json \"tags\" array: ${suggestion}`);\n console.error(' (\"all\" = a library usable by any side — angular, react and express can all import it.)');\n console.error(' This libType drives architecture/dependencies.json and the library-types-match-client rule.');\n console.error('');\n\n for (const project of untagged) {\n console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);\n console.error(` add e.g. \"tags\": [\"${FRAMEWORK_TAG_PREFIX}all\"]`);\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nfunction resolveMode(normalMode: ProjectMode, epoch: number | undefined, branchPattern: string | undefined): ProjectMode {\n if (normalMode === 'OFF') {\n return normalMode;\n }\n const skip = shouldSkipRule(epoch, branchPattern);\n if (skip.skip) {\n console.log(`\\n⏭️ Skipping framework-tag validation (${skip.reason})`);\n console.log('');\n return 'OFF';\n }\n return normalMode;\n}\n\nasync function runValidatorImpl(options: FrameworkTagConfig, workspaceRoot: string): Promise<ExecutorResult> {\n const mode: ProjectMode = resolveMode(options.mode ?? 'OFF', options.ignoreModifiedUntilEpoch, options.ignoreRuleWhileOnBranch);\n const knownTypes = options.knownTypes && options.knownTypes.length > 0 ? options.knownTypes : DEFAULT_KNOWN_TYPES;\n\n if (mode === 'OFF') {\n console.log('\\n⏭️ Skipping framework-tag validation (mode: OFF)');\n console.log('');\n return { success: true };\n }\n\n console.log('\\n📏 Validating Framework Tag\\n');\n console.log(` Mode: ${mode}`);\n\n let base = process.env['NX_BASE'];\n const head = process.env['NX_HEAD'];\n\n if (!base) {\n base = detectBase(workspaceRoot) ?? undefined;\n if (!base) {\n console.log('\\n⏭️ Skipping framework-tag validation (could not detect base branch)');\n console.log('');\n return { success: true };\n }\n }\n\n console.log(` Base: ${base}`);\n console.log(` Head: ${head ?? 'working tree (includes uncommitted changes)'}`);\n console.log('');\n\n // Project-level rule: ANY changed file (not just .ts) makes its owning\n // project require a tag, so include non-code files too.\n const changedFiles = getChangedFiles(workspaceRoot, base, head, { tsOnly: false });\n if (changedFiles.length === 0) {\n console.log('✅ No changed files in any project');\n return { success: true };\n }\n\n const untagged = findUntaggedProjects(workspaceRoot, changedFiles);\n if (untagged.length === 0) {\n console.log('✅ Every modified project declares a framework tag');\n return { success: true };\n }\n\n reportViolations(untagged, knownTypes, mode);\n return { success: false };\n}\n\nexport class FrameworkTagValidator extends CodeValidator<FrameworkTagConfig> {\n constructor(config: FrameworkTagConfig) {\n super(config, 'framework-tag');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runValidatorImpl(this.config, workspaceRoot);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"validate-framework-tag.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-framework-tag.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;;;AAaH,oDAEC;AAZD,qDAAiE;AACjE,yCAAqG;AAErG,MAAM,oBAAoB,GAAG,YAAY,CAAC;AAC1C,MAAM,mBAAmB,GAAG,CAAC,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;AAEnE;;;GAGG;AACH,SAAgB,oBAAoB,CAAC,aAAqB,EAAE,YAAsB;IAC9E,OAAO,IAAA,iCAAsB,EAAC,aAAa,EAAE,YAAY,EAAE,oBAAoB,CAAC,CAAC;AACrF,CAAC;AAED,SAAS,gBAAgB,CAAC,QAA6B,EAAE,UAAoB,EAAE,IAAiB;IAC5F,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,oBAAoB,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC5F,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,uFAAuF,CAAC,CAAC;IACvG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,mEAAmE,UAAU,EAAE,CAAC,CAAC;IAC/F,OAAO,CAAC,KAAK,CAAC,2FAA2F,CAAC,CAAC;IAC3G,OAAO,CAAC,KAAK,CAAC,gGAAgG,CAAC,CAAC;IAChH,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAElB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,IAAI,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QAClE,OAAO,CAAC,KAAK,CAAC,2BAA2B,oBAAoB,OAAO,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,kBAAkB,GAAG,IAAI,sBAAW,CACtC,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,gBAAgB,CACnB,CAAC;AAEF,MAAa,qBAAsB,SAAQ,8BAAiC;IACxE,YAAY,MAA0B;QAClC,KAAK,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,aAAqB;QAC3B,OAAO,IAAA,0BAAe,EAAC,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,kBAAkB,CAAC,CAAC;IAC3E,CAAC;CACJ;AARD,sDAQC","sourcesContent":["/**\n * Validate Framework Tag Executor\n *\n * Every project that a changed source file belongs to must carry a\n * `framework:<value>` nx tag in its project.json. That tag is the project's\n * \"libType\" — which client side it targets:\n *\n * framework:angular | framework:react | framework:express | framework:all\n *\n * (\"all\" = a library usable by any side.) It is the source of truth for the\n * `framework` field written into architecture/dependencies.json and for the\n * `library-types-match-client` rule, which uses it to keep an express project\n * from depending on an angular-only library (and vice-versa).\n *\n * The project-walking + diff-scoping + mode logic is shared with the `role-tag`\n * rule in `tag-rule.ts`; this file supplies only the framework-specific prefix,\n * known values, and violation message.\n *\n * ============================================================================\n * MODES (PROJECT-LEVEL)\n * ============================================================================\n * - OFF: Skip validation entirely.\n * - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY\n * changed file (not just .ts, and not line-scoped).\n */\n\nimport { ProjectMode, FrameworkTagConfig } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { MissingTagProject, TagRuleSpec, findProjectsMissingTag, runTagValidator } from './tag-rule';\n\nconst FRAMEWORK_TAG_PREFIX = 'framework:';\nconst DEFAULT_KNOWN_TYPES = ['angular', 'react', 'express', 'all'];\n\n/**\n * Back-compat export used by tests: the framework-tag flavor of the shared\n * missing-tag scan.\n */\nexport function findUntaggedProjects(workspaceRoot: string, changedFiles: string[]): MissingTagProject[] {\n return findProjectsMissingTag(workspaceRoot, changedFiles, FRAMEWORK_TAG_PREFIX);\n}\n\nfunction reportViolations(untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode): void {\n const suggestion = knownTypes.map((t: string) => `${FRAMEWORK_TAG_PREFIX}${t}`).join(' | ');\n console.error('');\n console.error('❌ Every modified project must declare which client side it targets (a framework tag)!');\n console.error('');\n console.error(` Add ONE of these to the project's project.json \"tags\" array: ${suggestion}`);\n console.error(' (\"all\" = a library usable by any side — angular, react and express can all import it.)');\n console.error(' This libType drives architecture/dependencies.json and the library-types-match-client rule.');\n console.error('');\n\n for (const project of untagged) {\n console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);\n console.error(` add e.g. \"tags\": [\"${FRAMEWORK_TAG_PREFIX}all\"]`);\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nconst FRAMEWORK_TAG_SPEC = new TagRuleSpec(\n FRAMEWORK_TAG_PREFIX,\n 'framework-tag',\n 'Framework Tag',\n DEFAULT_KNOWN_TYPES,\n reportViolations\n);\n\nexport class FrameworkTagValidator extends CodeValidator<FrameworkTagConfig> {\n constructor(config: FrameworkTagConfig) {\n super(config, 'framework-tag');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runTagValidator(this.config, workspaceRoot, FRAMEWORK_TAG_SPEC);\n }\n}\n"]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate Role Tag Executor
|
|
3
|
+
*
|
|
4
|
+
* Every project that a changed source file belongs to must carry a
|
|
5
|
+
* `role:<value>` nx tag in its project.json. That tag is the project's ROLE
|
|
6
|
+
* (orthogonal to its `framework` libType):
|
|
7
|
+
*
|
|
8
|
+
* role:server | role:designed-lib | role:lib | role:client
|
|
9
|
+
*
|
|
10
|
+
* It is the source of truth for the `role` field written into
|
|
11
|
+
* architecture/dependencies.json, for the `role-dependency` edge rule (apps are
|
|
12
|
+
* never depended upon), and for DI-design generation (server → @Controller,
|
|
13
|
+
* designed-lib → @ApiImplementation, lib → none, client → angular design).
|
|
14
|
+
*
|
|
15
|
+
* The project-walking + diff-scoping + mode logic is shared with the
|
|
16
|
+
* `framework-tag` rule in `tag-rule.ts`; this file supplies only the
|
|
17
|
+
* role-specific prefix, known values, and violation message.
|
|
18
|
+
*/
|
|
19
|
+
import { RoleTagConfig } from '@webpieces/rules-config';
|
|
20
|
+
import { CodeValidator, ExecutorResult } from './code-validator';
|
|
21
|
+
import { MissingTagProject } from './tag-rule';
|
|
22
|
+
/** The role-tag flavor of the shared missing-tag scan (exported for tests). */
|
|
23
|
+
export declare function findRoleUntaggedProjects(workspaceRoot: string, changedFiles: string[]): MissingTagProject[];
|
|
24
|
+
export declare class RoleTagValidator extends CodeValidator<RoleTagConfig> {
|
|
25
|
+
constructor(config: RoleTagConfig);
|
|
26
|
+
run(workspaceRoot: string): Promise<ExecutorResult>;
|
|
27
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Validate Role Tag Executor
|
|
4
|
+
*
|
|
5
|
+
* Every project that a changed source file belongs to must carry a
|
|
6
|
+
* `role:<value>` nx tag in its project.json. That tag is the project's ROLE
|
|
7
|
+
* (orthogonal to its `framework` libType):
|
|
8
|
+
*
|
|
9
|
+
* role:server | role:designed-lib | role:lib | role:client
|
|
10
|
+
*
|
|
11
|
+
* It is the source of truth for the `role` field written into
|
|
12
|
+
* architecture/dependencies.json, for the `role-dependency` edge rule (apps are
|
|
13
|
+
* never depended upon), and for DI-design generation (server → @Controller,
|
|
14
|
+
* designed-lib → @ApiImplementation, lib → none, client → angular design).
|
|
15
|
+
*
|
|
16
|
+
* The project-walking + diff-scoping + mode logic is shared with the
|
|
17
|
+
* `framework-tag` rule in `tag-rule.ts`; this file supplies only the
|
|
18
|
+
* role-specific prefix, known values, and violation message.
|
|
19
|
+
*/
|
|
20
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
21
|
+
exports.RoleTagValidator = void 0;
|
|
22
|
+
exports.findRoleUntaggedProjects = findRoleUntaggedProjects;
|
|
23
|
+
const code_validator_1 = require("./code-validator");
|
|
24
|
+
const tag_rule_1 = require("./tag-rule");
|
|
25
|
+
const ROLE_TAG_PREFIX = 'role:';
|
|
26
|
+
const DEFAULT_KNOWN_TYPES = ['server', 'designed-lib', 'lib', 'client'];
|
|
27
|
+
/** The role-tag flavor of the shared missing-tag scan (exported for tests). */
|
|
28
|
+
function findRoleUntaggedProjects(workspaceRoot, changedFiles) {
|
|
29
|
+
return (0, tag_rule_1.findProjectsMissingTag)(workspaceRoot, changedFiles, ROLE_TAG_PREFIX);
|
|
30
|
+
}
|
|
31
|
+
function reportViolations(untagged, knownTypes, mode) {
|
|
32
|
+
const suggestion = knownTypes.map((t) => `${ROLE_TAG_PREFIX}${t}`).join(' | ');
|
|
33
|
+
console.error('');
|
|
34
|
+
console.error('❌ Every modified project must declare its role (a role tag)!');
|
|
35
|
+
console.error('');
|
|
36
|
+
console.error(` Add ONE of these to the project's project.json "tags" array: ${suggestion}`);
|
|
37
|
+
console.error(' (server = runnable app · designed-lib = library with an @ApiImplementation DI design ·');
|
|
38
|
+
console.error(' lib = plain library, no design · client = client app e.g. angular.)');
|
|
39
|
+
console.error(' This role drives DI-design generation, the arch graph, and the role-dependency rule.');
|
|
40
|
+
console.error('');
|
|
41
|
+
for (const project of untagged) {
|
|
42
|
+
console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);
|
|
43
|
+
console.error(` add e.g. "tags": ["${ROLE_TAG_PREFIX}lib"]`);
|
|
44
|
+
}
|
|
45
|
+
console.error('');
|
|
46
|
+
console.error(` Current mode: ${mode}`);
|
|
47
|
+
console.error('');
|
|
48
|
+
}
|
|
49
|
+
const ROLE_TAG_SPEC = new tag_rule_1.TagRuleSpec(ROLE_TAG_PREFIX, 'role-tag', 'Role Tag', DEFAULT_KNOWN_TYPES, reportViolations);
|
|
50
|
+
class RoleTagValidator extends code_validator_1.CodeValidator {
|
|
51
|
+
constructor(config) {
|
|
52
|
+
super(config, 'role-tag');
|
|
53
|
+
}
|
|
54
|
+
async run(workspaceRoot) {
|
|
55
|
+
return (0, tag_rule_1.runTagValidator)(this.config, workspaceRoot, ROLE_TAG_SPEC);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.RoleTagValidator = RoleTagValidator;
|
|
59
|
+
//# sourceMappingURL=validate-role-tag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate-role-tag.js","sourceRoot":"","sources":["../../../../../packages/tooling/code-rules/src/validate-role-tag.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;;;;GAiBG;;;AAUH,4DAEC;AATD,qDAAiE;AACjE,yCAAqG;AAErG,MAAM,eAAe,GAAG,OAAO,CAAC;AAChC,MAAM,mBAAmB,GAAG,CAAC,QAAQ,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC;AAExE,+EAA+E;AAC/E,SAAgB,wBAAwB,CAAC,aAAqB,EAAE,YAAsB;IAClF,OAAO,IAAA,iCAAsB,EAAC,aAAa,EAAE,YAAY,EAAE,eAAe,CAAC,CAAC;AAChF,CAAC;AAED,SAAS,gBAAgB,CAAC,QAA6B,EAAE,UAAoB,EAAE,IAAiB;IAC5F,MAAM,UAAU,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAS,EAAE,EAAE,CAAC,GAAG,eAAe,GAAG,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IACvF,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,8DAA8D,CAAC,CAAC;IAC9E,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,mEAAmE,UAAU,EAAE,CAAC,CAAC;IAC/F,OAAO,CAAC,KAAK,CAAC,2FAA2F,CAAC,CAAC;IAC3G,OAAO,CAAC,KAAK,CAAC,yEAAyE,CAAC,CAAC;IACzF,OAAO,CAAC,KAAK,CAAC,yFAAyF,CAAC,CAAC;IACzG,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAElB,KAAK,MAAM,OAAO,IAAI,QAAQ,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,OAAO,OAAO,CAAC,IAAI,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC,CAAC;QAClE,OAAO,CAAC,KAAK,CAAC,2BAA2B,eAAe,OAAO,CAAC,CAAC;IACrE,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClB,OAAO,CAAC,KAAK,CAAC,oBAAoB,IAAI,EAAE,CAAC,CAAC;IAC1C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AACtB,CAAC;AAED,MAAM,aAAa,GAAG,IAAI,sBAAW,CAAC,eAAe,EAAE,UAAU,EAAE,UAAU,EAAE,mBAAmB,EAAE,gBAAgB,CAAC,CAAC;AAEtH,MAAa,gBAAiB,SAAQ,8BAA4B;IAC9D,YAAY,MAAqB;QAC7B,KAAK,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,aAAqB;QAC3B,OAAO,IAAA,0BAAe,EAAC,IAAI,CAAC,MAAM,EAAE,aAAa,EAAE,aAAa,CAAC,CAAC;IACtE,CAAC;CACJ;AARD,4CAQC","sourcesContent":["/**\n * Validate Role Tag Executor\n *\n * Every project that a changed source file belongs to must carry a\n * `role:<value>` nx tag in its project.json. That tag is the project's ROLE\n * (orthogonal to its `framework` libType):\n *\n * role:server | role:designed-lib | role:lib | role:client\n *\n * It is the source of truth for the `role` field written into\n * architecture/dependencies.json, for the `role-dependency` edge rule (apps are\n * never depended upon), and for DI-design generation (server → @Controller,\n * designed-lib → @ApiImplementation, lib → none, client → angular design).\n *\n * The project-walking + diff-scoping + mode logic is shared with the\n * `framework-tag` rule in `tag-rule.ts`; this file supplies only the\n * role-specific prefix, known values, and violation message.\n */\n\nimport { ProjectMode, RoleTagConfig } from '@webpieces/rules-config';\nimport { CodeValidator, ExecutorResult } from './code-validator';\nimport { MissingTagProject, TagRuleSpec, findProjectsMissingTag, runTagValidator } from './tag-rule';\n\nconst ROLE_TAG_PREFIX = 'role:';\nconst DEFAULT_KNOWN_TYPES = ['server', 'designed-lib', 'lib', 'client'];\n\n/** The role-tag flavor of the shared missing-tag scan (exported for tests). */\nexport function findRoleUntaggedProjects(workspaceRoot: string, changedFiles: string[]): MissingTagProject[] {\n return findProjectsMissingTag(workspaceRoot, changedFiles, ROLE_TAG_PREFIX);\n}\n\nfunction reportViolations(untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode): void {\n const suggestion = knownTypes.map((t: string) => `${ROLE_TAG_PREFIX}${t}`).join(' | ');\n console.error('');\n console.error('❌ Every modified project must declare its role (a role tag)!');\n console.error('');\n console.error(` Add ONE of these to the project's project.json \"tags\" array: ${suggestion}`);\n console.error(' (server = runnable app · designed-lib = library with an @ApiImplementation DI design ·');\n console.error(' lib = plain library, no design · client = client app e.g. angular.)');\n console.error(' This role drives DI-design generation, the arch graph, and the role-dependency rule.');\n console.error('');\n\n for (const project of untagged) {\n console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);\n console.error(` add e.g. \"tags\": [\"${ROLE_TAG_PREFIX}lib\"]`);\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nconst ROLE_TAG_SPEC = new TagRuleSpec(ROLE_TAG_PREFIX, 'role-tag', 'Role Tag', DEFAULT_KNOWN_TYPES, reportViolations);\n\nexport class RoleTagValidator extends CodeValidator<RoleTagConfig> {\n constructor(config: RoleTagConfig) {\n super(config, 'role-tag');\n }\n\n async run(workspaceRoot: string): Promise<ExecutorResult> {\n return runTagValidator(this.config, workspaceRoot, ROLE_TAG_SPEC);\n }\n}\n"]}
|