@cosmicdrift/kumiko-guards 0.1.1 → 0.3.0
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 +4 -1
- package/src/_lib/guard-kit.ts +74 -19
- package/src/_lib/qn.ts +21 -0
- package/src/_lib/security-baseline-cli.ts +3 -3
- package/src/_lib/security-baseline.ts +8 -8
- package/src/changes.json +32 -0
- package/src/check-as-casts.ts +648 -0
- package/src/check-complexity.ts +292 -0
- package/src/check-predicates.ts +218 -0
- package/src/check-secret-literals.ts +126 -0
- package/src/cli.ts +59 -0
- package/src/guard-admin-api.ts +1 -1
- package/src/guard-app-feature-structure.ts +114 -0
- package/src/guard-broker-subscribe.ts +99 -0
- package/src/guard-error-reasons.ts +185 -0
- package/src/guard-escape-hatch-declared.ts +26 -19
- package/src/guard-fake-tests.ts +1 -1
- package/src/guard-feature-integration-tests.ts +184 -0
- package/src/guard-html-escape.ts +1 -1
- package/src/guard-i18n-keys.ts +440 -0
- package/src/guard-i18n-locale-mount.ts +317 -0
- package/src/guard-i18n-locale-terminology.ts +117 -0
- package/src/guard-i18n-ui-strings.ts +248 -0
- package/src/guard-lib-test-coverage.ts +156 -0
- package/src/guard-loadall-events.ts +133 -0
- package/src/guard-no-custom-primitives.ts +9 -10
- package/src/guard-no-date-api.ts +1 -1
- package/src/guard-no-direct-fs.ts +1 -1
- package/src/guard-no-inline-styles.ts +4 -4
- package/src/guard-no-logic-in-views.ts +3 -3
- package/src/guard-no-raw-hooks.ts +4 -5
- package/src/guard-open-to-all-reason.ts +1 -1
- package/src/guard-pii-annotations.ts +267 -0
- package/src/guard-pre-es-patterns.ts +1 -1
- package/src/guard-primitives-discipline.ts +3 -3
- package/src/guard-raw-classname.ts +3 -3
- package/src/guard-raw-interactive-elements.ts +3 -3
- package/src/guard-raw-sql.ts +2 -2
- package/src/guard-renderer-boundaries.ts +1 -1
- package/src/guard-restricted-symbols.ts +1 -1
- package/src/guard-screen-conventions.ts +161 -0
- package/src/guard-silent-skip.ts +1 -1
- package/src/guard-table-ddl.ts +159 -0
- package/src/guard-tailwind-scan-surface.ts +12 -12
- package/src/guard-test-stack-drift.ts +147 -0
- package/src/guard-text-field-stance.ts +222 -0
- package/src/guard-thin-wrappers.ts +6 -1
- package/src/guard-unsafe-json-parse.ts +1 -1
- package/src/guard-write-handler-qns.ts +242 -0
- package/src/run-guards.ts +36 -3
- package/src/run-repo-checks.ts +10 -1
- package/src/run-ui-guards.ts +11 -2
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
/**
|
|
3
|
+
* Guard: prüft dass `dispatcher.write("qn:literal:...")`-Aufrufe in
|
|
4
|
+
* Custom-Screens gültige Write-Handler-QNs referenzieren. Tippfehler
|
|
5
|
+
* im QN fallen sonst erst zur Runtime als 404 auf.
|
|
6
|
+
*
|
|
7
|
+
* Validierung in zwei Stufen:
|
|
8
|
+
* 1. **Strukturell**: jeder QN muss `:write:` enthalten — fängt
|
|
9
|
+
* offensichtliche Tippfehler ("feautre:write:create").
|
|
10
|
+
* 2. **Gegen Manifest**: wenn `feature-manifest.json` im Repo-Root
|
|
11
|
+
* liegt und `writeHandlers` enthält, matched der Guard dagegen.
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* bun infra/guards/guard-write-handler-qns.ts
|
|
15
|
+
*/
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import * as path from "node:path";
|
|
18
|
+
import { type Node, type SourceFile, SyntaxKind } from "ts-morph";
|
|
19
|
+
import { type AstGuard, runStandalone, type ScanSpec } from "./_lib/guard-kit";
|
|
20
|
+
import { toKebab, VALID_QN_RE } from "./_lib/qn";
|
|
21
|
+
import { type RepoRoot, resolveRepoRoots } from "./_lib/roots";
|
|
22
|
+
|
|
23
|
+
export { toKebab, VALID_QN_RE };
|
|
24
|
+
|
|
25
|
+
const ROOT = process.cwd();
|
|
26
|
+
|
|
27
|
+
const SCAN: ScanSpec = {
|
|
28
|
+
scope: "source",
|
|
29
|
+
extensions: ["tsx"],
|
|
30
|
+
frameworkWithin: ["packages/*/src/**", "samples/**"],
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Tests nutzen absichtlich generische QNs wie dispatcher.write("x", {}).
|
|
34
|
+
const EXCLUDE = /(__tests__|\.test\.tsx$|\.integration\.tsx$|\/node_modules\/|\/dist\/)/;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Reads writeHandlers per manifest found under each repo root, keyed by
|
|
38
|
+
* the manifest's own directory. Stage-2 matching is subtree-scoped to that
|
|
39
|
+
* directory — prevents cross-app false positives when a repo hosts several
|
|
40
|
+
* independent sample apps, each with its own (or no) manifest.
|
|
41
|
+
*/
|
|
42
|
+
interface ManifestEntry {
|
|
43
|
+
readonly baseDir: string;
|
|
44
|
+
readonly known: Set<string>;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function loadKnownQnsByRepo(roots: ReadonlyArray<RepoRoot>): Map<string, ManifestEntry[]> {
|
|
48
|
+
const byRepo = new Map<string, ManifestEntry[]>();
|
|
49
|
+
|
|
50
|
+
const manifestCandidates = [
|
|
51
|
+
"feature-manifest.json",
|
|
52
|
+
"samples/apps/use-all-bundled/feature-manifest.json",
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
for (const repo of roots) {
|
|
56
|
+
const entries: ManifestEntry[] = [];
|
|
57
|
+
for (const rel of manifestCandidates) {
|
|
58
|
+
const manifestPath = path.join(repo.absPath, rel);
|
|
59
|
+
if (!existsSync(manifestPath)) continue;
|
|
60
|
+
try {
|
|
61
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
62
|
+
const manifest = JSON.parse(raw) as {
|
|
63
|
+
readonly features?: ReadonlyArray<{
|
|
64
|
+
readonly writeHandlers?: readonly string[];
|
|
65
|
+
}>;
|
|
66
|
+
};
|
|
67
|
+
if (!manifest.features) continue;
|
|
68
|
+
const known = new Set<string>();
|
|
69
|
+
for (const f of manifest.features) {
|
|
70
|
+
if (f.writeHandlers) {
|
|
71
|
+
for (const qn of f.writeHandlers) known.add(qn);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (known.size > 0) {
|
|
75
|
+
entries.push({ baseDir: path.dirname(manifestPath), known });
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.warn(`[WARN] Manifest ${manifestPath} not readable: ${err}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (entries.length > 0) byRepo.set(repo.absPath, entries);
|
|
82
|
+
}
|
|
83
|
+
return byRepo;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Picks the manifest whose base directory is the longest prefix of `filePath`. */
|
|
87
|
+
function findKnownQns(filePath: string, entries: ReadonlyArray<ManifestEntry>): Set<string> {
|
|
88
|
+
let best: ManifestEntry | undefined;
|
|
89
|
+
for (const entry of entries) {
|
|
90
|
+
if (filePath !== entry.baseDir && !filePath.startsWith(`${entry.baseDir}${path.sep}`)) continue;
|
|
91
|
+
if (!best || entry.baseDir.length > best.baseDir.length) best = entry;
|
|
92
|
+
}
|
|
93
|
+
return best?.known ?? new Set<string>();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Extrahiert den Literal-Wert aus einem String-Literal, einem
|
|
98
|
+
* Backtick-Literal ohne Interpolation (`NoSubstitutionTemplateLiteral`) oder
|
|
99
|
+
* einem `<literal> as T`-Cast. Backtick-Konstanten (`const QN = \`x:write:y\``)
|
|
100
|
+
* wurden vorher nur als direktes Argument, nicht als aufgelöste Deklaration
|
|
101
|
+
* erkannt.
|
|
102
|
+
*/
|
|
103
|
+
function literalValueOf(node: Node): string | undefined {
|
|
104
|
+
if (
|
|
105
|
+
node.isKind(SyntaxKind.StringLiteral) ||
|
|
106
|
+
node.isKind(SyntaxKind.NoSubstitutionTemplateLiteral)
|
|
107
|
+
) {
|
|
108
|
+
return node.getLiteralValue();
|
|
109
|
+
}
|
|
110
|
+
if (node.isKind(SyntaxKind.AsExpression)) {
|
|
111
|
+
return literalValueOf(node.getExpression());
|
|
112
|
+
}
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Resolve Handler-constant refs (`Handlers.foo`) to string literals when static. */
|
|
117
|
+
export function resolveWriteQnFromArg(node: Node): string | undefined {
|
|
118
|
+
const direct = literalValueOf(node);
|
|
119
|
+
if (direct !== undefined) return direct;
|
|
120
|
+
|
|
121
|
+
if (!node.isKind(SyntaxKind.Identifier) && !node.isKind(SyntaxKind.PropertyAccessExpression)) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const symbol = node.getSymbol() ?? node.getType().getSymbol();
|
|
126
|
+
if (!symbol) return undefined;
|
|
127
|
+
|
|
128
|
+
for (const decl of symbol.getDeclarations()) {
|
|
129
|
+
if (decl.isKind(SyntaxKind.PropertyAssignment) || decl.isKind(SyntaxKind.VariableDeclaration)) {
|
|
130
|
+
const init = decl.getInitializer();
|
|
131
|
+
const value = init ? literalValueOf(init) : undefined;
|
|
132
|
+
if (value !== undefined) return value;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Scannt eine SourceFile nach `dispatcher.write(<stringLiteral>, ...)`-
|
|
141
|
+
* oder `<expr>.write(<stringLiteral>, ...)`-Aufrufen.
|
|
142
|
+
*/
|
|
143
|
+
export function scanDispatcherWriteCalls(
|
|
144
|
+
sf: SourceFile,
|
|
145
|
+
): Array<{ line: number; qn: string; snippet: string }> {
|
|
146
|
+
const hits: Array<{ line: number; qn: string; snippet: string }> = [];
|
|
147
|
+
for (const call of sf.getDescendantsOfKind(SyntaxKind.CallExpression)) {
|
|
148
|
+
const expr = call.getExpression();
|
|
149
|
+
const exprText = expr.getText();
|
|
150
|
+
|
|
151
|
+
// `dispatcher.write(...)` oder irgendein `<obj>.write(...)` (ein
|
|
152
|
+
// aliaster Dispatcher heißt nicht zwingend "dispatcher").
|
|
153
|
+
const isDispatcherCall = exprText === "dispatcher.write";
|
|
154
|
+
if (!isDispatcherCall && !exprText.endsWith(".write")) continue;
|
|
155
|
+
|
|
156
|
+
// Erster Parameter muss ein String-Literal sein — dynamische
|
|
157
|
+
// QNs (Handler-Konstanten, Template-Literale) werden nicht
|
|
158
|
+
// validiert (sind entweder typ-safe oder nicht prüfbar).
|
|
159
|
+
const args = call.getArguments();
|
|
160
|
+
const first = args[0];
|
|
161
|
+
if (!first) continue;
|
|
162
|
+
|
|
163
|
+
const qn = resolveWriteQnFromArg(first);
|
|
164
|
+
if (qn === undefined) continue;
|
|
165
|
+
|
|
166
|
+
// `.endsWith(".write")` matcht auch Nicht-Dispatcher-Writes:
|
|
167
|
+
// `res.write(...)`, `stream.write(...)`, SSE `res.write("data: …")`.
|
|
168
|
+
// Deren Argument ist kein QN → würde sonst als "ungültiges QN-Format"
|
|
169
|
+
// false-positiv gemeldet. Außerhalb eines expliziten `dispatcher.write`
|
|
170
|
+
// nur prüfen, wenn das Literal ein Write-QN ist (enthält ":write:").
|
|
171
|
+
if (!isDispatcherCall && !qn.includes(":write:")) continue;
|
|
172
|
+
|
|
173
|
+
hits.push({
|
|
174
|
+
line: call.getStartLineNumber(),
|
|
175
|
+
qn,
|
|
176
|
+
snippet: call.getText().slice(0, 120),
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
return hits;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export const guard: AstGuard = {
|
|
183
|
+
name: "Write-Handler-QN Guard",
|
|
184
|
+
scan: SCAN,
|
|
185
|
+
hint: "Typo in the write-handler QN? Check feature name + handler name.",
|
|
186
|
+
run(files) {
|
|
187
|
+
const roots = resolveRepoRoots();
|
|
188
|
+
const knownQnsByRepo = loadKnownQnsByRepo(roots);
|
|
189
|
+
|
|
190
|
+
const violations: Array<{
|
|
191
|
+
file: string;
|
|
192
|
+
line: number;
|
|
193
|
+
message: string;
|
|
194
|
+
}> = [];
|
|
195
|
+
|
|
196
|
+
for (const sf of files) {
|
|
197
|
+
const filePath = sf.getFilePath();
|
|
198
|
+
if (EXCLUDE.test(filePath)) continue;
|
|
199
|
+
|
|
200
|
+
const repo = roots.find((r) => filePath.startsWith(`${r.absPath}${path.sep}`));
|
|
201
|
+
const knownQns = repo
|
|
202
|
+
? findKnownQns(filePath, knownQnsByRepo.get(repo.absPath) ?? [])
|
|
203
|
+
: new Set<string>();
|
|
204
|
+
|
|
205
|
+
for (const hit of scanDispatcherWriteCalls(sf)) {
|
|
206
|
+
// Stufe 1: strukturelle Validierung
|
|
207
|
+
if (!VALID_QN_RE.test(hit.qn)) {
|
|
208
|
+
violations.push({
|
|
209
|
+
file: path.relative(ROOT, filePath),
|
|
210
|
+
line: hit.line,
|
|
211
|
+
message: `invalid QN format: "${hit.qn}" — must match "<feature>:write:<handler>"`,
|
|
212
|
+
});
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Stufe 2: gegen Manifest matchen (wenn vorhanden).
|
|
217
|
+
// QN wird vor dem Match auf kebab-case normalisiert, sodass
|
|
218
|
+
// camelCase- und kebab-case-Eingaben gleich behandelt werden.
|
|
219
|
+
const normalizedQn = toKebab(hit.qn);
|
|
220
|
+
if (knownQns.size > 0 && !knownQns.has(normalizedQn)) {
|
|
221
|
+
violations.push({
|
|
222
|
+
file: path.relative(ROOT, filePath),
|
|
223
|
+
line: hit.line,
|
|
224
|
+
message: `unknown write handler: "${hit.qn}" — not found in feature-manifest.json`,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Info-Log wenn kein Manifest geladen wurde — kein Fehler, aber
|
|
231
|
+
// der Guard läuft dann nur mit struktureller Prüfung.
|
|
232
|
+
if (knownQnsByRepo.size === 0 && violations.length === 0) {
|
|
233
|
+
console.warn(
|
|
234
|
+
" [INFO] No feature-manifest.json with writeHandlers found — structural check only.",
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return { violations };
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
if (import.meta.main) runStandalone(guard);
|
package/src/run-guards.ts
CHANGED
|
@@ -11,27 +11,44 @@ import {
|
|
|
11
11
|
buildSharedProject,
|
|
12
12
|
explainGuards,
|
|
13
13
|
isSecurityGuard,
|
|
14
|
+
printGuardKitBanner,
|
|
14
15
|
reportResults,
|
|
15
16
|
runGuards,
|
|
16
17
|
} from "./_lib/guard-kit";
|
|
17
18
|
import { writeSecurityBaselines } from "./_lib/security-baseline-cli";
|
|
19
|
+
import { guard as asCasts } from "./check-as-casts";
|
|
20
|
+
import { guard as complexity } from "./check-complexity";
|
|
21
|
+
import { guard as predicateExtraction } from "./check-predicates";
|
|
18
22
|
import { guard as accessDeniedTest } from "./guard-access-denied-test";
|
|
19
23
|
import { guard as adminApi } from "./guard-admin-api";
|
|
24
|
+
import { guard as appFeatureStructure } from "./guard-app-feature-structure";
|
|
25
|
+
import { guard as brokerSubscribe } from "./guard-broker-subscribe";
|
|
20
26
|
import { guard as crossFeatureImports } from "./guard-cross-feature-imports";
|
|
21
27
|
import { guard as directEntityWrites } from "./guard-direct-entity-writes";
|
|
22
28
|
import { guard as directFetch } from "./guard-direct-fetch";
|
|
29
|
+
import { guard as errorReasons } from "./guard-error-reasons";
|
|
23
30
|
import { guard as escapeHatchDeclared } from "./guard-escape-hatch-declared";
|
|
24
31
|
import { guard as fakeTests } from "./guard-fake-tests";
|
|
25
32
|
import { guard as htmlEscape } from "./guard-html-escape";
|
|
33
|
+
import { guard as i18nKeys } from "./guard-i18n-keys";
|
|
34
|
+
import { guard as i18nLocaleMount } from "./guard-i18n-locale-mount";
|
|
35
|
+
import { guard as i18nLocaleTerminology } from "./guard-i18n-locale-terminology";
|
|
36
|
+
import { guard as libTestCoverage } from "./guard-lib-test-coverage";
|
|
37
|
+
import { guard as loadallEvents } from "./guard-loadall-events";
|
|
26
38
|
import { guard as noDateApi } from "./guard-no-date-api";
|
|
27
39
|
import { guard as noDirectFs } from "./guard-no-direct-fs";
|
|
28
40
|
import { guard as noLogicInViews } from "./guard-no-logic-in-views";
|
|
29
41
|
import { guard as openToAllReason } from "./guard-open-to-all-reason";
|
|
42
|
+
import { guard as piiAnnotations } from "./guard-pii-annotations";
|
|
30
43
|
import { guard as preEsPatterns } from "./guard-pre-es-patterns";
|
|
31
44
|
import { guard as restrictedSymbols } from "./guard-restricted-symbols";
|
|
45
|
+
import { guard as screenConventions } from "./guard-screen-conventions";
|
|
32
46
|
import { guard as silentSkip } from "./guard-silent-skip";
|
|
47
|
+
import { guard as tableDdl } from "./guard-table-ddl";
|
|
33
48
|
import { guard as tenantEscalation } from "./guard-tenant-escalation";
|
|
49
|
+
import { guard as textFieldStance } from "./guard-text-field-stance";
|
|
34
50
|
import { guard as unsafeJsonParse } from "./guard-unsafe-json-parse";
|
|
51
|
+
import { guard as writeHandlerQns } from "./guard-write-handler-qns";
|
|
35
52
|
|
|
36
53
|
export const GUARDS = [
|
|
37
54
|
accessDeniedTest,
|
|
@@ -51,6 +68,22 @@ export const GUARDS = [
|
|
|
51
68
|
restrictedSymbols,
|
|
52
69
|
fakeTests,
|
|
53
70
|
noLogicInViews,
|
|
71
|
+
brokerSubscribe,
|
|
72
|
+
errorReasons,
|
|
73
|
+
i18nKeys,
|
|
74
|
+
i18nLocaleMount,
|
|
75
|
+
i18nLocaleTerminology,
|
|
76
|
+
piiAnnotations,
|
|
77
|
+
textFieldStance,
|
|
78
|
+
complexity,
|
|
79
|
+
predicateExtraction,
|
|
80
|
+
screenConventions,
|
|
81
|
+
writeHandlerQns,
|
|
82
|
+
asCasts,
|
|
83
|
+
loadallEvents,
|
|
84
|
+
tableDdl,
|
|
85
|
+
appFeatureStructure,
|
|
86
|
+
libTestCoverage,
|
|
54
87
|
];
|
|
55
88
|
|
|
56
89
|
// Only run on direct invocation — otherwise `import { GUARDS }` would kick
|
|
@@ -71,8 +104,8 @@ if (import.meta.main) {
|
|
|
71
104
|
}
|
|
72
105
|
const strictSecurityBaseline = process.argv.includes("--strict-security-baseline");
|
|
73
106
|
const guards = strictSecurityBaseline ? GUARDS.filter(isSecurityGuard) : GUARDS;
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
);
|
|
107
|
+
const project = buildSharedProject(guards);
|
|
108
|
+
printGuardKitBanner(guards.length, project);
|
|
109
|
+
const failed = reportResults(runGuards(guards, project, { strictSecurityBaseline }));
|
|
77
110
|
process.exit(failed > 0 ? 1 : 0);
|
|
78
111
|
}
|
package/src/run-repo-checks.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { printGuardKitBanner, reportResults, runRepoChecks } from "./_lib/guard-kit";
|
|
2
3
|
// Standalone-`main()` guards ported as RepoCheck — run in-process, no
|
|
3
4
|
// per-guard subprocess/project.
|
|
4
|
-
import {
|
|
5
|
+
import { check as secretLiterals } from "./check-secret-literals";
|
|
6
|
+
import { check as featureIntegrationTests } from "./guard-feature-integration-tests";
|
|
5
7
|
import { check as noDirectProcessEnv } from "./guard-no-direct-process-env";
|
|
6
8
|
import { check as primitivesDiscipline } from "./guard-primitives-discipline";
|
|
7
9
|
import { check as rawSql } from "./guard-raw-sql";
|
|
8
10
|
import { check as rendererBoundaries } from "./guard-renderer-boundaries";
|
|
11
|
+
import { check as testStackDrift } from "./guard-test-stack-drift";
|
|
9
12
|
import { check as thinWrappers } from "./guard-thin-wrappers";
|
|
10
13
|
|
|
11
14
|
export const REPO_CHECKS = [
|
|
@@ -14,9 +17,15 @@ export const REPO_CHECKS = [
|
|
|
14
17
|
rendererBoundaries,
|
|
15
18
|
primitivesDiscipline,
|
|
16
19
|
thinWrappers,
|
|
20
|
+
secretLiterals,
|
|
21
|
+
featureIntegrationTests,
|
|
22
|
+
testStackDrift,
|
|
17
23
|
];
|
|
18
24
|
|
|
19
25
|
if (import.meta.main) {
|
|
26
|
+
// No shared ts-morph Project here — RepoCheck.run() does its own file
|
|
27
|
+
// walk per check, so the banner omits the "Project: N files" line.
|
|
28
|
+
printGuardKitBanner(REPO_CHECKS.length);
|
|
20
29
|
const failed = reportResults(await runRepoChecks(REPO_CHECKS));
|
|
21
30
|
process.exit(failed > 0 ? 1 : 0);
|
|
22
31
|
}
|
package/src/run-ui-guards.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// UI-guard bundle (App-Mounting 2.0, infra#208): one process, shared ts-morph
|
|
3
3
|
// Project over the UI enforcement guards.
|
|
4
|
-
import {
|
|
4
|
+
import {
|
|
5
|
+
buildSharedProject,
|
|
6
|
+
printGuardKitBanner,
|
|
7
|
+
reportResults,
|
|
8
|
+
runGuards,
|
|
9
|
+
} from "./_lib/guard-kit";
|
|
10
|
+
import { guard as i18nUiStrings } from "./guard-i18n-ui-strings";
|
|
5
11
|
import { guard as noCustomPrimitives } from "./guard-no-custom-primitives";
|
|
6
12
|
import { guard as noInlineStyles } from "./guard-no-inline-styles";
|
|
7
13
|
import { guard as noRawHooks } from "./guard-no-raw-hooks";
|
|
@@ -16,10 +22,13 @@ export const UI_GUARDS = [
|
|
|
16
22
|
noRawHooks,
|
|
17
23
|
tailwindScanSurface,
|
|
18
24
|
rawInteractiveElements,
|
|
25
|
+
i18nUiStrings,
|
|
19
26
|
];
|
|
20
27
|
|
|
21
28
|
// Same as run-guards.ts: only run on direct invocation.
|
|
22
29
|
if (import.meta.main) {
|
|
23
|
-
const
|
|
30
|
+
const project = buildSharedProject(UI_GUARDS);
|
|
31
|
+
printGuardKitBanner(UI_GUARDS.length, project);
|
|
32
|
+
const failed = reportResults(runGuards(UI_GUARDS, project));
|
|
24
33
|
process.exit(failed > 0 ? 1 : 0);
|
|
25
34
|
}
|