depbot-policy 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +587 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +7 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +158 -0
- package/dist/dependabot.d.ts +7 -0
- package/dist/dependabot.js +40 -0
- package/dist/files.d.ts +8 -0
- package/dist/files.js +12 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +6 -0
- package/dist/locate.d.ts +10 -0
- package/dist/locate.js +39 -0
- package/dist/parse.d.ts +29 -0
- package/dist/parse.js +58 -0
- package/dist/rotation.d.ts +9 -0
- package/dist/rotation.js +26 -0
- package/dist/schema.d.ts +71 -0
- package/dist/schema.js +141 -0
- package/dist/starter.d.ts +2 -0
- package/dist/starter.js +34 -0
- package/dist/workflow.d.ts +7 -0
- package/dist/workflow.js +98 -0
- package/package.json +50 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Munawirul Hadi
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,588 @@
|
|
|
1
|
-
#
|
|
1
|
+
# depbot-policy
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/heyhadi/depbot-policy/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/depbot-policy)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
|
|
7
|
+
Write one small policy file. Get a `dependabot.yml` and a GitHub Actions workflow that
|
|
8
|
+
auto-merges only the dependency updates you consider safe, and hands everything else to this
|
|
9
|
+
week's reviewer.
|
|
10
|
+
|
|
11
|
+
```yaml
|
|
12
|
+
# depbot.policy.yml
|
|
13
|
+
version: 1
|
|
14
|
+
ecosystems:
|
|
15
|
+
- type: npm
|
|
16
|
+
directory: /
|
|
17
|
+
autoMerge:
|
|
18
|
+
updateTypes: [patch]
|
|
19
|
+
block:
|
|
20
|
+
- name: react
|
|
21
|
+
reason: Pinned until the React 19 migration
|
|
22
|
+
review:
|
|
23
|
+
rotation: [alice, bob, carol]
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```sh
|
|
27
|
+
npx depbot-policy generate
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
- `.github/dependabot.yml`: what Dependabot updates, how often, and what it ignores, with your
|
|
31
|
+
reasons kept as comments.
|
|
32
|
+
- `.github/workflows/dependabot-auto-merge.yml`: merges patch updates once CI passes, never major
|
|
33
|
+
ones, and requests a review from this week's person for everything else.
|
|
34
|
+
|
|
35
|
+
**[Try it in the browser →](https://heyhadi.github.io/depbot-policy/)**
|
|
36
|
+
|
|
37
|
+
## Contents
|
|
38
|
+
|
|
39
|
+
- [Why](#why)
|
|
40
|
+
- [Quick start](#quick-start)
|
|
41
|
+
- [CLI](#cli)
|
|
42
|
+
- [Policy reference](#policy-reference)
|
|
43
|
+
- [What gets generated](#what-gets-generated)
|
|
44
|
+
- [Repository setup](#repository-setup)
|
|
45
|
+
- [Keeping files in sync in CI](#keeping-files-in-sync-in-ci)
|
|
46
|
+
- [Validation and errors](#validation-and-errors)
|
|
47
|
+
- [Web playground](#web-playground)
|
|
48
|
+
- [Library API](#library-api)
|
|
49
|
+
- [Development](#development)
|
|
50
|
+
- [Releasing](#releasing)
|
|
51
|
+
- [Design notes](#design-notes)
|
|
52
|
+
|
|
53
|
+
## Why
|
|
54
|
+
|
|
55
|
+
Dependabot opens a pull request for every dependency update. Teams end up either merging them
|
|
56
|
+
without looking or letting them pile up. Setting up something better usually means hand-writing
|
|
57
|
+
two YAML files that have to agree with each other:
|
|
58
|
+
|
|
59
|
+
- `dependabot.yml`, for what to update and what to ignore, and
|
|
60
|
+
- an auto-merge workflow full of `fetch-metadata` outputs and GitHub expressions.
|
|
61
|
+
|
|
62
|
+
depbot-policy replaces both with one validated file:
|
|
63
|
+
|
|
64
|
+
- **Safe updates merge themselves.** Patch updates (and minor ones, if you opt in) merge after your
|
|
65
|
+
CI passes.
|
|
66
|
+
- **Risky updates go to a person.** Major versions, packages whose maintainers changed, and
|
|
67
|
+
anything else outside the policy get a review request from whoever's turn it is this week.
|
|
68
|
+
- **Blocked packages stay blocked, with a reason.** The reason is kept next to the rule, so the
|
|
69
|
+
block list can be cleaned up later.
|
|
70
|
+
|
|
71
|
+
## Quick start
|
|
72
|
+
|
|
73
|
+
Requires Node 22 or newer.
|
|
74
|
+
|
|
75
|
+
```sh
|
|
76
|
+
# 1. Create a starter policy (valid as-is, with every option explained)
|
|
77
|
+
npx depbot-policy init
|
|
78
|
+
|
|
79
|
+
# 2. Edit depbot.policy.yml, then generate the files
|
|
80
|
+
npx depbot-policy generate
|
|
81
|
+
|
|
82
|
+
# 3. Commit all three files
|
|
83
|
+
git add depbot.policy.yml .github/dependabot.yml .github/workflows/dependabot-auto-merge.yml
|
|
84
|
+
git commit -m "Manage Dependabot with depbot-policy"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Then do the one-time [repository setup](#repository-setup), so that auto-merge waits for CI.
|
|
88
|
+
|
|
89
|
+
To change anything later, edit `depbot.policy.yml` and run `npx depbot-policy generate` again.
|
|
90
|
+
Don't edit the generated files by hand. [`check`](#keeping-files-in-sync-in-ci) catches that.
|
|
91
|
+
|
|
92
|
+
## CLI
|
|
93
|
+
|
|
94
|
+
```text
|
|
95
|
+
depbot-policy <command> [options]
|
|
96
|
+
|
|
97
|
+
Commands:
|
|
98
|
+
init Create a starter depbot.policy.yml
|
|
99
|
+
generate Write .github/dependabot.yml and the auto-merge workflow
|
|
100
|
+
check Fail if the policy is invalid or the generated files are out of date
|
|
101
|
+
reviewer Print this week's reviewer from review.rotation
|
|
102
|
+
|
|
103
|
+
Options:
|
|
104
|
+
--policy <file> Policy file (default: depbot.policy.yml)
|
|
105
|
+
--out <dir> Repository root to write to or check (default: .)
|
|
106
|
+
--dry-run generate: print the files instead of writing them
|
|
107
|
+
--force init: overwrite an existing policy file
|
|
108
|
+
--date <date> reviewer: use this date instead of today (e.g. 2026-10-12)
|
|
109
|
+
-h, --help Show this help
|
|
110
|
+
-v, --version Show the version
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
| Command | Exit code |
|
|
114
|
+
|---|---|
|
|
115
|
+
| Success | `0` |
|
|
116
|
+
| Invalid policy, or `check` found missing or outdated files | `1` |
|
|
117
|
+
| Wrong usage (unknown command or option, bad `--date`) | `2` |
|
|
118
|
+
|
|
119
|
+
Examples:
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
npx depbot-policy generate --dry-run # preview without writing anything
|
|
123
|
+
npx depbot-policy generate --policy ops/deps.yml # policy somewhere else
|
|
124
|
+
npx depbot-policy reviewer # who's on duty this week?
|
|
125
|
+
npx depbot-policy reviewer --date 2026-12-28 # ...and in the last week of the year?
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
You can also install it as a dev dependency (`npm install --save-dev depbot-policy`) and call
|
|
129
|
+
`depbot-policy` from npm scripts.
|
|
130
|
+
|
|
131
|
+
## Policy reference
|
|
132
|
+
|
|
133
|
+
A complete policy, with every option:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
version: 1 # required, always 1
|
|
137
|
+
|
|
138
|
+
ecosystems: # required, at least one
|
|
139
|
+
- type: npm # required
|
|
140
|
+
directory: / # required, starts with "/"
|
|
141
|
+
schedule: weekly # daily | weekly | monthly (default: weekly)
|
|
142
|
+
- type: github-actions
|
|
143
|
+
directory: /
|
|
144
|
+
schedule: monthly
|
|
145
|
+
|
|
146
|
+
autoMerge: # optional; defaults shown
|
|
147
|
+
updateTypes: [patch] # patch, minor
|
|
148
|
+
dependencyTypes: [development, production]
|
|
149
|
+
mergeMethod: squash # squash | merge | rebase
|
|
150
|
+
|
|
151
|
+
block: # optional, default: []
|
|
152
|
+
- name: react # package name or glob, e.g. "@types/*"
|
|
153
|
+
reason: Pinned until the React 19 migration # required
|
|
154
|
+
ecosystems: [npm] # optional; default: every ecosystem
|
|
155
|
+
|
|
156
|
+
review: # optional
|
|
157
|
+
rotation: [alice, bob, carol] # GitHub usernames
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### `version`
|
|
161
|
+
|
|
162
|
+
Must be `1`. It's there so that a future, incompatible format can be detected and migrated.
|
|
163
|
+
|
|
164
|
+
### `ecosystems`
|
|
165
|
+
|
|
166
|
+
One entry for each package manager and directory that Dependabot should watch.
|
|
167
|
+
|
|
168
|
+
| Field | Required | Values | Default |
|
|
169
|
+
|---|---|---|---|
|
|
170
|
+
| `type` | yes | `bundler`, `cargo`, `composer`, `docker`, `github-actions`, `gomod`, `gradle`, `maven`, `mix`, `npm`, `nuget`, `pip`, `pub`, `swift`, `terraform` | |
|
|
171
|
+
| `directory` | yes | Path from the repository root, starting with `/` (e.g. `/`, `/apps/web`) | |
|
|
172
|
+
| `schedule` | no | `daily`, `weekly`, `monthly` | `weekly` |
|
|
173
|
+
|
|
174
|
+
- Yarn, pnpm and Bun projects use `npm`; Poetry and Pipenv use `pip`. If you write one of those
|
|
175
|
+
names instead, the error message tells you which one to use.
|
|
176
|
+
- The same `type` can appear more than once with different directories, which is how monorepos
|
|
177
|
+
are handled. The same `type` and `directory` twice is an error.
|
|
178
|
+
- Include `github-actions` if you can. It keeps your workflows' actions up to date, including the
|
|
179
|
+
pinned `fetch-metadata` action in the generated workflow.
|
|
180
|
+
|
|
181
|
+
### `autoMerge`
|
|
182
|
+
|
|
183
|
+
Which Dependabot pull requests merge without a person.
|
|
184
|
+
|
|
185
|
+
| Field | Values | Default |
|
|
186
|
+
|---|---|---|
|
|
187
|
+
| `updateTypes` | `patch`, `minor` | `[patch]` |
|
|
188
|
+
| `dependencyTypes` | `development`, `production` | `[development, production]` |
|
|
189
|
+
| `mergeMethod` | `squash`, `merge`, `rebase` | `squash` |
|
|
190
|
+
|
|
191
|
+
- `major` is deliberately not allowed. Major versions can contain breaking changes, so a person
|
|
192
|
+
should always read them.
|
|
193
|
+
- `development` and `production` mean direct dependencies (Dependabot's `direct:development` and
|
|
194
|
+
`direct:production`). Indirect (transitive) updates are never auto-merged.
|
|
195
|
+
- The merge method must be enabled in your repository settings.
|
|
196
|
+
|
|
197
|
+
If you leave out `autoMerge`, the defaults apply: patch updates to all direct dependencies,
|
|
198
|
+
squash-merged.
|
|
199
|
+
|
|
200
|
+
### `block`
|
|
201
|
+
|
|
202
|
+
Packages Dependabot should never update. Each entry becomes an `ignore` rule.
|
|
203
|
+
|
|
204
|
+
| Field | Required | Description |
|
|
205
|
+
|---|---|---|
|
|
206
|
+
| `name` | yes | Exact package name, or a glob such as `@types/*` |
|
|
207
|
+
| `reason` | yes | Why it's blocked. Kept as a comment in `dependabot.yml`. |
|
|
208
|
+
| `ecosystems` | no | Only block it for these ecosystem types (each must appear in `ecosystems`). Without this, it's blocked everywhere. |
|
|
209
|
+
|
|
210
|
+
### `review`
|
|
211
|
+
|
|
212
|
+
| Field | Required | Description |
|
|
213
|
+
|---|---|---|
|
|
214
|
+
| `rotation` | yes, if `review` is present | GitHub usernames, without `@`. Compared case-insensitively, so `alice` and `Alice` count as duplicates. |
|
|
215
|
+
|
|
216
|
+
Every Dependabot pull request that **isn't** auto-merged gets a review request from one person:
|
|
217
|
+
|
|
218
|
+
- **One person per week.** Weeks run from Monday 00:00 to Sunday 23:59 UTC, and people take turns
|
|
219
|
+
in list order.
|
|
220
|
+
- **Based on when the pull request was opened**, so re-runs always pick the same person.
|
|
221
|
+
- **Nothing is stored anywhere.** The turn is worked out from the date alone, so
|
|
222
|
+
`npx depbot-policy reviewer` (and the playground) can tell you who's on duty.
|
|
223
|
+
- **Changing the rota:** to cover a holiday, reorder or edit the list and regenerate.
|
|
224
|
+
|
|
225
|
+
Reviewers must have access to the repository, or GitHub rejects the request.
|
|
226
|
+
|
|
227
|
+
## What gets generated
|
|
228
|
+
|
|
229
|
+
For the [example policy](examples/depbot.policy.yml):
|
|
230
|
+
|
|
231
|
+
<details>
|
|
232
|
+
<summary><code>.github/dependabot.yml</code></summary>
|
|
233
|
+
|
|
234
|
+
```yaml
|
|
235
|
+
# Generated by depbot-policy from depbot.policy.yml. Do not edit by hand:
|
|
236
|
+
# change the policy file and regenerate.
|
|
237
|
+
|
|
238
|
+
version: 2
|
|
239
|
+
updates:
|
|
240
|
+
- package-ecosystem: npm
|
|
241
|
+
directory: /
|
|
242
|
+
schedule:
|
|
243
|
+
interval: weekly
|
|
244
|
+
ignore:
|
|
245
|
+
- dependency-name: react # Pinned until the React 19 migration
|
|
246
|
+
- dependency-name: "@types/*" # Type packages are updated together with their runtime package
|
|
247
|
+
- package-ecosystem: github-actions
|
|
248
|
+
directory: /
|
|
249
|
+
schedule:
|
|
250
|
+
interval: monthly
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
</details>
|
|
254
|
+
|
|
255
|
+
<details>
|
|
256
|
+
<summary><code>.github/workflows/dependabot-auto-merge.yml</code></summary>
|
|
257
|
+
|
|
258
|
+
```yaml
|
|
259
|
+
# Generated by depbot-policy from depbot.policy.yml. Do not edit by hand:
|
|
260
|
+
# change the policy file and regenerate.
|
|
261
|
+
|
|
262
|
+
# Requires, in the repository settings:
|
|
263
|
+
# - "Allow auto-merge" enabled.
|
|
264
|
+
# - Branch protection on the default branch with required status checks.
|
|
265
|
+
# Without required checks, GitHub merges at once instead of waiting for CI.
|
|
266
|
+
|
|
267
|
+
name: Dependabot auto-merge
|
|
268
|
+
on: pull_request
|
|
269
|
+
permissions:
|
|
270
|
+
contents: write
|
|
271
|
+
pull-requests: write
|
|
272
|
+
jobs:
|
|
273
|
+
auto-merge:
|
|
274
|
+
runs-on: ubuntu-latest
|
|
275
|
+
if: github.event.pull_request.user.login == 'dependabot[bot]' && github.actor == 'dependabot[bot]'
|
|
276
|
+
steps:
|
|
277
|
+
- id: metadata
|
|
278
|
+
uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
|
|
279
|
+
with:
|
|
280
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
281
|
+
- id: auto-merge
|
|
282
|
+
name: Enable auto-merge
|
|
283
|
+
if: steps.metadata.outputs.update-type == 'version-update:semver-patch' && (steps.metadata.outputs.dependency-type == 'direct:development' || steps.metadata.outputs.dependency-type == 'direct:production') && steps.metadata.outputs.maintainer-changes != 'true'
|
|
284
|
+
run: gh pr merge --auto --squash "$PR_URL"
|
|
285
|
+
env:
|
|
286
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
287
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
288
|
+
- name: Request a review from this week's reviewer
|
|
289
|
+
if: steps.auto-merge.outcome == 'skipped'
|
|
290
|
+
run: |-
|
|
291
|
+
read -ra reviewers <<< "$REVIEWERS"
|
|
292
|
+
created=$(date -u -d "$PR_CREATED_AT" +%s)
|
|
293
|
+
week=$(( (created + 259200) / 604800 ))
|
|
294
|
+
reviewer="${reviewers[week % ${#reviewers[@]}]}"
|
|
295
|
+
gh pr edit "$PR_URL" --add-reviewer "$reviewer"
|
|
296
|
+
env:
|
|
297
|
+
REVIEWERS: alice bob carol
|
|
298
|
+
PR_CREATED_AT: ${{ github.event.pull_request.created_at }}
|
|
299
|
+
PR_URL: ${{ github.event.pull_request.html_url }}
|
|
300
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
</details>
|
|
304
|
+
|
|
305
|
+
### When does a pull request get auto-merged?
|
|
306
|
+
|
|
307
|
+
All of these must be true:
|
|
308
|
+
|
|
309
|
+
1. **Dependabot opened the pull request, and Dependabot triggered this run.** If anyone else
|
|
310
|
+
pushes a commit to a Dependabot branch, that run doesn't qualify.
|
|
311
|
+
2. **The update type is allowed** by `autoMerge.updateTypes`. For grouped updates,
|
|
312
|
+
`fetch-metadata` reports the largest change in the group, so one minor bump makes the whole
|
|
313
|
+
group "minor".
|
|
314
|
+
3. **The dependency type is allowed** by `autoMerge.dependencyTypes`.
|
|
315
|
+
4. **The package's maintainers haven't changed.** A new maintainer is a common sign of a
|
|
316
|
+
supply-chain attack, so those updates wait for a person.
|
|
317
|
+
|
|
318
|
+
The workflow then runs `gh pr merge --auto`. That doesn't merge immediately. It tells GitHub to
|
|
319
|
+
merge once the branch's protection rules pass, which is why the setup below matters. If any
|
|
320
|
+
condition is false and the policy has a rotation, the next step requests a review instead.
|
|
321
|
+
|
|
322
|
+
## Repository setup
|
|
323
|
+
|
|
324
|
+
Do this once in each repository that uses the generated files.
|
|
325
|
+
|
|
326
|
+
1. **Allow auto-merge:** *Settings → General → Pull Requests →* check **Allow auto-merge**.
|
|
327
|
+
2. **Allow your merge method:** in the same section, make sure the method in
|
|
328
|
+
`autoMerge.mergeMethod` (squash by default) is enabled.
|
|
329
|
+
3. **Require CI before merging:** *Settings → Branches* (or *Settings → Rules → Rulesets*) → add a
|
|
330
|
+
rule for your default branch → **Require status checks to pass**, and select your CI checks.
|
|
331
|
+
|
|
332
|
+
> ⚠️ Without required status checks, `gh pr merge --auto` merges straight away, without waiting
|
|
333
|
+
> for CI.
|
|
334
|
+
|
|
335
|
+
4. **Commit the policy and the generated files** to the default branch.
|
|
336
|
+
|
|
337
|
+
You don't need any extra tokens or secrets. The workflow uses the built-in `GITHUB_TOKEN` and
|
|
338
|
+
declares the two permissions it needs. If your organization stops workflows from raising token
|
|
339
|
+
permissions, allow it for this repository under *Settings → Actions → General → Workflow
|
|
340
|
+
permissions*.
|
|
341
|
+
|
|
342
|
+
**To check it works:** the next Dependabot patch update should show "Auto-merge enabled" and merge
|
|
343
|
+
by itself once CI passes. A major update should get a review request instead.
|
|
344
|
+
|
|
345
|
+
## Keeping files in sync in CI
|
|
346
|
+
|
|
347
|
+
Add `check` to your CI, so a policy change can't be merged without regenerating the files:
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
- run: npx depbot-policy check
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
It fails, with exit code 1, when the policy is invalid or when either generated file is missing or
|
|
354
|
+
differs from what the policy produces:
|
|
355
|
+
|
|
356
|
+
```text
|
|
357
|
+
.github/workflows/dependabot-auto-merge.yml is out of date
|
|
358
|
+
Run `depbot-policy generate` and commit the result.
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
## Validation and errors
|
|
362
|
+
|
|
363
|
+
The policy is validated before anything is generated, and every problem is reported at once, not
|
|
364
|
+
just the first. Each line is `file:line:column: path: message`, which editors and CI logs can
|
|
365
|
+
link to:
|
|
366
|
+
|
|
367
|
+
```text
|
|
368
|
+
$ npx depbot-policy check
|
|
369
|
+
depbot.policy.yml:4:5: ecosystems[0].type: Dependabot covers yarn under "npm"; use type: npm
|
|
370
|
+
depbot.policy.yml:5:5: ecosystems[0].directory: Must start with "/" (paths are relative to the repository root)
|
|
371
|
+
depbot.policy.yml:6:5: ecosystems[0].shedule: Unknown key
|
|
372
|
+
depbot.policy.yml:9:24: autoMerge.updateTypes[1]: Major updates are never auto-merged; they always need a human review
|
|
373
|
+
depbot.policy.yml:12:5: block[0].reason: Required
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Rules worth knowing:
|
|
377
|
+
|
|
378
|
+
- **Unknown keys are errors**, so a typo like `automerge:` or `shedule:` is caught instead of
|
|
379
|
+
being silently ignored.
|
|
380
|
+
- **Duplicates are errors**, and the error points at the second occurrence. This covers the same
|
|
381
|
+
ecosystem and directory, the same blocked package, the same username in the rotation (ignoring
|
|
382
|
+
case), and the same value twice in any list.
|
|
383
|
+
- **YAML syntax errors** and **duplicate YAML keys** are reported with their position too.
|
|
384
|
+
|
|
385
|
+
## Web playground
|
|
386
|
+
|
|
387
|
+
**[heyhadi.github.io/depbot-policy](https://heyhadi.github.io/depbot-policy/)** lets you write a
|
|
388
|
+
policy and see the generated files as you type.
|
|
389
|
+
|
|
390
|
+
- **Live validation.** Errors are underlined in the editor and listed below it in line order.
|
|
391
|
+
Click one to jump to the exact text.
|
|
392
|
+
- **Generated files in tabs**, with Copy and Download buttons. While the policy has errors, the
|
|
393
|
+
last valid output stays visible, dimmed.
|
|
394
|
+
- **This week's reviewer** is shown when the policy has a rotation.
|
|
395
|
+
- **Presets:** full example, minimal, monorepo, and one with deliberate mistakes.
|
|
396
|
+
- **Share link:** stores the policy in the URL after the `#`. That part of a URL is never sent to
|
|
397
|
+
a server, so nothing leaves your browser.
|
|
398
|
+
- Light and dark mode follow your system settings, the layout works on phones, and the file tabs
|
|
399
|
+
can be used with the keyboard.
|
|
400
|
+
|
|
401
|
+
It's built with Next.js 16 (static export), React 19, CodeMirror 6 and Tailwind CSS 4, and it
|
|
402
|
+
imports the library straight from [`src/`](src/), so it always matches the code in the same commit.
|
|
403
|
+
|
|
404
|
+
## Library API
|
|
405
|
+
|
|
406
|
+
```sh
|
|
407
|
+
npm install depbot-policy
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
```ts
|
|
411
|
+
import { generateFiles, parsePolicy } from "depbot-policy";
|
|
412
|
+
|
|
413
|
+
const result = parsePolicy(source); // source: the policy's YAML text
|
|
414
|
+
|
|
415
|
+
if (!result.ok) {
|
|
416
|
+
for (const error of result.errors) {
|
|
417
|
+
// error.path: "ecosystems[0].type"
|
|
418
|
+
// error.message: 'Dependabot covers yarn under "npm"; use type: npm'
|
|
419
|
+
// error.location?: { start, end, line, column } (offsets, plus 1-based line and column)
|
|
420
|
+
}
|
|
421
|
+
} else {
|
|
422
|
+
for (const file of generateFiles(result.policy)) {
|
|
423
|
+
// file.path: ".github/dependabot.yml", ".github/workflows/dependabot-auto-merge.yml"
|
|
424
|
+
// file.contents: the YAML to write
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
| Export | Description |
|
|
430
|
+
|---|---|
|
|
431
|
+
| `parsePolicy(source: string): ParseResult` | Parses and validates YAML. Returns `{ ok: true, policy }` with defaults filled in, or `{ ok: false, errors }`. Never throws for bad input. |
|
|
432
|
+
| `generateFiles(policy): GeneratedFile[]` | Every generated file, as `{ path, contents }`. |
|
|
433
|
+
| `generateDependabotConfig(policy): string` | Just `.github/dependabot.yml`. |
|
|
434
|
+
| `generateAutoMergeWorkflow(policy): string` | Just `.github/workflows/dependabot-auto-merge.yml`. |
|
|
435
|
+
| `reviewerFor(rotation: string[], date: Date): string` | The reviewer on duty at `date`, using the same formula as the workflow. |
|
|
436
|
+
| `policySchema` | The Zod schema, if you need to validate an already-parsed object. |
|
|
437
|
+
| `ecosystemTypes` | The supported ecosystem names. |
|
|
438
|
+
| Types: `Policy`, `ParseResult`, `PolicyError`, `SourceLocation`, `GeneratedFile` | |
|
|
439
|
+
|
|
440
|
+
Everything is pure: no file system, network or global state. That's what lets the same code run in
|
|
441
|
+
the CLI, in tests and in the browser.
|
|
442
|
+
|
|
443
|
+
## Development
|
|
444
|
+
|
|
445
|
+
Requires Node 22 or newer (see [`.nvmrc`](.nvmrc)). Node runs the TypeScript source directly
|
|
446
|
+
during development; only the published package is compiled.
|
|
447
|
+
|
|
448
|
+
```sh
|
|
449
|
+
git clone https://github.com/heyhadi/depbot-policy.git
|
|
450
|
+
cd depbot-policy
|
|
451
|
+
npm install
|
|
452
|
+
npm test
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
### Commands
|
|
456
|
+
|
|
457
|
+
At the repository root:
|
|
458
|
+
|
|
459
|
+
| Command | What it does |
|
|
460
|
+
|---|---|
|
|
461
|
+
| `npm test` | Library and CLI tests (Vitest) |
|
|
462
|
+
| `npm run typecheck` | Type-check the library, CLI and tests |
|
|
463
|
+
| `npm run cli -- <command>` | Run the CLI from source, e.g. `npm run cli -- generate --dry-run --policy examples/depbot.policy.yml` |
|
|
464
|
+
| `npm run build` | Compile `src/` to `dist/` (JavaScript and type declarations) |
|
|
465
|
+
|
|
466
|
+
In `playground/` (run `npm install` at the root first; the playground uses the library source):
|
|
467
|
+
|
|
468
|
+
| Command | What it does |
|
|
469
|
+
|---|---|
|
|
470
|
+
| `npm install` | Install the playground's own dependencies |
|
|
471
|
+
| `npm run dev` | Dev server on http://localhost:3000 |
|
|
472
|
+
| `npm run build` | Static site in `playground/out/` |
|
|
473
|
+
| `npm test` | Playground tests (Vitest, React Testing Library, jsdom) |
|
|
474
|
+
| `npm run typecheck` | Type-check the playground |
|
|
475
|
+
|
|
476
|
+
### Project structure
|
|
477
|
+
|
|
478
|
+
```text
|
|
479
|
+
src/
|
|
480
|
+
schema.ts Policy schema and validation rules (Zod)
|
|
481
|
+
parse.ts parsePolicy: YAML → validated policy or errors with locations
|
|
482
|
+
locate.ts Maps an error path to its position in the YAML source
|
|
483
|
+
dependabot.ts dependabot.yml generator
|
|
484
|
+
workflow.ts Auto-merge workflow generator
|
|
485
|
+
rotation.ts Weekly reviewer: TypeScript formula and the workflow's shell version
|
|
486
|
+
files.ts generateFiles: every generated file and its path
|
|
487
|
+
cli.ts, bin.ts The depbot-policy command (bin.ts is the executable entry point)
|
|
488
|
+
starter.ts The policy written by `init`
|
|
489
|
+
index.ts Public exports
|
|
490
|
+
test/ Library and CLI tests; __snapshots__/ holds generated files
|
|
491
|
+
examples/ The example policy (the playground's default preset must match it)
|
|
492
|
+
playground/ Next.js web playground (app/, components/, lib/, test/)
|
|
493
|
+
depbot.policy.yml This repository's own policy; .github/dependabot.yml and the
|
|
494
|
+
auto-merge workflow are generated from it
|
|
495
|
+
.github/workflows/
|
|
496
|
+
ci.yml Tests, checks and linting on every pull request
|
|
497
|
+
pages.yml Deploys the playground to GitHub Pages
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
### Tests
|
|
501
|
+
|
|
502
|
+
- **Validation:** every rule has a test that checks the exact error path and message;
|
|
503
|
+
`locate.test.ts` checks the text each error points at.
|
|
504
|
+
- **Generators:** two kinds of test for each file.
|
|
505
|
+
- **Snapshots** (`test/__snapshots__/*.yml`) catch any change to the output's formatting. They're
|
|
506
|
+
real YAML files, so changes are easy to read in a diff.
|
|
507
|
+
- **Content tests** parse the output and check what it means, whatever the formatting.
|
|
508
|
+
- **Rotation:** the workflow's shell script runs in bash, with stand-in `date` and `gh` commands,
|
|
509
|
+
and must pick the same person as `reviewerFor` on every test date.
|
|
510
|
+
- **CLI:** every command runs against a real temporary directory.
|
|
511
|
+
- **Playground:** unit tests for its logic, plus tests of the whole page with React Testing Library.
|
|
512
|
+
CodeMirror can't run in jsdom, so the tests replace the two small editor components with plain
|
|
513
|
+
elements.
|
|
514
|
+
|
|
515
|
+
When you change a generator on purpose, update the snapshots and review the diff before
|
|
516
|
+
committing:
|
|
517
|
+
|
|
518
|
+
```sh
|
|
519
|
+
npx vitest run -u
|
|
520
|
+
git diff test/__snapshots__
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
### Continuous integration
|
|
524
|
+
|
|
525
|
+
[`ci.yml`](.github/workflows/ci.yml) runs on every pull request and every push to `main`:
|
|
526
|
+
|
|
527
|
+
- **Library:**
|
|
528
|
+
- Type-check and tests.
|
|
529
|
+
- `depbot-policy check` on this repository's own generated files.
|
|
530
|
+
- A **packed-package smoke test**: `npm pack`, install the tarball in an empty project, then run
|
|
531
|
+
`init`, `generate` and `check`.
|
|
532
|
+
- [actionlint](https://github.com/rhysd/actionlint) on this repository's workflows *and* on the
|
|
533
|
+
generated workflow snapshots, which also runs shellcheck on their scripts. actionlint is
|
|
534
|
+
downloaded from a pinned release and checked against its SHA-256.
|
|
535
|
+
- **Playground:** type-check, tests and a production build.
|
|
536
|
+
|
|
537
|
+
[`pages.yml`](.github/workflows/pages.yml) deploys the playground to GitHub Pages when `src/` or
|
|
538
|
+
`playground/` changes on `main`.
|
|
539
|
+
|
|
540
|
+
### Updating the pinned `fetch-metadata` action
|
|
541
|
+
|
|
542
|
+
The generated workflow pins `dependabot/fetch-metadata` to a commit SHA. To move to a new
|
|
543
|
+
release:
|
|
544
|
+
|
|
545
|
+
1. Find the release's commit: `gh api repos/dependabot/fetch-metadata/commits/<tag> -q .sha`
|
|
546
|
+
2. Update `action` and `version` in `fetchMetadata` in [`src/workflow.ts`](src/workflow.ts).
|
|
547
|
+
3. Check that the outputs used in the generated `if:` conditions still exist in that release.
|
|
548
|
+
4. Update the snapshots (`npx vitest run -u`), run the tests, and regenerate this repository's own
|
|
549
|
+
files (`npm run cli -- generate`).
|
|
550
|
+
|
|
551
|
+
## Releasing
|
|
552
|
+
|
|
553
|
+
1. Update `version` in `package.json` (for example `npm version minor`).
|
|
554
|
+
2. `npm publish`. `prepublishOnly` runs the type-check, the tests and the build first, so a broken
|
|
555
|
+
package can't be published by accident. Only `dist/`, `README.md`, `LICENSE` and
|
|
556
|
+
`package.json` are included (about 11 kB).
|
|
557
|
+
3. Push the commit and tag.
|
|
558
|
+
|
|
559
|
+
## Design notes
|
|
560
|
+
|
|
561
|
+
Short versions of the main decisions. The pull requests have the full reasoning.
|
|
562
|
+
|
|
563
|
+
- **One source of truth.** The Zod schema defines both the validation rules and the `Policy`
|
|
564
|
+
TypeScript type, so they can't disagree.
|
|
565
|
+
- **Strict validation.** Unknown keys are errors, because silently ignoring `automerge:` would leave
|
|
566
|
+
auto-merge on its defaults without you knowing.
|
|
567
|
+
- **Return errors, don't throw.** Invalid input is an expected case for a config tool, and callers
|
|
568
|
+
need every error, not just the first.
|
|
569
|
+
- **Pure core.** No I/O in the library, so it runs unchanged in the CLI, in tests and in the
|
|
570
|
+
browser.
|
|
571
|
+
- **Generated YAML via the `yaml` document API**, not string templates. The library handles quoting
|
|
572
|
+
(`"@types/*"` must be quoted) and comments.
|
|
573
|
+
- **Stateless rotation.** The reviewer is a function of the week, so there's nothing to store, sync
|
|
574
|
+
or get out of date.
|
|
575
|
+
- **Pinned actions.** Actions are referenced by commit SHA with a `# vX.Y.Z` comment, both in the
|
|
576
|
+
generated workflow and in this repository's own workflows. Tags can be moved; SHAs can't.
|
|
577
|
+
Dependabot understands the comment and updates both.
|
|
578
|
+
- **Least-privilege workflows.**
|
|
579
|
+
- Only the permissions each job needs.
|
|
580
|
+
- `pull_request`, not `pull_request_target`.
|
|
581
|
+
- No `${{ }}` expressions inside `run:` scripts. Values go through environment variables, which
|
|
582
|
+
prevents script injection.
|
|
583
|
+
- **It uses itself.** This repository's Dependabot setup is generated from its own
|
|
584
|
+
[`depbot.policy.yml`](depbot.policy.yml), and CI checks it.
|
|
585
|
+
|
|
586
|
+
## License
|
|
587
|
+
|
|
588
|
+
[MIT](LICENSE) © Munawirul Hadi
|
package/dist/bin.d.ts
ADDED
package/dist/bin.js
ADDED
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type PolicyError } from "./parse.ts";
|
|
2
|
+
export interface CliIo {
|
|
3
|
+
cwd: string;
|
|
4
|
+
stdout: (text: string) => void;
|
|
5
|
+
stderr: (text: string) => void;
|
|
6
|
+
}
|
|
7
|
+
/** Exit codes: 0 success, 1 invalid policy or outdated files, 2 wrong usage. */
|
|
8
|
+
export declare const exitCodes: {
|
|
9
|
+
readonly ok: 0;
|
|
10
|
+
readonly failed: 1;
|
|
11
|
+
readonly usage: 2;
|
|
12
|
+
};
|
|
13
|
+
/** Runs the CLI and returns its exit code. Side effects go through `io` and the file system. */
|
|
14
|
+
export declare function run(argv: readonly string[], io: CliIo): number;
|
|
15
|
+
/** `file:line:column: path: message`, the format editors and CI annotations understand. */
|
|
16
|
+
export declare function formatError(file: string, error: PolicyError): string;
|