@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
@@ -0,0 +1,95 @@
1
+ // What a project's own specifications say about a route, and whether one of them scans it.
2
+ //
3
+ // Two rules ask this same question of two different things. `admin-view-coverage` asks it of a custom
4
+ // admin view, which sits inside the `/admin` prefix the pinned sweep skips; `route-coverage` asks it
5
+ // of an application route the anonymous link crawl never reached. Neither can be answered by ploaness
6
+ // itself - it cannot sign in, and it cannot press the button that creates a record - so both answer it
7
+ // the same way: the project is required to have written the scan somewhere a check can see.
8
+ //
9
+ // It is one module rather than two copies because the load-bearing part is easy to omit and expensive
10
+ // to omit. `routeText` strips import specifiers before any route is searched for, and the comment on
11
+ // `MODULE_SPECIFIER` records what happened without it: `@/lib/calendar/interval` contains `/calendar`,
12
+ // so every unit spec of a `calendar` module looked like a test driving the view, and one that also
13
+ // imported the shared axe helper then answered for a view nothing scans. A second copy that forgot
14
+ // that line would ship the same false pass again.
15
+ import { stripComments } from './source-text.js';
16
+ // What proves a file reaches axe. The builder's class name covers the Playwright and WebdriverIO
17
+ // packages alike, the scoped package name covers a bare import, and `axe.run` covers the browser
18
+ // script form. A project driving axe some fourth way is asked to say so rather than assumed to have
19
+ // nothing.
20
+ const AXE_MARKERS = ['AxeBuilder', '@axe-core', 'axe.run'];
21
+ const REGEX_METACHARACTERS = /[$()*+.?[\\\]^{|}]/gu;
22
+ const escaped = (text) => text.replaceAll(REGEX_METACHARACTERS, String.raw `\$&`);
23
+ // An import specifier is a path too, and `@/lib/calendar/interval` contains `/calendar`. Read as a
24
+ // route, that made every unit spec of a `calendar` module look like a test driving the view - and a
25
+ // spec that also imported the shared axe helper would then have answered for a view nothing scans,
26
+ // which is a false PASS and the one failure a gate must never have. Specifiers and comments are
27
+ // removed before the search; `importedSources` reads the specifiers from the original text, where
28
+ // they still are.
29
+ const MODULE_SPECIFIER = /\b(?:from|import)\s*(?:\(\s*)?['"][^'"]*['"]/gu;
30
+ // A regular expression literal, read loosely. `stripComments` blanks one along with the comments,
31
+ // because a `//` inside a pattern would otherwise open a comment that never closes - correct for a
32
+ // lexer, and fatal here: `page.waitForURL(/\/welcome$/u)` is how a Playwright specification names the
33
+ // address it is waiting for, and blanking it made the commonest idiom in the file invisible. So the
34
+ // literals are put back, extracted from the original text and searched alongside it.
35
+ //
36
+ // The pattern admits a little more than a real literal does - a division written with spaces around
37
+ // it can look like one - which only ever adds text to search, never removes it. What it costs is
38
+ // narrow and worth naming: a route spelled as a regular expression INSIDE a comment is no longer told
39
+ // apart from one in code, and will answer for its page. Refusing regular expressions altogether was
40
+ // the alternative, and it fails the ordinary case to close a hole nobody falls into by accident.
41
+ const REGEX_LITERAL = /(?<![\w)\]])\/(?![/*])(?:\\.|\[(?:\\.|[^\]\\])*\]|[^/\\\n[])+\/[dgimsuvy]*/gu;
42
+ // Comments go first, because a route named in one explaining why a page is NOT scanned would
43
+ // otherwise answer for that page.
44
+ const routeText = (source) => [
45
+ stripComments(source).replaceAll(MODULE_SPECIFIER, ''),
46
+ ...(source.match(REGEX_LITERAL) ?? []),
47
+ ].join('\n');
48
+ /**
49
+ * Whether a specification names one route.
50
+ *
51
+ * A route is a prefix of every longer route beneath it, so a bare `includes` would let a spec for
52
+ * `/calendar-archive` answer for `/calendar`. The character after the match has to end the route.
53
+ * @param source the specification's text.
54
+ * @param route the route being looked for.
55
+ * @returns true when the text drives that route and not merely one whose name starts the same way.
56
+ */
57
+ export const containsRoute = (source, route) => new RegExp(String.raw `${escaped(route)}(?![\w-])`, 'u').test(routeText(source));
58
+ /**
59
+ * Whether a specification BUILDS a URL beneath one route prefix.
60
+ *
61
+ * The evidence a dynamic route needs, and deliberately narrower than `containsRoute`. A spec cannot
62
+ * contain `/play/[id]`, because that is a file path rather than an address: what it contains is
63
+ * `/play/${gameId}`. Asking merely for the prefix would let a spec that visits `/play` answer for
64
+ * `/play/[id]`, which is the false pass this whole family of rules exists to avoid - so what is
65
+ * required is the prefix, a separator, and an interpolation, which together mean the spec computed an
66
+ * address rather than typed one.
67
+ * @param source the specification's text.
68
+ * @param prefix the static part of the route, before its first dynamic segment.
69
+ * @returns true when the text builds a URL below that prefix.
70
+ */
71
+ export const containsBuiltRoute = (source, prefix) => routeText(source).includes(`${prefix === '/' ? '' : prefix}/\${`);
72
+ const carriesAxe = (source) => AXE_MARKERS.some((marker) => source.includes(marker));
73
+ const RELATIVE_IMPORT = /\bfrom\s*['"](\.[^'"]*)['"]/gu;
74
+ // Resolved by the tail of the specifier rather than by walking the filesystem, because this module
75
+ // takes plain values and owns no path resolver. `../helpers/accessibility` matches
76
+ // `tests/helpers/accessibility.ts`, which is the shape every project writes; a specifier that resolves
77
+ // somewhere genuinely different would have to end in the same segments to be confused with it.
78
+ const matchesFile = (specifier, filePath) => {
79
+ const tail = specifier.replace(/^(?:\.\.?\/)+/u, '');
80
+ return ['.ts', '.tsx', '/index.ts', '/index.tsx'].some((suffix) => filePath.endsWith(`${tail}${suffix}`));
81
+ };
82
+ const importedSources = (spec, everyFile) => [...spec.source.matchAll(RELATIVE_IMPORT)].flatMap((match) => everyFile.filter((candidate) => matchesFile(match[1] ?? '', candidate.path)));
83
+ /**
84
+ * Whether a specification scans with axe, directly or through a helper it imports.
85
+ *
86
+ * One hop, deliberately. A spec that scans through a helper is the ordinary arrangement and has to be
87
+ * recognised; a chain deeper than that is a project hiding its own scan from itself, and following it
88
+ * would trade a rule anyone can predict for one nobody can.
89
+ * @param spec the specification being judged.
90
+ * @param everyFile every file it may import a scan from, the specifications included.
91
+ * @returns true when running that spec reaches axe.
92
+ */
93
+ export const reachesAxe = (spec, everyFile) => carriesAxe(spec.source) ||
94
+ importedSources(spec, everyFile).some((imported) => carriesAxe(imported.source));
95
+ //# sourceMappingURL=axe-coverage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"axe-coverage.js","sourceRoot":"","sources":["../src/axe-coverage.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"banned-typography.d.ts","sourceRoot":"","sources":["../src/banned-typography.ts"],"names":[],"mappings":"AAmDA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAiCD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,SAAS,mBAAmB,EAM/E,CAAA"}
1
+ {"version":3,"file":"banned-typography.d.ts","sourceRoot":"","sources":["../src/banned-typography.ts"],"names":[],"mappings":"AA6EA,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAiCD;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,SAAS,mBAAmB,EAM/E,CAAA"}
@@ -5,6 +5,7 @@
5
5
  // referenced via its code point only, so neither this module nor its callers trip the check on
6
6
  // themselves.
7
7
  const STRAIGHT_DOUBLE_QUOTE = 'a straight double quote';
8
+ const STRAIGHT_SINGLE_QUOTE = 'a straight single quote "\'"';
8
9
  const PLAIN_HYPHEN = 'a hyphen "-"';
9
10
  // The code points are named rather than written at each use, because a bare number in the table below
10
11
  // would be exactly the unexplained literal the standard bans - and this module cannot spell the
@@ -15,6 +16,16 @@ const ELLIPSIS = 0x20_26;
15
16
  const LEFT_DOUBLE_QUOTE = 0x20_1c;
16
17
  const RIGHT_DOUBLE_QUOTE = 0x20_1d;
17
18
  const LOW_DOUBLE_QUOTE = 0x20_1e;
19
+ // The three single forms, absent for long enough to be worth naming rather than merely adding. The
20
+ // standard bans "typographic quotation marks" without qualifying which, and the shipped guide says
21
+ // "curly quotation mark"; both phrases cover these, and the scanner covered only their double
22
+ // counterparts. U+2019 is the expensive half of that gap: it is the apostrophe in "it's" and "don't",
23
+ // which makes it the banned character a model emits most often and the one a reader is least likely to
24
+ // notice. The other two are here because a set admitting U+201E while refusing U+201A is not a rule
25
+ // anybody can hold in their head.
26
+ const LEFT_SINGLE_QUOTE = 0x20_18;
27
+ const RIGHT_SINGLE_QUOTE = 0x20_19;
28
+ const LOW_SINGLE_QUOTE = 0x20_1a;
18
29
  const BANNED_CHARACTERS = [
19
30
  { char: String.fromCodePoint(EM_DASH), label: 'em dash (U+2014)', replacement: PLAIN_HYPHEN },
20
31
  { char: String.fromCodePoint(EN_DASH), label: 'en dash (U+2013)', replacement: PLAIN_HYPHEN },
@@ -38,6 +49,21 @@ const BANNED_CHARACTERS = [
38
49
  label: 'low double quote (U+201E)',
39
50
  replacement: STRAIGHT_DOUBLE_QUOTE,
40
51
  },
52
+ {
53
+ char: String.fromCodePoint(LEFT_SINGLE_QUOTE),
54
+ label: 'left single quote (U+2018)',
55
+ replacement: STRAIGHT_SINGLE_QUOTE,
56
+ },
57
+ {
58
+ char: String.fromCodePoint(RIGHT_SINGLE_QUOTE),
59
+ label: 'right single quote (U+2019)',
60
+ replacement: STRAIGHT_SINGLE_QUOTE,
61
+ },
62
+ {
63
+ char: String.fromCodePoint(LOW_SINGLE_QUOTE),
64
+ label: 'low single quote (U+201A)',
65
+ replacement: STRAIGHT_SINGLE_QUOTE,
66
+ },
41
67
  ];
42
68
  const BY_CHARACTER = new Map(BANNED_CHARACTERS.map((banned) => [
43
69
  banned.char,
@@ -1 +1 @@
1
- {"version":3,"file":"banned-typography.js","sourceRoot":"","sources":["../src/banned-typography.ts"],"names":[],"mappings":"AAAA,qGAAqG;AACrG,oGAAoG;AACpG,wGAAwG;AACxG,iEAAiE;AACjE,+FAA+F;AAC/F,cAAc;AAQd,MAAM,qBAAqB,GAAW,yBAAyB,CAAA;AAC/D,MAAM,YAAY,GAAW,cAAc,CAAA;AAE3C,sGAAsG;AACtG,gGAAgG;AAChG,6EAA6E;AAC7E,MAAM,OAAO,GAAW,OAAO,CAAA;AAC/B,MAAM,OAAO,GAAW,OAAO,CAAA;AAC/B,MAAM,QAAQ,GAAW,OAAO,CAAA;AAChC,MAAM,iBAAiB,GAAW,OAAO,CAAA;AACzC,MAAM,kBAAkB,GAAW,OAAO,CAAA;AAC1C,MAAM,gBAAgB,GAAW,OAAO,CAAA;AAExC,MAAM,iBAAiB,GAA+B;IACpD,EAAE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE;IAC7F,EAAE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,KAAK,EAAE,kBAAkB,EAAE,WAAW,EAAE,YAAY,EAAE;IAC7F;QACE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,QAAQ,CAAC;QACpC,KAAK,EAAE,mBAAmB;QAC1B,WAAW,EAAE,kBAAkB;KAChC;IACD;QACE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,iBAAiB,CAAC;QAC7C,KAAK,EAAE,4BAA4B;QACnC,WAAW,EAAE,qBAAqB;KACnC;IACD;QACE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,kBAAkB,CAAC;QAC9C,KAAK,EAAE,6BAA6B;QACpC,WAAW,EAAE,qBAAqB;KACnC;IACD;QACE,IAAI,EAAE,MAAM,CAAC,aAAa,CAAC,gBAAgB,CAAC;QAC5C,KAAK,EAAE,2BAA2B;QAClC,WAAW,EAAE,qBAAqB;KACnC;CACF,CAAA;AASD,MAAM,YAAY,GAAyC,IAAI,GAAG,CAChE,iBAAiB,CAAC,GAAG,CAAC,CAAC,MAAuB,EAAsC,EAAE,CAAC;IACrF,MAAM,CAAC,IAAI;IACX,MAAM;CACP,CAAC,CACH,CAAA;AAED,gEAAgE;AAChE,EAAE;AACF,mGAAmG;AACnG,qGAAqG;AACrG,oGAAoG;AACpG,iGAAiG;AACjG,MAAM,gBAAgB,GAAG,CAAC,IAAY,EAAE,UAAkB,EAAkC,EAAE;AAC5F,mGAAmG;AACnG,wDAAwD;AACxD,sFAAsF;AACtF,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,SAAiB,EAAE,KAAa,EAAkC,EAAE;IACrF,MAAM,MAAM,GAAgC,YAAY,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;IACvE,OAAO,MAAM,KAAK,SAAS;QACzB,CAAC,CAAC,EAAE;QACJ,CAAC,CAAC;YACE;gBACE,IAAI,EAAE,UAAU;gBAChB,MAAM,EAAE,KAAK,GAAG,CAAC;gBACjB,KAAK,EAAE,MAAM,CAAC,KAAK;gBACnB,WAAW,EAAE,MAAM,CAAC,WAAW;aAChC;SACF,CAAA;AACP,CAAC,CAAC,CAAA;AAEJ;;;;GAIG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAAG,CAAC,IAAY,EAAkC,EAAE,CACvF,IAAI;KACD,UAAU,CAAC,MAAM,EAAE,IAAI,CAAC;KACxB,KAAK,CAAC,IAAI,CAAC;KACX,OAAO,CAAC,CAAC,IAAY,EAAE,KAAa,EAAkC,EAAE,CACvE,gBAAgB,CAAC,IAAI,EAAE,KAAK,GAAG,CAAC,CAAC,CAClC,CAAA"}
1
+ {"version":3,"file":"banned-typography.js","sourceRoot":"","sources":["../src/banned-typography.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Explain a build failure whose own message names the wrong cause.
3
+ *
4
+ * Returns nothing for output it does not recognise, which is the common case: this adds a paragraph to
5
+ * a failure that already stands on its own, and says nothing rather than guessing.
6
+ * @param output the build's combined stdout and stderr.
7
+ * @returns hint lines to append to the gate's findings, in the order they were matched.
8
+ */
9
+ export declare const diagnoseBuildFailure: (output: string) => readonly string[];
10
+ //# sourceMappingURL=build-diagnosis.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-diagnosis.d.ts","sourceRoot":"","sources":["../src/build-diagnosis.ts"],"names":[],"mappings":"AAsCA;;;;;;;GAOG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,KAAG,SAAS,MAAM,EAGlE,CAAA"}
@@ -0,0 +1,38 @@
1
+ // Naming a build failure whose message points at the wrong layer.
2
+ //
3
+ // The `build` gate runs the project's own build script, which routes through the test-database wrapper
4
+ // so a build cannot read, or write, the database a developer is using. That isolation is worth having
5
+ // and it is also what surfaces a latent Next issue as an unreadable Postgres error: a page that reads
6
+ // the Payload Local API and uses no request-time API is PRERENDERED at build time, so it runs against
7
+ // the freshly created, schema-less database and dies with `relation "..." does not exist`.
8
+ //
9
+ // Nothing about that message says the page is the problem. A plain `next build` does not fail this way,
10
+ // because it reaches the development database, which has a schema from dev push-mode - so the natural
11
+ // reading is that the wrapper is broken. This module says what actually happened, and it is a
12
+ // DIAGNOSIS appended to a failure rather than a rule: the gate already failed, and would fail without
13
+ // it. That is why the pattern being wrong can cost a confusing extra line and nothing else.
14
+ // Matched on the shape Postgres itself prints, unquoted or quoted, rather than on any table name: the
15
+ // name is the project's and the message is the driver's.
16
+ const MISSING_RELATION = /relation "?[\w.$]+"? does not exist/i;
17
+ const PRERENDER_HINT = [
18
+ 'a page that reads the Payload Local API was prerendered against the empty build database',
19
+ 'the build runs through the test-database wrapper, which creates a database with no schema, so a ' +
20
+ 'page rendered at build time finds no tables; a plain `next build` hides this by reaching the ' +
21
+ 'development database instead',
22
+ 'await `connection()` from `next/server` in the function that reads Payload, which makes the page ' +
23
+ 'render per request rather than at build time',
24
+ "`export const dynamic = 'force-dynamic'` still works, but it has moved to the \"Caching " +
25
+ '(Previous Model)" guide and is removed under Cache Components, so `connection()` is the current ' +
26
+ 'spelling',
27
+ ];
28
+ const DIAGNOSES = [{ pattern: MISSING_RELATION, hint: PRERENDER_HINT }];
29
+ /**
30
+ * Explain a build failure whose own message names the wrong cause.
31
+ *
32
+ * Returns nothing for output it does not recognise, which is the common case: this adds a paragraph to
33
+ * a failure that already stands on its own, and says nothing rather than guessing.
34
+ * @param output the build's combined stdout and stderr.
35
+ * @returns hint lines to append to the gate's findings, in the order they were matched.
36
+ */
37
+ export const diagnoseBuildFailure = (output) => DIAGNOSES.flatMap((diagnosis) => diagnosis.pattern.test(output) ? diagnosis.hint : []);
38
+ //# sourceMappingURL=build-diagnosis.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-diagnosis.js","sourceRoot":"","sources":["../src/build-diagnosis.ts"],"names":[],"mappings":"AAAA,kEAAkE;AAClE,EAAE;AACF,uGAAuG;AACvG,sGAAsG;AACtG,sGAAsG;AACtG,sGAAsG;AACtG,2FAA2F;AAC3F,EAAE;AACF,wGAAwG;AACxG,sGAAsG;AACtG,8FAA8F;AAC9F,sGAAsG;AACtG,4FAA4F;AAQ5F,sGAAsG;AACtG,yDAAyD;AACzD,MAAM,gBAAgB,GAAW,sCAAsC,CAAA;AAEvE,MAAM,cAAc,GAAsB;IACxC,0FAA0F;IAC1F,kGAAkG;QAChG,+FAA+F;QAC/F,8BAA8B;IAChC,mGAAmG;QACjG,8CAA8C;IAChD,0FAA0F;QACxF,kGAAkG;QAClG,UAAU;CACb,CAAA;AAED,MAAM,SAAS,GAAyB,CAAC,EAAE,OAAO,EAAE,gBAAgB,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAA;AAE7F;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAc,EAAqB,EAAE,CACxE,SAAS,CAAC,OAAO,CAAC,CAAC,SAAoB,EAAqB,EAAE,CAC5D,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CACrD,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"document-references.d.ts","sourceRoot":"","sources":["../src/document-references.ts"],"names":[],"mappings":"AAYA,kFAAkF;AAClF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,iHAAiH;AACjH,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACzC,QAAQ,CAAC,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAA;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC3C;AAqDD;;;GAGG;AACH,eAAO,MAAM,+BAA+B,GAC1C,QAAQ,uBAAuB,KAC9B,SAAS,iBAAiB,EA4B5B,CAAA;AAgBD,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAKD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,aAAa,SAAS,MAAM,EAAE,EAC9B,gBAAgB,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,KAChD,SAAS,gBAAgB,EAOuD,CAAA"}
1
+ {"version":3,"file":"document-references.d.ts","sourceRoot":"","sources":["../src/document-references.ts"],"names":[],"mappings":"AAYA,kFAAkF;AAClF,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,iHAAiH;AACjH,UAAU,uBAAuB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACzC,QAAQ,CAAC,cAAc,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,CAAA;IAC1D;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IAC3C;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;CAC3C;AA2DD;;;GAGG;AACH,eAAO,MAAM,+BAA+B,GAC1C,QAAQ,uBAAuB,KAC9B,SAAS,iBAAiB,EA4B5B,CAAA;AAgBD,iEAAiE;AACjE,MAAM,WAAW,gBAAgB;IAC/B,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,8FAA8F;IAC9F,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;CAC3B;AAKD;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAC7B,aAAa,SAAS,MAAM,EAAE,EAC9B,gBAAgB,CAAC,YAAY,EAAE,MAAM,KAAK,OAAO,KAChD,SAAS,gBAAgB,EAOuD,CAAA"}
@@ -11,7 +11,13 @@
11
11
  import { ROOT_MEMBER_PATH } from './workspace-policy.js';
12
12
  const BACKTICK_TOKEN = /`[^`\n]+`/g;
13
13
  const PNPM_RUN = /pnpm run [a-z][a-z0-9:_-]*/g;
14
- const SCRIPT_TOKEN = /^(?:verify(?::full)?|format|knip|(?:lint|test|generate):[a-z][a-z:-]*|ensure:db|with:test-db)$/;
14
+ // The FAMILIES a backticked token has to belong to before it is read as a script reference. This named
15
+ // `ensure:db` and `with:test-db` as literals, so the whitelist recognised two particular scripts rather
16
+ // than the shape of one: a project that renamed `ensure:db` to `ensure:services` had every backticked
17
+ // mention of the new name quietly stop being checked, and the gate then passed having dropped the
18
+ // reference rather than resolved it. A family is what a whitelist here is for; a specific script name
19
+ // is what it must never be.
20
+ const SCRIPT_TOKEN = /^(?:verify(?::full)?|format|knip|(?:lint|test|generate|ensure|with|seed):[a-z][a-z:-]*)$/;
15
21
  const PATH_EXTENSION = /\.(?:tsx?|mts|cts|mjs|cjs|js|jsonc?|ya?ml|md|css|scss|grit)$/;
16
22
  const PNPM_RUN_PREFIX = 'pnpm run ';
17
23
  const backtickTokens = (markdown) => (markdown.match(BACKTICK_TOKEN) ?? []).map((raw) => raw.slice(1, -1).trim());
@@ -1 +1 @@
1
- {"version":3,"file":"document-references.js","sourceRoot":"","sources":["../src/document-references.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"document-references.js","sourceRoot":"","sources":["../src/document-references.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ /** Where a variable was declared, for a message that names the two places rather than one. */
2
+ export type EnvironmentOrigin = 'application' | 'compose';
3
+ /** An environment variable declared in one place and absent from another it has to reach. */
4
+ export interface EnvironmentViolation {
5
+ readonly name: string;
6
+ readonly origin: EnvironmentOrigin;
7
+ readonly reason: string;
8
+ }
9
+ /** A workflow, as the rule needs it: what to call it in a finding, and what it says. */
10
+ export interface WorkflowFile {
11
+ readonly file: string;
12
+ readonly content: string;
13
+ }
14
+ /** Inputs for {@link findEnvironmentViolations}, already read so the core stays pure. */
15
+ export interface EnvironmentInputs {
16
+ /** The contents of every validated environment module the repository holds, one per member. */
17
+ readonly applicationSources: readonly string[];
18
+ /** The contents of the example file, or undefined when the repository ships none. */
19
+ readonly example: string | undefined;
20
+ /** The contents of every compose file. */
21
+ readonly composeSources: readonly string[];
22
+ /** Every workflow the repository ships. Only the verifying ones are judged. */
23
+ readonly workflows: readonly WorkflowFile[];
24
+ }
25
+ /**
26
+ * The module a Payload project reads `process.env` in, which is the only module ploaness exempts from
27
+ * the ban and therefore the only one this rule can read a variable name out of.
28
+ */
29
+ export declare const VALIDATED_ENVIRONMENT_MODULE: string;
30
+ /**
31
+ * Every module exempt from the `process.env` ban.
32
+ *
33
+ * `src/proxy.ts` is exempt for a structural reason rather than a convenience one - Next mandates the
34
+ * file and runs it in the edge runtime, where the validated module is not reachable - and what it reads
35
+ * there is `NODE_ENV`, which the framework sets. So it is exempt from the LINT rule and is deliberately
36
+ * not read by this one: it holds no project configuration to document.
37
+ */
38
+ export declare const ENVIRONMENT_READ_EXEMPTIONS: readonly string[];
39
+ /**
40
+ * The example files a repository may document its environment in, most conventional first.
41
+ *
42
+ * A list rather than one name because the convention is not universal, and a project using
43
+ * `.env.sample` has documented its variables just as well as one using `.env.example`.
44
+ */
45
+ export declare const ENVIRONMENT_EXAMPLE_FILES: readonly string[];
46
+ /**
47
+ * The variables an application reads out of its validated environment module.
48
+ * @param source the contents of that module.
49
+ * @returns each name once, sorted, so a finding list is deterministic.
50
+ */
51
+ export declare const readEnvironmentNames: (source: string) => readonly string[];
52
+ /**
53
+ * The variables an example file documents.
54
+ * @param example the contents of the example file.
55
+ * @returns each name once, sorted.
56
+ */
57
+ export declare const documentedEnvironmentNames: (example: string) => readonly string[];
58
+ /**
59
+ * The variables a compose file interpolates without supplying a default.
60
+ * @param compose the contents of the compose file.
61
+ * @returns each name once, sorted.
62
+ */
63
+ export declare const interpolatedEnvironmentNames: (compose: string) => readonly string[];
64
+ /**
65
+ * The variables a workflow supplies, by any means and at any nesting depth.
66
+ * @param workflow the contents of the workflow file.
67
+ * @returns each name once, sorted.
68
+ */
69
+ export declare const workflowSuppliedNames: (workflow: string) => readonly string[];
70
+ /**
71
+ * Whether a workflow runs a ploaness verification, and therefore evaluates the compose files.
72
+ * @param workflow the contents of the workflow file.
73
+ * @returns true when the workflow invokes verification or a single gate.
74
+ */
75
+ export declare const isVerifyingWorkflow: (workflow: string) => boolean;
76
+ /**
77
+ * Every environment variable declared in one place and absent from another it has to reach.
78
+ *
79
+ * Three containments, each one-directional. What the application reads must be documented. What a
80
+ * compose file interpolates must be documented, because `docker compose config` reads the example file
81
+ * a developer copied. And what a compose file interpolates must be supplied by every workflow that
82
+ * verifies, because a workflow has no copied file to interpolate from.
83
+ * @param inputs the files, already read.
84
+ * @returns the violations, sorted by name within each rule. An empty array means the four places agree.
85
+ */
86
+ export declare const findEnvironmentViolations: (inputs: EnvironmentInputs) => readonly EnvironmentViolation[];
87
+ //# sourceMappingURL=environment-coherence.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment-coherence.d.ts","sourceRoot":"","sources":["../src/environment-coherence.ts"],"names":[],"mappings":"AAwBA,8FAA8F;AAC9F,MAAM,MAAM,iBAAiB,GAAG,aAAa,GAAG,SAAS,CAAA;AAEzD,6FAA6F;AAC7F,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAA;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,wFAAwF;AACxF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,yFAAyF;AACzF,MAAM,WAAW,iBAAiB;IAChC,+FAA+F;IAC/F,QAAQ,CAAC,kBAAkB,EAAE,SAAS,MAAM,EAAE,CAAA;IAC9C,qFAAqF;IACrF,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAAA;IACpC,0CAA0C;IAC1C,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAA;IAC1C,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,SAAS,YAAY,EAAE,CAAA;CAC5C;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,EAAE,MAAiC,CAAA;AAE5E;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,EAAE,SAAS,MAAM,EAGxD,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,SAAS,MAAM,EAItD,CAAA;AA4CD;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,MAAM,KAAG,SAAS,MAAM,EAChB,CAAA;AAErD;;;;GAIG;AACH,eAAO,MAAM,0BAA0B,GAAI,SAAS,MAAM,KAAG,SAAS,MAAM,EAClB,CAAA;AAE1D;;;;GAIG;AACH,eAAO,MAAM,4BAA4B,GAAI,SAAS,MAAM,KAAG,SAAS,MAAM,EACjB,CAAA;AAE7D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,UAAU,MAAM,KAAG,SAAS,MAAM,EAIpE,CAAA;AAEJ;;;;GAIG;AACH,eAAO,MAAM,mBAAmB,GAAI,UAAU,MAAM,KAAG,OAC4B,CAAA;AA+BnF;;;;;;;;;GASG;AACH,eAAO,MAAM,yBAAyB,GACpC,QAAQ,iBAAiB,KACxB,SAAS,oBAAoB,EAkC/B,CAAA"}
@@ -0,0 +1,150 @@
1
+ // Environment-coherence policy: the pure logic that finds an environment variable declared in one place
2
+ // and missing from another it must also reach. The `environment` gate in
3
+ // packages/cli/src/checks/environment.ts reads the files.
4
+ //
5
+ // A variable lands in more than one place by nature, and nothing links those places. The application
6
+ // reads it, `.env.example` documents it for a clone, a compose file interpolates it, and a workflow
7
+ // exports it into the job. Miss one and the failure is somewhere other than where the omission is: a
8
+ // clone boots on a default it never chose, or `docker compose config` fails on CI alone, several steps
9
+ // from the commit that added the name. This is the sibling of document-references.ts and
10
+ // config-references.ts, applied to environment variables instead of scripts, docs, or config paths.
11
+ //
12
+ // EVERY RULE IS ONE-DIRECTIONAL, which is what makes the gate false-positive-free. What is read, or
13
+ // interpolated, must be documented; what is documented need not be read. `.env.example` legitimately
14
+ // carries an optional variable no code path requires - a key for a manual script, a value only a
15
+ // developer regenerating an asset ever sets - and demanding the reverse containment would report those
16
+ // as rot.
17
+ //
18
+ // It is NOT a YAML parser and must not become one, for the reason yaml-blocks.ts states. The workflow
19
+ // half asks only whether a name appears in the file at all, not whether it appears in the block that
20
+ // would put it in the job's environment. That is deliberate: adjudicating YAML scoping would need a
21
+ // parser, and the mistake this gate exists to catch is forgetting the variable altogether rather than
22
+ // declaring it one nesting level from where it was needed. Over-accepting costs a missed finding;
23
+ // guessing at scope would cost a wrong one.
24
+ /**
25
+ * The module a Payload project reads `process.env` in, which is the only module ploaness exempts from
26
+ * the ban and therefore the only one this rule can read a variable name out of.
27
+ */
28
+ export const VALIDATED_ENVIRONMENT_MODULE = 'src/lib/environment.ts';
29
+ /**
30
+ * Every module exempt from the `process.env` ban.
31
+ *
32
+ * `src/proxy.ts` is exempt for a structural reason rather than a convenience one - Next mandates the
33
+ * file and runs it in the edge runtime, where the validated module is not reachable - and what it reads
34
+ * there is `NODE_ENV`, which the framework sets. So it is exempt from the LINT rule and is deliberately
35
+ * not read by this one: it holds no project configuration to document.
36
+ */
37
+ export const ENVIRONMENT_READ_EXEMPTIONS = [
38
+ VALIDATED_ENVIRONMENT_MODULE,
39
+ 'src/proxy.ts',
40
+ ];
41
+ /**
42
+ * The example files a repository may document its environment in, most conventional first.
43
+ *
44
+ * A list rather than one name because the convention is not universal, and a project using
45
+ * `.env.sample` has documented its variables just as well as one using `.env.example`.
46
+ */
47
+ export const ENVIRONMENT_EXAMPLE_FILES = [
48
+ '.env.example',
49
+ '.env.sample',
50
+ '.env.template',
51
+ ];
52
+ // BRACKET ACCESS ONLY, and that is the rule rather than a shortcut. `process.env` is an index
53
+ // signature, so a variable this project invented can only be read with brackets; a variable node or the
54
+ // framework DECLARES - `NODE_ENV` above all - is a known property and is read with a dot. The two forms
55
+ // therefore separate project configuration, which `.env.example` owes a reader, from the ambient
56
+ // variables a runtime sets and no example file should claim to document.
57
+ const BRACKETED_READ = /process\.env\[\s*(?<quote>['"])(?<name>[A-Za-z_]\w*)\k<quote>\s*\]/g;
58
+ // A dotenv assignment: a name at the start of a line, optionally exported, followed by `=`. A comment
59
+ // line cannot match, because `#` is not a name character.
60
+ const EXAMPLE_ASSIGNMENT = /^[ \t]*(?:export[ \t]+)?(?<name>[A-Za-z_]\w*)[ \t]*=/gm;
61
+ // A compose interpolation with NO default. `${NAME}` must be supplied; `${NAME:-5432}` and `${NAME-x}`
62
+ // supply themselves, and `${NAME:?message}` declares its own failure, so none of the three is this
63
+ // gate's business. The braced form alone is read: `$NAME` is legal in compose and rare in practice, and
64
+ // telling it apart from a `$$` escape or a shell fragment inside a `command:` is guesswork.
65
+ const COMPOSE_INTERPOLATION = /\$\{(?<name>[A-Z_]\w*)\}/gi;
66
+ // A name a workflow supplies: a mapping key in SCREAMING_SNAKE, or a reference to a secret, a variable,
67
+ // or the job environment. The key form is matched at any indentation, for the reason the header states.
68
+ const WORKFLOW_KEY = /^[ \t]*(?<name>[A-Z_][A-Z0-9_]*)[ \t]*:/gm;
69
+ const WORKFLOW_CONTEXT_REFERENCE = /\$\{\{[^}]*?\b(?:secrets|vars|env)\.(?<name>[A-Za-z_]\w*)/g;
70
+ // What makes a workflow one whose job environment has to carry the compose variables: it runs a
71
+ // ploaness verification, and verification validates every compose file the repository ships. A workflow
72
+ // that publishes a release or labels an issue evaluates no compose file and is owed nothing.
73
+ const VERIFYING_COMMANDS = ['ploaness verify', 'ploaness gate', 'run verify'];
74
+ // Read through a NAMED group rather than a positional one. The bracketed-read pattern needs a
75
+ // back-reference to match a quote with its own kind, which puts the name in the second group and the
76
+ // quote in the first - a number that says nothing at the call site and that the magic-number rule is
77
+ // right to refuse. Every pattern here therefore captures into `name`.
78
+ const namesMatching = (source, pattern) => [...source.matchAll(pattern)].map((match) => String(match.groups?.['name']));
79
+ const uniqueSorted = (names) => [...new Set(names)].sort((left, right) => left.localeCompare(right));
80
+ /**
81
+ * The variables an application reads out of its validated environment module.
82
+ * @param source the contents of that module.
83
+ * @returns each name once, sorted, so a finding list is deterministic.
84
+ */
85
+ export const readEnvironmentNames = (source) => uniqueSorted(namesMatching(source, BRACKETED_READ));
86
+ /**
87
+ * The variables an example file documents.
88
+ * @param example the contents of the example file.
89
+ * @returns each name once, sorted.
90
+ */
91
+ export const documentedEnvironmentNames = (example) => uniqueSorted(namesMatching(example, EXAMPLE_ASSIGNMENT));
92
+ /**
93
+ * The variables a compose file interpolates without supplying a default.
94
+ * @param compose the contents of the compose file.
95
+ * @returns each name once, sorted.
96
+ */
97
+ export const interpolatedEnvironmentNames = (compose) => uniqueSorted(namesMatching(compose, COMPOSE_INTERPOLATION));
98
+ /**
99
+ * The variables a workflow supplies, by any means and at any nesting depth.
100
+ * @param workflow the contents of the workflow file.
101
+ * @returns each name once, sorted.
102
+ */
103
+ export const workflowSuppliedNames = (workflow) => uniqueSorted([
104
+ ...namesMatching(workflow, WORKFLOW_KEY),
105
+ ...namesMatching(workflow, WORKFLOW_CONTEXT_REFERENCE),
106
+ ]);
107
+ /**
108
+ * Whether a workflow runs a ploaness verification, and therefore evaluates the compose files.
109
+ * @param workflow the contents of the workflow file.
110
+ * @returns true when the workflow invokes verification or a single gate.
111
+ */
112
+ export const isVerifyingWorkflow = (workflow) => VERIFYING_COMMANDS.some((command) => workflow.includes(command));
113
+ const undocumented = (names, documented, origin, reason) => names
114
+ .filter((name) => !documented.has(name))
115
+ .map((name) => ({ name, origin, reason }));
116
+ const missingFromWorkflow = (names, workflow) => {
117
+ const supplied = new Set(workflowSuppliedNames(workflow.content));
118
+ return names
119
+ .filter((name) => !supplied.has(name))
120
+ .map((name) => ({
121
+ name,
122
+ origin: 'compose',
123
+ reason: [
124
+ 'interpolated by a compose file but not supplied by',
125
+ `${workflow.file}, where verification will evaluate it`,
126
+ ].join(' '),
127
+ }));
128
+ };
129
+ /**
130
+ * Every environment variable declared in one place and absent from another it has to reach.
131
+ *
132
+ * Three containments, each one-directional. What the application reads must be documented. What a
133
+ * compose file interpolates must be documented, because `docker compose config` reads the example file
134
+ * a developer copied. And what a compose file interpolates must be supplied by every workflow that
135
+ * verifies, because a workflow has no copied file to interpolate from.
136
+ * @param inputs the files, already read.
137
+ * @returns the violations, sorted by name within each rule. An empty array means the four places agree.
138
+ */
139
+ export const findEnvironmentViolations = (inputs) => {
140
+ const documented = new Set(inputs.example === undefined ? [] : documentedEnvironmentNames(inputs.example));
141
+ const read = uniqueSorted(inputs.applicationSources.flatMap((source) => readEnvironmentNames(source)));
142
+ const interpolated = uniqueSorted(inputs.composeSources.flatMap((source) => interpolatedEnvironmentNames(source)));
143
+ const verifying = inputs.workflows.filter((workflow) => isVerifyingWorkflow(workflow.content));
144
+ return [
145
+ ...undocumented(read, documented, 'application', 'read by the validated environment module but documented in no example file'),
146
+ ...undocumented(interpolated, documented, 'compose', 'interpolated by a compose file but documented in no example file'),
147
+ ...verifying.flatMap((workflow) => missingFromWorkflow(interpolated, workflow)),
148
+ ];
149
+ };
150
+ //# sourceMappingURL=environment-coherence.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"environment-coherence.js","sourceRoot":"","sources":["../src/environment-coherence.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /** One generated artefact, as the gate saw it either side of regenerating. */
2
+ export interface RegeneratedArtefact {
3
+ /** The repo-relative path the generator owns. */
4
+ readonly target: string;
5
+ /** Whether git tracks the path, so that what the generator produced is reviewable. */
6
+ readonly isTracked: boolean;
7
+ /** The bytes on disk before the generator ran, or undefined when there were none. */
8
+ readonly before: string | undefined;
9
+ /** The bytes on disk after it ran, or undefined when the configuration produces no such artefact. */
10
+ readonly after: string | undefined;
11
+ }
12
+ /**
13
+ * Report every generated artefact that regeneration changed, or that git does not track.
14
+ * @param artefacts each generated path, with the bytes either side of running its generator.
15
+ * @returns one message per artefact that drifted or is untracked, in the order given.
16
+ */
17
+ export declare const findGeneratedDrift: (artefacts: readonly RegeneratedArtefact[]) => readonly string[];
18
+ //# sourceMappingURL=generated-drift.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-drift.d.ts","sourceRoot":"","sources":["../src/generated-drift.ts"],"names":[],"mappings":"AAaA,8EAA8E;AAC9E,MAAM,WAAW,mBAAmB;IAClC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,sFAAsF;IACtF,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,qFAAqF;IACrF,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAA;IACnC,qGAAqG;IACrG,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,CAAA;CACnC;AAmBD;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,WAAW,SAAS,mBAAmB,EAAE,KAAG,SAAS,MAAM,EACF,CAAA"}
@@ -0,0 +1,35 @@
1
+ // Drift between a generated artefact and the configuration that produces it.
2
+ //
3
+ // The gate regenerates, then asks whether anything changed. What "changed" means is the whole of the
4
+ // decision, and asking git answers a DIFFERENT question: whether the file differs from the INDEX. The
5
+ // two part company the moment a developer regenerates as part of the change they are making. The
6
+ // artefact then matches its configuration exactly - which is the only thing this gate claims to check -
7
+ // and the run fails anyway, telling them to commit a file they have already brought up to date. The
8
+ // only way out was to stage the artefact before every run, a step nothing announces and no other gate
9
+ // needs.
10
+ //
11
+ // Comparing the bytes before regeneration with the bytes after asks the question the gate is named for,
12
+ // and it holds whatever git has or has not been told.
13
+ const driftOf = (artefact) => {
14
+ // An artefact the configuration does not produce is not this gate's business. A project with no admin
15
+ // panel has no import map, and demanding one would be a rule about the shape of the project.
16
+ if (artefact.after === undefined) {
17
+ return [];
18
+ }
19
+ // Asked before drift, because "it drifted" is a strange thing to say about a file git has never seen.
20
+ // An untracked artefact regenerates from a configuration nobody can review, which is a worse fault
21
+ // than drift rather than a milder one.
22
+ if (!artefact.isTracked) {
23
+ return [
24
+ `${artefact.target} is not tracked by git, so no committed version exists to compare against`,
25
+ ];
26
+ }
27
+ return artefact.before === artefact.after ? [] : [`${artefact.target} changed when regenerated`];
28
+ };
29
+ /**
30
+ * Report every generated artefact that regeneration changed, or that git does not track.
31
+ * @param artefacts each generated path, with the bytes either side of running its generator.
32
+ * @returns one message per artefact that drifted or is untracked, in the order given.
33
+ */
34
+ export const findGeneratedDrift = (artefacts) => artefacts.flatMap((artefact) => driftOf(artefact));
35
+ //# sourceMappingURL=generated-drift.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generated-drift.js","sourceRoot":"","sources":["../src/generated-drift.ts"],"names":[],"mappings":"AAAA,6EAA6E;AAC7E,EAAE;AACF,qGAAqG;AACrG,sGAAsG;AACtG,iGAAiG;AACjG,wGAAwG;AACxG,oGAAoG;AACpG,sGAAsG;AACtG,SAAS;AACT,EAAE;AACF,wGAAwG;AACxG,sDAAsD;AActD,MAAM,OAAO,GAAG,CAAC,QAA6B,EAAqB,EAAE;IACnE,sGAAsG;IACtG,6FAA6F;IAC7F,IAAI,QAAQ,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QACjC,OAAO,EAAE,CAAA;IACX,CAAC;IACD,sGAAsG;IACtG,mGAAmG;IACnG,uCAAuC;IACvC,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC;QACxB,OAAO;YACL,GAAG,QAAQ,CAAC,MAAM,2EAA2E;SAC9F,CAAA;IACH,CAAC;IACD,OAAO,QAAQ,CAAC,MAAM,KAAK,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,MAAM,2BAA2B,CAAC,CAAA;AAClG,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,SAAyC,EAAqB,EAAE,CACjG,SAAS,CAAC,OAAO,CAAC,CAAC,QAA6B,EAAqB,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAA"}