@webpieces/code-rules 0.3.261 → 0.3.263

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webpieces/code-rules",
3
- "version": "0.3.261",
3
+ "version": "0.3.263",
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.261"
23
+ "@webpieces/rules-config": "0.3.263"
24
24
  },
25
25
  "publishConfig": {
26
26
  "access": "public"
package/src/tag-rule.d.ts CHANGED
@@ -14,6 +14,19 @@ export declare class MissingTagProject {
14
14
  readonly projectJsonPath: string;
15
15
  constructor(name: string, projectJsonPath: string);
16
16
  }
17
+ /**
18
+ * A project carrying one or more `<prefix>` tag VALUES outside the known set
19
+ * (e.g. `framework:all`). `role-tag` never uses this — only rules that opt into
20
+ * value validation via {@link TagRuleSpec.validateValues}.
21
+ */
22
+ export declare class InvalidTagProject {
23
+ readonly name: string;
24
+ readonly projectJsonPath: string;
25
+ readonly badValues: string[];
26
+ constructor(name: string, projectJsonPath: string, badValues: string[]);
27
+ }
28
+ /** Print the invalid-value report for projects carrying out-of-set tag values. */
29
+ export type ReportInvalidValues = (invalid: InvalidTagProject[], knownTypes: string[], mode: ProjectMode) => void;
17
30
  /** The knobs and messages that make a concrete tag rule out of the shared engine. */
18
31
  export declare class TagRuleSpec {
19
32
  /** Tag prefix, e.g. `framework:` or `role:`. */
@@ -26,6 +39,14 @@ export declare class TagRuleSpec {
26
39
  readonly defaultKnownTypes: string[];
27
40
  /** Print the violation report for the missing-tag projects. */
28
41
  readonly report: (untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode) => void;
42
+ /**
43
+ * When true, ALSO validate that every `<prefix>` tag value is in the
44
+ * known set (framework-tag opts in to reject `framework:all` and typos;
45
+ * role-tag leaves this false so its single-value behavior is unchanged).
46
+ */
47
+ readonly validateValues: boolean;
48
+ /** Report for out-of-set values; required when {@link validateValues} is true. */
49
+ readonly reportInvalidValues: ReportInvalidValues | null;
29
50
  constructor(
30
51
  /** Tag prefix, e.g. `framework:` or `role:`. */
31
52
  tagPrefix: string,
@@ -36,7 +57,15 @@ export declare class TagRuleSpec {
36
57
  /** Default allowed values when config.knownTypes is empty. */
37
58
  defaultKnownTypes: string[],
38
59
  /** Print the violation report for the missing-tag projects. */
39
- report: (untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode) => void);
60
+ report: (untagged: MissingTagProject[], knownTypes: string[], mode: ProjectMode) => void,
61
+ /**
62
+ * When true, ALSO validate that every `<prefix>` tag value is in the
63
+ * known set (framework-tag opts in to reject `framework:all` and typos;
64
+ * role-tag leaves this false so its single-value behavior is unchanged).
65
+ */
66
+ validateValues?: boolean,
67
+ /** Report for out-of-set values; required when {@link validateValues} is true. */
68
+ reportInvalidValues?: ReportInvalidValues | null);
40
69
  }
41
70
  /** The config fields the shared engine reads (shared by every tag rule config). */
42
71
  export interface TagRuleConfig {
@@ -47,5 +76,13 @@ export interface TagRuleConfig {
47
76
  }
48
77
  /** Every owning project of a changed file that is missing a `<tagPrefix>` tag. */
49
78
  export declare function findProjectsMissingTag(workspaceRoot: string, changedFiles: string[], tagPrefix: string): MissingTagProject[];
79
+ /**
80
+ * Every owning project of a changed file that carries one or more `<tagPrefix>`
81
+ * tag VALUES outside `knownTypes`. Multiple values per project are allowed (an
82
+ * env set) — this only flags values not in the known set (e.g. `framework:all`
83
+ * or a typo). Projects with no `<tagPrefix>` tag are skipped here (the
84
+ * missing-tag scan owns that case).
85
+ */
86
+ export declare function findProjectsWithInvalidTagValues(workspaceRoot: string, changedFiles: string[], tagPrefix: string, knownTypes: string[]): InvalidTagProject[];
50
87
  /** Run a concrete tag rule (framework-tag / role-tag) against the git diff. */
51
88
  export declare function runTagValidator(options: TagRuleConfig, workspaceRoot: string, spec: TagRuleSpec): Promise<ExecutorResult>;
package/src/tag-rule.js CHANGED
@@ -8,8 +8,9 @@
8
8
  * rule supplies only a {@link TagRuleSpec}.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.TagRuleSpec = exports.MissingTagProject = void 0;
11
+ exports.TagRuleSpec = exports.InvalidTagProject = exports.MissingTagProject = void 0;
12
12
  exports.findProjectsMissingTag = findProjectsMissingTag;
13
+ exports.findProjectsWithInvalidTagValues = findProjectsWithInvalidTagValues;
13
14
  exports.runTagValidator = runTagValidator;
14
15
  const tslib_1 = require("tslib");
15
16
  const fs = tslib_1.__importStar(require("fs"));
@@ -26,6 +27,22 @@ class MissingTagProject {
26
27
  }
27
28
  }
28
29
  exports.MissingTagProject = MissingTagProject;
30
+ /**
31
+ * A project carrying one or more `<prefix>` tag VALUES outside the known set
32
+ * (e.g. `framework:all`). `role-tag` never uses this — only rules that opt into
33
+ * value validation via {@link TagRuleSpec.validateValues}.
34
+ */
35
+ class InvalidTagProject {
36
+ name;
37
+ projectJsonPath;
38
+ badValues;
39
+ constructor(name, projectJsonPath, badValues) {
40
+ this.name = name;
41
+ this.projectJsonPath = projectJsonPath;
42
+ this.badValues = badValues;
43
+ }
44
+ }
45
+ exports.InvalidTagProject = InvalidTagProject;
29
46
  /** The knobs and messages that make a concrete tag rule out of the shared engine. */
30
47
  class TagRuleSpec {
31
48
  tagPrefix;
@@ -33,6 +50,8 @@ class TagRuleSpec {
33
50
  ruleLabel;
34
51
  defaultKnownTypes;
35
52
  report;
53
+ validateValues;
54
+ reportInvalidValues;
36
55
  constructor(
37
56
  /** Tag prefix, e.g. `framework:` or `role:`. */
38
57
  tagPrefix,
@@ -43,12 +62,22 @@ class TagRuleSpec {
43
62
  /** Default allowed values when config.knownTypes is empty. */
44
63
  defaultKnownTypes,
45
64
  /** Print the violation report for the missing-tag projects. */
46
- report) {
65
+ report,
66
+ /**
67
+ * When true, ALSO validate that every `<prefix>` tag value is in the
68
+ * known set (framework-tag opts in to reject `framework:all` and typos;
69
+ * role-tag leaves this false so its single-value behavior is unchanged).
70
+ */
71
+ validateValues = false,
72
+ /** Report for out-of-set values; required when {@link validateValues} is true. */
73
+ reportInvalidValues = null) {
47
74
  this.tagPrefix = tagPrefix;
48
75
  this.ruleName = ruleName;
49
76
  this.ruleLabel = ruleLabel;
50
77
  this.defaultKnownTypes = defaultKnownTypes;
51
78
  this.report = report;
79
+ this.validateValues = validateValues;
80
+ this.reportInvalidValues = reportInvalidValues;
52
81
  }
53
82
  }
54
83
  exports.TagRuleSpec = TagRuleSpec;
@@ -102,8 +131,15 @@ function findOwningProjectJson(changedFile, workspaceRoot) {
102
131
  function hasTag(tags, tagPrefix) {
103
132
  return tags.some((tag) => tag.startsWith(tagPrefix) && tag.slice(tagPrefix.length).trim().length > 0);
104
133
  }
105
- /** Every owning project of a changed file that is missing a `<tagPrefix>` tag. */
106
- function findProjectsMissingTag(workspaceRoot, changedFiles, tagPrefix) {
134
+ /** The non-empty values of every `<tagPrefix>` tag on a project (the env/role set). */
135
+ function tagValues(tags, tagPrefix) {
136
+ return tags
137
+ .filter((tag) => tag.startsWith(tagPrefix))
138
+ .map((tag) => tag.slice(tagPrefix.length).trim())
139
+ .filter((value) => value.length > 0);
140
+ }
141
+ /** The distinct owning project.json paths of a set of changed files. */
142
+ function collectOwningProjectJsons(workspaceRoot, changedFiles) {
107
143
  const projectJsonPaths = new Set();
108
144
  for (const file of changedFiles) {
109
145
  const owning = findOwningProjectJson(file, workspaceRoot);
@@ -111,8 +147,12 @@ function findProjectsMissingTag(workspaceRoot, changedFiles, tagPrefix) {
111
147
  projectJsonPaths.add(owning);
112
148
  }
113
149
  }
150
+ return Array.from(projectJsonPaths).sort();
151
+ }
152
+ /** Every owning project of a changed file that is missing a `<tagPrefix>` tag. */
153
+ function findProjectsMissingTag(workspaceRoot, changedFiles, tagPrefix) {
114
154
  const missing = [];
115
- for (const projectJsonPath of Array.from(projectJsonPaths).sort()) {
155
+ for (const projectJsonPath of collectOwningProjectJsons(workspaceRoot, changedFiles)) {
116
156
  const projectJson = readProjectJson(projectJsonPath, workspaceRoot);
117
157
  if (!hasTag(projectJson.tags, tagPrefix)) {
118
158
  const name = projectJson.name ?? path.dirname(projectJsonPath);
@@ -121,6 +161,26 @@ function findProjectsMissingTag(workspaceRoot, changedFiles, tagPrefix) {
121
161
  }
122
162
  return missing;
123
163
  }
164
+ /**
165
+ * Every owning project of a changed file that carries one or more `<tagPrefix>`
166
+ * tag VALUES outside `knownTypes`. Multiple values per project are allowed (an
167
+ * env set) — this only flags values not in the known set (e.g. `framework:all`
168
+ * or a typo). Projects with no `<tagPrefix>` tag are skipped here (the
169
+ * missing-tag scan owns that case).
170
+ */
171
+ function findProjectsWithInvalidTagValues(workspaceRoot, changedFiles, tagPrefix, knownTypes) {
172
+ const known = new Set(knownTypes);
173
+ const invalid = [];
174
+ for (const projectJsonPath of collectOwningProjectJsons(workspaceRoot, changedFiles)) {
175
+ const projectJson = readProjectJson(projectJsonPath, workspaceRoot);
176
+ const badValues = tagValues(projectJson.tags, tagPrefix).filter((value) => !known.has(value));
177
+ if (badValues.length > 0) {
178
+ const name = projectJson.name ?? path.dirname(projectJsonPath);
179
+ invalid.push(new InvalidTagProject(name, projectJsonPath, badValues));
180
+ }
181
+ }
182
+ return invalid;
183
+ }
124
184
  function resolveMode(normalMode, epoch, branchPattern, ruleName) {
125
185
  if (normalMode === 'OFF') {
126
186
  return normalMode;
@@ -165,11 +225,19 @@ async function runTagValidator(options, workspaceRoot, spec) {
165
225
  return { success: true };
166
226
  }
167
227
  const missing = findProjectsMissingTag(workspaceRoot, changedFiles, spec.tagPrefix);
168
- if (missing.length === 0) {
169
- console.log(`✅ Every modified project declares a ${spec.tagPrefix.replace(/:$/, '')} tag`);
228
+ const invalid = spec.validateValues && spec.reportInvalidValues !== null
229
+ ? findProjectsWithInvalidTagValues(workspaceRoot, changedFiles, spec.tagPrefix, knownTypes)
230
+ : [];
231
+ if (missing.length === 0 && invalid.length === 0) {
232
+ console.log(`✅ Every modified project declares a valid ${spec.tagPrefix.replace(/:$/, '')} tag`);
170
233
  return { success: true };
171
234
  }
172
- spec.report(missing, knownTypes, mode);
235
+ if (missing.length > 0) {
236
+ spec.report(missing, knownTypes, mode);
237
+ }
238
+ if (invalid.length > 0 && spec.reportInvalidValues !== null) {
239
+ spec.reportInvalidValues(invalid, knownTypes, mode);
240
+ }
173
241
  return { success: false };
174
242
  }
175
243
  //# sourceMappingURL=tag-rule.js.map
@@ -1 +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"]}
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/**\n * A project carrying one or more `<prefix>` tag VALUES outside the known set\n * (e.g. `framework:all`). `role-tag` never uses this — only rules that opt into\n * value validation via {@link TagRuleSpec.validateValues}.\n */\nexport class InvalidTagProject {\n constructor(\n public readonly name: string,\n public readonly projectJsonPath: string,\n public readonly badValues: string[]\n ) {}\n}\n\n/** Print the invalid-value report for projects carrying out-of-set tag values. */\nexport type ReportInvalidValues = (\n invalid: InvalidTagProject[],\n knownTypes: string[],\n mode: ProjectMode\n) => void;\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 * When true, ALSO validate that every `<prefix>` tag value is in the\n * known set (framework-tag opts in to reject `framework:all` and typos;\n * role-tag leaves this false so its single-value behavior is unchanged).\n */\n public readonly validateValues: boolean = false,\n /** Report for out-of-set values; required when {@link validateValues} is true. */\n public readonly reportInvalidValues: ReportInvalidValues | null = null\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/** The non-empty values of every `<tagPrefix>` tag on a project (the env/role set). */\nfunction tagValues(tags: string[], tagPrefix: string): string[] {\n return tags\n .filter((tag: string) => tag.startsWith(tagPrefix))\n .map((tag: string) => tag.slice(tagPrefix.length).trim())\n .filter((value: string) => value.length > 0);\n}\n\n/** The distinct owning project.json paths of a set of changed files. */\nfunction collectOwningProjectJsons(workspaceRoot: string, changedFiles: string[]): string[] {\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 return Array.from(projectJsonPaths).sort();\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 missing: MissingTagProject[] = [];\n for (const projectJsonPath of collectOwningProjectJsons(workspaceRoot, changedFiles)) {\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\n/**\n * Every owning project of a changed file that carries one or more `<tagPrefix>`\n * tag VALUES outside `knownTypes`. Multiple values per project are allowed (an\n * env set) — this only flags values not in the known set (e.g. `framework:all`\n * or a typo). Projects with no `<tagPrefix>` tag are skipped here (the\n * missing-tag scan owns that case).\n */\nexport function findProjectsWithInvalidTagValues(\n workspaceRoot: string,\n changedFiles: string[],\n tagPrefix: string,\n knownTypes: string[]\n): InvalidTagProject[] {\n const known = new Set(knownTypes);\n const invalid: InvalidTagProject[] = [];\n for (const projectJsonPath of collectOwningProjectJsons(workspaceRoot, changedFiles)) {\n const projectJson = readProjectJson(projectJsonPath, workspaceRoot);\n const badValues = tagValues(projectJson.tags, tagPrefix).filter((value: string) => !known.has(value));\n if (badValues.length > 0) {\n const name = projectJson.name ?? path.dirname(projectJsonPath);\n invalid.push(new InvalidTagProject(name, projectJsonPath, badValues));\n }\n }\n return invalid;\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 const invalid =\n spec.validateValues && spec.reportInvalidValues !== null\n ? findProjectsWithInvalidTagValues(workspaceRoot, changedFiles, spec.tagPrefix, knownTypes)\n : [];\n\n if (missing.length === 0 && invalid.length === 0) {\n console.log(`✅ Every modified project declares a valid ${spec.tagPrefix.replace(/:$/, '')} tag`);\n return { success: true };\n }\n\n if (missing.length > 0) {\n spec.report(missing, knownTypes, mode);\n }\n if (invalid.length > 0 && spec.reportInvalidValues !== null) {\n spec.reportInvalidValues(invalid, knownTypes, mode);\n }\n return { success: false };\n}\n"]}
@@ -1,27 +1,35 @@
1
1
  /**
2
2
  * Validate Framework Tag Executor
3
3
  *
4
- * Every project that a changed source file belongs to must carry a
5
- * `framework:<value>` nx tag in its project.json. That tag is the project's
6
- * "libType" — which client side it targets:
4
+ * Every project that a changed source file belongs to must carry at least one
5
+ * `framework:<value>` nx tag in its project.json. Those tags are the project's
6
+ * "libType" — the SET of runtime environments it is validated to run in, drawn
7
+ * from the atomic values:
7
8
  *
8
- * framework:angular | framework:react | framework:express | framework:all
9
+ * framework:browser | framework:react | framework:angular
10
+ * framework:node | framework:express
9
11
  *
10
- * ("all" = a library usable by any side.) It is the source of truth for the
11
- * `framework` field written into architecture/dependencies.json and for the
12
- * `library-types-match-client` rule, which uses it to keep an express project
13
- * from depending on an angular-only library (and vice-versa).
12
+ * A project lists EVERY environment it promises to run in (e.g.
13
+ * `framework:browser` + `framework:node`). This env set is the source of truth
14
+ * for the `framework` field written into architecture/dependencies.json and for
15
+ * the `library-types-match-client` rule, which uses the compatibility lattice
16
+ * (react/angular→browser, express→node) to keep an express project from
17
+ * depending on a browser-only library (and vice-versa).
18
+ *
19
+ * The legacy single-value `framework:all` "usable by any side" bucket is
20
+ * REMOVED — it is a hard error, and the author must declare the actual env set.
14
21
  *
15
22
  * The project-walking + diff-scoping + mode logic is shared with the `role-tag`
16
23
  * rule in `tag-rule.ts`; this file supplies only the framework-specific prefix,
17
- * known values, and violation message.
24
+ * known values, and violation messages. Unlike role-tag, framework-tag ALSO
25
+ * validates tag VALUES (allowing multiple) via {@link TagRuleSpec.validateValues}.
18
26
  *
19
27
  * ============================================================================
20
28
  * MODES (PROJECT-LEVEL)
21
29
  * ============================================================================
22
30
  * - OFF: Skip validation entirely.
23
- * - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY
24
- * changed file (not just .ts, and not line-scoped).
31
+ * - MODIFIED_PROJECTS: Require >=1 valid framework tag on every project that
32
+ * owns ANY changed file (not just .ts, and not line-scoped).
25
33
  */
26
34
  import { FrameworkTagConfig } from '@webpieces/rules-config';
27
35
  import { CodeValidator, ExecutorResult } from './code-validator';
@@ -2,27 +2,35 @@
2
2
  /**
3
3
  * Validate Framework Tag Executor
4
4
  *
5
- * Every project that a changed source file belongs to must carry a
6
- * `framework:<value>` nx tag in its project.json. That tag is the project's
7
- * "libType" — which client side it targets:
5
+ * Every project that a changed source file belongs to must carry at least one
6
+ * `framework:<value>` nx tag in its project.json. Those tags are the project's
7
+ * "libType" — the SET of runtime environments it is validated to run in, drawn
8
+ * from the atomic values:
8
9
  *
9
- * framework:angular | framework:react | framework:express | framework:all
10
+ * framework:browser | framework:react | framework:angular
11
+ * framework:node | framework:express
10
12
  *
11
- * ("all" = a library usable by any side.) It is the source of truth for the
12
- * `framework` field written into architecture/dependencies.json and for the
13
- * `library-types-match-client` rule, which uses it to keep an express project
14
- * from depending on an angular-only library (and vice-versa).
13
+ * A project lists EVERY environment it promises to run in (e.g.
14
+ * `framework:browser` + `framework:node`). This env set is the source of truth
15
+ * for the `framework` field written into architecture/dependencies.json and for
16
+ * the `library-types-match-client` rule, which uses the compatibility lattice
17
+ * (react/angular→browser, express→node) to keep an express project from
18
+ * depending on a browser-only library (and vice-versa).
19
+ *
20
+ * The legacy single-value `framework:all` "usable by any side" bucket is
21
+ * REMOVED — it is a hard error, and the author must declare the actual env set.
15
22
  *
16
23
  * The project-walking + diff-scoping + mode logic is shared with the `role-tag`
17
24
  * rule in `tag-rule.ts`; this file supplies only the framework-specific prefix,
18
- * known values, and violation message.
25
+ * known values, and violation messages. Unlike role-tag, framework-tag ALSO
26
+ * validates tag VALUES (allowing multiple) via {@link TagRuleSpec.validateValues}.
19
27
  *
20
28
  * ============================================================================
21
29
  * MODES (PROJECT-LEVEL)
22
30
  * ============================================================================
23
31
  * - OFF: Skip validation entirely.
24
- * - MODIFIED_PROJECTS: Require a framework tag on every project that owns ANY
25
- * changed file (not just .ts, and not line-scoped).
32
+ * - MODIFIED_PROJECTS: Require >=1 valid framework tag on every project that
33
+ * owns ANY changed file (not just .ts, and not line-scoped).
26
34
  */
27
35
  Object.defineProperty(exports, "__esModule", { value: true });
28
36
  exports.FrameworkTagValidator = void 0;
@@ -30,7 +38,9 @@ exports.findUntaggedProjects = findUntaggedProjects;
30
38
  const code_validator_1 = require("./code-validator");
31
39
  const tag_rule_1 = require("./tag-rule");
32
40
  const FRAMEWORK_TAG_PREFIX = 'framework:';
33
- const DEFAULT_KNOWN_TYPES = ['angular', 'react', 'express', 'all'];
41
+ const DEFAULT_KNOWN_TYPES = ['browser', 'react', 'angular', 'node', 'express'];
42
+ /** The removed legacy libType, kept only to emit a targeted migration message. */
43
+ const REMOVED_ALL_VALUE = 'all';
34
44
  /**
35
45
  * Back-compat export used by tests: the framework-tag flavor of the shared
36
46
  * missing-tag scan.
@@ -41,21 +51,45 @@ function findUntaggedProjects(workspaceRoot, changedFiles) {
41
51
  function reportViolations(untagged, knownTypes, mode) {
42
52
  const suggestion = knownTypes.map((t) => `${FRAMEWORK_TAG_PREFIX}${t}`).join(' | ');
43
53
  console.error('');
44
- console.error('❌ Every modified project must declare which client side it targets (a framework tag)!');
54
+ console.error('❌ Every modified project must declare the env set it runs in (>=1 framework tag)!');
45
55
  console.error('');
46
- console.error(` Add ONE of these to the project's project.json "tags" array: ${suggestion}`);
47
- console.error(' ("all" = a library usable by any side angular, react and express can all import it.)');
56
+ console.error(` Add ONE OR MORE of these to the project's project.json "tags" array: ${suggestion}`);
57
+ console.error(' (react/angular specialize browser; express specializes node. A project that runs in');
58
+ console.error(' both the browser and node declares BOTH, e.g. framework:browser + framework:node.)');
48
59
  console.error(' This libType drives architecture/dependencies.json and the library-types-match-client rule.');
49
60
  console.error('');
50
61
  for (const project of untagged) {
51
62
  console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);
52
- console.error(` add e.g. "tags": ["${FRAMEWORK_TAG_PREFIX}all"]`);
63
+ console.error(` add e.g. "tags": ["${FRAMEWORK_TAG_PREFIX}browser", "${FRAMEWORK_TAG_PREFIX}node"]`);
64
+ }
65
+ console.error('');
66
+ console.error(` Current mode: ${mode}`);
67
+ console.error('');
68
+ }
69
+ function reportInvalidValues(invalid, knownTypes, mode) {
70
+ const known = knownTypes.map((t) => `${FRAMEWORK_TAG_PREFIX}${t}`).join(' | ');
71
+ console.error('');
72
+ console.error('❌ Some modified projects carry an unknown framework tag value!');
73
+ console.error('');
74
+ console.error(` Allowed values: ${known}`);
75
+ console.error('');
76
+ for (const project of invalid) {
77
+ console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);
78
+ for (const bad of project.badValues) {
79
+ if (bad === REMOVED_ALL_VALUE) {
80
+ console.error(` framework:all is removed — declare the actual env set, ` +
81
+ `e.g. framework:browser + framework:node`);
82
+ }
83
+ else {
84
+ console.error(` unknown value "${FRAMEWORK_TAG_PREFIX}${bad}" — use one of: ${known}`);
85
+ }
86
+ }
53
87
  }
54
88
  console.error('');
55
89
  console.error(` Current mode: ${mode}`);
56
90
  console.error('');
57
91
  }
58
- const FRAMEWORK_TAG_SPEC = new tag_rule_1.TagRuleSpec(FRAMEWORK_TAG_PREFIX, 'framework-tag', 'Framework Tag', DEFAULT_KNOWN_TYPES, reportViolations);
92
+ const FRAMEWORK_TAG_SPEC = new tag_rule_1.TagRuleSpec(FRAMEWORK_TAG_PREFIX, 'framework-tag', 'Framework Tag', DEFAULT_KNOWN_TYPES, reportViolations, true, reportInvalidValues);
59
93
  class FrameworkTagValidator extends code_validator_1.CodeValidator {
60
94
  constructor(config) {
61
95
  super(config, 'framework-tag');
@@ -1 +1 @@
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"]}
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 at least one\n * `framework:<value>` nx tag in its project.json. Those tags are the project's\n * \"libType\" — the SET of runtime environments it is validated to run in, drawn\n * from the atomic values:\n *\n * framework:browser | framework:react | framework:angular\n * framework:node | framework:express\n *\n * A project lists EVERY environment it promises to run in (e.g.\n * `framework:browser` + `framework:node`). This env set is the source of truth\n * for the `framework` field written into architecture/dependencies.json and for\n * the `library-types-match-client` rule, which uses the compatibility lattice\n * (react/angular→browser, express→node) to keep an express project from\n * depending on a browser-only library (and vice-versa).\n *\n * The legacy single-value `framework:all` \"usable by any side\" bucket is\n * REMOVED — it is a hard error, and the author must declare the actual env set.\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 messages. Unlike role-tag, framework-tag ALSO\n * validates tag VALUES (allowing multiple) via {@link TagRuleSpec.validateValues}.\n *\n * ============================================================================\n * MODES (PROJECT-LEVEL)\n * ============================================================================\n * - OFF: Skip validation entirely.\n * - MODIFIED_PROJECTS: Require >=1 valid framework tag on every project that\n * owns ANY 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 {\n InvalidTagProject,\n MissingTagProject,\n TagRuleSpec,\n findProjectsMissingTag,\n runTagValidator,\n} from './tag-rule';\n\nconst FRAMEWORK_TAG_PREFIX = 'framework:';\nconst DEFAULT_KNOWN_TYPES = ['browser', 'react', 'angular', 'node', 'express'];\n\n/** The removed legacy libType, kept only to emit a targeted migration message. */\nconst REMOVED_ALL_VALUE = '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 the env set it runs in (>=1 framework tag)!');\n console.error('');\n console.error(` Add ONE OR MORE of these to the project's project.json \"tags\" array: ${suggestion}`);\n console.error(' (react/angular specialize browser; express specializes node. A project that runs in');\n console.error(' both the browser and node declares BOTH, e.g. framework:browser + framework:node.)');\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}browser\", \"${FRAMEWORK_TAG_PREFIX}node\"]`);\n }\n console.error('');\n console.error(` Current mode: ${mode}`);\n console.error('');\n}\n\nfunction reportInvalidValues(invalid: InvalidTagProject[], knownTypes: string[], mode: ProjectMode): void {\n const known = knownTypes.map((t: string) => `${FRAMEWORK_TAG_PREFIX}${t}`).join(' | ');\n console.error('');\n console.error('❌ Some modified projects carry an unknown framework tag value!');\n console.error('');\n console.error(` Allowed values: ${known}`);\n console.error('');\n\n for (const project of invalid) {\n console.error(` ❌ ${project.name} — ${project.projectJsonPath}`);\n for (const bad of project.badValues) {\n if (bad === REMOVED_ALL_VALUE) {\n console.error(\n ` framework:all is removed — declare the actual env set, ` +\n `e.g. framework:browser + framework:node`\n );\n } else {\n console.error(` unknown value \"${FRAMEWORK_TAG_PREFIX}${bad}\" — use one of: ${known}`);\n }\n }\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 true,\n reportInvalidValues\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"]}