@ploaness/governance 1.0.0 → 1.1.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-hit-target.d.ts +35 -0
- package/dist/a11y-hit-target.d.ts.map +1 -0
- package/dist/a11y-hit-target.js +102 -0
- package/dist/a11y-hit-target.js.map +1 -0
- package/dist/a11y-incomplete.d.ts +18 -0
- package/dist/a11y-incomplete.d.ts.map +1 -0
- package/dist/a11y-incomplete.js +82 -0
- package/dist/a11y-incomplete.js.map +1 -0
- package/dist/access-boundary.d.ts +19 -5
- package/dist/access-boundary.d.ts.map +1 -1
- package/dist/access-boundary.js +58 -4
- package/dist/access-boundary.js.map +1 -1
- package/dist/admin-view-coverage.d.ts +25 -0
- package/dist/admin-view-coverage.d.ts.map +1 -0
- package/dist/admin-view-coverage.js +90 -0
- package/dist/admin-view-coverage.js.map +1 -0
- package/dist/axe-coverage.d.ts +41 -0
- package/dist/axe-coverage.d.ts.map +1 -0
- package/dist/axe-coverage.js +95 -0
- package/dist/axe-coverage.js.map +1 -0
- package/dist/banned-typography.d.ts.map +1 -1
- package/dist/banned-typography.js +26 -0
- package/dist/banned-typography.js.map +1 -1
- package/dist/build-diagnosis.d.ts +10 -0
- package/dist/build-diagnosis.d.ts.map +1 -0
- package/dist/build-diagnosis.js +38 -0
- package/dist/build-diagnosis.js.map +1 -0
- package/dist/document-references.d.ts.map +1 -1
- package/dist/document-references.js +7 -1
- package/dist/document-references.js.map +1 -1
- package/dist/environment-coherence.d.ts +87 -0
- package/dist/environment-coherence.d.ts.map +1 -0
- package/dist/environment-coherence.js +150 -0
- package/dist/environment-coherence.js.map +1 -0
- package/dist/generated-drift.d.ts +18 -0
- package/dist/generated-drift.d.ts.map +1 -0
- package/dist/generated-drift.js +35 -0
- package/dist/generated-drift.js.map +1 -0
- package/dist/index.d.ts +10 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/index.js.map +1 -1
- package/dist/payload-access.d.ts.map +1 -1
- package/dist/payload-access.js +23 -49
- package/dist/payload-access.js.map +1 -1
- package/dist/payload-configs.d.ts +24 -0
- package/dist/payload-configs.d.ts.map +1 -0
- package/dist/payload-configs.js +77 -0
- package/dist/payload-configs.js.map +1 -0
- package/dist/payload-field-access.d.ts +4 -0
- package/dist/payload-field-access.d.ts.map +1 -0
- package/dist/payload-field-access.js +51 -0
- package/dist/payload-field-access.js.map +1 -0
- package/dist/payload-policy.d.ts.map +1 -1
- package/dist/payload-policy.js +111 -13
- package/dist/payload-policy.js.map +1 -1
- package/dist/payload-source.js +2 -2
- package/dist/payload-source.js.map +1 -1
- package/dist/relationship-cleanup.d.ts +18 -0
- package/dist/relationship-cleanup.d.ts.map +1 -0
- package/dist/relationship-cleanup.js +96 -0
- package/dist/relationship-cleanup.js.map +1 -0
- package/dist/route-coverage.d.ts +27 -0
- package/dist/route-coverage.d.ts.map +1 -0
- package/dist/route-coverage.js +57 -0
- package/dist/route-coverage.js.map +1 -0
- package/dist/route-map.d.ts +65 -0
- package/dist/route-map.d.ts.map +1 -0
- package/dist/route-map.js +183 -0
- package/dist/route-map.js.map +1 -0
- package/dist/secret-policy.js +4 -4
- package/dist/secret-policy.js.map +1 -1
- package/dist/settings.d.ts +10 -0
- package/dist/settings.d.ts.map +1 -1
- package/dist/settings.js +5 -1
- package/dist/settings.js.map +1 -1
- package/dist/source-security.d.ts +4 -0
- package/dist/source-security.d.ts.map +1 -0
- package/dist/source-security.js +56 -0
- package/dist/source-security.js.map +1 -0
- package/dist/source-text.d.ts +14 -0
- package/dist/source-text.d.ts.map +1 -1
- package/dist/source-text.js +39 -0
- package/dist/source-text.js.map +1 -1
- package/dist/version-policy.d.ts.map +1 -1
- package/dist/version-policy.js +31 -0
- package/dist/version-policy.js.map +1 -1
- package/package.json +1 -1
- package/dist/url-safety.d.ts +0 -12
- package/dist/url-safety.d.ts.map +0 -1
- package/dist/url-safety.js +0 -58
- package/dist/url-safety.js.map +0 -1
package/dist/payload-policy.js
CHANGED
|
@@ -11,6 +11,8 @@
|
|
|
11
11
|
// relative to the config that declares it, which does not survive being extended from node_modules, so
|
|
12
12
|
// they are reimplemented here instead. The move also makes them unit-testable, which they were not.
|
|
13
13
|
import { findAnonymousDraftReads, findUndeclaredAccess, findUnhardenedAuth, findUnrestrictedUploads, } from './payload-access.js';
|
|
14
|
+
import { findUnprotectedPrivilegedFields } from './payload-field-access.js';
|
|
15
|
+
import { findFailOpenSecretGuards } from './source-security.js';
|
|
14
16
|
import { balancedArguments, lineOf, occurrences, stripComments, topLevelSlice, } from './source-text.js';
|
|
15
17
|
// The Local API reads that must declare their bound. `find` returns a page, so either `depth` or `limit`
|
|
16
18
|
// bounds it; the single-document and global reads return one document, so only `depth` applies.
|
|
@@ -40,20 +42,34 @@ const BOUNDED_CALLS = [
|
|
|
40
42
|
// every such call went unjudged. The chain is admitted only in front of `req`, so a property named
|
|
41
43
|
// `payload` on some unrelated object is still left alone.
|
|
42
44
|
const PAYLOAD_RECEIVER = /(?:^|[^\w$.])(?:payload|(?:[\w$]+\.)*req\.payload|this\.payload)$/;
|
|
43
|
-
const declaresKey = (topLevel, key) => new RegExp(String.raw `(
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const argumentText = balancedArguments(source, found + rule.call.length - 1);
|
|
52
|
-
if (argumentText === undefined || !argumentText.includes('{') || argumentText.includes('...')) {
|
|
45
|
+
const declaresKey = (topLevel, key) => new RegExp(String.raw `(?:^|,)\s*${key}\s*:`).test(topLevel);
|
|
46
|
+
// A shorthand property has no colon, so `declaresKey` cannot see it. The property boundary must be the
|
|
47
|
+
// start of the literal or a comma: admitting arbitrary whitespace made the `req` VALUE in
|
|
48
|
+
// `request: req` look like a `req` PROPERTY and vouched for a transaction the call never received.
|
|
49
|
+
const declaresProperty = (topLevel, key) => new RegExp(String.raw `(?:^|,)\s*${key}\s*(?::|,|$)`).test(topLevel);
|
|
50
|
+
// The depth-one text of a call's options literal, or undefined when the call is not one to judge.
|
|
51
|
+
const topLevelOptionsAt = (source, call, found, receiver) => {
|
|
52
|
+
if (!receiver.test(source.slice(0, found))) {
|
|
53
53
|
return undefined;
|
|
54
54
|
}
|
|
55
|
-
const
|
|
56
|
-
|
|
55
|
+
const argumentText = balancedArguments(source, found + call.length - 1);
|
|
56
|
+
return argumentText?.includes('{') === true ? topLevelSlice(argumentText) : undefined;
|
|
57
|
+
};
|
|
58
|
+
// A top-level spread makes an absent key unknowable, so the rules that ask only whether a key exists
|
|
59
|
+
// stay silent. A spread nested inside `data` is elided by `topLevelSlice` and changes none of the
|
|
60
|
+
// options these rules judge.
|
|
61
|
+
//
|
|
62
|
+
// Shared by the two rules that read an options object. They ask different questions - is the read
|
|
63
|
+
// bounded, is the request threaded - of the same three facts, and a second copy of the three guards is a
|
|
64
|
+
// second place for the receiver pattern to fall out of step with this one.
|
|
65
|
+
const optionKeysAt = (source, call, found, receiver) => {
|
|
66
|
+
const topLevel = topLevelOptionsAt(source, call, found, receiver);
|
|
67
|
+
return topLevel === undefined || topLevel.includes('...') ? undefined : topLevel;
|
|
68
|
+
};
|
|
69
|
+
const unboundedCallAt = (source, rule, found) => {
|
|
70
|
+
const topLevel = optionKeysAt(source, rule.call, found, PAYLOAD_RECEIVER);
|
|
71
|
+
if (topLevel === undefined ||
|
|
72
|
+
rule.required.some((key) => declaresKey(topLevel, key))) {
|
|
57
73
|
return undefined;
|
|
58
74
|
}
|
|
59
75
|
return { line: lineOf(source, found), rule: rule.rule, reason: rule.reason };
|
|
@@ -61,6 +77,82 @@ const unboundedCallAt = (source, rule, found) => {
|
|
|
61
77
|
const findUnboundedCalls = (source) => BOUNDED_CALLS.flatMap((rule) => occurrences(source, rule.call)
|
|
62
78
|
.map((found) => unboundedCallAt(source, rule, found))
|
|
63
79
|
.filter((violation) => violation !== undefined));
|
|
80
|
+
// The collection, global, and version operations whose options carry both the caller and the request.
|
|
81
|
+
// One catalogue feeds both rules below: adding a Payload operation to one contract and not the other
|
|
82
|
+
// would leave a call either outside its transaction or running as an administrator with a decorative
|
|
83
|
+
// user value.
|
|
84
|
+
const LOCAL_API_CALLS = [
|
|
85
|
+
'.count(',
|
|
86
|
+
'.create(',
|
|
87
|
+
'.delete(',
|
|
88
|
+
'.find(',
|
|
89
|
+
'.findByID(',
|
|
90
|
+
'.findDistinct(',
|
|
91
|
+
'.findGlobal(',
|
|
92
|
+
'.findGlobalVersionByID(',
|
|
93
|
+
'.findGlobalVersions(',
|
|
94
|
+
'.findVersionByID(',
|
|
95
|
+
'.findVersions(',
|
|
96
|
+
'.restoreGlobalVersion(',
|
|
97
|
+
'.restoreVersion(',
|
|
98
|
+
'.update(',
|
|
99
|
+
'.updateGlobal(',
|
|
100
|
+
];
|
|
101
|
+
// Only the request-scoped instance is judged, which is the whole reason the rule can be trusted. A bare
|
|
102
|
+
// `payload` from `getPayload()` - a script, a seed, a Server Component that opened its own instance -
|
|
103
|
+
// has no request to thread, and demanding one would be asking for a value that does not exist. Reaching
|
|
104
|
+
// the instance THROUGH `req` is the proof that one does, so the omission is never a decision.
|
|
105
|
+
const REQUEST_RECEIVER = /(?:^|[^\w$.])(?:[\w$]+\.)*req\.payload$/;
|
|
106
|
+
const unthreadedCallAt = (source, call, found) => {
|
|
107
|
+
const topLevel = optionKeysAt(source, call, found, REQUEST_RECEIVER);
|
|
108
|
+
if (topLevel === undefined || declaresProperty(topLevel, 'req')) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
line: lineOf(source, found),
|
|
113
|
+
rule: 'no-unthreaded-req',
|
|
114
|
+
reason: `pass req to ${call.slice(1, -1)}() so it joins the caller's transaction; reached through ` +
|
|
115
|
+
'req.payload without it, the operation opens its own - it cannot see the in-flight write, and ' +
|
|
116
|
+
'it is not rolled back with it',
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
// The defect this catches is invisible in a passing test suite and in a code review. A hook that reads
|
|
120
|
+
// or writes through `req.payload` without `req` looks identical to one that threads it, runs correctly
|
|
121
|
+
// whenever no transaction is open, and corrupts a document only when one is - which is precisely when a
|
|
122
|
+
// hook runs. Payload's own documentation calls threading it critical, and no type checker can require it
|
|
123
|
+
// because the parameter is optional for the callers that genuinely have no request.
|
|
124
|
+
const findUnthreadedRequests = (source) => LOCAL_API_CALLS.flatMap((call) => occurrences(source, call)
|
|
125
|
+
.map((found) => unthreadedCallAt(source, call, found))
|
|
126
|
+
.filter((violation) => violation !== undefined));
|
|
127
|
+
const OVERRIDE_PROPERTY = /(?:^|,)\s*overrideAccess\s*:\s*([^,]*)/g;
|
|
128
|
+
const overrideProperties = (topLevel) => [...topLevel.matchAll(OVERRIDE_PROPERTY)].map((match) => ({
|
|
129
|
+
index: match.index,
|
|
130
|
+
value: (match[1] ?? '').trim(),
|
|
131
|
+
}));
|
|
132
|
+
// A later spread can replace an earlier false. A false written after the last spread is the only form
|
|
133
|
+
// whose effective value this text reader can prove without resolving another object.
|
|
134
|
+
const hasEffectiveAccessControl = (topLevel) => {
|
|
135
|
+
const last = overrideProperties(topLevel).at(-1);
|
|
136
|
+
return last?.value === 'false' && last.index > topLevel.lastIndexOf('...');
|
|
137
|
+
};
|
|
138
|
+
const userAccessViolationAt = (source, call, found) => {
|
|
139
|
+
const topLevel = topLevelOptionsAt(source, call, found, PAYLOAD_RECEIVER);
|
|
140
|
+
if (topLevel === undefined ||
|
|
141
|
+
!declaresProperty(topLevel, 'user') ||
|
|
142
|
+
hasEffectiveAccessControl(topLevel) ||
|
|
143
|
+
overrideProperties(topLevel).some((property) => property.value === 'true')) {
|
|
144
|
+
return undefined;
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
line: lineOf(source, found),
|
|
148
|
+
rule: 'require-user-access-control',
|
|
149
|
+
reason: `set overrideAccess: false on ${call.slice(1, -1)}() when passing user; Payload otherwise ` +
|
|
150
|
+
'runs the operation as an administrator and ignores that user for access control',
|
|
151
|
+
};
|
|
152
|
+
};
|
|
153
|
+
const findIgnoredUsers = (source) => LOCAL_API_CALLS.flatMap((call) => occurrences(source, call)
|
|
154
|
+
.map((found) => userAccessViolationAt(source, call, found))
|
|
155
|
+
.filter((violation) => violation !== undefined));
|
|
64
156
|
const OVERRIDE_ACCESS = /overrideAccess\s*:\s*true/g;
|
|
65
157
|
const findOverrideAccess = (source) => [...source.matchAll(OVERRIDE_ACCESS)].map((match) => ({
|
|
66
158
|
line: lineOf(source, match.index),
|
|
@@ -102,7 +194,10 @@ const findDeepRelativeImports = (source) => [...source.matchAll(RELATIVE_IMPORT)
|
|
|
102
194
|
* @param source the file's text.
|
|
103
195
|
* @returns one violation per offending line.
|
|
104
196
|
*/
|
|
105
|
-
export const findSourceViolations = (source) =>
|
|
197
|
+
export const findSourceViolations = (source) => [
|
|
198
|
+
...findDeepRelativeImports(stripComments(source)),
|
|
199
|
+
...findFailOpenSecretGuards(source),
|
|
200
|
+
];
|
|
106
201
|
/**
|
|
107
202
|
* The rules that are about Payload itself, which only a Payload package can break.
|
|
108
203
|
* @param source the file's text.
|
|
@@ -114,11 +209,14 @@ export const findPayloadViolations = (source) => {
|
|
|
114
209
|
const code = stripComments(source);
|
|
115
210
|
return [
|
|
116
211
|
...findUnboundedCalls(code),
|
|
212
|
+
...findUnthreadedRequests(code),
|
|
213
|
+
...findIgnoredUsers(code),
|
|
117
214
|
...findOverrideAccess(code),
|
|
118
215
|
...findUndeclaredAccess(code),
|
|
119
216
|
...findUnhardenedAuth(code),
|
|
120
217
|
...findAnonymousDraftReads(code),
|
|
121
218
|
...findUnrestrictedUploads(code),
|
|
219
|
+
...findUnprotectedPrivilegedFields(code),
|
|
122
220
|
];
|
|
123
221
|
};
|
|
124
222
|
//# sourceMappingURL=payload-policy.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payload-policy.js","sourceRoot":"","sources":["../src/payload-policy.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,mGAAmG;AACnG,EAAE;AACF,wGAAwG;AACxG,qGAAqG;AACrG,uGAAuG;AACvG,wGAAwG;AACxG,sGAAsG;AACtG,EAAE;AACF,mGAAmG;AACnG,uGAAuG;AACvG,oGAAoG;AACpG,OAAO,EACL,uBAAuB,EACvB,oBAAoB,EACpB,kBAAkB,EAClB,uBAAuB,GACxB,MAAM,qBAAqB,CAAA;
|
|
1
|
+
{"version":3,"file":"payload-policy.js","sourceRoot":"","sources":["../src/payload-policy.ts"],"names":[],"mappings":"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"}
|
package/dist/payload-source.js
CHANGED
|
@@ -41,8 +41,8 @@ export const depthOneValue = (wrapped, key) => {
|
|
|
41
41
|
* @returns the block's depth-one keys, or an empty list when the key is absent.
|
|
42
42
|
*/
|
|
43
43
|
export const depthOneBlockKeys = (wrapped, key) => {
|
|
44
|
-
const
|
|
45
|
-
return
|
|
44
|
+
const value = depthOneValue(wrapped, key);
|
|
45
|
+
return value?.trimStart().startsWith('{') === true ? topLevelKeys(value, 0) : [];
|
|
46
46
|
};
|
|
47
47
|
const withClose = (spans, close) => {
|
|
48
48
|
const last = spans.at(LAST_CHARACTER);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payload-source.js","sourceRoot":"","sources":["../src/payload-source.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,6BAA6B;AAC7B,EAAE;AACF,qGAAqG;AACrG,mGAAmG;AACnG,mDAAmD;AACnD,OAAO,EACL,iBAAiB,EAEjB,cAAc,EACd,SAAS,EAET,aAAa,EACb,YAAY,GACb,MAAM,kBAAkB,CAAA;AASzB,wGAAwG;AACxG,yGAAyG;AACzG,sEAAsE;AACtE,EAAE;AACF,sGAAsG;AACtG,uGAAuG;AACvG,uGAAuG;AACvG,sGAAsG;AACtG,iGAAiG;AACjG,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAE,GAAW,EAAU,EAAE;IAC7D,MAAM,OAAO,GAAW,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAA,WAAW,GAAG,MAAM,CAAC,CAAA;IAClE,OAAO,aAAa,CAClB,IAAI,EACJ,CAAC,EACD,CAAC,KAAa,EAAE,IAAc,EAAkB,EAAE,CAChD,IAAI,CAAC,KAAK,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACtD,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE;QACnC,CAAC,CAAC,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EACnC,SAAS,CACV,CAAA;AACH,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,OAAe,EAAE,GAAW,EAAsB,EAAE;IAChF,MAAM,EAAE,GAAW,gBAAgB,CAAC,OAAO,EAAE,GAAG,CAAC,CAAA;IACjD,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;QACrB,OAAO,SAAS,CAAA;IAClB,CAAC;IACD,MAAM,KAAK,GAAW,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAC9C,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAA;AACnE,CAAC,CAAA;AAED;;;;;GAKG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAE,GAAW,EAAqB,EAAE;IACnF,MAAM,
|
|
1
|
+
{"version":3,"file":"payload-source.js","sourceRoot":"","sources":["../src/payload-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { SpecSource } from './axe-coverage.js';
|
|
2
|
+
import { type PayloadViolation } from './payload-source.js';
|
|
3
|
+
/** A violation together with the file it was found in, because this rule reads more than one. */
|
|
4
|
+
export interface LocatedViolation {
|
|
5
|
+
readonly path: string;
|
|
6
|
+
readonly violation: PayloadViolation;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Every required single-value relationship whose target collection cannot be deleted because of it.
|
|
10
|
+
*
|
|
11
|
+
* Reads the whole candidate set at once, because the collection a `relationTo` names is declared in
|
|
12
|
+
* another file. A target this set does not declare - a collection a plugin contributes, say - is left
|
|
13
|
+
* alone rather than reported, since nothing here can see whether it takes its dependants down.
|
|
14
|
+
* @param files every source file the gate collected, as a path and its text.
|
|
15
|
+
* @returns one violation per unguarded relationship, located in the file that declares it.
|
|
16
|
+
*/
|
|
17
|
+
export declare const findUnguardedRelationships: (files: readonly SpecSource[]) => readonly LocatedViolation[];
|
|
18
|
+
//# sourceMappingURL=relationship-cleanup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relationship-cleanup.d.ts","sourceRoot":"","sources":["../src/relationship-cleanup.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AACnD,OAAO,EAIL,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAA;AAG5B,iGAAiG;AACjG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;CACrC;AAqGD;;;;;;;;GAQG;AACH,eAAO,MAAM,0BAA0B,GACrC,OAAO,SAAS,UAAU,EAAE,KAC3B,SAAS,gBAAgB,EA4B3B,CAAA"}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { configBody, depthOneBlockKeys, depthOneValue, } from './payload-source.js';
|
|
2
|
+
import { enclosingLiteral, lineOf, occurrences, stripComments } from './source-text.js';
|
|
3
|
+
// Only the annotation and `satisfies` forms a collection is actually written in, matching
|
|
4
|
+
// `payload-access.ts`. A global has no relationship column of its own to constrain.
|
|
5
|
+
const COLLECTION_DECLARATION = /(:|satisfies)\s*CollectionConfig(?=[<=,)\s]|$)/g;
|
|
6
|
+
const SATISFIES = 'satisfies';
|
|
7
|
+
// The two field types that put a foreign key on the table rather than in the join table. `upload` is
|
|
8
|
+
// one of them: it is a relationship to the media collection wearing a different name.
|
|
9
|
+
const KEYED_FIELD_TYPES = ['relationship', 'upload'];
|
|
10
|
+
// Biome normalises a project to single quotes; nothing obliges a project to have run it yet.
|
|
11
|
+
const QUOTE_STYLES = ["'", '"'];
|
|
12
|
+
const TRUE_VALUE = /^\s*true\b/;
|
|
13
|
+
const QUOTED_VALUE = /^\s*['"`]([^'"`]+)['"`]/;
|
|
14
|
+
const DRAFTS_ENABLED = /drafts\s*:\s*(?:true|\{)/;
|
|
15
|
+
const CLEANUP_HOOK = 'beforeDelete';
|
|
16
|
+
const declaresTrue = (field, key) => TRUE_VALUE.test(depthOneValue(field, key) ?? '');
|
|
17
|
+
// The slug a `relationTo` names, and only when it names exactly one. An array is the polymorphic form,
|
|
18
|
+
// which Payload stores in the join table instead and which therefore carries no column to constrain.
|
|
19
|
+
const singleTarget = (field) => QUOTED_VALUE.exec(depthOneValue(field, 'relationTo') ?? '')?.[1];
|
|
20
|
+
const slugOf = (body) => QUOTED_VALUE.exec(depthOneValue(body, 'slug') ?? '')?.[1];
|
|
21
|
+
// A collection with drafts enabled gets no null constraint anywhere, so none of its required fields can
|
|
22
|
+
// reach the contradiction.
|
|
23
|
+
const declaresDrafts = (body) => DRAFTS_ENABLED.test(depthOneValue(body, 'versions') ?? '');
|
|
24
|
+
// Comments are blanked before anything is read, the way every other Payload rule reads a file. The keys
|
|
25
|
+
// this rule looks for sit at depth one, and a depth-one key is recognised by the delimiter in front of
|
|
26
|
+
// it - so a comment between the previous entry and `hooks:` hid the hook, and the rule reported a
|
|
27
|
+
// collection that was guarded. `stripComments` preserves offsets and newlines, so the reported line is
|
|
28
|
+
// still the line of the source.
|
|
29
|
+
const collectionsIn = (file) => {
|
|
30
|
+
const source = stripComments(file.source);
|
|
31
|
+
return [...source.matchAll(COLLECTION_DECLARATION)].flatMap((match) => {
|
|
32
|
+
const body = configBody(source, match.index, match[1] === SATISFIES);
|
|
33
|
+
const slug = body === undefined ? undefined : slugOf(body);
|
|
34
|
+
return body === undefined || slug === undefined
|
|
35
|
+
? []
|
|
36
|
+
: [{ path: file.path, slug, body, line: lineOf(source, match.index) }];
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
// Every field of one collection that puts a NOT NULL column against another collection's primary key.
|
|
40
|
+
// The field literal is recovered from the offset of its own `type` key, so a relationship nested inside
|
|
41
|
+
// an array or a group is read exactly like one declared at the top: both get a column, on their own
|
|
42
|
+
// table, with the same pair of settings.
|
|
43
|
+
const constrainedTargets = (collection) => {
|
|
44
|
+
if (declaresDrafts(collection.body)) {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return KEYED_FIELD_TYPES.flatMap((fieldType) => QUOTE_STYLES.flatMap((quote) => occurrences(collection.body, `${quote}${fieldType}${quote}`))
|
|
48
|
+
.map((at) => {
|
|
49
|
+
const field = enclosingLiteral(collection.body, at);
|
|
50
|
+
return field === undefined || !isConstrained(field, fieldType)
|
|
51
|
+
? undefined
|
|
52
|
+
: singleTarget(field);
|
|
53
|
+
})
|
|
54
|
+
.filter((slug) => slug !== undefined));
|
|
55
|
+
};
|
|
56
|
+
// The four conditions the adapter itself applies, read back off the field. `hasMany` moves the
|
|
57
|
+
// relationship into the join table; `admin.condition` and an array `relationTo` each drop the null
|
|
58
|
+
// constraint; and without `required` there is nothing to contradict.
|
|
59
|
+
const isConstrained = (field, fieldType) => QUOTED_VALUE.exec(depthOneValue(field, 'type') ?? '')?.[1] === fieldType &&
|
|
60
|
+
declaresTrue(field, 'required') &&
|
|
61
|
+
!declaresTrue(field, 'hasMany') &&
|
|
62
|
+
!depthOneBlockKeys(field, 'admin').includes('condition') &&
|
|
63
|
+
singleTarget(field) !== undefined;
|
|
64
|
+
const declaresCleanup = (target) => depthOneBlockKeys(target.body, 'hooks').includes(CLEANUP_HOOK);
|
|
65
|
+
const reason = (target) => `a required relationship to \`${target}\` gives this table a NOT NULL column against a foreign key ` +
|
|
66
|
+
`Payload declares ON DELETE SET NULL, so deleting a ${target} row aborts on this table's ` +
|
|
67
|
+
`constraint; declare a ${CLEANUP_HOOK} hook on ${target} that removes its dependants first`;
|
|
68
|
+
/**
|
|
69
|
+
* Every required single-value relationship whose target collection cannot be deleted because of it.
|
|
70
|
+
*
|
|
71
|
+
* Reads the whole candidate set at once, because the collection a `relationTo` names is declared in
|
|
72
|
+
* another file. A target this set does not declare - a collection a plugin contributes, say - is left
|
|
73
|
+
* alone rather than reported, since nothing here can see whether it takes its dependants down.
|
|
74
|
+
* @param files every source file the gate collected, as a path and its text.
|
|
75
|
+
* @returns one violation per unguarded relationship, located in the file that declares it.
|
|
76
|
+
*/
|
|
77
|
+
export const findUnguardedRelationships = (files) => {
|
|
78
|
+
const collections = files.flatMap((file) => collectionsIn(file));
|
|
79
|
+
const bySlug = new Map(collections.map((collection) => [
|
|
80
|
+
collection.slug,
|
|
81
|
+
collection,
|
|
82
|
+
]));
|
|
83
|
+
return collections.flatMap((collection) => constrainedTargets(collection)
|
|
84
|
+
.map((slug) => bySlug.get(slug))
|
|
85
|
+
.filter((target) => target !== undefined)
|
|
86
|
+
.filter((target) => !declaresCleanup(target))
|
|
87
|
+
.map((target) => ({
|
|
88
|
+
path: collection.path,
|
|
89
|
+
violation: {
|
|
90
|
+
line: collection.line,
|
|
91
|
+
rule: 'require-relationship-cleanup',
|
|
92
|
+
reason: reason(target.slug),
|
|
93
|
+
},
|
|
94
|
+
})));
|
|
95
|
+
};
|
|
96
|
+
//# sourceMappingURL=relationship-cleanup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relationship-cleanup.js","sourceRoot":"","sources":["../src/relationship-cleanup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type SpecSource } from './axe-coverage.js';
|
|
2
|
+
import { type DeclaredRoute } from './route-map.js';
|
|
3
|
+
/** Everything the coverage question is answered from. */
|
|
4
|
+
export interface RouteSweep {
|
|
5
|
+
readonly declaredRoutes: readonly DeclaredRoute[];
|
|
6
|
+
/** The addresses the crawl reached AND scanned. */
|
|
7
|
+
readonly visitedRoutes: readonly string[];
|
|
8
|
+
/** The addresses that answered, including those that forwarded off the public surface. */
|
|
9
|
+
readonly answeredRoutes: readonly string[];
|
|
10
|
+
readonly skippedPrefixes: readonly string[];
|
|
11
|
+
readonly specs: readonly SpecSource[];
|
|
12
|
+
readonly everyFile: readonly SpecSource[];
|
|
13
|
+
}
|
|
14
|
+
/** A declared page nothing looked at. */
|
|
15
|
+
export interface UnsweptRoute {
|
|
16
|
+
readonly file: string;
|
|
17
|
+
readonly route: string;
|
|
18
|
+
readonly rule: string;
|
|
19
|
+
readonly reason: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The declared pages the sweep did not cover and nothing else scans.
|
|
23
|
+
* @param sweep what the crawl reached, what the project declares, and where a scan would be written.
|
|
24
|
+
* @returns one violation per page nothing looked at, in the order the routes were declared.
|
|
25
|
+
*/
|
|
26
|
+
export declare const findUnsweptRoutes: (sweep: RouteSweep) => readonly UnsweptRoute[];
|
|
27
|
+
//# sourceMappingURL=route-coverage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-coverage.d.ts","sourceRoot":"","sources":["../src/route-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiD,KAAK,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAClG,OAAO,EAAE,KAAK,aAAa,EAAgC,MAAM,gBAAgB,CAAA;AAqBjF,yDAAyD;AACzD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,cAAc,EAAE,SAAS,aAAa,EAAE,CAAA;IACjD,mDAAmD;IACnD,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,0FAA0F;IAC1F,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAA;IAC3C,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAA;IACrC,QAAQ,CAAC,SAAS,EAAE,SAAS,UAAU,EAAE,CAAA;CAC1C;AAED,yCAAyC;AACzC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AA4DD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAAI,OAAO,UAAU,KAAG,SAAS,YAAY,EAMvE,CAAA"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { containsBuiltRoute, containsRoute, reachesAxe } from './axe-coverage.js';
|
|
2
|
+
import { matchesRoute, staticPrefixOf } from './route-map.js';
|
|
3
|
+
const REMEDIES = 'link it from a page the crawl reaches, scan it in a specification of your own with axe, or ' +
|
|
4
|
+
'declare it in `ploaness.accessibilitySkipRoutes` with the reason it is out of scope';
|
|
5
|
+
// The same plain prefix test the crawl itself applies in `toRoute`, deliberately. A stricter one -
|
|
6
|
+
// requiring the prefix to end on a segment boundary - would report a route the crawl was told to skip
|
|
7
|
+
// and did skip, which is a finding about the harness disagreeing with itself.
|
|
8
|
+
const isSkipped = (route, prefixes) => prefixes.some((prefix) => route.startsWith(prefix));
|
|
9
|
+
// A page the crawl asked for, that answered, and that forwarded somewhere outside the public surface.
|
|
10
|
+
// There is nothing at that address for axe to judge - what renders is wherever it forwarded to, which
|
|
11
|
+
// has a declaration of its own - so a redirect is covered rather than unswept.
|
|
12
|
+
const isRedirect = (sweep, route) => sweep.answeredRoutes.some((answered) => matchesRoute(route.route, answered)) &&
|
|
13
|
+
!sweep.visitedRoutes.some((visited) => matchesRoute(route.route, visited));
|
|
14
|
+
const isScanned = (sweep, route) => sweep.visitedRoutes.some((visited) => matchesRoute(route.route, visited));
|
|
15
|
+
// What counts as a specification driving this route. A static address can be looked for as itself; a
|
|
16
|
+
// dynamic one cannot, because a spec never contains `/posts/[slug]` - it contains `/posts/` and then a
|
|
17
|
+
// value it computed. See `containsBuiltRoute` for why the prefix alone is not enough.
|
|
18
|
+
const isDrivenBy = (spec, route) => route.isDynamic
|
|
19
|
+
? containsBuiltRoute(spec.source, staticPrefixOf(route.route))
|
|
20
|
+
: containsRoute(spec.source, route.route);
|
|
21
|
+
const unsweptRoute = (sweep, route) => {
|
|
22
|
+
const driving = sweep.specs.filter((spec) => isDrivenBy(spec, route));
|
|
23
|
+
if (driving.length === 0) {
|
|
24
|
+
return [
|
|
25
|
+
{
|
|
26
|
+
file: route.file,
|
|
27
|
+
route: route.route,
|
|
28
|
+
rule: 'route-unswept',
|
|
29
|
+
reason: `the accessibility sweep never reached "${route.route}", and no specification drives it ` +
|
|
30
|
+
`either, so this page has no accessibility coverage at all: ${REMEDIES}`,
|
|
31
|
+
},
|
|
32
|
+
];
|
|
33
|
+
}
|
|
34
|
+
return driving.some((spec) => reachesAxe(spec, sweep.everyFile))
|
|
35
|
+
? []
|
|
36
|
+
: [
|
|
37
|
+
{
|
|
38
|
+
file: route.file,
|
|
39
|
+
route: route.route,
|
|
40
|
+
rule: 'route-unscanned',
|
|
41
|
+
reason: `the accessibility sweep never reached "${route.route}". A specification drives it but ` +
|
|
42
|
+
`nothing scans it with axe, so this page has no accessibility coverage: ${REMEDIES}`,
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The declared pages the sweep did not cover and nothing else scans.
|
|
48
|
+
* @param sweep what the crawl reached, what the project declares, and where a scan would be written.
|
|
49
|
+
* @returns one violation per page nothing looked at, in the order the routes were declared.
|
|
50
|
+
*/
|
|
51
|
+
export const findUnsweptRoutes = (sweep) => sweep.declaredRoutes.flatMap((route) => {
|
|
52
|
+
if (isSkipped(route.route, sweep.skippedPrefixes) || isScanned(sweep, route)) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
return isRedirect(sweep, route) ? [] : unsweptRoute(sweep, route);
|
|
56
|
+
});
|
|
57
|
+
//# sourceMappingURL=route-coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-coverage.js","sourceRoot":"","sources":["../src/route-coverage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,aAAa,EAAE,UAAU,EAAmB,MAAM,mBAAmB,CAAA;AAClG,OAAO,EAAsB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAyCjF,MAAM,QAAQ,GACZ,6FAA6F;IAC7F,qFAAqF,CAAA;AAEvF,mGAAmG;AACnG,sGAAsG;AACtG,8EAA8E;AAC9E,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,QAA2B,EAAW,EAAE,CACxE,QAAQ,CAAC,IAAI,CAAC,CAAC,MAAc,EAAW,EAAE,CAAC,KAAK,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAA;AAEtE,sGAAsG;AACtG,sGAAsG;AACtG,+EAA+E;AAC/E,MAAM,UAAU,GAAG,CAAC,KAAiB,EAAE,KAAoB,EAAW,EAAE,CACtE,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,QAAgB,EAAW,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAC7F,CAAC,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,OAAe,EAAW,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;AAE7F,MAAM,SAAS,GAAG,CAAC,KAAiB,EAAE,KAAoB,EAAW,EAAE,CACrE,KAAK,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC,OAAe,EAAW,EAAE,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAA;AAE5F,qGAAqG;AACrG,uGAAuG;AACvG,sFAAsF;AACtF,MAAM,UAAU,GAAG,CAAC,IAAgB,EAAE,KAAoB,EAAW,EAAE,CACrE,KAAK,CAAC,SAAS;IACb,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,EAAE,cAAc,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC9D,CAAC,CAAC,aAAa,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,KAAK,CAAC,CAAA;AAE7C,MAAM,YAAY,GAAG,CAAC,KAAiB,EAAE,KAAoB,EAAkB,EAAE;IAC/E,MAAM,OAAO,GAA0B,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAgB,EAAW,EAAE,CACtF,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CACxB,CAAA;IACD,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO;YACL;gBACE,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,IAAI,EAAE,eAAe;gBACrB,MAAM,EACJ,0CAA0C,KAAK,CAAC,KAAK,oCAAoC;oBACzF,8DAA8D,QAAQ,EAAE;aAC3E;SACF,CAAA;IACH,CAAC;IACD,OAAO,OAAO,CAAC,IAAI,CAAC,CAAC,IAAgB,EAAW,EAAE,CAAC,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACnF,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;YACE;gBACE,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,KAAK,EAAE,KAAK,CAAC,KAAK;gBAClB,IAAI,EAAE,iBAAiB;gBACvB,MAAM,EACJ,0CAA0C,KAAK,CAAC,KAAK,mCAAmC;oBACxF,0EAA0E,QAAQ,EAAE;aACvF;SACF,CAAA;AACP,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,KAAiB,EAA2B,EAAE,CAC9E,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,KAAoB,EAAkB,EAAE;IACpE,IAAI,SAAS,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,eAAe,CAAC,IAAI,SAAS,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAC;QAC7E,OAAO,EAAE,CAAA;IACX,CAAC;IACD,OAAO,UAAU,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,YAAY,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;AACnE,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/** A page a project's file tree declares, and the address it answers at. */
|
|
2
|
+
export interface DeclaredRoute {
|
|
3
|
+
/** The file declaring it, relative to the member root. */
|
|
4
|
+
readonly file: string;
|
|
5
|
+
/** The address, with dynamic segments left in their file-tree spelling. */
|
|
6
|
+
readonly route: string;
|
|
7
|
+
/** Whether that address carries a dynamic segment, and so names a family rather than one page. */
|
|
8
|
+
readonly isDynamic: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Which directory holds the application's routes.
|
|
12
|
+
*
|
|
13
|
+
* Chosen from the conventional two rather than searched for, and confirmed by the root layout Next.js
|
|
14
|
+
* requires, so a library with a `src/app` module of its own is not mistaken for an application.
|
|
15
|
+
* @param paths every file of the member, relative to its root, separated by forward slashes.
|
|
16
|
+
* @returns the app directory, or nothing when the member declares no routes.
|
|
17
|
+
*/
|
|
18
|
+
export declare const appRootOf: (paths: readonly string[]) => string | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* Strip a trailing slash, so an address written with one and an address written without are one.
|
|
21
|
+
*
|
|
22
|
+
* The crawl records whatever `URL.pathname` gave it and a link may be written either way, while a file
|
|
23
|
+
* tree never produces a trailing slash at all. Comparing the two without this reports every page of a
|
|
24
|
+
* project that sets `trailingSlash`.
|
|
25
|
+
* @param route the address to normalise.
|
|
26
|
+
* @returns the address without its trailing slash, and the root route unchanged.
|
|
27
|
+
*/
|
|
28
|
+
export declare const normalisedRoute: (route: string) => string;
|
|
29
|
+
/**
|
|
30
|
+
* The address one file answers at, if it declares a page at all.
|
|
31
|
+
* @param file the file, relative to the app directory.
|
|
32
|
+
* @returns the declared route, or nothing when the file declares no independently reachable address.
|
|
33
|
+
*/
|
|
34
|
+
export declare const declaredRouteOf: (file: string) => DeclaredRoute | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Every address a member's file tree declares.
|
|
37
|
+
*
|
|
38
|
+
* Deduplicated by address, because a parallel route's slot and the page beside it reduce to the same
|
|
39
|
+
* one and reporting it twice would ask a project to answer for the same page under two file names.
|
|
40
|
+
* @param paths every file of the member, relative to its root, separated by forward slashes.
|
|
41
|
+
* @param appRoot the app directory, as `appRootOf` chose it.
|
|
42
|
+
* @returns one entry per declared address, in the order the paths were given.
|
|
43
|
+
*/
|
|
44
|
+
export declare const declaredRoutesOf: (paths: readonly string[], appRoot: string) => readonly DeclaredRoute[];
|
|
45
|
+
/**
|
|
46
|
+
* The part of an address before its first dynamic segment.
|
|
47
|
+
*
|
|
48
|
+
* What a specification can be searched for when the address itself cannot be: a spec never contains
|
|
49
|
+
* `/posts/[slug]`, it contains `/posts/` and then a value.
|
|
50
|
+
* @param route the declared address.
|
|
51
|
+
* @returns the leading static part, which is the root route when the first segment is dynamic.
|
|
52
|
+
*/
|
|
53
|
+
export declare const staticPrefixOf: (route: string) => string;
|
|
54
|
+
/**
|
|
55
|
+
* Whether one visited address is a page the declared route produces.
|
|
56
|
+
*
|
|
57
|
+
* A static route matches itself. A dynamic one matches any address its segments admit, which is what
|
|
58
|
+
* lets the crawl answer for it: if the sweep scanned `/posts/hello`, the file behind `/posts/[slug]`
|
|
59
|
+
* has been scanned, whatever else it can also serve.
|
|
60
|
+
* @param route the declared address, dynamic segments in their file-tree spelling.
|
|
61
|
+
* @param visited an address the crawl reached.
|
|
62
|
+
* @returns true when scanning that address scanned this route.
|
|
63
|
+
*/
|
|
64
|
+
export declare const matchesRoute: (route: string, visited: string) => boolean;
|
|
65
|
+
//# sourceMappingURL=route-map.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-map.d.ts","sourceRoot":"","sources":["../src/route-map.ts"],"names":[],"mappings":"AAiBA,4EAA4E;AAC5E,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,2EAA2E;IAC3E,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,kGAAkG;IAClG,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;CAC5B;AA8CD;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,GAAI,OAAO,SAAS,MAAM,EAAE,KAAG,MAAM,GAAG,SAG3D,CAAA;AAyBH;;;;;;;;GAQG;AACH,eAAO,MAAM,eAAe,GAAI,OAAO,MAAM,KAAG,MACoC,CAAA;AAEpF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,aAAa,GAAG,SAsB9D,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,gBAAgB,GAC3B,OAAO,SAAS,MAAM,EAAE,EACxB,SAAS,MAAM,KACd,SAAS,aAAa,EAaxB,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,GAAI,OAAO,MAAM,KAAG,MAK9C,CAAA;AAmCD;;;;;;;;;GASG;AACH,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,EAAE,SAAS,MAAM,KAAG,OACL,CAAA"}
|