@ploaness/governance 1.12.0 → 1.14.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/dist/a11y-focus-guard.d.ts +18 -0
- package/dist/a11y-focus-guard.d.ts.map +1 -0
- package/dist/a11y-focus-guard.js +17 -0
- package/dist/a11y-focus-guard.js.map +1 -0
- package/dist/a11y-hit-target.d.ts +7 -1
- package/dist/a11y-hit-target.d.ts.map +1 -1
- package/dist/a11y-hit-target.js +11 -0
- package/dist/a11y-hit-target.js.map +1 -1
- package/dist/asset-policy.d.ts +8 -4
- package/dist/asset-policy.d.ts.map +1 -1
- package/dist/asset-policy.js +28 -19
- package/dist/asset-policy.js.map +1 -1
- package/dist/browser-discovery.d.ts +116 -0
- package/dist/browser-discovery.d.ts.map +1 -0
- package/dist/browser-discovery.js +161 -0
- package/dist/browser-discovery.js.map +1 -0
- package/dist/browser-exemptions.d.ts +107 -0
- package/dist/browser-exemptions.d.ts.map +1 -0
- package/dist/browser-exemptions.js +163 -0
- package/dist/browser-exemptions.js.map +1 -0
- package/dist/browser-findings.d.ts +54 -0
- package/dist/browser-findings.d.ts.map +1 -0
- package/dist/browser-findings.js +52 -0
- package/dist/browser-findings.js.map +1 -0
- package/dist/browser-health.d.ts +45 -0
- package/dist/browser-health.d.ts.map +1 -0
- package/dist/browser-health.js +80 -0
- package/dist/browser-health.js.map +1 -0
- package/dist/browser-rules.d.ts +169 -0
- package/dist/browser-rules.d.ts.map +1 -0
- package/dist/browser-rules.js +345 -0
- package/dist/browser-rules.js.map +1 -0
- package/dist/browser-run.d.ts +110 -0
- package/dist/browser-run.d.ts.map +1 -0
- package/dist/browser-run.js +125 -0
- package/dist/browser-run.js.map +1 -0
- package/dist/browser-viewports.d.ts +77 -0
- package/dist/browser-viewports.d.ts.map +1 -0
- package/dist/browser-viewports.js +139 -0
- package/dist/browser-viewports.js.map +1 -0
- package/dist/change-scope.d.ts +158 -0
- package/dist/change-scope.d.ts.map +1 -0
- package/dist/change-scope.js +340 -0
- package/dist/change-scope.js.map +1 -0
- package/dist/cloudfront-functions.d.ts +34 -0
- package/dist/cloudfront-functions.d.ts.map +1 -0
- package/dist/cloudfront-functions.js +124 -0
- package/dist/cloudfront-functions.js.map +1 -0
- package/dist/generated-denial.d.ts +4 -1
- package/dist/generated-denial.d.ts.map +1 -1
- package/dist/generated-denial.js +10 -3
- package/dist/generated-denial.js.map +1 -1
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -1
- package/dist/javascript-runtimes.d.ts +36 -0
- package/dist/javascript-runtimes.d.ts.map +1 -0
- package/dist/javascript-runtimes.js +40 -0
- package/dist/javascript-runtimes.js.map +1 -0
- package/dist/jsx-accessibility-scope.d.ts.map +1 -1
- package/dist/jsx-accessibility-scope.js +3 -0
- package/dist/jsx-accessibility-scope.js.map +1 -1
- package/dist/layout-boxes.d.ts.map +1 -1
- package/dist/layout-boxes.js +12 -14
- package/dist/layout-boxes.js.map +1 -1
- package/dist/layout-clip.d.ts +24 -0
- package/dist/layout-clip.d.ts.map +1 -0
- package/dist/layout-clip.js +82 -0
- package/dist/layout-clip.js.map +1 -0
- package/dist/layout-defects.d.ts +47 -4
- package/dist/layout-defects.d.ts.map +1 -1
- package/dist/layout-defects.js +94 -76
- package/dist/layout-defects.js.map +1 -1
- package/dist/layout-snapshot.d.ts +60 -2
- package/dist/layout-snapshot.d.ts.map +1 -1
- package/dist/layout-snapshot.js +65 -1
- package/dist/layout-snapshot.js.map +1 -1
- package/dist/layout-text.d.ts +4 -1
- package/dist/layout-text.d.ts.map +1 -1
- package/dist/layout-text.js +56 -7
- package/dist/layout-text.js.map +1 -1
- package/dist/route-map.d.ts +13 -0
- package/dist/route-map.d.ts.map +1 -1
- package/dist/route-map.js +13 -6
- package/dist/route-map.js.map +1 -1
- package/dist/run-plan.d.ts +27 -3
- package/dist/run-plan.d.ts.map +1 -1
- package/dist/run-plan.js +20 -3
- package/dist/run-plan.js.map +1 -1
- package/dist/settings-layering.d.ts.map +1 -1
- package/dist/settings-layering.js +6 -4
- package/dist/settings-layering.js.map +1 -1
- package/dist/settings.d.ts +32 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +51 -12
- package/dist/settings.js.map +1 -1
- package/dist/terraform-blocks.d.ts +13 -0
- package/dist/terraform-blocks.d.ts.map +1 -0
- package/dist/terraform-blocks.js +27 -0
- package/dist/terraform-blocks.js.map +1 -0
- package/dist/terraform-policy.d.ts.map +1 -1
- package/dist/terraform-policy.js +3 -14
- package/dist/terraform-policy.js.map +1 -1
- package/dist/toolchain-pins.d.ts.map +1 -1
- package/dist/toolchain-pins.js +3 -1
- package/dist/toolchain-pins.js.map +1 -1
- package/dist/wiring-policy.d.ts +5 -2
- package/dist/wiring-policy.d.ts.map +1 -1
- package/dist/wiring-policy.js +16 -6
- package/dist/wiring-policy.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// Which pages a change can affect, so `delta` verification sweeps those and nothing else.
|
|
2
|
+
//
|
|
3
|
+
// The change is everything that differs from the merge-base of HEAD with the default branch of the first
|
|
4
|
+
// remote: commits not on that branch, staged and unstaged edits, and untracked files alike. A changed
|
|
5
|
+
// file reaches pages through the import graph. A route is in scope when one of its entry modules - its
|
|
6
|
+
// page, and the layout, template, loading, error and not-found modules above it - imports the file,
|
|
7
|
+
// directly or not; a story is in scope when its story file does. Whatever the graph cannot follow is
|
|
8
|
+
// global, and a global change sweeps everything. Nothing is remembered between runs: the scope is
|
|
9
|
+
// derived from Git each time, and it is safe only because `full` sweeps the default branch completely.
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { BROWSER_ENDPOINT_ENVIRONMENT_VARIABLE } from './browser-run.js';
|
|
12
|
+
import { isWithinSourceRoots, STYLESHEET_EXTENSIONS } from './file-roles.js';
|
|
13
|
+
import { isArray, isRecord } from './json-shapes.js';
|
|
14
|
+
import { appRootOf, declaredRoutesOf, isRootLayout, resolvedRouteOf } from './route-map.js';
|
|
15
|
+
/** Names the file that holds the change scope a `delta` run hands its browser sweep. */
|
|
16
|
+
export const SWEEP_SCOPE_ENVIRONMENT_VARIABLE = 'PLOANESS_SWEEP_SCOPE';
|
|
17
|
+
/**
|
|
18
|
+
* Whether the browser sweep runs its engine pass in Firefox and WebKit as well as in Chromium. Only an
|
|
19
|
+
* unscoped run with the pinned browsers does: a `delta` run sweeps the change in Chromium alone.
|
|
20
|
+
* @param environment the runner's environment.
|
|
21
|
+
* @returns true when the other engines run.
|
|
22
|
+
*/
|
|
23
|
+
export const coversEveryEngine = (environment) => environment[BROWSER_ENDPOINT_ENVIRONMENT_VARIABLE] !== undefined &&
|
|
24
|
+
environment[SWEEP_SCOPE_ENVIRONMENT_VARIABLE] === undefined;
|
|
25
|
+
/** The default branch a remote names when its own `HEAD` is not recorded. */
|
|
26
|
+
const DEFAULT_BRANCH = 'main';
|
|
27
|
+
/** The remote a clone names first, and the one the baseline is read from when it exists. */
|
|
28
|
+
const PREFERRED_REMOTE = 'origin';
|
|
29
|
+
/**
|
|
30
|
+
* The remote whose default branch is the baseline: `origin` when there is one, otherwise the first.
|
|
31
|
+
* @param remotes the repository's remotes, as `git remote` lists them.
|
|
32
|
+
* @returns the remote, or undefined when the repository has none.
|
|
33
|
+
*/
|
|
34
|
+
export const baselineRemote = (remotes) => remotes.includes(PREFERRED_REMOTE) ? PREFERRED_REMOTE : remotes[0];
|
|
35
|
+
const REMOTES_PREFIX = 'refs/remotes/';
|
|
36
|
+
/**
|
|
37
|
+
* The remote-tracking branch that stands for the remote's default branch.
|
|
38
|
+
* @param remote the remote.
|
|
39
|
+
* @param symbolicHead what `refs/remotes/<remote>/HEAD` points at, or undefined when it is not recorded.
|
|
40
|
+
* @returns a ref such as `origin/main`.
|
|
41
|
+
*/
|
|
42
|
+
export const trackingBranch = (remote, symbolicHead) => symbolicHead?.startsWith(`${REMOTES_PREFIX}${remote}/`) === true
|
|
43
|
+
? symbolicHead.slice(REMOTES_PREFIX.length)
|
|
44
|
+
: `${remote}/${DEFAULT_BRANCH}`;
|
|
45
|
+
/**
|
|
46
|
+
* Decide the commit a change is measured from. Local `main` is never it: a commit made on it would be
|
|
47
|
+
* its own baseline and never be checked. A shallow clone may lack the true merge-base, so it has none.
|
|
48
|
+
* @param facts what Git said.
|
|
49
|
+
* @returns the merge-base, or why the sweep has to be complete.
|
|
50
|
+
*/
|
|
51
|
+
export const changeBaseline = (facts) => {
|
|
52
|
+
if (facts.branch === undefined) {
|
|
53
|
+
return {
|
|
54
|
+
commit: undefined,
|
|
55
|
+
problem: `the repository has no remote-tracking default branch, such as ${PREFERRED_REMOTE}/${DEFAULT_BRANCH}`,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
if (facts.isShallow) {
|
|
59
|
+
return { commit: undefined, problem: `the clone is shallow, so its merge-base with ${facts.branch} is unknown` };
|
|
60
|
+
}
|
|
61
|
+
return facts.mergeBase === undefined
|
|
62
|
+
? { commit: undefined, problem: `HEAD and ${facts.branch} share no merge-base` }
|
|
63
|
+
: { commit: facts.mergeBase, branch: facts.branch };
|
|
64
|
+
};
|
|
65
|
+
// Repository files that decide what is installed and how it runs, for every member alike.
|
|
66
|
+
const REPOSITORY_INPUTS = new Set([
|
|
67
|
+
'package.json',
|
|
68
|
+
'pnpm-lock.yaml',
|
|
69
|
+
'pnpm-workspace.yaml',
|
|
70
|
+
'.npmrc',
|
|
71
|
+
'.nvmrc',
|
|
72
|
+
]);
|
|
73
|
+
// Relative to the member root. A theme or token file needs no entry: a Tailwind theme is a global
|
|
74
|
+
// stylesheet, a token module is followed through the graph, and anything else under a source root is
|
|
75
|
+
// unknown and therefore global.
|
|
76
|
+
const MEMBER_INPUTS = [
|
|
77
|
+
{ pattern: /^package\.json$/u, what: 'the manifest' },
|
|
78
|
+
{ pattern: /^tsconfig\.json$/u, what: 'the TypeScript configuration, which decides how imports resolve' },
|
|
79
|
+
{ pattern: /^next\.config\.[cm]?[jt]s$/u, what: 'the Next.js configuration' },
|
|
80
|
+
{ pattern: /^(?:tailwind|postcss)\.config\.[cm]?[jt]s$/u, what: 'the style tool configuration' },
|
|
81
|
+
{
|
|
82
|
+
pattern: /^(?:src\/)?(?:middleware|proxy|instrumentation(?:-client)?)\.[cm]?[jt]sx?$/u,
|
|
83
|
+
what: 'request handling every route passes through',
|
|
84
|
+
},
|
|
85
|
+
{ pattern: /^(?:\.env[^/]*|[^/]+\.env)$/u, what: 'an environment file' },
|
|
86
|
+
{ pattern: /^public\//u, what: 'a public file pages may load' },
|
|
87
|
+
{ pattern: /(?:^|\/)\.storybook\//u, what: 'the Storybook configuration' },
|
|
88
|
+
];
|
|
89
|
+
const CSS_MODULE = /\.module\.[a-z]+$/u;
|
|
90
|
+
const isGlobalStylesheet = (file) => STYLESHEET_EXTENSIONS.some((extension) => file.endsWith(extension)) && !CSS_MODULE.test(file);
|
|
91
|
+
const ROOT_MEMBER = '.';
|
|
92
|
+
const isInside = (file, member) => member === ROOT_MEMBER || file === member || file.startsWith(`${member}/`);
|
|
93
|
+
// The deepest member holding the file: a nested member's files are its own, not its parent's.
|
|
94
|
+
const ownerOf = (file, members) => members
|
|
95
|
+
.filter((member) => isInside(file, member))
|
|
96
|
+
.reduce((deepest, member) => deepest === undefined || deepest === ROOT_MEMBER || member.length > deepest.length ? member : deepest, undefined);
|
|
97
|
+
const relativeTo = (file, member) => member === ROOT_MEMBER ? file : file.slice(member.length + 1);
|
|
98
|
+
const NO_EFFECT = { kind: 'none' };
|
|
99
|
+
const globalEffect = (reason) => ({ kind: 'global', reason });
|
|
100
|
+
// What makes a member file global, or undefined when nothing does.
|
|
101
|
+
const memberInputKind = (file, appRoot) => {
|
|
102
|
+
const input = MEMBER_INPUTS.find((candidate) => candidate.pattern.test(file));
|
|
103
|
+
if (input !== undefined) {
|
|
104
|
+
return input.what;
|
|
105
|
+
}
|
|
106
|
+
if (isGlobalStylesheet(file)) {
|
|
107
|
+
return 'a global stylesheet';
|
|
108
|
+
}
|
|
109
|
+
return appRoot !== undefined && isRootLayout(file, appRoot) ? 'the root layout' : undefined;
|
|
110
|
+
};
|
|
111
|
+
// A file of this application: global, followed through the graph, or outside what pages are built from.
|
|
112
|
+
// A deleted module is in no graph, and it reaches nothing on its own: a module still importing it no
|
|
113
|
+
// longer builds, and one that stopped importing it is itself changed.
|
|
114
|
+
const ownEffect = (changed, inputs, appRoot) => {
|
|
115
|
+
const file = relativeTo(changed.path, inputs.member);
|
|
116
|
+
const kind = memberInputKind(file, appRoot);
|
|
117
|
+
if (kind !== undefined) {
|
|
118
|
+
return globalEffect(`${changed.path} is ${kind}`);
|
|
119
|
+
}
|
|
120
|
+
if (!isWithinSourceRoots(file, inputs.sourceRoots)) {
|
|
121
|
+
return NO_EFFECT;
|
|
122
|
+
}
|
|
123
|
+
if (inputs.graph[file] !== undefined) {
|
|
124
|
+
return { kind: 'graph', file };
|
|
125
|
+
}
|
|
126
|
+
return changed.isDeleted
|
|
127
|
+
? NO_EFFECT
|
|
128
|
+
: globalEffect(`${changed.path} is not in the import graph, so what it reaches is unknown`);
|
|
129
|
+
};
|
|
130
|
+
const effectOf = (changed, inputs, appRoot) => {
|
|
131
|
+
if (REPOSITORY_INPUTS.has(changed.path)) {
|
|
132
|
+
return globalEffect(`${changed.path} decides what is installed for every member`);
|
|
133
|
+
}
|
|
134
|
+
const owner = ownerOf(changed.path, inputs.members);
|
|
135
|
+
if (owner === inputs.member) {
|
|
136
|
+
return ownEffect(changed, inputs, appRoot);
|
|
137
|
+
}
|
|
138
|
+
return owner !== undefined && inputs.related.includes(owner)
|
|
139
|
+
? globalEffect(`${changed.path} belongs to ${owner}, which this application runs beside or depends on`)
|
|
140
|
+
: NO_EFFECT;
|
|
141
|
+
};
|
|
142
|
+
const edgesOf = (graph) => Object.entries(graph).flatMap(([from, imports]) => imports.map((to) => ({ from, to })));
|
|
143
|
+
// Every module that imports one of the frontier's, directly or not, added to what is already reached.
|
|
144
|
+
const reachedFrom = (frontier, reached, edges) => {
|
|
145
|
+
const next = new Set(edges
|
|
146
|
+
.filter((edge) => frontier.has(edge.to) && !reached.has(edge.from))
|
|
147
|
+
.map((edge) => edge.from));
|
|
148
|
+
return next.size === 0 ? reached : reachedFrom(next, new Set([...reached, ...next]), edges);
|
|
149
|
+
};
|
|
150
|
+
const SEGMENT_MODULE = /^(?:layout|template|loading|error|not-found)\.(?:tsx|ts|jsx|js)$/u;
|
|
151
|
+
// The page, and every segment module in its directory or above it inside the app directory.
|
|
152
|
+
const entryModules = (page, appRoot, files) => {
|
|
153
|
+
const directory = path.posix.dirname(page);
|
|
154
|
+
const isAbove = (candidate) => {
|
|
155
|
+
const own = path.posix.dirname(candidate);
|
|
156
|
+
return (own === directory || directory.startsWith(`${own}/`)) && isInside(own, appRoot);
|
|
157
|
+
};
|
|
158
|
+
return [
|
|
159
|
+
page,
|
|
160
|
+
...files.filter((file) => SEGMENT_MODULE.test(path.posix.basename(file)) && isAbove(file) && file !== page),
|
|
161
|
+
];
|
|
162
|
+
};
|
|
163
|
+
const reasonFor = (entries, reaches) => {
|
|
164
|
+
const hits = entries.flatMap((entry) => reaches.filter((reach) => reach.importers.has(entry)).map((reach) => [entry, reach]));
|
|
165
|
+
const [first] = hits;
|
|
166
|
+
if (first === undefined) {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
const [entry, reach] = first;
|
|
170
|
+
const others = new Set(hits.map(([, hit]) => hit.changed)).size - 1;
|
|
171
|
+
const subject = entry === reach.changed ? `${entry} changed` : `${entry} reaches ${reach.changed}`;
|
|
172
|
+
return others === 0 ? subject : `${subject} and ${String(others)} more changed file(s)`;
|
|
173
|
+
};
|
|
174
|
+
const STORY_FILE = /\.stories\.[cm]?[jt]sx?$/u;
|
|
175
|
+
const routeTargets = (inputs, appRoot, reaches) => appRoot === undefined
|
|
176
|
+
? []
|
|
177
|
+
: declaredRoutesOf(inputs.files, appRoot).flatMap((route) => {
|
|
178
|
+
const reason = reasonFor(entryModules(route.file, appRoot, inputs.files), reaches);
|
|
179
|
+
return reason === undefined ? [] : [{ target: route.route, reason }];
|
|
180
|
+
});
|
|
181
|
+
const storyTargets = (inputs, reaches) => Object.keys(inputs.graph)
|
|
182
|
+
.filter((module) => STORY_FILE.test(module))
|
|
183
|
+
.flatMap((story) => {
|
|
184
|
+
const reason = reasonFor([story], reaches);
|
|
185
|
+
return reason === undefined ? [] : [{ target: story, reason }];
|
|
186
|
+
});
|
|
187
|
+
/**
|
|
188
|
+
* Decide what one application's browser sweep covers for a change.
|
|
189
|
+
*
|
|
190
|
+
* Everything, when there is no baseline, or when a changed file is a global input: a dependency or
|
|
191
|
+
* toolchain file, the application's manifest, TypeScript, Next.js, Tailwind or PostCSS configuration,
|
|
192
|
+
* request handling, an environment file, a public file, the Storybook configuration, a global
|
|
193
|
+
* stylesheet, the root layout, a file under a source root the import graph does not hold, or any file
|
|
194
|
+
* of a member the application runs beside or depends on. Otherwise the routes and stories whose entry
|
|
195
|
+
* modules reach a changed module. A file outside every source root, such as documentation or a
|
|
196
|
+
* workflow, and a file of an unrelated member, change no page.
|
|
197
|
+
* @param inputs the baseline, the change, and the application's members, files and graph.
|
|
198
|
+
* @returns the scope, with a reason for each route and story or for covering everything.
|
|
199
|
+
*/
|
|
200
|
+
export const sweepScopeOf = (inputs) => {
|
|
201
|
+
const changed = inputs.changed.map((file) => file.path);
|
|
202
|
+
if (inputs.baseline.commit === undefined) {
|
|
203
|
+
return { isComplete: true, baseline: undefined, branch: undefined, changed, reasons: [inputs.baseline.problem] };
|
|
204
|
+
}
|
|
205
|
+
const appRoot = appRootOf(inputs.files);
|
|
206
|
+
const effects = inputs.changed.map((file) => effectOf(file, inputs, appRoot));
|
|
207
|
+
const reasons = effects.flatMap((effect) => effect.kind === 'global' ? [effect.reason] : []);
|
|
208
|
+
if (reasons.length > 0) {
|
|
209
|
+
return { isComplete: true, baseline: inputs.baseline.commit, branch: inputs.baseline.branch, changed, reasons };
|
|
210
|
+
}
|
|
211
|
+
const edges = edgesOf(inputs.graph);
|
|
212
|
+
const reaches = effects.flatMap((effect) => {
|
|
213
|
+
if (effect.kind !== 'graph') {
|
|
214
|
+
return [];
|
|
215
|
+
}
|
|
216
|
+
const start = new Set([effect.file]);
|
|
217
|
+
return [{ changed: effect.file, importers: reachedFrom(start, start, edges) }];
|
|
218
|
+
});
|
|
219
|
+
return {
|
|
220
|
+
isComplete: false,
|
|
221
|
+
baseline: inputs.baseline.commit,
|
|
222
|
+
branch: inputs.baseline.branch,
|
|
223
|
+
changed,
|
|
224
|
+
routes: routeTargets(inputs, appRoot, reaches),
|
|
225
|
+
stories: storyTargets(inputs, reaches),
|
|
226
|
+
};
|
|
227
|
+
};
|
|
228
|
+
const isTargets = (value) => isArray(value) &&
|
|
229
|
+
value.every((entry) => isRecord(entry) && typeof entry['target'] === 'string' && typeof entry['reason'] === 'string');
|
|
230
|
+
const isStrings = (value) => isArray(value) && value.every((entry) => typeof entry === 'string');
|
|
231
|
+
/**
|
|
232
|
+
* Whether a parsed value is a scope as `sweepScopeOf` produces it.
|
|
233
|
+
* @param value the parsed value.
|
|
234
|
+
* @returns whether it carries every field its kind has.
|
|
235
|
+
*/
|
|
236
|
+
export const isSweepScope = (value) => {
|
|
237
|
+
if (!(isRecord(value) && isStrings(value['changed']))) {
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
return value['isComplete'] === true
|
|
241
|
+
? isStrings(value['reasons'])
|
|
242
|
+
: value['isComplete'] === false &&
|
|
243
|
+
typeof value['baseline'] === 'string' &&
|
|
244
|
+
typeof value['branch'] === 'string' &&
|
|
245
|
+
isTargets(value['routes']) &&
|
|
246
|
+
isTargets(value['stories']);
|
|
247
|
+
};
|
|
248
|
+
const SHORT_COMMIT = 12;
|
|
249
|
+
const sinceText = (baseline, branch) => baseline === undefined || branch === undefined
|
|
250
|
+
? ''
|
|
251
|
+
: ` since ${baseline.slice(0, SHORT_COMMIT)}, the merge-base of HEAD and ${branch}`;
|
|
252
|
+
/**
|
|
253
|
+
* One line naming the level and what it covers.
|
|
254
|
+
* @param scope the scope.
|
|
255
|
+
* @returns the line.
|
|
256
|
+
*/
|
|
257
|
+
export const scopeHeadline = (scope) => scope.isComplete
|
|
258
|
+
? `delta${sinceText(scope.baseline, scope.branch)}, complete because ${scope.reasons.join('; ')}`
|
|
259
|
+
: `delta${sinceText(scope.baseline, scope.branch)}: ${String(scope.changed.length)} changed file(s) reach ` +
|
|
260
|
+
`${String(scope.routes.length)} route(s) and ${String(scope.stories.length)} story file(s)`;
|
|
261
|
+
const IN_COMPLETE_SCOPE = 'the sweep is complete';
|
|
262
|
+
/**
|
|
263
|
+
* Why the sweep scans a crawled route, or undefined when the change cannot reach it. A route no
|
|
264
|
+
* declaration renders is scanned, because what reaches it is unknown.
|
|
265
|
+
* @param scope the scope.
|
|
266
|
+
* @param declared every route the application's file tree declares.
|
|
267
|
+
* @param route the crawled address.
|
|
268
|
+
* @returns the reason, or undefined.
|
|
269
|
+
*/
|
|
270
|
+
export const routeReason = (scope, declared, route) => {
|
|
271
|
+
if (scope.isComplete) {
|
|
272
|
+
return IN_COMPLETE_SCOPE;
|
|
273
|
+
}
|
|
274
|
+
const resolved = resolvedRouteOf(declared, route);
|
|
275
|
+
return resolved === undefined
|
|
276
|
+
? 'no route file declares it, so what reaches it is unknown'
|
|
277
|
+
: scope.routes.find((target) => target.target === resolved.route)?.reason;
|
|
278
|
+
};
|
|
279
|
+
/**
|
|
280
|
+
* The file a story is declared in, relative to the application's root.
|
|
281
|
+
* @param importPath the story's `importPath`, relative to the Storybook's own directory.
|
|
282
|
+
* @param directory that directory, relative to the application's root; `.` when it is the same.
|
|
283
|
+
* @returns the file.
|
|
284
|
+
*/
|
|
285
|
+
export const storyFileOf = (importPath, directory) => path.posix.normalize(path.posix.join(directory, importPath));
|
|
286
|
+
/**
|
|
287
|
+
* Why the sweep scans a story, or undefined when the change cannot reach it. A story declared outside
|
|
288
|
+
* the application, or in no file Storybook names, is scanned, because what reaches it is unknown.
|
|
289
|
+
* @param scope the scope.
|
|
290
|
+
* @param storyFile the story's file, from `storyFileOf`, or empty when Storybook names none.
|
|
291
|
+
* @returns the reason, or undefined.
|
|
292
|
+
*/
|
|
293
|
+
export const storyReason = (scope, storyFile) => {
|
|
294
|
+
if (scope.isComplete) {
|
|
295
|
+
return IN_COMPLETE_SCOPE;
|
|
296
|
+
}
|
|
297
|
+
if (storyFile === '' || storyFile === '..' || storyFile.startsWith('../')) {
|
|
298
|
+
return 'its file is outside this application, so what reaches it is unknown';
|
|
299
|
+
}
|
|
300
|
+
return scope.stories.find((target) => target.target === storyFile)?.reason;
|
|
301
|
+
};
|
|
302
|
+
const isCruisedModule = (value) => isRecord(value) && typeof value['source'] === 'string' && isArray(value['dependencies']);
|
|
303
|
+
/**
|
|
304
|
+
* The import graph in dependency-cruiser's JSON report, the analyzer the architecture gate runs.
|
|
305
|
+
* @param report the parsed report.
|
|
306
|
+
* @returns each module, relative to where the analyzer ran, and the modules it imports; or undefined
|
|
307
|
+
* when the value is not such a report.
|
|
308
|
+
*/
|
|
309
|
+
export const importGraphOf = (report) => {
|
|
310
|
+
const modules = isRecord(report) ? report['modules'] : undefined;
|
|
311
|
+
if (!(isArray(modules) && modules.every((module) => isCruisedModule(module)))) {
|
|
312
|
+
return undefined;
|
|
313
|
+
}
|
|
314
|
+
return Object.fromEntries(modules
|
|
315
|
+
.filter((module) => isCruisedModule(module))
|
|
316
|
+
.map((module) => [
|
|
317
|
+
module.source,
|
|
318
|
+
module.dependencies.flatMap((dependency) => {
|
|
319
|
+
const resolved = isRecord(dependency) ? dependency['resolved'] : undefined;
|
|
320
|
+
return typeof resolved === 'string' ? [resolved] : [];
|
|
321
|
+
}),
|
|
322
|
+
]));
|
|
323
|
+
};
|
|
324
|
+
/**
|
|
325
|
+
* The scope as a reader sees it: the level and baseline, every changed file, and each route and story
|
|
326
|
+
* with the reason it is swept.
|
|
327
|
+
* @param scope the scope.
|
|
328
|
+
* @returns one line each, indented below the headline.
|
|
329
|
+
*/
|
|
330
|
+
export const scopeLines = (scope) => [
|
|
331
|
+
scopeHeadline(scope),
|
|
332
|
+
...scope.changed.map((file) => ` changed ${file}`),
|
|
333
|
+
...(scope.isComplete
|
|
334
|
+
? []
|
|
335
|
+
: [
|
|
336
|
+
...scope.routes.map((target) => ` route ${target.target}: ${target.reason}`),
|
|
337
|
+
...scope.stories.map((target) => ` story ${target.target}: ${target.reason}`),
|
|
338
|
+
]),
|
|
339
|
+
];
|
|
340
|
+
//# sourceMappingURL=change-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"change-scope.js","sourceRoot":"","sources":["../src/change-scope.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { type RuntimeRole } from './javascript-runtimes.js';
|
|
2
|
+
import type { TerraformViolation } from './terraform-policy.js';
|
|
3
|
+
/** One CloudFront Function a source deploys, as far as a line reader can follow it. */
|
|
4
|
+
export interface CloudFrontFunction {
|
|
5
|
+
readonly line: number;
|
|
6
|
+
/** The literal runtime, or undefined when the argument is absent or an expression. */
|
|
7
|
+
readonly runtime: string | undefined;
|
|
8
|
+
/** The repo-relative file it deploys, or undefined when `code` is anything this reader cannot follow. */
|
|
9
|
+
readonly code: string | undefined;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The repo-relative path `file("${path.module}/<relative>")` names, read from a source at `source`.
|
|
13
|
+
* @param source the repo-relative path of the source that declares the function.
|
|
14
|
+
* @param relative the path written after `${path.module}/`.
|
|
15
|
+
* @returns the resolved path, or undefined when it climbs out of the repository.
|
|
16
|
+
*/
|
|
17
|
+
export declare const resolveModulePath: (source: string, relative: string) => string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Every CloudFront Function a source declares.
|
|
20
|
+
* @param source the repo-relative path of the source, which `${path.module}` resolves against.
|
|
21
|
+
* @param text the source's text, as tracked.
|
|
22
|
+
* @returns one function per resource block, in source order.
|
|
23
|
+
*/
|
|
24
|
+
export declare const cloudFrontFunctionsIn: (source: string, text: string) => readonly CloudFrontFunction[];
|
|
25
|
+
/**
|
|
26
|
+
* Report every CloudFront Function whose deployed file is not linted for the runtime that runs it.
|
|
27
|
+
* @param source the repo-relative path of the source.
|
|
28
|
+
* @param text the source's text, as tracked.
|
|
29
|
+
* @param tracked the repo-relative paths of the tracked files.
|
|
30
|
+
* @param roles every allowlist role with the runtime it declares.
|
|
31
|
+
* @returns one violation per defect, ordered by the line it sits on.
|
|
32
|
+
*/
|
|
33
|
+
export declare const findCloudFrontRuntimeViolations: (source: string, text: string, tracked: ReadonlySet<string>, roles: readonly RuntimeRole[]) => readonly TerraformViolation[];
|
|
34
|
+
//# sourceMappingURL=cloudfront-functions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudfront-functions.d.ts","sourceRoot":"","sources":["../src/cloudfront-functions.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsB,KAAK,WAAW,EAAc,MAAM,0BAA0B,CAAA;AAG3F,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAA;AAiB/D,uFAAuF;AACvF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,sFAAsF;IACtF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,yGAAyG;IACzG,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAA;CAClC;AAkBD;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,EAAE,UAAU,MAAM,KAAG,MAAM,GAAG,SAO7E,CAAA;AAaD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,KAAG,SAAS,kBAAkB,EAO7F,CAAA;AA0EH;;;;;;;GAOG;AACH,eAAO,MAAM,+BAA+B,GAC1C,QAAQ,MAAM,EACd,MAAM,MAAM,EACZ,SAAS,WAAW,CAAC,MAAM,CAAC,EAC5B,OAAO,SAAS,WAAW,EAAE,KAC5B,SAAS,kBAAkB,EAI1B,CAAA"}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
// Tie each CloudFront Function an OpenTofu or Terraform source deploys to the allowlist entry that lints it.
|
|
2
|
+
//
|
|
3
|
+
// The lint rules for a CloudFront Function apply only to a file whose allowlist entry declares the
|
|
4
|
+
// CloudFront runtime, and nothing in the file itself says which engine runs it. The resource that
|
|
5
|
+
// deploys it does say so. Reading the two together means a file that CloudFront runs cannot be declared
|
|
6
|
+
// a Node.js program by mistake. A `code` argument this reader cannot follow fails, rather than being
|
|
7
|
+
// skipped, because a skipped function is deployed with nothing having linted it.
|
|
8
|
+
import { CLOUDFRONT_RUNTIME, runtimesOf } from './javascript-runtimes.js';
|
|
9
|
+
import { stripComments } from './source-text.js';
|
|
10
|
+
import { resourceBlocksIn } from './terraform-blocks.js';
|
|
11
|
+
const FUNCTION_TYPE = 'aws_cloudfront_function';
|
|
12
|
+
// HCL's own interpolation, spelled so it is not read as a misplaced template hole.
|
|
13
|
+
const DOLLAR = '$';
|
|
14
|
+
const HOLE = `${DOLLAR}{`;
|
|
15
|
+
const MODULE_DIRECTORY = `${HOLE}path.module}`;
|
|
16
|
+
const RULE = 'cloudfront-runtime';
|
|
17
|
+
// The one spelling followed: the module's own directory plus a literal path. A bare relative path is
|
|
18
|
+
// resolved against the directory OpenTofu runs in, which a source does not state, and every other form
|
|
19
|
+
// is an expression or a built artefact this reader cannot evaluate.
|
|
20
|
+
const MODULE_FILE = /^[ \t]*code[ \t]*=[ \t]*file\([ \t]*"\$\{path\.module\}\/(?<path>[^"$]+)"[ \t]*\)[ \t]*$/;
|
|
21
|
+
const CODE_ARGUMENT = /^[ \t]*code[ \t]*=/;
|
|
22
|
+
const LITERAL_RUNTIME = /^[ \t]*runtime[ \t]*=[ \t]*"(?<runtime>[^"$]*)"[ \t]*$/;
|
|
23
|
+
const directoryOf = (file) => file.slice(0, Math.max(0, file.lastIndexOf('/')));
|
|
24
|
+
const PARENT = '..';
|
|
25
|
+
// Undefined once a `..` climbs above the repository, which no tracked file can sit at.
|
|
26
|
+
const normalisedSegments = (segments) => segments.reduce((resolved, segment) => {
|
|
27
|
+
if (resolved === undefined || segment === '.' || segment === '') {
|
|
28
|
+
return resolved;
|
|
29
|
+
}
|
|
30
|
+
if (segment !== PARENT) {
|
|
31
|
+
return [...resolved, segment];
|
|
32
|
+
}
|
|
33
|
+
return resolved.length === 0 ? undefined : resolved.slice(0, -1);
|
|
34
|
+
}, []);
|
|
35
|
+
/**
|
|
36
|
+
* The repo-relative path `file("${path.module}/<relative>")` names, read from a source at `source`.
|
|
37
|
+
* @param source the repo-relative path of the source that declares the function.
|
|
38
|
+
* @param relative the path written after `${path.module}/`.
|
|
39
|
+
* @returns the resolved path, or undefined when it climbs out of the repository.
|
|
40
|
+
*/
|
|
41
|
+
export const resolveModulePath = (source, relative) => {
|
|
42
|
+
const directory = directoryOf(source);
|
|
43
|
+
const segments = normalisedSegments([
|
|
44
|
+
...(directory.length === 0 ? [] : directory.split('/')),
|
|
45
|
+
...relative.split('/'),
|
|
46
|
+
]);
|
|
47
|
+
return segments === undefined || segments.length === 0 ? undefined : segments.join('/');
|
|
48
|
+
};
|
|
49
|
+
const codeOf = (source, body) => {
|
|
50
|
+
const line = body.find((candidate) => CODE_ARGUMENT.test(candidate));
|
|
51
|
+
const relative = line === undefined ? undefined : MODULE_FILE.exec(line)?.groups?.['path'];
|
|
52
|
+
return relative === undefined ? undefined : resolveModulePath(source, relative);
|
|
53
|
+
};
|
|
54
|
+
const runtimeOf = (body) => body
|
|
55
|
+
.map((line) => LITERAL_RUNTIME.exec(line)?.groups?.['runtime'])
|
|
56
|
+
.find((runtime) => runtime !== undefined);
|
|
57
|
+
/**
|
|
58
|
+
* Every CloudFront Function a source declares.
|
|
59
|
+
* @param source the repo-relative path of the source, which `${path.module}` resolves against.
|
|
60
|
+
* @param text the source's text, as tracked.
|
|
61
|
+
* @returns one function per resource block, in source order.
|
|
62
|
+
*/
|
|
63
|
+
export const cloudFrontFunctionsIn = (source, text) => resourceBlocksIn(stripComments(text).split('\n'), FUNCTION_TYPE).map((block) => ({
|
|
64
|
+
line: block.line,
|
|
65
|
+
runtime: runtimeOf(block.body),
|
|
66
|
+
code: codeOf(source, block.body),
|
|
67
|
+
}));
|
|
68
|
+
const violation = (line, reason) => ({ line, rule: RULE, reason });
|
|
69
|
+
const runtimeViolations = (deployed) => {
|
|
70
|
+
if (deployed.runtime === CLOUDFRONT_RUNTIME) {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const stated = deployed.runtime === undefined
|
|
74
|
+
? 'declares no literal runtime, so AWS runs cloudfront-js-1.0'
|
|
75
|
+
: `runs runtime ${deployed.runtime}`;
|
|
76
|
+
return [
|
|
77
|
+
violation(deployed.line, `this CloudFront function ${stated}, which this harness does not lint for - ` +
|
|
78
|
+
`set runtime = "${CLOUDFRONT_RUNTIME}"`),
|
|
79
|
+
];
|
|
80
|
+
};
|
|
81
|
+
const allowlistViolations = (deployed, file, roles) => {
|
|
82
|
+
const declared = runtimesOf(file, roles);
|
|
83
|
+
if (declared.length === 0) {
|
|
84
|
+
return [
|
|
85
|
+
violation(deployed.line, `this CloudFront function deploys ${file}, which no javascriptAllowlist entry admits - ` +
|
|
86
|
+
`add one declaring "runtime": "${CLOUDFRONT_RUNTIME}"`),
|
|
87
|
+
];
|
|
88
|
+
}
|
|
89
|
+
return declared.some((runtime) => runtime !== CLOUDFRONT_RUNTIME)
|
|
90
|
+
? [
|
|
91
|
+
violation(deployed.line, `this CloudFront function deploys ${file}, which javascriptAllowlist declares as runtime ` +
|
|
92
|
+
`${declared.join(' and ')} - declare "runtime": "${CLOUDFRONT_RUNTIME}" so it is linted for ` +
|
|
93
|
+
'the engine that runs it'),
|
|
94
|
+
]
|
|
95
|
+
: [];
|
|
96
|
+
};
|
|
97
|
+
const codeViolations = (deployed, tracked, roles) => {
|
|
98
|
+
if (deployed.code === undefined) {
|
|
99
|
+
return [
|
|
100
|
+
violation(deployed.line, 'cannot tell which file this CloudFront function deploys - load a tracked, allowlisted file with ' +
|
|
101
|
+
`code = file("${MODULE_DIRECTORY}/<path>")`),
|
|
102
|
+
];
|
|
103
|
+
}
|
|
104
|
+
if (!tracked.has(deployed.code)) {
|
|
105
|
+
return [
|
|
106
|
+
violation(deployed.line, `this CloudFront function deploys ${deployed.code}, which the repository does not track - deploy ` +
|
|
107
|
+
'a tracked file, so the file that runs is the file that is linted'),
|
|
108
|
+
];
|
|
109
|
+
}
|
|
110
|
+
return allowlistViolations(deployed, deployed.code, roles);
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* Report every CloudFront Function whose deployed file is not linted for the runtime that runs it.
|
|
114
|
+
* @param source the repo-relative path of the source.
|
|
115
|
+
* @param text the source's text, as tracked.
|
|
116
|
+
* @param tracked the repo-relative paths of the tracked files.
|
|
117
|
+
* @param roles every allowlist role with the runtime it declares.
|
|
118
|
+
* @returns one violation per defect, ordered by the line it sits on.
|
|
119
|
+
*/
|
|
120
|
+
export const findCloudFrontRuntimeViolations = (source, text, tracked, roles) => cloudFrontFunctionsIn(source, text).flatMap((deployed) => [
|
|
121
|
+
...runtimeViolations(deployed),
|
|
122
|
+
...codeViolations(deployed, tracked, roles),
|
|
123
|
+
]);
|
|
124
|
+
//# sourceMappingURL=cloudfront-functions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudfront-functions.js","sourceRoot":"","sources":["../src/cloudfront-functions.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,8 +10,11 @@ export declare const GENERATED_ARTEFACTS: readonly string[];
|
|
|
10
10
|
* ploaness repository denies its own regenerated asset bodies through the same shaped list, written by
|
|
11
11
|
* hand in `.claude/settings.json` because nothing here runs `ploaness sync`. Closing over the constant
|
|
12
12
|
* would have made the rule untestable against any set but that one.
|
|
13
|
+
*
|
|
14
|
+
* The runtime matches a file path only through `Edit(...)`, which binds every tool that writes a file;
|
|
15
|
+
* it reports a `Write(...)` path rule as one that never matches.
|
|
13
16
|
* @param artefacts the repo-relative paths a generator owns.
|
|
14
|
-
* @returns the
|
|
17
|
+
* @returns the denials the runtime settings must carry.
|
|
15
18
|
*/
|
|
16
19
|
export declare const requiredDenyRules: (artefacts: readonly string[]) => readonly string[];
|
|
17
20
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-denial.d.ts","sourceRoot":"","sources":["../src/generated-denial.ts"],"names":[],"mappings":"AAGA,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,EAAE,MAAkC,CAAA;AAExE,sGAAsG;AACtG,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAIhD,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"generated-denial.d.ts","sourceRoot":"","sources":["../src/generated-denial.ts"],"names":[],"mappings":"AAGA,iFAAiF;AACjF,eAAO,MAAM,uBAAuB,EAAE,MAAkC,CAAA;AAExE,sGAAsG;AACtG,eAAO,MAAM,mBAAmB,EAAE,SAAS,MAAM,EAIhD,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,GAAI,WAAW,SAAS,MAAM,EAAE,KAAG,SAAS,MAAM,EACd,CAAA;AAalE;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,UAAU,OAAO,EAAE,WAAW,SAAS,MAAM,EAAE,KAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAWtG,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,GAC/B,UAAU,OAAO,EACjB,eAAe,OAAO,EACtB,WAAW,SAAS,MAAM,EAAE,KAC3B,SAAS,MAAM,EAgBjB,CAAA;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,cAAc,GAAI,aAAa,SAAS,MAAM,EAAE,EAAE,WAAW,SAAS,MAAM,EAAE,KAAG,SAAS,MAAM,EAG1G,CAAA"}
|
package/dist/generated-denial.js
CHANGED
|
@@ -16,10 +16,17 @@ export const GENERATED_ARTEFACTS = [
|
|
|
16
16
|
* ploaness repository denies its own regenerated asset bodies through the same shaped list, written by
|
|
17
17
|
* hand in `.claude/settings.json` because nothing here runs `ploaness sync`. Closing over the constant
|
|
18
18
|
* would have made the rule untestable against any set but that one.
|
|
19
|
+
*
|
|
20
|
+
* The runtime matches a file path only through `Edit(...)`, which binds every tool that writes a file;
|
|
21
|
+
* it reports a `Write(...)` path rule as one that never matches.
|
|
19
22
|
* @param artefacts the repo-relative paths a generator owns.
|
|
20
|
-
* @returns the
|
|
23
|
+
* @returns the denials the runtime settings must carry.
|
|
21
24
|
*/
|
|
22
|
-
export const requiredDenyRules = (artefacts) => artefacts.
|
|
25
|
+
export const requiredDenyRules = (artefacts) => artefacts.map((artefact) => `Edit(${artefact})`);
|
|
26
|
+
// The `Write(...)` form of a required denial never matches, so it protects nothing and only makes the
|
|
27
|
+
// runtime warn. It is removed for the artefacts given; a `Write(...)` rule for any other path is the
|
|
28
|
+
// project's and stays.
|
|
29
|
+
const isInertDenial = (rule, artefacts) => artefacts.some((artefact) => rule === `Write(${artefact})`);
|
|
23
30
|
const permissionStrings = (settings, key) => {
|
|
24
31
|
const inner = readKey(readKey(settings, 'permissions'), key);
|
|
25
32
|
return isArray(inner) ? inner.filter((entry) => typeof entry === 'string') : [];
|
|
@@ -33,7 +40,7 @@ const permissionStrings = (settings, key) => {
|
|
|
33
40
|
export const applyDenyRules = (existing, artefacts) => {
|
|
34
41
|
const base = { ...asRecord(existing) };
|
|
35
42
|
const permissions = { ...asRecord(base['permissions']) };
|
|
36
|
-
const deny = permissionStrings(base, 'deny');
|
|
43
|
+
const deny = permissionStrings(base, 'deny').filter((rule) => !isInertDenial(rule, artefacts));
|
|
37
44
|
const merged = [
|
|
38
45
|
...deny,
|
|
39
46
|
...requiredDenyRules(artefacts).filter((rule) => !deny.includes(rule)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated-denial.js","sourceRoot":"","sources":["../src/generated-denial.ts"],"names":[],"mappings":"AAAA,gGAAgG;AAChG,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAE7D,iFAAiF;AACjF,MAAM,CAAC,MAAM,uBAAuB,GAAW,yBAAyB,CAAA;AAExE,sGAAsG;AACtG,MAAM,CAAC,MAAM,mBAAmB,GAAsB;IACpD,sBAAsB;IACtB,GAAG,uBAAuB,eAAe;IACzC,iCAAiC;CAClC,CAAA;AAED
|
|
1
|
+
{"version":3,"file":"generated-denial.js","sourceRoot":"","sources":["../src/generated-denial.ts"],"names":[],"mappings":"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"}
|