@avi2dg/checks 0.52.0 → 0.53.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.
@@ -23,7 +23,7 @@ It refuses a step or job with `if: false` or `continue-on-error: true`.
23
23
  It also refuses a workflow that does not trigger on both `opened` and `synchronize` pull requests to the target branch.
24
24
  A path filter cannot cover every pull request and therefore cannot satisfy the check.
25
25
  It refuses a reference to a workflow or job the repository does not hold.
26
- Each literal `checks-release-pr` and `checks-release-tag` argument names a workflow file, so every one must match a file under `.github/workflows/`.
26
+ Each literal `checks-release-pr`, `checks-release-push` and `checks-release-tag` argument names a workflow file, so every one must match a file under `.github/workflows/`.
27
27
  A call counts directly, through `bunx`, or through `bun` or `bun run` with the bin's path or its source file.
28
28
  An argument built from a variable or an expression is known only when the job runs, so the check passes over it.
29
29
  An argument built by command substitution is known only when the job runs, so the check passes over it.
@@ -15,7 +15,7 @@ A commit that GitHub committed allows any author name with an allowed email.
15
15
  GitHub authors a web merge under the account's display name.
16
16
  The same author on a commit with any other committer must match the allowed name too.
17
17
  `github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>` is allowed as author only of a commit whose subject is `chore: release <version>`, with or without the pull request number a squash merge adds.
18
- GitHub attributes the release commit [checks-release-pr](checks-release-pr.md) makes to that bot, since it commits through the workflow token.
18
+ GitHub attributes the release commit [checks-release-pr](checks-release-pr.md) or [checks-release-push](checks-release-push.md) makes to that bot, since each commits through the workflow token.
19
19
  Such a commit may carry a `Co-authored-by` trailer naming that bot, since GitHub adds one to some squash merges of a pull request the bot opened, and every other trailer is refused.
20
20
 
21
21
  ## What it reads
@@ -8,12 +8,13 @@ audience: consumers
8
8
 
9
9
  ## What it checks
10
10
 
11
- It lints the pull request title and nothing else.
11
+ It lints the pull request title, or the head subject when no open pull request heads the branch.
12
12
  The title comes from `PR_TITLE` when the event carries one.
13
13
  A dispatched run carries no pull request, so the bin reads the title of the one open pull request its branch heads.
14
- It fails when there is none.
14
+ When none heads the branch, it lints the head subject as it stands and without ` (#0000)`.
15
+ It fails when more than one heads the branch.
15
16
  A run with no title to lint passes and says so.
16
- The bin lints the title with ` (#0000)` attached, since GitHub appends the pull request number to the squashed subject.
17
+ The bin lints a pull request title with ` (#0000)` attached, since GitHub appends the pull request number to the squashed subject.
17
18
  It moves the git comment character off `#`, so a title starting with `#` is linted like any other.
18
19
 
19
20
  ## What it reads
@@ -31,7 +32,7 @@ The bin takes no arguments.
31
32
  | Code | Result |
32
33
  | --- | --- |
33
34
  | 0 | The title lints clean, or there is no title to lint. |
34
- | 1 | The title breaks the rules, or no one open pull request heads the branch. |
35
+ | 1 | The title or the head subject breaks the rules, or more than one open pull request heads the branch. |
35
36
  | 2 | The kit config or binary cannot be resolved, or a dispatched run misses its environment. |
36
37
 
37
38
  ## Sample output
@@ -13,9 +13,9 @@ It holds them to the three separator rules of [The prose rules](checks-docs.md#t
13
13
 
14
14
  | Refused | For example | Passes |
15
15
  | --- | --- | --- |
16
- | an em dash | `hexn.io — платформа` | `hexn.io, платформа` |
16
+ | an em dash | `example.com — платформа` | `example.com, платформа` |
17
17
  | an en dash | `pages 1–5` | `pages 1 to 5` |
18
- | a hyphen used as a dash | `hexn.io - платформа` or `a -- b` | `well-known`, `kebab-case`, `1-5` and `--flag` |
18
+ | a hyphen used as a dash | `example.com - платформа` or `a -- b` | `well-known`, `kebab-case`, `1-5` and `--flag` |
19
19
 
20
20
  A hyphen used as a dash is a run of one to three hyphens with white space on both sides and text on both sides.
21
21
  So a hyphen inside a word or a number, a flag and a list marker all pass.
@@ -117,7 +117,7 @@ jobs:
117
117
  path: reports/mutation/mutation.json
118
118
  retention-days: 1
119
119
  tests:
120
- runs-on: ${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}
120
+ runs-on: ubuntu-latest
121
121
  steps:
122
122
  - uses: actions/checkout@v5
123
123
  - uses: oven-sh/setup-bun@v2
@@ -135,7 +135,7 @@ jobs:
135
135
  report:
136
136
  needs: [mutation, tests]
137
137
  if: ${{ !cancelled() && needs.tests.result == 'success' }}
138
- runs-on: ${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}
138
+ runs-on: ubuntu-latest
139
139
  permissions:
140
140
  contents: read
141
141
  actions: read
@@ -176,9 +176,9 @@ It passes `--mutation` only when the mutation job succeeded, and the failed muta
176
176
  The concurrency group queues a run started by hand behind the scheduled one, so two runs never file the same finding twice.
177
177
  A dispatch on a branch runs both jobs and lists its findings in the report job's log, and `if-no-files-found: ignore` lets that run upload no record.
178
178
  `issues: write` on the report job is the one permission beyond reading the repository and its runs.
179
- A public repository keeps `runs-on: ubuntu-latest` on the mutation job.
179
+ A public repository keeps `runs-on: ubuntu-latest` on every job.
180
180
  A private repository sets `runs-on` on the mutation job to the labels declared under `checks.mutationRunner` in `package.json`, and [checks-ci-wiring](checks-ci-wiring.md#runners) refuses a mutation job that names `CI_RUNS_ON`.
181
- The report job reads `CI_RUNS_ON`, because it runs no mutant.
181
+ A private repository sets `runs-on: ${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}` on the tests and report jobs.
182
182
  A successful run uploads `night-run`, so the next night compares with it, and a failed run uploads nothing a later night reads.
183
183
  The artifact keeps 90 days, so a gap of nights still leaves a previous night to compare with.
184
184
 
@@ -0,0 +1,90 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
5
+ # checks-pr-text
6
+
7
+ `checks-pr-text` scans the title, the body and every comment of a public pull request for a secret or a term of the private pack.
8
+
9
+ ## What it checks
10
+
11
+ It scans the title, the body, each conversation comment, each review and each review comment, with the rules [checks-secrets](checks-secrets.md) runs on a commit.
12
+ On a fork's pull request a review or a review comment starts no scan, because GitHub gives those runs no secret.
13
+ That text is scanned only when another trigger runs next.
14
+ It adds the private pack when `CHECKS_SECRETS_PACK` names one, as [The private pack](checks-secrets.md#the-private-pack) describes, and its last line says whether the pack ran.
15
+ With `CHECKS_SECRETS_PACK` set but the pack unable to serve, it exits 2 and scans nothing.
16
+ It reads the text as it stands now, so a text edited clean passes on the next run.
17
+ A finding names the text, its line and its rule, and never the matched text.
18
+ A finding location prints as it stands, such as a link.
19
+ So a term inside a repository name shows there.
20
+ That location is already public in the scanned pull request.
21
+
22
+ Deleting a finding from the current text leaves it in GitHub's edit history of that text, which the text's edit menu deletes revision by revision.
23
+ The timeline keeps a renamed title's old text, and only GitHub Support removes it.
24
+
25
+ ## What it reads
26
+
27
+ It calls `gh api` with `GH_TOKEN` for the pull request and its three kinds of comment, in the repository of the checkout's remote.
28
+ It reads the path of the private pack from `CHECKS_SECRETS_PACK`, and the kit's rules from `src/delivery/gitleaks.toml` in the installed package.
29
+ It writes each text to a file in a temporary directory, and deletes the directory when the scan ends.
30
+
31
+ ## Arguments
32
+
33
+ | Argument | Meaning |
34
+ | --- | --- |
35
+ | `<pull-request-number>` | the number of the pull request to scan |
36
+
37
+ ## Exit codes
38
+
39
+ | Code | Result |
40
+ | --- | --- |
41
+ | 0 | No text carries a secret or a term of the pack. |
42
+ | 1 | A text carries a secret or a term of the pack. |
43
+ | 2 | The argument is not one pull request number, `gh api` fails, no verified scanner is at hand, gitleaks fails, or `CHECKS_SECRETS_PACK` is set and the pack cannot serve. |
44
+
45
+ ## Sample output
46
+
47
+ ```
48
+ pr-text: pull request 42 carries 2 secret(s) or private term(s); delete each from the text it names, and delete the revision GitHub keeps in that text's edit history:
49
+ the body:3 private-domain: A domain the private pack lists
50
+ https://github.com/owner/repo/pull/42#issuecomment-1:1 private-name: A name form the private pack lists
51
+ pr-text: the kit's rules and the private pack CHECKS_SECRETS_PACK names ran
52
+ ```
53
+
54
+ ## When it runs
55
+
56
+ A public repository runs it from a workflow of its own on every change to a pull request's text:
57
+
58
+ ```yaml
59
+ on:
60
+ pull_request_target:
61
+ types: [opened, edited, synchronize, reopened]
62
+ issue_comment:
63
+ types: [created, edited, deleted]
64
+ pull_request_review:
65
+ types: [submitted, edited]
66
+ pull_request_review_comment:
67
+ types: [created, edited, deleted]
68
+ ```
69
+
70
+ The job writes the pack from a secret, as [The private pack](checks-secrets.md#the-private-pack) shows, then runs `bunx checks-pr-text` with the pull request's number.
71
+ It skips the scan when the secret is unset, as [When no pack is configured](#when-no-pack-is-configured) says.
72
+ `pull_request_target` runs the base branch's workflow with the repository's secrets, even for a fork's pull request.
73
+ The job reads the text through the API and checks out none of the pull request's code, so a fork runs nothing there.
74
+ A comment's run reports on no pull request, so the job posts its result as a `pr-text` status on the head commit.
75
+ The kit's own `.github/workflows/pr-text.yml` is a complete example.
76
+
77
+ ## When no pack is configured
78
+
79
+ The kit's workflow skips the scan when `CHECKS_SECRETS_PACK` is unset or empty, instead of failing.
80
+ It writes a notice and a job summary line that say so, and posts a `pr-text` status of success whose description reads `scan skipped: no private pack is configured (CHECKS_SECRETS_PACK is unset)`.
81
+ A skip is a coverage gap, not a pass: no private term was checked on that pull request.
82
+ Only the repository secret decides a skip, so no input from a pull request's author can cause one.
83
+ To scan with the pack, set the repository secret `CHECKS_SECRETS_PACK` to the pack's JSON, as [The private pack](checks-secrets.md#the-private-pack) describes.
84
+ The next eligible run then scans the text with it.
85
+ On a fork's pull request a review or a review comment still starts no run, so that text waits for another trigger.
86
+
87
+ ## Related topics
88
+
89
+ - [checks-secrets](checks-secrets.md)
90
+ - [checks-commit-title](checks-commit-title.md)
@@ -47,7 +47,7 @@ Released 2026-09-27.
47
47
 
48
48
  Each consumer owns `.github/workflows/release.yml` and calls the kit bins from it.
49
49
  The version lives in `package.json`, and the tag names the same version with a leading `v`.
50
- The build writes the changelog, so the release pull request carries the notes before the tag exists.
50
+ The build writes the changelog, so the release commit carries the notes before the tag exists.
51
51
  A repository that publishes a package to npm releases with this workflow:
52
52
 
53
53
  ```yaml
@@ -80,7 +80,7 @@ jobs:
80
80
  with:
81
81
  node-version: 24
82
82
  registry-url: https://registry.npmjs.org
83
- - run: npm publish
83
+ - run: ./node_modules/.bin/checks-release-publish
84
84
  github-release:
85
85
  needs: publish
86
86
  runs-on: ubuntu-latest
@@ -140,8 +140,9 @@ jobs:
140
140
  ```
141
141
 
142
142
  A private repository sets `runs-on: ${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}` on each job in either workflow, as [checks-ci-wiring](checks-ci-wiring.md#runners) requires.
143
- A release is a pull request that holds only the version bump and the built changelog.
144
- [checks-release-pr](checks-release-pr.md) opens it on the daily release's schedule, or on every push to `main` in a package repository.
143
+ A release is one commit that holds only the version bump and the built changelog.
144
+ A package repository lands it on every push to `main` through [checks-release-push](checks-release-push.md), which tags it and dispatches this workflow on the tag.
145
+ Another repository takes it as the pull request [checks-release-pr](checks-release-pr.md) opens on the daily release's schedule.
145
146
  When it lands, [checks-release-tag](checks-release-tag.md) tags the merge commit and dispatches this workflow on the tag.
146
147
  A tag the workflow token pushes starts no `push` run, so the workflow triggers on `workflow_dispatch` as well, and refuses a dispatch on a ref that is not a tag.
147
148
  A tag a person pushes starts the same workflow through its `push` trigger.
@@ -156,3 +157,4 @@ A repository with no versioned releases does not need it.
156
157
 
157
158
  - [checks-changelog](checks-changelog.md)
158
159
  - [checks-release-report](checks-release-report.md)
160
+ - [checks-release-publish](checks-release-publish.md)
@@ -75,15 +75,13 @@ release-pr: https://github.com/acme/widget/pull/12 releases 0.4.0 from 3f2a9c81d
75
75
  ## When it runs
76
76
 
77
77
  The daily release workflow below runs it once a day on the default branch.
78
- A package repository runs it on every push to `main` other than a release commit.
78
+ A package published to npm releases each push to `main` through [checks-release-push](checks-release-push.md) instead, with no pull request.
79
79
  Run the workflow by hand with `gh workflow run daily-release` to refresh the release pull request sooner.
80
80
 
81
81
  ## Running it in CI
82
82
 
83
- A repository takes the daily release as `.github/workflows/daily-release.yml`.
83
+ A repository takes the daily release as the `daily-release` workflow below.
84
84
  A consuming repository pins daily-release between 03:00 and 04:00 UTC at a minute chosen so repositories do not start together.
85
- A package repository released on push drops the `schedule` trigger, so every push to `main` but a release commit opens or refreshes the release pull request.
86
- Its `pull-request` job's `if` reads `"github.event_name != 'push' || !startsWith(github.event.head_commit.message, 'chore: release ')"`.
87
85
  The `pull-request` job checks out its branch's tip when it starts, so a run that starts late never rebuilds the release on an older commit.
88
86
  The `pull-request` job runs on the schedule, and the `tag` job runs when a release commit lands on `main`, as [checks-release-tag](checks-release-tag.md) says:
89
87
 
@@ -186,5 +184,6 @@ gh api --method PUT repos/<owner>/<repo>/actions/permissions/workflow -F can_app
186
184
 
187
185
  - [checks-release-report](checks-release-report.md)
188
186
  - [checks-release-tag](checks-release-tag.md)
187
+ - [checks-release-push](checks-release-push.md)
189
188
  - [checks-changelog](checks-changelog.md)
190
189
  - [checks-release-notes](checks-release-notes.md)
@@ -0,0 +1,63 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
5
+ # checks-release-publish
6
+
7
+ `checks-release-publish` publishes the version `package.json` holds to npm, unless npm already holds it or a newer version.
8
+
9
+ ## What it checks
10
+
11
+ It reads the `latest` dist-tag of the package from npm.
12
+ It runs `npm publish` when npm holds no version of the package yet, or when the version is above `latest`.
13
+ It publishes nothing and ends cleanly when `latest` is the version, so a rerun of a release that published passes.
14
+ It publishes nothing and ends cleanly when `latest` is above the version.
15
+ The release workflow refuses a tag off `main`, so the newer version already carries every change of the older one.
16
+ When `npm publish` fails, it reads `latest` again.
17
+ A newer version that published while it ran ends it cleanly as well, and any other failure fails it.
18
+
19
+ Two uploads that overlap can still leave `latest` on the older version, since the order check runs before either upload completes.
20
+ `checks-release-push` holds its concurrency group until the release workflow it dispatched finishes for that reason, so releases it cuts publish one at a time, while a release workflow dispatched or rerun by hand relies on this bin alone.
21
+ That hold ends early in two cases.
22
+ One is when the 45 minute wait times out.
23
+ The other is when polling fails past the retry bound or the dispatch of the release workflow fails after GitHub accepted it.
24
+ In those cases an unfinished publisher can overlap the next release, and if latest lands on the older version the fix is npm dist-tag add <package>@<newer version> latest.
25
+
26
+ ## What it reads
27
+
28
+ It reads `name` and `version` from `package.json` in the working directory.
29
+ It reads `latest` with `npm view <name> dist-tags --json`, and takes npm's `E404` answer for a package npm does not hold yet.
30
+ `npm publish` takes its registry and credentials from the workflow, as trusted publishing sets them.
31
+
32
+ ## Arguments
33
+
34
+ ```sh
35
+ checks-release-publish
36
+ ```
37
+
38
+ It takes no arguments.
39
+
40
+ ## Exit codes
41
+
42
+ | Code | When |
43
+ | --- | --- |
44
+ | 0 | the version published, npm already holds it, or npm holds a newer version |
45
+ | 2 | the arguments do not parse, `package.json` or npm's answer does not parse, or npm refuses the publish for a reason other than a newer version |
46
+
47
+ ## Sample output
48
+
49
+ A release that runs after a newer one published prints one line:
50
+
51
+ ```
52
+ release-publish: npm already holds @acme/widget@0.4.1, which carries every change in 0.4.0, so 0.4.0 stays off npm
53
+ ```
54
+
55
+ ## When it runs
56
+
57
+ The release workflow of a package published to npm runs it in place of `npm publish`, as [checks-release-notes](checks-release-notes.md#release-workflow) shows.
58
+ A repository that never publishes to npm does not need it.
59
+
60
+ ## Related topics
61
+
62
+ - [checks-release-notes](checks-release-notes.md)
63
+ - [checks-release-push](checks-release-push.md)
@@ -0,0 +1,153 @@
1
+ ---
2
+ kind: reference
3
+ audience: consumers
4
+ ---
5
+ # checks-release-push
6
+
7
+ `checks-release-push` lands the next version of a package on its branch once the checks pass.
8
+ It then tags the release and dispatches the release workflow on the tag.
9
+
10
+ ## What it checks
11
+
12
+ It lists the unreleased changes the way [checks-release-report](checks-release-report.md) does, and does nothing when there are none.
13
+ It bumps the version `package.json` holds by the rule [checks-release-pr](checks-release-pr.md#what-it-checks) states, and refuses what that bin refuses.
14
+ It refuses when `v<version>` of the next version already tags a commit, since a version is released once.
15
+ It writes the next version into `package.json` and runs `bun run build`, which writes `CHANGELOG.md`.
16
+ It commits both as `chore: release <version>` on top of `HEAD` through the GitHub API, as `checks-release-pr` does.
17
+ It points the branch `release/<branch>` at that commit and dispatches every check workflow its arguments name there.
18
+ It waits up to 45 minutes in all for those workflows to finish on the release commit.
19
+ It reads the newest run of each, so a rerun stands in for the run it repeats.
20
+ A failed read of the runs list does not end the wait.
21
+ The wait retries the read at the same interval.
22
+ A successful read resets the count of failed reads in a row.
23
+ Past four failed reads in a row the run fails with the last read error.
24
+ When any of them concludes short of success, it refuses and leaves `<branch>` and the tags alone.
25
+ When all of them pass, it moves `<branch>` to the release commit without a force.
26
+ GitHub refuses that update when `<branch>` moved while the checks ran.
27
+ The run then ends cleanly, since the run of the push that moved it releases the new head.
28
+ When the update fails but `<branch>` already holds the release commit, the update landed and the run goes on.
29
+ It then tags the release commit `v<version>`, pushes the tag and dispatches the release workflow on it, and waits up to 45 minutes for that workflow's run on the release commit to finish.
30
+ When that run concludes short of success, or does not finish in time, the bin fails, though `<branch>` already holds the release and the tag exists.
31
+ When `HEAD` is itself a release commit, it tags it as [checks-release-tag](checks-release-tag.md) does, and waits for the release workflow it dispatches the same way.
32
+ So a rerun after a failed tag push finishes the release.
33
+ It leaves the working tree as it found it.
34
+
35
+ No pull request is opened, so a package release needs neither a merge by hand nor the setting that lets the token open pull requests.
36
+
37
+ ## What it reads
38
+
39
+ It reads the `v*` tags, the commit subjects since the last one and `package.json` from the checkout.
40
+ It refuses a shallow checkout, a detached `HEAD` and a working tree with changes to tracked files.
41
+ It reads the tags and the branch from `origin` with `git ls-remote`, and pushes the tag with the credentials the checkout holds.
42
+ It calls the GitHub API through `gh api`, which takes the repository from the checkout's remote and the token from `GH_TOKEN`.
43
+ The token needs `contents: write` and `actions: write`.
44
+ The branch's protection must let the workflow token push a commit whose required checks passed, so it requires status checks and no pull request review.
45
+
46
+ ## Arguments
47
+
48
+ ```sh
49
+ checks-release-push <release-workflow> <workflow>...
50
+ ```
51
+
52
+ The first argument names the release workflow file under `.github/workflows/`, such as `release.yml`, which triggers on `workflow_dispatch` and keeps its own guards, as [checks-release-notes](checks-release-notes.md) shows.
53
+ Each later argument names a workflow file whose jobs report the checks the branch requires, such as `ci.yml`.
54
+ Each named workflow triggers on `workflow_dispatch`.
55
+ Name no check workflow that runs something else on dispatch, such as a mutation baseline.
56
+
57
+ ## Exit codes
58
+
59
+ | Code | When |
60
+ | --- | --- |
61
+ | 0 | nothing is unreleased, the branch moved while the checks ran, the release tag already tags `HEAD`, or the release landed and its release workflow finished |
62
+ | 2 | the arguments do not parse, a refusal above applies, a check fails or does not finish in time, the build fails, the push or a GitHub API call fails, or the dispatched release workflow fails or does not finish in time |
63
+
64
+ ## Sample output
65
+
66
+ A run that releases prints the build's own output, then three lines:
67
+
68
+ ```
69
+ release-push: dispatched ci.yml on release/main to check 0.4.0 at 3f2a9c81d0b4
70
+ release-push: released 0.4.0 as 3f2a9c81d0b4 on main, tagged it v0.4.0, and dispatched release.yml on it
71
+ release-push: release.yml finished on v0.4.0
72
+ ```
73
+
74
+ A run whose release workflow fails refuses with the run that failed, though the branch already holds the release and the tag exists:
75
+
76
+ ```
77
+ checks-release-push: the release workflow failed on 3f2a9c81d0b4: release.yml concluded failure at https://github.com/acme/widget/actions/runs/7
78
+ ```
79
+
80
+ A run whose checks fail refuses with the run that failed:
81
+
82
+ ```
83
+ checks-release-push: the checks failed on 3f2a9c81d0b4, so the release stays off the branch: ci.yml concluded failure at https://github.com/acme/widget/actions/runs/7
84
+ ```
85
+
86
+ ## When it runs
87
+
88
+ A package published to npm runs it on every push to `main`, so each merged feature or fix ships as a versioned release.
89
+ Run the workflow by hand with `gh workflow run release-push` to cut a release now.
90
+
91
+ ## Running it in CI
92
+
93
+ A package repository takes it as `.github/workflows/release-push.yml`:
94
+
95
+ ```yaml
96
+ name: release-push
97
+ on:
98
+ workflow_dispatch:
99
+ push:
100
+ branches: [main]
101
+ permissions:
102
+ contents: read
103
+ jobs:
104
+ release:
105
+ if: github.ref == 'refs/heads/main'
106
+ runs-on: ubuntu-latest
107
+ timeout-minutes: 120
108
+ concurrency:
109
+ group: release-push
110
+ cancel-in-progress: false
111
+ permissions:
112
+ contents: write
113
+ actions: write
114
+ steps:
115
+ - uses: actions/checkout@v5
116
+ with:
117
+ ref: ${{ github.ref }}
118
+ fetch-depth: 0
119
+ - uses: oven-sh/setup-bun@v2
120
+ with:
121
+ bun-version-file: .bun-version
122
+ - run: bun install --frozen-lockfile
123
+ - name: release what main holds once its checks pass
124
+ env:
125
+ GH_TOKEN: ${{ github.token }}
126
+ run: ./node_modules/.bin/checks-release-push release.yml ci.yml
127
+ ```
128
+
129
+ The push the workflow token makes starts no run, so landing the release commit never starts the workflow again.
130
+ The concurrency group holds from reading the branch until the release workflow it dispatched finishes, so releases cut by release-push publish one at a time, and a queued run the group cancels had cut nothing.
131
+ That hold ends early in two cases.
132
+ One is when the 45 minute wait times out.
133
+ The other is when polling fails past the retry bound or the dispatch of the release workflow fails after GitHub accepted it.
134
+ In those cases an unfinished publisher can overlap the next release, and if latest lands on the older version the fix is npm dist-tag add <package>@<newer version> latest.
135
+ [checks-release-publish](checks-release-publish.md) stays the guard for a release workflow dispatched or rerun by hand.
136
+ A run that waits behind it checks out the branch's tip when it starts, so it releases on top of what landed.
137
+ The job waits on the checks it dispatched and on the release workflow, so a hosted runner bills the minutes those workflows take.
138
+ A private repository sets `runs-on: ${{ vars.CI_RUNS_ON || 'ubuntu-latest' }}`, as [checks-ci-wiring](checks-ci-wiring.md#runners) requires of every job but a mutation job.
139
+
140
+ The other workflows accept the dispatch the way [checks-release-pr](checks-release-pr.md#running-it-in-ci) lists:
141
+
142
+ - `.github/workflows/ci.yml` triggers on `workflow_dispatch` and grants `statuses: write` and `pull-requests: read`.
143
+ - Each required job ends with the step that reports its result as a commit status.
144
+ - The title lint lints the subject of the head commit when no open pull request heads the dispatched branch, as [Commit messages](../configs/commit-messages.md) says.
145
+ - `.github/workflows/release.yml` triggers on `workflow_dispatch` and refuses a ref that is not a tag.
146
+
147
+ ## Related topics
148
+
149
+ - [checks-release-report](checks-release-report.md)
150
+ - [checks-release-pr](checks-release-pr.md)
151
+ - [checks-release-tag](checks-release-tag.md)
152
+ - [checks-release-notes](checks-release-notes.md)
153
+ - [checks-release-publish](checks-release-publish.md)
@@ -56,7 +56,7 @@ release-report: no unreleased changes since v0.1.0
56
56
  ## When it runs
57
57
 
58
58
  A person runs it to see what the next release holds.
59
- [checks-release-pr](checks-release-pr.md) reads the same changes before it opens the release pull request.
59
+ [checks-release-pr](checks-release-pr.md) and [checks-release-push](checks-release-push.md) read the same changes before they cut a release.
60
60
  A repository with no versioned releases does not need it.
61
61
 
62
62
  ## Related topics
@@ -64,3 +64,4 @@ A repository with no versioned releases does not need it.
64
64
  - [checks-changelog](checks-changelog.md)
65
65
  - [checks-release-notes](checks-release-notes.md)
66
66
  - [checks-release-pr](checks-release-pr.md)
67
+ - [checks-release-push](checks-release-push.md)
@@ -56,8 +56,10 @@ release-tag: tagged 3f2a9c81d0b4 as v0.4.0, and dispatched release.yml on it
56
56
  ## When it runs
57
57
 
58
58
  The `tag` job of the daily release workflow runs it on each push to `main` whose head commit reads as a release, as [checks-release-pr](checks-release-pr.md#running-it-in-ci) shows.
59
+ A package repository needs no `tag` job, since [checks-release-push](checks-release-push.md) tags the release it lands.
59
60
 
60
61
  ## Related topics
61
62
 
62
63
  - [checks-release-pr](checks-release-pr.md)
64
+ - [checks-release-push](checks-release-push.md)
63
65
  - [checks-release-notes](checks-release-notes.md)
@@ -5,6 +5,7 @@ audience: consumers
5
5
  # checks-secrets
6
6
 
7
7
  `checks-secrets` is the gate that fails a range whose commits add a secret, such as an API token, a private key, a VPN key or a proxy link that carries its credential.
8
+ A private pack adds terms no public file may carry, such as a person's name, an employer's domain, host names and IP ranges.
8
9
 
9
10
  ## What it checks
10
11
 
@@ -39,12 +40,90 @@ These rules do not catch:
39
40
  No setting accepts a finding.
40
41
  It ignores a repository's `.gitleaks.toml`, its `.gitleaksignore` and every `gitleaks:allow` comment.
41
42
  It skips the paths gitleaks' default config skips, such as lockfiles, images and `node_modules/`.
43
+ It scans what a commit adds to its files, and not its message or its author, which [checks-commit-identity](checks-commit-identity.md) judges.
44
+
45
+ ## The private pack
46
+
47
+ The private pack is a JSON file of terms that lives outside every public repository, and `CHECKS_SECRETS_PACK` names its path.
48
+ The kit ships no pack and no term, so a public repository can run the pack without carrying what it hides.
49
+ With `CHECKS_SECRETS_PACK` unset the gate runs the kit's rules alone, exactly as it does without a pack, and its last line says so.
50
+ With it set the gate adds a rule for each kind of term the pack lists, and fails a range that adds any of them.
51
+
52
+ Each key is optional, and each holds a list of strings:
53
+
54
+ | Key | Rule | What it matches |
55
+ | --- | --- | --- |
56
+ | `names` | `private-name` | a person's name form, such as a full name, a handle or a login |
57
+ | `domains` | `private-domain` | an employer's or any other domain, also inside an email address or a subdomain |
58
+ | `hosts` | `private-host` | a host name |
59
+ | `runners` | `private-runner` | a CI runner's name |
60
+ | `ipRanges` | `private-ip-range` | an IPv4 address inside a CIDR range, such as `192.0.2.0/24`, or a single IPv4 address |
61
+
62
+ A pack with obviously fake terms reads like this:
63
+
64
+ ```json
65
+ {
66
+ "names": ["Pat Example", "pexample"],
67
+ "domains": ["employer.example"],
68
+ "hosts": ["build-01.corp.example"],
69
+ "runners": ["runner-blue-7"],
70
+ "ipRanges": ["192.0.2.0/24", "198.51.100.7"]
71
+ }
72
+ ```
73
+
74
+ A term matches in any case, and the spaces inside it match any run of ASCII whitespace or Unicode space separators.
75
+ A term's end that is an ASCII letter, digit or underscore matches only at a word boundary, so `pexample` does not match inside `pexamples`.
76
+ An address matches only as a whole, so `192.0.2.0/24` does not match `1192.0.2.5`.
77
+ An IPv6 range has no key, so a pack lists its prefix under `hosts` as its addresses are written, such as `2001:db8:`.
78
+ The gitleaks default allowlist skips paths such as lockfiles, go.mod and go.sum, images and documents such as PDFs.
79
+ It also skips node_modules and some Go and Ruby vendor paths, so the pack terms are not searched there.
80
+ A lockfile's resolved URLs can carry a private registry host, so keep such a host out of lockfiles, go.mod and go.sum by other means.
81
+ Commit messages are not scanned by the pack, and checks-commit-identity covers only the author, the committer and Co-authored-by trailers.
82
+
83
+ The gate exits 2, and scans nothing, when `CHECKS_SECRETS_PACK` is set but the pack cannot serve:
84
+
85
+ - the variable is set to an empty value, as a CI secret that is missing expands
86
+ - the file is missing, unreadable or a directory
87
+ - the file is not JSON or holds a key the table does not name
88
+ - a term is blank or holds a control character, or a range is not IPv4
89
+ - the pack lists no term at all
90
+
91
+ No report or error prints a matched term text or the pack path, because a public CI log would publish either.
92
+ A finding names its file, its line and its rule, and the description of each pack rule says only which kind of term matched.
93
+ A finding location prints as it stands, such as a file name or a link.
94
+ So a term inside a file name or a repository name shows there.
95
+ That location is already public in the scanned commit.
96
+
97
+ The pack lives where only its owner reads it.
98
+ On a laptop it is a file outside every checkout, and the shell profile exports its path:
99
+
100
+ ```sh
101
+ export CHECKS_SECRETS_PACK="$HOME/.config/checks/secrets-pack.json"
102
+ ```
103
+
104
+ In GitHub Actions a repository or organisation secret holds the JSON, and the job writes it to a file before the gate runs:
105
+
106
+ ```yaml
107
+ - name: write the private pack
108
+ env:
109
+ PACK: ${{ secrets.CHECKS_SECRETS_PACK }}
110
+ run: |
111
+ printf '%s' "$PACK" > "$RUNNER_TEMP/secrets-pack.json"
112
+ echo "CHECKS_SECRETS_PACK=$RUNNER_TEMP/secrets-pack.json" >> "$GITHUB_ENV"
113
+ - run: bun run lint
114
+ ```
115
+
116
+ A fork's `pull_request` run gets no secret, so the file is empty and the gate exits 2 rather than pass without the pack.
117
+ Unlike [checks-pr-text](checks-pr-text.md), this gate has no skip: a job that writes the pack from an unset secret exits 2.
118
+ [checks-pr-text](checks-pr-text.md) scans a pull request's title, body and comments with the same pack.
42
119
 
43
120
  ## What it reads
44
121
 
45
122
  It reads the commits of the range from git, not the working tree.
46
123
  It needs git 2.36 or newer, which diffs a merge against the merge git would make on its own, and it exits 2 on an older git.
47
124
  It reads its rules from `src/delivery/gitleaks.toml` in the installed package.
125
+ It reads the path of the private pack from `CHECKS_SECRETS_PACK`.
126
+ It writes the kit's rules and the pack's into one config in a temporary directory it deletes when the scan ends.
48
127
 
49
128
  It runs the gitleaks release `src/delivery/gitleaks.ts` pins, by the SHA-256 of each platform's archive and of the binary inside it.
50
129
  On first use it downloads the archive from the scanner's GitHub release and unpacks the binary into `~/.cache/avi2dg-checks/gitleaks/<version>/`.
@@ -69,7 +148,7 @@ With one it scans that commit alone.
69
148
  | --- | --- |
70
149
  | 0 | no commit in the range adds a secret |
71
150
  | 1 | a commit in the range adds a secret |
72
- | 2 | a ref does not resolve, or a commit it scans is an octopus merge, or no verified scanner is at hand, or git is older than 2.36, or gitleaks fails |
151
+ | 2 | a ref does not resolve, or a commit it scans is an octopus merge, or no verified scanner is at hand, or git is older than 2.36, or gitleaks fails, or `CHECKS_SECRETS_PACK` is set and the pack cannot serve |
73
152
 
74
153
  ## Sample output
75
154
 
@@ -78,6 +157,15 @@ secrets: the range adds 3 secret(s); put a placeholder such as <private-key> in
78
157
  vpn/links.txt:1 proxy-userinfo-link in e19e45c2: A proxy share link that carries its credential before the @, such as vless://, trojan://, socks5:// or wireguard://
79
158
  vpn/wg0.conf:2 wireguard-key in e19e45c2: A WireGuard or AmneziaWG private or pre-shared key
80
159
  vpn/wg0.conf:7 wireguard-key in e19e45c2: A WireGuard or AmneziaWG private or pre-shared key
160
+ secrets: CHECKS_SECRETS_PACK is unset, so the kit's rules ran without a private pack
161
+ ```
162
+
163
+ With a pack set, a finding of the pack names the kind of term and never the term:
164
+
165
+ ```
166
+ secrets: the range adds 1 secret(s); put a placeholder such as <private-key> in its place in the commit that added it, and rotate any secret that left this machine:
167
+ docs/setup.md:12 private-domain in 4f1c09ab: A domain the private pack lists
168
+ secrets: the kit's rules and the private pack CHECKS_SECRETS_PACK names ran
81
169
  ```
82
170
 
83
171
  The report never prints a secret.
@@ -90,4 +178,5 @@ A local `bun run lint` runs it over the commits the branch adds, so it fails bef
90
178
  ## Related topics
91
179
 
92
180
  - [checks-lint](checks-lint.md)
181
+ - [checks-pr-text](checks-pr-text.md)
93
182
  - [Why it is shaped this way](../design.md)