@safegres/test 0.0.1 → 1.30.7
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/README.md +825 -412
- package/adapters.d.ts +8 -0
- package/adapters.js +18 -0
- package/ast/helpers.d.ts +16 -0
- package/ast/helpers.js +40 -0
- package/ast/parse.d.ts +31 -0
- package/ast/parse.js +87 -0
- package/ast/walk.d.ts +27 -0
- package/ast/walk.js +79 -0
- package/callgraph/baseline.d.ts +28 -0
- package/callgraph/baseline.js +55 -0
- package/callgraph/extract.d.ts +93 -0
- package/callgraph/extract.js +340 -0
- package/callgraph/graph.d.ts +82 -0
- package/callgraph/graph.js +276 -0
- package/checks/anti-patterns.d.ts +53 -0
- package/checks/anti-patterns.js +247 -0
- package/checks/column-grants.d.ts +20 -0
- package/checks/column-grants.js +74 -0
- package/checks/coverage.d.ts +43 -0
- package/checks/coverage.js +110 -0
- package/checks/definer-function.d.ts +91 -0
- package/checks/definer-function.js +458 -0
- package/checks/definer-view.d.ts +167 -0
- package/checks/definer-view.js +392 -0
- package/checks/indexes.d.ts +82 -0
- package/checks/indexes.js +263 -0
- package/checks/lattice.d.ts +179 -0
- package/checks/lattice.js +487 -0
- package/checks/object-acls.d.ts +32 -0
- package/checks/object-acls.js +129 -0
- package/checks/policy-index.d.ts +78 -0
- package/checks/policy-index.js +349 -0
- package/checks/revocable-grants.d.ts +140 -0
- package/checks/revocable-grants.js +377 -0
- package/checks/rls-flags.d.ts +25 -0
- package/checks/rls-flags.js +82 -0
- package/checks/role-reach.d.ts +329 -0
- package/checks/role-reach.js +277 -0
- package/checks/role-trust.d.ts +45 -0
- package/checks/role-trust.js +91 -0
- package/checks/set-role.d.ts +21 -0
- package/checks/set-role.js +66 -0
- package/checks/stats.d.ts +52 -0
- package/checks/stats.js +192 -0
- package/checks/view-exposure.d.ts +68 -0
- package/checks/view-exposure.js +236 -0
- package/checks/view-writes.d.ts +88 -0
- package/checks/view-writes.js +350 -0
- package/ci/baseline.d.ts +104 -0
- package/ci/baseline.js +111 -0
- package/ci/github.d.ts +47 -0
- package/ci/github.js +197 -0
- package/cli/audit.d.ts +3 -0
- package/cli/audit.js +560 -0
- package/cli/baseline.d.ts +3 -0
- package/cli/baseline.js +104 -0
- package/cli/commands.d.ts +2 -0
- package/cli/commands.js +60 -0
- package/cli/doctor.d.ts +3 -0
- package/cli/doctor.js +72 -0
- package/cli/eval.d.ts +3 -0
- package/cli/eval.js +153 -0
- package/cli/print-config.d.ts +3 -0
- package/cli/print-config.js +54 -0
- package/cli/shared.d.ts +43 -0
- package/cli/shared.js +153 -0
- package/cli.d.ts +2 -0
- package/cli.js +69 -0
- package/commands/audit.d.ts +65 -0
- package/commands/audit.js +779 -0
- package/commands/doctor.d.ts +19 -0
- package/commands/doctor.js +181 -0
- package/commands/eval.d.ts +67 -0
- package/commands/eval.js +93 -0
- package/config/fingerprint.d.ts +40 -0
- package/config/fingerprint.js +71 -0
- package/config/loader.d.ts +44 -0
- package/config/loader.js +210 -0
- package/config/presets.d.ts +95 -0
- package/config/presets.js +327 -0
- package/config/resolve.d.ts +47 -0
- package/config/resolve.js +192 -0
- package/config/schema.d.ts +54 -0
- package/config/schema.js +269 -0
- package/config/types.d.ts +562 -0
- package/config/validate.d.ts +14 -0
- package/config/validate.js +125 -0
- package/corpus/README.md +83 -0
- package/corpus/bootstrap.sql +14 -0
- package/corpus/cases/01-anon-write-grant/case.json +26 -0
- package/corpus/cases/01-anon-write-grant/schema.sql +19 -0
- package/corpus/cases/02-grant-to-public/case.json +25 -0
- package/corpus/cases/02-grant-to-public/schema.sql +18 -0
- package/corpus/cases/03-rls-off-reachable-by-anon/case.json +30 -0
- package/corpus/cases/03-rls-off-reachable-by-anon/schema.sql +13 -0
- package/corpus/cases/04-permissive-write-policy/case.json +30 -0
- package/corpus/cases/04-permissive-write-policy/schema.sql +21 -0
- package/corpus/cases/05-public-read-policy/case.json +25 -0
- package/corpus/cases/05-public-read-policy/schema.sql +16 -0
- package/corpus/cases/06-update-without-with-check/case.json +25 -0
- package/corpus/cases/06-update-without-with-check/schema.sql +19 -0
- package/corpus/cases/07-rls-enabled-no-policies/case.json +29 -0
- package/corpus/cases/07-rls-enabled-no-policies/schema.sql +14 -0
- package/corpus/cases/08-select-grant-no-policy/case.json +25 -0
- package/corpus/cases/08-select-grant-no-policy/schema.sql +20 -0
- package/corpus/cases/09-write-grant-no-policy/case.json +24 -0
- package/corpus/cases/09-write-grant-no-policy/schema.sql +18 -0
- package/corpus/cases/10-rls-not-forced/case.json +24 -0
- package/corpus/cases/10-rls-not-forced/schema.sql +18 -0
- package/corpus/cases/11-session-user-in-policy/case.json +24 -0
- package/corpus/cases/11-session-user-in-policy/schema.sql +18 -0
- package/corpus/cases/12-volatile-function-in-policy/case.json +24 -0
- package/corpus/cases/12-volatile-function-in-policy/schema.sql +21 -0
- package/corpus/cases/13-security-definer-policy-wrapper/case.json +24 -0
- package/corpus/cases/13-security-definer-policy-wrapper/schema.sql +21 -0
- package/corpus/cases/14-dead-policy/case.json +24 -0
- package/corpus/cases/14-dead-policy/schema.sql +18 -0
- package/corpus/cases/15-unreachable-grant/case.json +24 -0
- package/corpus/cases/15-unreachable-grant/schema.sql +18 -0
- package/corpus/cases/16-anon-permissive-write-policy/case.json +34 -0
- package/corpus/cases/16-anon-permissive-write-policy/schema.sql +17 -0
- package/corpus/cases/17-foreign-key-without-index/case.json +24 -0
- package/corpus/cases/17-foreign-key-without-index/schema.sql +18 -0
- package/corpus/cases/18-policy-column-unindexed/case.json +24 -0
- package/corpus/cases/18-policy-column-unindexed/schema.sql +23 -0
- package/corpus/cases/19-policy-column-cast/case.json +24 -0
- package/corpus/cases/19-policy-column-cast/schema.sql +19 -0
- package/corpus/cases/20-stable-function-per-row/case.json +24 -0
- package/corpus/cases/20-stable-function-per-row/schema.sql +25 -0
- package/corpus/cases/21-table-without-primary-key/case.json +24 -0
- package/corpus/cases/21-table-without-primary-key/schema.sql +14 -0
- package/corpus/cases/22-redundant-index/case.json +24 -0
- package/corpus/cases/22-redundant-index/schema.sql +16 -0
- package/corpus/cases/23-tsvector-without-index/case.json +24 -0
- package/corpus/cases/23-tsvector-without-index/schema.sql +13 -0
- package/corpus/cases/24-anon-set-role-escalation/case.json +29 -0
- package/corpus/cases/24-anon-set-role-escalation/schema.sql +30 -0
- package/corpus/cases/25-definer-view-bypass/case.json +31 -0
- package/corpus/cases/25-definer-view-bypass/schema.sql +36 -0
- package/corpus/cases/26-invoker-view-no-bypass/case.json +31 -0
- package/corpus/cases/26-invoker-view-no-bypass/schema.sql +44 -0
- package/corpus/cases/27-definer-view-write/case.json +36 -0
- package/corpus/cases/27-definer-view-write/schema.sql +40 -0
- package/corpus/cases/28-invoker-view-no-write/case.json +31 -0
- package/corpus/cases/28-invoker-view-no-write/schema.sql +37 -0
- package/corpus/cases/29-rewrite-rule-bypass/case.json +36 -0
- package/corpus/cases/29-rewrite-rule-bypass/schema.sql +52 -0
- package/corpus/cases/30-instead-nothing-rule/case.json +31 -0
- package/corpus/cases/30-instead-nothing-rule/schema.sql +44 -0
- package/corpus/cases/31-matview-snapshot/case.json +32 -0
- package/corpus/cases/31-matview-snapshot/schema.sql +36 -0
- package/corpus/cases/32-matview-no-rls-no-bypass/case.json +30 -0
- package/corpus/cases/32-matview-no-rls-no-bypass/schema.sql +18 -0
- package/corpus/cases/33-leaky-filter-view/case.json +36 -0
- package/corpus/cases/33-leaky-filter-view/schema.sql +29 -0
- package/corpus/cases/34-barrier-view-no-leak/case.json +32 -0
- package/corpus/cases/34-barrier-view-no-leak/schema.sql +29 -0
- package/corpus/cases/35-column-grant-projection/case.json +27 -0
- package/corpus/cases/35-column-grant-projection/schema.sql +21 -0
- package/corpus/cases/35-policy-function-not-leakproof/case.json +24 -0
- package/corpus/cases/35-policy-function-not-leakproof/schema.sql +27 -0
- package/corpus/cases/36-column-grant-rls-mediated/case.json +27 -0
- package/corpus/cases/36-column-grant-rls-mediated/schema.sql +32 -0
- package/corpus/cases/36-policy-function-leakproof/case.json +24 -0
- package/corpus/cases/36-policy-function-leakproof/schema.sql +22 -0
- package/corpus/cases/37-sort-column-unindexed/case.json +24 -0
- package/corpus/cases/37-sort-column-unindexed/schema.sql +14 -0
- package/corpus/cases/37-unaudited-base-relation/case.json +30 -0
- package/corpus/cases/37-unaudited-base-relation/schema.sql +45 -0
- package/corpus/cases/38-foreign-key-with-index/case.json +22 -0
- package/corpus/cases/38-foreign-key-with-index/schema.sql +17 -0
- package/corpus/cases/38-invoker-view-unaudited-base/case.json +30 -0
- package/corpus/cases/38-invoker-view-unaudited-base/schema.sql +39 -0
- package/corpus/cases/39-definer-view-column-subset/case.json +30 -0
- package/corpus/cases/39-definer-view-column-subset/schema.sql +29 -0
- package/corpus/cases/39-policy-cast-with-expression-index/case.json +23 -0
- package/corpus/cases/39-policy-cast-with-expression-index/schema.sql +19 -0
- package/corpus/cases/40-definer-view-column-overreach/case.json +27 -0
- package/corpus/cases/40-definer-view-column-overreach/schema.sql +29 -0
- package/corpus/cases/40-stable-function-hoisted/case.json +23 -0
- package/corpus/cases/40-stable-function-hoisted/schema.sql +25 -0
- package/corpus/cases/41-index-prefix-not-redundant/case.json +21 -0
- package/corpus/cases/41-index-prefix-not-redundant/schema.sql +15 -0
- package/corpus/cases/41-unreadable-view-body/case.json +33 -0
- package/corpus/cases/41-unreadable-view-body/schema.sql +28 -0
- package/corpus/cases/42-invoker-view-unreadable-body/case.json +30 -0
- package/corpus/cases/42-invoker-view-unreadable-body/schema.sql +27 -0
- package/corpus/cases/42-search-column-indexed/case.json +21 -0
- package/corpus/cases/42-search-column-indexed/schema.sql +12 -0
- package/corpus/cases/43-anon-sequence-grant/case.json +27 -0
- package/corpus/cases/43-anon-sequence-grant/schema.sql +23 -0
- package/corpus/cases/44-serial-sequence-usage/case.json +27 -0
- package/corpus/cases/44-serial-sequence-usage/schema.sql +25 -0
- package/corpus/cases/45-anon-foreign-table/case.json +27 -0
- package/corpus/cases/45-anon-foreign-table/schema.sql +23 -0
- package/corpus/cases/46-unchecked-writable-view/case.json +41 -0
- package/corpus/cases/46-unchecked-writable-view/schema.sql +37 -0
- package/corpus/cases/47-checked-writable-view/case.json +37 -0
- package/corpus/cases/47-checked-writable-view/schema.sql +38 -0
- package/corpus/cases/48-definer-function-reach/case.json +38 -0
- package/corpus/cases/48-definer-function-reach/schema.sql +41 -0
- package/corpus/cases/49-invoker-function-no-reach/case.json +32 -0
- package/corpus/cases/49-invoker-function-no-reach/schema.sql +38 -0
- package/corpus/cases/50-instead-of-trigger-write/case.json +38 -0
- package/corpus/cases/50-instead-of-trigger-write/schema.sql +58 -0
- package/corpus/cases/51-invoker-trigger-no-write/case.json +33 -0
- package/corpus/cases/51-invoker-trigger-no-write/schema.sql +54 -0
- package/corpus/cases/52-revocable-execute-grant/case.json +30 -0
- package/corpus/cases/52-revocable-execute-grant/schema.sql +45 -0
- package/corpus/cases/53-policy-predicate-retains-execute/case.json +23 -0
- package/corpus/cases/53-policy-predicate-retains-execute/schema.sql +49 -0
- package/corpus/index.d.ts +79 -0
- package/corpus/index.js +125 -0
- package/esm/adapters.d.ts +8 -0
- package/esm/adapters.js +6 -0
- package/esm/ast/helpers.d.ts +16 -0
- package/esm/ast/helpers.js +36 -0
- package/esm/ast/parse.d.ts +31 -0
- package/esm/ast/parse.js +82 -0
- package/esm/ast/walk.d.ts +27 -0
- package/esm/ast/walk.js +72 -0
- package/esm/callgraph/baseline.d.ts +28 -0
- package/esm/callgraph/baseline.js +48 -0
- package/esm/callgraph/extract.d.ts +93 -0
- package/esm/callgraph/extract.js +333 -0
- package/esm/callgraph/graph.d.ts +82 -0
- package/esm/callgraph/graph.js +273 -0
- package/esm/checks/anti-patterns.d.ts +53 -0
- package/esm/checks/anti-patterns.js +240 -0
- package/esm/checks/column-grants.d.ts +20 -0
- package/esm/checks/column-grants.js +71 -0
- package/esm/checks/coverage.d.ts +43 -0
- package/esm/checks/coverage.js +104 -0
- package/esm/checks/definer-function.d.ts +91 -0
- package/esm/checks/definer-function.js +452 -0
- package/esm/checks/definer-view.d.ts +167 -0
- package/esm/checks/definer-view.js +382 -0
- package/esm/checks/indexes.d.ts +82 -0
- package/esm/checks/indexes.js +256 -0
- package/esm/checks/lattice.d.ts +179 -0
- package/esm/checks/lattice.js +475 -0
- package/esm/checks/object-acls.d.ts +32 -0
- package/esm/checks/object-acls.js +125 -0
- package/esm/checks/policy-index.d.ts +78 -0
- package/esm/checks/policy-index.js +342 -0
- package/esm/checks/revocable-grants.d.ts +140 -0
- package/esm/checks/revocable-grants.js +373 -0
- package/esm/checks/rls-flags.d.ts +25 -0
- package/esm/checks/rls-flags.js +77 -0
- package/esm/checks/role-reach.d.ts +329 -0
- package/esm/checks/role-reach.js +270 -0
- package/esm/checks/role-trust.d.ts +45 -0
- package/esm/checks/role-trust.js +86 -0
- package/esm/checks/set-role.d.ts +21 -0
- package/esm/checks/set-role.js +63 -0
- package/esm/checks/stats.d.ts +52 -0
- package/esm/checks/stats.js +184 -0
- package/esm/checks/view-exposure.d.ts +68 -0
- package/esm/checks/view-exposure.js +231 -0
- package/esm/checks/view-writes.d.ts +88 -0
- package/esm/checks/view-writes.js +344 -0
- package/esm/ci/baseline.d.ts +104 -0
- package/esm/ci/baseline.js +107 -0
- package/esm/ci/github.d.ts +47 -0
- package/esm/ci/github.js +161 -0
- package/esm/cli/audit.d.ts +3 -0
- package/esm/cli/audit.js +525 -0
- package/esm/cli/baseline.d.ts +3 -0
- package/esm/cli/baseline.js +102 -0
- package/esm/cli/commands.d.ts +2 -0
- package/esm/cli/commands.js +53 -0
- package/esm/cli/doctor.d.ts +3 -0
- package/esm/cli/doctor.js +67 -0
- package/esm/cli/eval.d.ts +3 -0
- package/esm/cli/eval.js +115 -0
- package/esm/cli/print-config.d.ts +3 -0
- package/esm/cli/print-config.js +52 -0
- package/esm/cli/shared.d.ts +43 -0
- package/esm/cli/shared.js +112 -0
- package/esm/cli.d.ts +2 -0
- package/esm/cli.js +67 -0
- package/esm/commands/audit.d.ts +65 -0
- package/esm/commands/audit.js +776 -0
- package/esm/commands/doctor.d.ts +19 -0
- package/esm/commands/doctor.js +178 -0
- package/esm/commands/eval.d.ts +67 -0
- package/esm/commands/eval.js +90 -0
- package/esm/config/fingerprint.d.ts +40 -0
- package/esm/config/fingerprint.js +68 -0
- package/esm/config/loader.d.ts +44 -0
- package/esm/config/loader.js +172 -0
- package/esm/config/presets.d.ts +95 -0
- package/esm/config/presets.js +324 -0
- package/esm/config/resolve.d.ts +47 -0
- package/esm/config/resolve.js +180 -0
- package/esm/config/schema.d.ts +54 -0
- package/esm/config/schema.js +265 -0
- package/esm/config/types.d.ts +562 -0
- package/esm/config/validate.d.ts +14 -0
- package/esm/config/validate.js +122 -0
- package/esm/corpus/index.d.ts +79 -0
- package/esm/corpus/index.js +85 -0
- package/esm/exposure/adapters.d.ts +152 -0
- package/esm/exposure/adapters.js +463 -0
- package/esm/exposure/planes.d.ts +59 -0
- package/esm/exposure/planes.js +144 -0
- package/esm/exposure/reach.d.ts +78 -0
- package/esm/exposure/reach.js +105 -0
- package/esm/exposure/routines.d.ts +36 -0
- package/esm/exposure/routines.js +54 -0
- package/esm/index.d.ts +96 -0
- package/esm/index.js +57 -6
- package/esm/lint/index.d.ts +11 -0
- package/esm/lint/index.js +10 -0
- package/esm/perf/baseline.d.ts +40 -0
- package/esm/perf/baseline.js +83 -0
- package/esm/perf/explain.d.ts +46 -0
- package/esm/perf/explain.js +201 -0
- package/esm/pg/acl.d.ts +72 -0
- package/esm/pg/acl.js +186 -0
- package/esm/pg/behaviors.d.ts +114 -0
- package/esm/pg/behaviors.js +206 -0
- package/esm/pg/exposure.d.ts +74 -0
- package/esm/pg/exposure.js +198 -0
- package/esm/pg/functions.d.ts +52 -0
- package/esm/pg/functions.js +128 -0
- package/esm/pg/indexes.d.ts +192 -0
- package/esm/pg/indexes.js +395 -0
- package/esm/pg/introspect.d.ts +134 -0
- package/esm/pg/introspect.js +274 -0
- package/esm/pg/objects.d.ts +37 -0
- package/esm/pg/objects.js +108 -0
- package/esm/pg/paths.d.ts +114 -0
- package/esm/pg/paths.js +159 -0
- package/esm/pg/proc.d.ts +23 -0
- package/esm/pg/proc.js +55 -0
- package/esm/pg/reach-inputs.d.ts +47 -0
- package/esm/pg/reach-inputs.js +133 -0
- package/esm/pg/roles.d.ts +28 -0
- package/esm/pg/roles.js +50 -0
- package/esm/pg/stats.d.ts +65 -0
- package/esm/pg/stats.js +129 -0
- package/esm/pgpm-test.d.ts +22 -0
- package/esm/pgpm-test.js +39 -0
- package/esm/report/callgraph.d.ts +9 -0
- package/esm/report/callgraph.js +74 -0
- package/esm/report/compare.d.ts +143 -0
- package/esm/report/compare.js +195 -0
- package/esm/report/github.d.ts +74 -0
- package/esm/report/github.js +290 -0
- package/esm/report/json.d.ts +4 -0
- package/esm/report/json.js +3 -0
- package/esm/report/markdown.d.ts +25 -0
- package/esm/report/markdown.js +352 -0
- package/esm/report/pretty.d.ts +14 -0
- package/esm/report/pretty.js +318 -0
- package/esm/report/sarif.d.ts +40 -0
- package/esm/report/sarif.js +189 -0
- package/esm/report/view.d.ts +82 -0
- package/esm/report/view.js +126 -0
- package/esm/rules/registry.d.ts +66 -0
- package/esm/rules/registry.js +562 -0
- package/esm/score/score.d.ts +84 -0
- package/esm/score/score.js +233 -0
- package/esm/score/scorecards.d.ts +68 -0
- package/esm/score/scorecards.js +157 -0
- package/esm/types.d.ts +292 -0
- package/esm/types.js +26 -0
- package/esm/version.d.ts +8 -0
- package/esm/version.js +9 -0
- package/exposure/adapters.d.ts +152 -0
- package/exposure/adapters.js +468 -0
- package/exposure/planes.d.ts +59 -0
- package/exposure/planes.js +151 -0
- package/exposure/reach.d.ts +78 -0
- package/exposure/reach.js +109 -0
- package/exposure/routines.d.ts +36 -0
- package/exposure/routines.js +57 -0
- package/index.d.ts +96 -6
- package/index.js +229 -6
- package/lint/index.d.ts +11 -0
- package/lint/index.js +19 -0
- package/package.json +54 -48
- package/perf/baseline.d.ts +40 -0
- package/perf/baseline.js +92 -0
- package/perf/explain.d.ts +46 -0
- package/perf/explain.js +204 -0
- package/pg/acl.d.ts +72 -0
- package/pg/acl.js +191 -0
- package/pg/behaviors.d.ts +114 -0
- package/pg/behaviors.js +217 -0
- package/pg/exposure.d.ts +74 -0
- package/pg/exposure.js +205 -0
- package/pg/functions.d.ts +52 -0
- package/pg/functions.js +131 -0
- package/pg/indexes.d.ts +192 -0
- package/pg/indexes.js +400 -0
- package/pg/introspect.d.ts +134 -0
- package/pg/introspect.js +280 -0
- package/pg/objects.d.ts +37 -0
- package/pg/objects.js +111 -0
- package/pg/paths.d.ts +114 -0
- package/pg/paths.js +166 -0
- package/pg/proc.d.ts +23 -0
- package/pg/proc.js +58 -0
- package/pg/reach-inputs.d.ts +47 -0
- package/pg/reach-inputs.js +136 -0
- package/pg/roles.d.ts +28 -0
- package/pg/roles.js +54 -0
- package/pg/stats.d.ts +65 -0
- package/pg/stats.js +133 -0
- package/pgpm-test.d.ts +22 -0
- package/pgpm-test.js +42 -0
- package/report/callgraph.d.ts +9 -0
- package/report/callgraph.js +81 -0
- package/report/compare.d.ts +143 -0
- package/report/compare.js +205 -0
- package/report/github.d.ts +74 -0
- package/report/github.js +333 -0
- package/report/json.d.ts +4 -0
- package/report/json.js +6 -0
- package/report/markdown.d.ts +25 -0
- package/report/markdown.js +355 -0
- package/report/pretty.d.ts +14 -0
- package/report/pretty.js +324 -0
- package/report/sarif.d.ts +40 -0
- package/report/sarif.js +193 -0
- package/report/view.d.ts +82 -0
- package/report/view.js +132 -0
- package/rules/registry.d.ts +66 -0
- package/rules/registry.js +569 -0
- package/schema/safegres.schema.json +1119 -0
- package/score/score.d.ts +84 -0
- package/score/score.js +238 -0
- package/score/scorecards.d.ts +68 -0
- package/score/scorecards.js +162 -0
- package/types.d.ts +292 -0
- package/types.js +32 -0
- package/version.d.ts +8 -0
- package/version.js +12 -0
- package/LICENSE +0 -23
- package/admin.d.ts +0 -26
- package/admin.js +0 -182
- package/connect.d.ts +0 -19
- package/connect.js +0 -83
- package/dist/README.md +0 -557
- package/dist/package.json +0 -72
- package/esm/admin.js +0 -178
- package/esm/connect.js +0 -78
- package/esm/manager.js +0 -136
- package/esm/roles.js +0 -32
- package/esm/seed/adapters.js +0 -23
- package/esm/seed/csv.js +0 -44
- package/esm/seed/index.js +0 -16
- package/esm/seed/json.js +0 -18
- package/esm/seed/launchql.js +0 -19
- package/esm/seed/sqitch.js +0 -17
- package/esm/stream.js +0 -43
- package/esm/test-client.js +0 -150
- package/manager.d.ts +0 -25
- package/manager.js +0 -140
- package/roles.d.ts +0 -17
- package/roles.js +0 -38
- package/seed/adapters.d.ts +0 -4
- package/seed/adapters.js +0 -28
- package/seed/csv.d.ts +0 -9
- package/seed/csv.js +0 -49
- package/seed/index.d.ts +0 -16
- package/seed/index.js +0 -33
- package/seed/json.d.ts +0 -6
- package/seed/json.js +0 -21
- package/seed/launchql.d.ts +0 -2
- package/seed/launchql.js +0 -22
- package/seed/sqitch.d.ts +0 -2
- package/seed/sqitch.js +0 -20
- package/seed/types.d.ts +0 -13
- package/stream.d.ts +0 -2
- package/stream.js +0 -46
- package/test-client.d.ts +0 -49
- package/test-client.js +0 -154
- /package/{seed → config}/types.js +0 -0
- /package/esm/{seed → config}/types.js +0 -0
package/ci/baseline.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Which report a delta is measured against — chosen, validated, explained.
|
|
3
|
+
*
|
|
4
|
+
* A comparison is only as trustworthy as the artifact it subtracts, and CI
|
|
5
|
+
* picking that artifact is where it goes wrong. The shape everyone writes is a
|
|
6
|
+
* one-row query for the base branch's last successful run:
|
|
7
|
+
*
|
|
8
|
+
* gh run list --workflow ci.yaml --branch main --status success --limit 1
|
|
9
|
+
*
|
|
10
|
+
* `--limit 1` is `per_page=1`, so one API row decides the baseline with nothing
|
|
11
|
+
* checking it and nothing printing which row it was. Observed cost, on the PR
|
|
12
|
+
* this module was written for: that query answered with a `main` run 13.6 days
|
|
13
|
+
* and ~150 successful runs stale, and a PR touching no SQL at all was reported
|
|
14
|
+
* as `Δ vs main: ▼ −0.8, findings 2877 → 3323` — every merge since. A finished,
|
|
15
|
+
* correct run existed; the artifacts had not expired. An unvalidated answer was
|
|
16
|
+
* simply trusted.
|
|
17
|
+
*
|
|
18
|
+
* The fix is not a cleverer query. This takes several candidates and picks the
|
|
19
|
+
* newest that survives three questions:
|
|
20
|
+
*
|
|
21
|
+
* 1. did it succeed, and is its report artifact still downloadable?
|
|
22
|
+
* 2. is it younger than `maxAgeMs`?
|
|
23
|
+
* 3. is its head commit an ancestor of this branch's merge base with the base?
|
|
24
|
+
*
|
|
25
|
+
* (3) is what makes the delta mean "what this branch did": a baseline from the
|
|
26
|
+
* tip of the base branch *after* the branch point charges the branch for merges
|
|
27
|
+
* it never contained, and one from rewritten or unrelated history is not
|
|
28
|
+
* comparable at all. (2) catches a stale baseline that is a legitimate ancestor.
|
|
29
|
+
*
|
|
30
|
+
* No acceptable candidate is not a failure — it is a report with no deltas and
|
|
31
|
+
* a stated reason (`--compare-skipped`). The absolute grade gate and the perf
|
|
32
|
+
* ratchet never read the comparison, so they still apply.
|
|
33
|
+
*
|
|
34
|
+
* This module is pure and provider-agnostic: ancestry and artifact download
|
|
35
|
+
* arrive as callbacks, which is what makes the decision testable without a
|
|
36
|
+
* network. `./github` supplies them from GitHub Actions.
|
|
37
|
+
*/
|
|
38
|
+
/** One run of the base branch, as a provider lists them (newest first). */
|
|
39
|
+
export interface BaselineCandidate {
|
|
40
|
+
/** Provider's id for the run, as it appears in a URL. */
|
|
41
|
+
runId: string;
|
|
42
|
+
/** Commit the run audited. */
|
|
43
|
+
headSha: string;
|
|
44
|
+
/** ISO 8601. When the run finished, or started if that is all there is. */
|
|
45
|
+
finishedAt: string;
|
|
46
|
+
/** `success` qualifies; anything else (including in-progress) does not. */
|
|
47
|
+
conclusion: string | null;
|
|
48
|
+
}
|
|
49
|
+
export interface SelectBaselineOptions {
|
|
50
|
+
/** The branch point this comparison must be relative to. */
|
|
51
|
+
mergeBaseSha: string;
|
|
52
|
+
/** Is `sha` in the merge base's history? */
|
|
53
|
+
isAncestor: (sha: string) => boolean;
|
|
54
|
+
/** Fetch a candidate's report; null when the artifact is gone. */
|
|
55
|
+
download: (candidate: BaselineCandidate) => string | null;
|
|
56
|
+
now?: number;
|
|
57
|
+
maxAgeMs?: number;
|
|
58
|
+
}
|
|
59
|
+
export interface RejectedCandidate {
|
|
60
|
+
runId: string;
|
|
61
|
+
headSha: string;
|
|
62
|
+
reason: string;
|
|
63
|
+
}
|
|
64
|
+
export interface ChosenBaseline extends BaselineCandidate {
|
|
65
|
+
/** Age at selection time, in milliseconds. */
|
|
66
|
+
ageMs: number;
|
|
67
|
+
/** Path to the downloaded report. */
|
|
68
|
+
path: string;
|
|
69
|
+
}
|
|
70
|
+
export interface BaselineSelection {
|
|
71
|
+
chosen: ChosenBaseline | null;
|
|
72
|
+
rejected: RejectedCandidate[];
|
|
73
|
+
/** One line for the job summary when `chosen` is null. */
|
|
74
|
+
reason: string | null;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* How stale a baseline may be by default: 48 hours.
|
|
78
|
+
*
|
|
79
|
+
* The window has to be wider than the gap between two base-branch runs, or a
|
|
80
|
+
* branch loses its delta for no reason. Those runs are per merge, so the gap is
|
|
81
|
+
* activity rather than a schedule: on the repository this was measured against,
|
|
82
|
+
* `main` averaged well under an hour between successful runs on a working day
|
|
83
|
+
* and went ~24 hours over a weekend. 48 hours clears the widest observed quiet
|
|
84
|
+
* stretch with room to spare, while still being a *comparison* — anything older
|
|
85
|
+
* describes a schema several merges removed from the branch point.
|
|
86
|
+
*/
|
|
87
|
+
export declare const MAX_BASELINE_AGE_MS: number;
|
|
88
|
+
/**
|
|
89
|
+
* How many runs to consider by default.
|
|
90
|
+
*
|
|
91
|
+
* More than one on principle — trusting a single row is the bug — and enough
|
|
92
|
+
* that an in-progress tip, a cancelled run and a couple of expired artifacts
|
|
93
|
+
* can all be skipped without falling out of the age window. Beyond ~20 the
|
|
94
|
+
* candidates are older than the window anyway, so they cost a request to
|
|
95
|
+
* reject.
|
|
96
|
+
*/
|
|
97
|
+
export declare const CANDIDATE_LIMIT = 20;
|
|
98
|
+
/**
|
|
99
|
+
* The newest candidate that is a sane comparison, and why the others were not.
|
|
100
|
+
*
|
|
101
|
+
* `isAncestor` and `download` are I/O in CI and stubs in tests, and are only
|
|
102
|
+
* asked about a candidate that already passed the cheap checks.
|
|
103
|
+
*/
|
|
104
|
+
export declare function selectBaseline(candidates: BaselineCandidate[], options: SelectBaselineOptions): BaselineSelection;
|
package/ci/baseline.js
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Which report a delta is measured against — chosen, validated, explained.
|
|
4
|
+
*
|
|
5
|
+
* A comparison is only as trustworthy as the artifact it subtracts, and CI
|
|
6
|
+
* picking that artifact is where it goes wrong. The shape everyone writes is a
|
|
7
|
+
* one-row query for the base branch's last successful run:
|
|
8
|
+
*
|
|
9
|
+
* gh run list --workflow ci.yaml --branch main --status success --limit 1
|
|
10
|
+
*
|
|
11
|
+
* `--limit 1` is `per_page=1`, so one API row decides the baseline with nothing
|
|
12
|
+
* checking it and nothing printing which row it was. Observed cost, on the PR
|
|
13
|
+
* this module was written for: that query answered with a `main` run 13.6 days
|
|
14
|
+
* and ~150 successful runs stale, and a PR touching no SQL at all was reported
|
|
15
|
+
* as `Δ vs main: ▼ −0.8, findings 2877 → 3323` — every merge since. A finished,
|
|
16
|
+
* correct run existed; the artifacts had not expired. An unvalidated answer was
|
|
17
|
+
* simply trusted.
|
|
18
|
+
*
|
|
19
|
+
* The fix is not a cleverer query. This takes several candidates and picks the
|
|
20
|
+
* newest that survives three questions:
|
|
21
|
+
*
|
|
22
|
+
* 1. did it succeed, and is its report artifact still downloadable?
|
|
23
|
+
* 2. is it younger than `maxAgeMs`?
|
|
24
|
+
* 3. is its head commit an ancestor of this branch's merge base with the base?
|
|
25
|
+
*
|
|
26
|
+
* (3) is what makes the delta mean "what this branch did": a baseline from the
|
|
27
|
+
* tip of the base branch *after* the branch point charges the branch for merges
|
|
28
|
+
* it never contained, and one from rewritten or unrelated history is not
|
|
29
|
+
* comparable at all. (2) catches a stale baseline that is a legitimate ancestor.
|
|
30
|
+
*
|
|
31
|
+
* No acceptable candidate is not a failure — it is a report with no deltas and
|
|
32
|
+
* a stated reason (`--compare-skipped`). The absolute grade gate and the perf
|
|
33
|
+
* ratchet never read the comparison, so they still apply.
|
|
34
|
+
*
|
|
35
|
+
* This module is pure and provider-agnostic: ancestry and artifact download
|
|
36
|
+
* arrive as callbacks, which is what makes the decision testable without a
|
|
37
|
+
* network. `./github` supplies them from GitHub Actions.
|
|
38
|
+
*/
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.CANDIDATE_LIMIT = exports.MAX_BASELINE_AGE_MS = void 0;
|
|
41
|
+
exports.selectBaseline = selectBaseline;
|
|
42
|
+
const compare_1 = require("../report/compare");
|
|
43
|
+
/**
|
|
44
|
+
* How stale a baseline may be by default: 48 hours.
|
|
45
|
+
*
|
|
46
|
+
* The window has to be wider than the gap between two base-branch runs, or a
|
|
47
|
+
* branch loses its delta for no reason. Those runs are per merge, so the gap is
|
|
48
|
+
* activity rather than a schedule: on the repository this was measured against,
|
|
49
|
+
* `main` averaged well under an hour between successful runs on a working day
|
|
50
|
+
* and went ~24 hours over a weekend. 48 hours clears the widest observed quiet
|
|
51
|
+
* stretch with room to spare, while still being a *comparison* — anything older
|
|
52
|
+
* describes a schema several merges removed from the branch point.
|
|
53
|
+
*/
|
|
54
|
+
exports.MAX_BASELINE_AGE_MS = 48 * 60 * 60 * 1000;
|
|
55
|
+
/**
|
|
56
|
+
* How many runs to consider by default.
|
|
57
|
+
*
|
|
58
|
+
* More than one on principle — trusting a single row is the bug — and enough
|
|
59
|
+
* that an in-progress tip, a cancelled run and a couple of expired artifacts
|
|
60
|
+
* can all be skipped without falling out of the age window. Beyond ~20 the
|
|
61
|
+
* candidates are older than the window anyway, so they cost a request to
|
|
62
|
+
* reject.
|
|
63
|
+
*/
|
|
64
|
+
exports.CANDIDATE_LIMIT = 20;
|
|
65
|
+
const short = (sha) => (sha || '').slice(0, 9);
|
|
66
|
+
/**
|
|
67
|
+
* The newest candidate that is a sane comparison, and why the others were not.
|
|
68
|
+
*
|
|
69
|
+
* `isAncestor` and `download` are I/O in CI and stubs in tests, and are only
|
|
70
|
+
* asked about a candidate that already passed the cheap checks.
|
|
71
|
+
*/
|
|
72
|
+
function selectBaseline(candidates, options) {
|
|
73
|
+
const { mergeBaseSha, isAncestor, download, now = Date.now(), maxAgeMs = exports.MAX_BASELINE_AGE_MS } = options;
|
|
74
|
+
const rejected = [];
|
|
75
|
+
const reject = (run, why) => {
|
|
76
|
+
rejected.push({ runId: run.runId, headSha: run.headSha, reason: why });
|
|
77
|
+
};
|
|
78
|
+
for (const run of candidates) {
|
|
79
|
+
if (run.conclusion !== 'success') {
|
|
80
|
+
reject(run, `conclusion ${run.conclusion || 'pending'}`);
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
83
|
+
const ageMs = now - Date.parse(run.finishedAt);
|
|
84
|
+
if (!(ageMs >= 0)) {
|
|
85
|
+
reject(run, `unreadable timestamp ${run.finishedAt}`);
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
if (ageMs > maxAgeMs) {
|
|
89
|
+
reject(run, `${(0, compare_1.formatAge)(ageMs)} old, older than the ${(0, compare_1.formatAge)(maxAgeMs)} window`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (!isAncestor(run.headSha)) {
|
|
93
|
+
reject(run, `${short(run.headSha)} is not an ancestor of merge base ${short(mergeBaseSha)}`);
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
const path = download(run);
|
|
97
|
+
if (!path) {
|
|
98
|
+
reject(run, 'no downloadable report artifact');
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
return { chosen: { ...run, ageMs, path }, rejected, reason: null };
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
chosen: null,
|
|
105
|
+
rejected,
|
|
106
|
+
reason: candidates.length > 0
|
|
107
|
+
? `no run within ${(0, compare_1.formatAge)(maxAgeMs)} whose head is an ancestor of merge base `
|
|
108
|
+
+ `${short(mergeBaseSha)} (${rejected.length} candidate(s) rejected)`
|
|
109
|
+
: 'no runs to compare against'
|
|
110
|
+
};
|
|
111
|
+
}
|
package/ci/github.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The GitHub Actions half of baseline selection: run discovery, merge base,
|
|
3
|
+
* artifact download. Everything provider-shaped lives here, so `./baseline`
|
|
4
|
+
* stays a pure decision and the report layer never learns what a workflow run
|
|
5
|
+
* is.
|
|
6
|
+
*
|
|
7
|
+
* The I/O goes through the `gh` CLI, which every GitHub-hosted runner has and
|
|
8
|
+
* which already handles pagination, auth from `GITHUB_TOKEN`, and unzipping an
|
|
9
|
+
* artifact. Reaching the API needs `permissions: actions: read`.
|
|
10
|
+
*/
|
|
11
|
+
import { type BaselineSelection } from './baseline';
|
|
12
|
+
export interface GithubBaselineOptions {
|
|
13
|
+
/** `owner/repo`. Default `$GITHUB_REPOSITORY`. */
|
|
14
|
+
repo?: string;
|
|
15
|
+
/** Branch whose runs are candidates — the PR's base. Default from the event. */
|
|
16
|
+
base?: string;
|
|
17
|
+
/** The branch tip to find the merge base of. Default from the event. */
|
|
18
|
+
headSha?: string;
|
|
19
|
+
/** Workflow file whose runs produced the reports. Default the current one. */
|
|
20
|
+
workflow?: string;
|
|
21
|
+
/** Artifact holding `safegres.json`. Default `safegres-reports`. */
|
|
22
|
+
artifact?: string;
|
|
23
|
+
/** Where to unpack it. Default `previous`. */
|
|
24
|
+
dir?: string;
|
|
25
|
+
maxAgeMs?: number;
|
|
26
|
+
candidateLimit?: number;
|
|
27
|
+
now?: number;
|
|
28
|
+
/** Where the selection log goes. Default stdout. */
|
|
29
|
+
log?: (line: string) => void;
|
|
30
|
+
}
|
|
31
|
+
/** What the caller needs to turn a selection into `safegres audit` flags. */
|
|
32
|
+
export interface GithubBaselineResult extends BaselineSelection {
|
|
33
|
+
base: string;
|
|
34
|
+
mergeBaseSha: string;
|
|
35
|
+
/** `https://github.com/<repo>/actions/runs/<id>`, when one was chosen. */
|
|
36
|
+
runUrl?: string;
|
|
37
|
+
/** Pre-formatted age of the chosen run. */
|
|
38
|
+
age?: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Select a baseline report from the base branch's recent runs.
|
|
42
|
+
*
|
|
43
|
+
* Throws only on a broken invocation (no repository, no PR context, `gh`
|
|
44
|
+
* missing). A candidate that cannot be used is rejected, not fatal: the caller
|
|
45
|
+
* renders the report without deltas and says why.
|
|
46
|
+
*/
|
|
47
|
+
export declare function selectGithubBaseline(options?: GithubBaselineOptions): GithubBaselineResult;
|
package/ci/github.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* The GitHub Actions half of baseline selection: run discovery, merge base,
|
|
4
|
+
* artifact download. Everything provider-shaped lives here, so `./baseline`
|
|
5
|
+
* stays a pure decision and the report layer never learns what a workflow run
|
|
6
|
+
* is.
|
|
7
|
+
*
|
|
8
|
+
* The I/O goes through the `gh` CLI, which every GitHub-hosted runner has and
|
|
9
|
+
* which already handles pagination, auth from `GITHUB_TOKEN`, and unzipping an
|
|
10
|
+
* artifact. Reaching the API needs `permissions: actions: read`.
|
|
11
|
+
*/
|
|
12
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
|
+
if (k2 === undefined) k2 = k;
|
|
14
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
15
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
16
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
17
|
+
}
|
|
18
|
+
Object.defineProperty(o, k2, desc);
|
|
19
|
+
}) : (function(o, m, k, k2) {
|
|
20
|
+
if (k2 === undefined) k2 = k;
|
|
21
|
+
o[k2] = m[k];
|
|
22
|
+
}));
|
|
23
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
24
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
25
|
+
}) : function(o, v) {
|
|
26
|
+
o["default"] = v;
|
|
27
|
+
});
|
|
28
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
29
|
+
var ownKeys = function(o) {
|
|
30
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
31
|
+
var ar = [];
|
|
32
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
33
|
+
return ar;
|
|
34
|
+
};
|
|
35
|
+
return ownKeys(o);
|
|
36
|
+
};
|
|
37
|
+
return function (mod) {
|
|
38
|
+
if (mod && mod.__esModule) return mod;
|
|
39
|
+
var result = {};
|
|
40
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
41
|
+
__setModuleDefault(result, mod);
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
})();
|
|
45
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
|
+
exports.selectGithubBaseline = selectGithubBaseline;
|
|
47
|
+
const child_process_1 = require("child_process");
|
|
48
|
+
const fs = __importStar(require("fs"));
|
|
49
|
+
const path = __importStar(require("path"));
|
|
50
|
+
const compare_1 = require("../report/compare");
|
|
51
|
+
const baseline_1 = require("./baseline");
|
|
52
|
+
const short = (sha) => sha.slice(0, 9);
|
|
53
|
+
const gh = (args) => (0, child_process_1.execFileSync)('gh', args, { encoding: 'utf8' });
|
|
54
|
+
/**
|
|
55
|
+
* `gh run` needs `-R` explicitly: the workspace may be an unpacked artifact
|
|
56
|
+
* with no `.git` for it to infer the repository from. `gh api` carries the
|
|
57
|
+
* repository in the path and rejects the flag.
|
|
58
|
+
*/
|
|
59
|
+
const ghRun = (repo, args) => gh(['run', ...args, '-R', repo]);
|
|
60
|
+
/** The workflow file of the run this is called from, e.g. `run-tests.yaml`. */
|
|
61
|
+
function currentWorkflow() {
|
|
62
|
+
const ref = process.env.GITHUB_WORKFLOW_REF; // owner/repo/.github/workflows/x.yml@refs/…
|
|
63
|
+
if (!ref)
|
|
64
|
+
return undefined;
|
|
65
|
+
const file = ref.split('@')[0].split('/').pop();
|
|
66
|
+
return file || undefined;
|
|
67
|
+
}
|
|
68
|
+
/** The PR's head sha, from the event payload the runner wrote to disk. */
|
|
69
|
+
function eventHeadSha() {
|
|
70
|
+
const file = process.env.GITHUB_EVENT_PATH;
|
|
71
|
+
if (!file || !fs.existsSync(file))
|
|
72
|
+
return undefined;
|
|
73
|
+
const event = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
74
|
+
return event.pull_request?.head?.sha;
|
|
75
|
+
}
|
|
76
|
+
function eventBaseRef() {
|
|
77
|
+
const file = process.env.GITHUB_EVENT_PATH;
|
|
78
|
+
if (!file || !fs.existsSync(file))
|
|
79
|
+
return undefined;
|
|
80
|
+
const event = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
81
|
+
return event.pull_request?.base?.ref ?? process.env.GITHUB_BASE_REF;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* The path, if it holds a report a delta can be measured against — an artifact
|
|
85
|
+
* that exists but is truncated or from an incompatible version would otherwise
|
|
86
|
+
* fail the audit at compare time, which is the one thing a baseline lookup must
|
|
87
|
+
* never do.
|
|
88
|
+
*/
|
|
89
|
+
function readableReport(file, log) {
|
|
90
|
+
if (!fs.existsSync(file)) {
|
|
91
|
+
log(`artifact holds no ${path.basename(file)}`);
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
try {
|
|
95
|
+
const report = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
96
|
+
if (report.summary === undefined)
|
|
97
|
+
throw new Error('no summary');
|
|
98
|
+
return file;
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
log(`unusable report — ${err.message}`);
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Select a baseline report from the base branch's recent runs.
|
|
107
|
+
*
|
|
108
|
+
* Throws only on a broken invocation (no repository, no PR context, `gh`
|
|
109
|
+
* missing). A candidate that cannot be used is rejected, not fatal: the caller
|
|
110
|
+
* renders the report without deltas and says why.
|
|
111
|
+
*/
|
|
112
|
+
function selectGithubBaseline(options = {}) {
|
|
113
|
+
const log = options.log ?? ((line) => console.log(line));
|
|
114
|
+
const repo = options.repo ?? process.env.GITHUB_REPOSITORY;
|
|
115
|
+
const base = options.base ?? eventBaseRef();
|
|
116
|
+
const headSha = options.headSha ?? eventHeadSha() ?? process.env.GITHUB_SHA;
|
|
117
|
+
const workflow = options.workflow ?? currentWorkflow();
|
|
118
|
+
const artifact = options.artifact ?? 'safegres-reports';
|
|
119
|
+
const dir = path.resolve(options.dir ?? 'previous');
|
|
120
|
+
if (!repo)
|
|
121
|
+
throw new Error('GITHUB_REPOSITORY is not set: pass --repo owner/repo');
|
|
122
|
+
if (!base)
|
|
123
|
+
throw new Error('no base branch: pass --base (this is a pull-request-only feature)');
|
|
124
|
+
if (!headSha)
|
|
125
|
+
throw new Error('no head commit: pass --head <sha>');
|
|
126
|
+
if (!workflow)
|
|
127
|
+
throw new Error('no workflow: pass --workflow <file.yaml>');
|
|
128
|
+
// The branch point, from the API rather than `git merge-base`: the audit job
|
|
129
|
+
// may not have checked the repository out, and a shallow clone has no history.
|
|
130
|
+
const mergeBaseSha = JSON.parse(gh(['api', `repos/${repo}/compare/${base}...${headSha}`])).merge_base_commit.sha;
|
|
131
|
+
log(`merge base with ${base}: ${short(mergeBaseSha)}`);
|
|
132
|
+
const runs = JSON.parse(ghRun(repo, [
|
|
133
|
+
'list',
|
|
134
|
+
'--workflow',
|
|
135
|
+
workflow,
|
|
136
|
+
'--branch',
|
|
137
|
+
base,
|
|
138
|
+
'--limit',
|
|
139
|
+
String(options.candidateLimit ?? baseline_1.CANDIDATE_LIMIT),
|
|
140
|
+
'--json',
|
|
141
|
+
'databaseId,headSha,createdAt,updatedAt,conclusion'
|
|
142
|
+
]));
|
|
143
|
+
const candidates = runs.map((r) => ({
|
|
144
|
+
runId: String(r.databaseId),
|
|
145
|
+
headSha: r.headSha,
|
|
146
|
+
// `updatedAt` is when the run concluded; `createdAt` until GitHub records it.
|
|
147
|
+
finishedAt: r.updatedAt || r.createdAt,
|
|
148
|
+
conclusion: r.conclusion
|
|
149
|
+
}));
|
|
150
|
+
const ancestry = new Map();
|
|
151
|
+
const isAncestor = (sha) => {
|
|
152
|
+
if (!ancestry.has(sha)) {
|
|
153
|
+
// `ahead` = the merge base is ahead of the candidate, i.e. the candidate
|
|
154
|
+
// is in its history; `identical` = the baseline *is* the branch point, the
|
|
155
|
+
// ideal case. `behind`/`diverged` are the ones to refuse.
|
|
156
|
+
const status = JSON.parse(gh(['api', `repos/${repo}/compare/${sha}...${mergeBaseSha}`])).status;
|
|
157
|
+
ancestry.set(sha, status === 'ahead' || status === 'identical');
|
|
158
|
+
}
|
|
159
|
+
return ancestry.get(sha) === true;
|
|
160
|
+
};
|
|
161
|
+
const download = (run) => {
|
|
162
|
+
// Emptied first: successive candidates unpack into the same directory, and a
|
|
163
|
+
// leftover report from a rejected one would make the next candidate look
|
|
164
|
+
// usable and put the wrong run's numbers in the delta.
|
|
165
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
166
|
+
try {
|
|
167
|
+
ghRun(repo, ['download', run.runId, '-n', artifact, '-D', dir]);
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
// The one ignorable failure here: an expired or never-uploaded artifact is
|
|
171
|
+
// a rejected candidate, not a broken job. Anything else (auth, network)
|
|
172
|
+
// fails the same way on the next candidate and surfaces as "no baseline".
|
|
173
|
+
log(` ${run.runId}: artifact download failed — ${String(err.message).trim()}`);
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
return readableReport(path.join(dir, 'safegres.json'), (why) => log(` ${run.runId}: ${why}`));
|
|
177
|
+
};
|
|
178
|
+
const selection = (0, baseline_1.selectBaseline)(candidates, {
|
|
179
|
+
mergeBaseSha,
|
|
180
|
+
isAncestor,
|
|
181
|
+
download,
|
|
182
|
+
...(options.now !== undefined && { now: options.now }),
|
|
183
|
+
maxAgeMs: options.maxAgeMs ?? baseline_1.MAX_BASELINE_AGE_MS
|
|
184
|
+
});
|
|
185
|
+
for (const r of selection.rejected) {
|
|
186
|
+
log(`rejected ${r.runId} (${short(r.headSha)}): ${r.reason}`);
|
|
187
|
+
}
|
|
188
|
+
if (!selection.chosen) {
|
|
189
|
+
log(`no comparison baseline: ${selection.reason}`);
|
|
190
|
+
return { ...selection, base, mergeBaseSha };
|
|
191
|
+
}
|
|
192
|
+
const age = (0, compare_1.formatAge)(selection.chosen.ageMs);
|
|
193
|
+
const runUrl = `${process.env.GITHUB_SERVER_URL ?? 'https://github.com'}/${repo}/actions/runs/${selection.chosen.runId}`;
|
|
194
|
+
log(`baseline: run ${selection.chosen.runId} ${base}@${short(selection.chosen.headSha)}, `
|
|
195
|
+
+ `${age} old (${selection.chosen.finishedAt})`);
|
|
196
|
+
return { ...selection, base, mergeBaseSha, runUrl, age };
|
|
197
|
+
}
|
package/cli/audit.d.ts
ADDED