@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.
Files changed (92) hide show
  1. package/dist/a11y-hit-target.d.ts +35 -0
  2. package/dist/a11y-hit-target.d.ts.map +1 -0
  3. package/dist/a11y-hit-target.js +102 -0
  4. package/dist/a11y-hit-target.js.map +1 -0
  5. package/dist/a11y-incomplete.d.ts +18 -0
  6. package/dist/a11y-incomplete.d.ts.map +1 -0
  7. package/dist/a11y-incomplete.js +82 -0
  8. package/dist/a11y-incomplete.js.map +1 -0
  9. package/dist/access-boundary.d.ts +19 -5
  10. package/dist/access-boundary.d.ts.map +1 -1
  11. package/dist/access-boundary.js +58 -4
  12. package/dist/access-boundary.js.map +1 -1
  13. package/dist/admin-view-coverage.d.ts +25 -0
  14. package/dist/admin-view-coverage.d.ts.map +1 -0
  15. package/dist/admin-view-coverage.js +90 -0
  16. package/dist/admin-view-coverage.js.map +1 -0
  17. package/dist/axe-coverage.d.ts +41 -0
  18. package/dist/axe-coverage.d.ts.map +1 -0
  19. package/dist/axe-coverage.js +95 -0
  20. package/dist/axe-coverage.js.map +1 -0
  21. package/dist/banned-typography.d.ts.map +1 -1
  22. package/dist/banned-typography.js +26 -0
  23. package/dist/banned-typography.js.map +1 -1
  24. package/dist/build-diagnosis.d.ts +10 -0
  25. package/dist/build-diagnosis.d.ts.map +1 -0
  26. package/dist/build-diagnosis.js +38 -0
  27. package/dist/build-diagnosis.js.map +1 -0
  28. package/dist/document-references.d.ts.map +1 -1
  29. package/dist/document-references.js +7 -1
  30. package/dist/document-references.js.map +1 -1
  31. package/dist/environment-coherence.d.ts +87 -0
  32. package/dist/environment-coherence.d.ts.map +1 -0
  33. package/dist/environment-coherence.js +150 -0
  34. package/dist/environment-coherence.js.map +1 -0
  35. package/dist/generated-drift.d.ts +18 -0
  36. package/dist/generated-drift.d.ts.map +1 -0
  37. package/dist/generated-drift.js +35 -0
  38. package/dist/generated-drift.js.map +1 -0
  39. package/dist/index.d.ts +10 -1
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +10 -1
  42. package/dist/index.js.map +1 -1
  43. package/dist/payload-access.d.ts.map +1 -1
  44. package/dist/payload-access.js +23 -49
  45. package/dist/payload-access.js.map +1 -1
  46. package/dist/payload-configs.d.ts +24 -0
  47. package/dist/payload-configs.d.ts.map +1 -0
  48. package/dist/payload-configs.js +77 -0
  49. package/dist/payload-configs.js.map +1 -0
  50. package/dist/payload-field-access.d.ts +4 -0
  51. package/dist/payload-field-access.d.ts.map +1 -0
  52. package/dist/payload-field-access.js +51 -0
  53. package/dist/payload-field-access.js.map +1 -0
  54. package/dist/payload-policy.d.ts.map +1 -1
  55. package/dist/payload-policy.js +111 -13
  56. package/dist/payload-policy.js.map +1 -1
  57. package/dist/payload-source.js +2 -2
  58. package/dist/payload-source.js.map +1 -1
  59. package/dist/relationship-cleanup.d.ts +18 -0
  60. package/dist/relationship-cleanup.d.ts.map +1 -0
  61. package/dist/relationship-cleanup.js +96 -0
  62. package/dist/relationship-cleanup.js.map +1 -0
  63. package/dist/route-coverage.d.ts +27 -0
  64. package/dist/route-coverage.d.ts.map +1 -0
  65. package/dist/route-coverage.js +57 -0
  66. package/dist/route-coverage.js.map +1 -0
  67. package/dist/route-map.d.ts +65 -0
  68. package/dist/route-map.d.ts.map +1 -0
  69. package/dist/route-map.js +183 -0
  70. package/dist/route-map.js.map +1 -0
  71. package/dist/secret-policy.js +4 -4
  72. package/dist/secret-policy.js.map +1 -1
  73. package/dist/settings.d.ts +10 -0
  74. package/dist/settings.d.ts.map +1 -1
  75. package/dist/settings.js +5 -1
  76. package/dist/settings.js.map +1 -1
  77. package/dist/source-security.d.ts +4 -0
  78. package/dist/source-security.d.ts.map +1 -0
  79. package/dist/source-security.js +56 -0
  80. package/dist/source-security.js.map +1 -0
  81. package/dist/source-text.d.ts +14 -0
  82. package/dist/source-text.d.ts.map +1 -1
  83. package/dist/source-text.js +39 -0
  84. package/dist/source-text.js.map +1 -1
  85. package/dist/version-policy.d.ts.map +1 -1
  86. package/dist/version-policy.js +31 -0
  87. package/dist/version-policy.js.map +1 -1
  88. package/package.json +1 -1
  89. package/dist/url-safety.d.ts +0 -12
  90. package/dist/url-safety.d.ts.map +0 -1
  91. package/dist/url-safety.js +0 -58
  92. package/dist/url-safety.js.map +0 -1
@@ -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 `(^|[\s,])${key}\s*:`).test(topLevel);
44
- // An unterminated call, an options variable rather than a literal, or an object spread all make the
45
- // bound impossible to decide statically. Staying silent there keeps the gate free of false positives;
46
- // the reviewer still sees the call.
47
- const unboundedCallAt = (source, rule, found) => {
48
- if (!PAYLOAD_RECEIVER.test(source.slice(0, found))) {
49
- return undefined;
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 topLevel = topLevelSlice(argumentText);
56
- if (rule.required.some((key) => declaresKey(topLevel, key))) {
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) => findDeepRelativeImports(stripComments(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":"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"}
1
+ {"version":3,"file":"payload-policy.js","sourceRoot":"","sources":["../src/payload-policy.ts"],"names":[],"mappings":"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"}
@@ -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 at = depthOneKeyIndex(wrapped, key);
45
- return at === NOT_FOUND ? [] : topLevelKeys(wrapped, at);
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":"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"}
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":"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"}
@@ -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"}