Skip to content

Commit addefce

Browse files
committed
feat(constructive-pnpm-policy): ship our policy config and first-party inventory
Data only — no code, no bin, no install scripts — so our workspaces pin one reviewed exemption list instead of each keeping a copy. Refreshed weekly by a workflow that opens a PR rather than committing: a name landing in the inventory is a name that stops being quarantined.
1 parent 4b13fed commit addefce

7 files changed

Lines changed: 3850 additions & 5256 deletions

File tree

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
name: Refresh pnpm-policy inventory
2+
3+
# The inventory is an exemption list: a package landing in it stops being
4+
# quarantined by minimumReleaseAge. So this opens a pull request for review
5+
# rather than committing to main — one human glance at "these 3 names became
6+
# exempt" is the whole point of keeping the file in git.
7+
8+
on:
9+
schedule:
10+
# Mondays, 07:00 UTC.
11+
- cron: '0 7 * * 1'
12+
workflow_dispatch:
13+
inputs:
14+
throttle:
15+
description: Milliseconds between registry requests
16+
required: false
17+
default: '1000'
18+
19+
permissions:
20+
contents: write
21+
pull-requests: write
22+
23+
concurrency:
24+
group: pnpm-policy-inventory
25+
cancel-in-progress: false
26+
27+
jobs:
28+
refresh:
29+
runs-on: ubuntu-latest
30+
31+
steps:
32+
- name: Checkout code
33+
uses: actions/checkout@v4
34+
35+
- name: Setup pnpm
36+
uses: pnpm/action-setup@v2
37+
with:
38+
version: 10.12.2
39+
40+
- name: Setup Node.js
41+
uses: actions/setup-node@v4
42+
with:
43+
node-version: "20.x"
44+
cache: "pnpm"
45+
46+
- name: Install dependencies
47+
run: pnpm install --frozen-lockfile
48+
49+
- name: Build pnpm-policy
50+
run: pnpm --filter pnpm-policy run build
51+
52+
# The npm search endpoint is anonymous — no registry token needed — but it
53+
# rate-limits bursts, so requests are spaced out.
54+
- name: Query npm for what we publish
55+
run: pnpm --filter @constructive-io/pnpm-policy exec pnpm-policy inventory --cwd . --throttle "${{ inputs.throttle || '1000' }}"
56+
57+
- name: Open a pull request if the inventory moved
58+
env:
59+
GH_TOKEN: ${{ github.token }}
60+
run: |
61+
set -euo pipefail
62+
file=packages/constructive-pnpm-policy/inventory.json
63+
64+
if git diff --quiet -- "$file"; then
65+
echo "Inventory unchanged."
66+
exit 0
67+
fi
68+
69+
# `generatedAt` changes on every run, so a timestamp-only diff is not a
70+
# change worth a pull request.
71+
if [ "$(git diff -U0 -- "$file" | grep -c '^[+-][^+-]')" -le 2 ] \
72+
&& [ -z "$(git diff -U0 -- "$file" | grep '^[+-][^+-]' | grep -v generatedAt)" ]; then
73+
echo "Only generatedAt changed."
74+
exit 0
75+
fi
76+
77+
branch="chore/pnpm-policy-inventory-$(date -u +%Y%m%d)"
78+
git config user.name "github-actions[bot]"
79+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
80+
git checkout -b "$branch"
81+
git add "$file"
82+
git commit -m "chore(constructive-pnpm-policy): refresh first-party inventory"
83+
git push -f origin "$branch"
84+
85+
if gh pr list --head "$branch" --state open --json number --jq 'length' | grep -qv '^0$'; then
86+
echo "Pull request already open for $branch."
87+
exit 0
88+
fi
89+
90+
{
91+
echo "Automated refresh of the first-party inventory from the npm maintainer search."
92+
echo
93+
echo "**This is an exemption list.** Every name added here stops waiting out"
94+
echo "\`minimumReleaseAge\`, so read the diff before merging: a name you do not"
95+
echo "recognise means an account in \`maintainers:\` published something new."
96+
echo
97+
echo '```diff'
98+
git diff HEAD~1 -- "$file" | head -c 60000
99+
echo '```'
100+
} > /tmp/pr-body.md
101+
102+
gh pr create \
103+
--base "${{ github.ref_name }}" \
104+
--head "$branch" \
105+
--title "chore(constructive-pnpm-policy): refresh first-party inventory" \
106+
--body-file /tmp/pr-body.md

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ A comprehensive collection of TypeScript utilities for working with schemas, JSO
3232
| **schema-typescript** | [![npm](https://img.shields.io/npm/v/schema-typescript.svg)](https://www.npmjs.com/package/schema-typescript) | [GitHub](./packages/schema-typescript) | Convert JSON Schema to TypeScript Definitions |
3333
| **strfy-js** | [![npm](https://img.shields.io/npm/v/strfy-js.svg)](https://www.npmjs.com/package/strfy-js) | [GitHub](./packages/strfy-js) | Stringify JSON as JavaScript with extended serialization capabilities |
3434
| **yanse** | [![npm](https://img.shields.io/npm/v/yanse.svg)](https://www.npmjs.com/package/yanse) | [GitHub](./packages/yanse) | Fast and lightweight terminal color styling library with chalk-like API |
35+
| **@constructive-io/pnpm-policy** | [![npm](https://img.shields.io/npm/v/@constructive-io/pnpm-policy.svg)](https://www.npmjs.com/package/@constructive-io/pnpm-policy) | [GitHub](./packages/constructive-pnpm-policy) | Constructive's pnpm-policy data — our config and generated first-party inventory |
3536
| **@constructive-io/fetch-api-client** | [![npm](https://img.shields.io/npm/v/@constructive-io/fetch-api-client.svg)](https://www.npmjs.com/package/@constructive-io/fetch-api-client) | [GitHub](./packages/fetch-api-client) | Universal Fetch-based HTTP client for Node.js and browsers |
3637
| **find-and-require-package-json** | [![npm](https://img.shields.io/npm/v/find-and-require-package-json.svg)](https://www.npmjs.com/package/find-and-require-package-json) | [GitHub](./packages/find-and-require-package-json) | Find the package.json file from within a build/package |
3738
| **@constructive-io/http-errors** | [![npm](https://img.shields.io/npm/v/@constructive-io/http-errors.svg)](https://www.npmjs.com/package/@constructive-io/http-errors) | [GitHub](./packages/http-errors) | HTTP error handling utilities for API clients |
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# @constructive-io/pnpm-policy
2+
3+
<p align="center" width="100%">
4+
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
5+
</p>
6+
7+
<p align="center" width="100%">
8+
<a href="https://github.com/constructive-io/dev-utils/actions/workflows/ci.yml">
9+
<img height="20" src="https://github.com/constructive-io/dev-utils/actions/workflows/ci.yml/badge.svg" />
10+
</a>
11+
<a href="https://github.com/constructive-io/dev-utils/blob/main/LICENSE">
12+
<img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
13+
</a>
14+
<a href="https://www.npmjs.com/package/@constructive-io/pnpm-policy"><img height="20" src="https://img.shields.io/github/package-json/v/constructive-io/dev-utils?filename=packages%2Fconstructive-pnpm-policy%2Fpackage.json"></a>
15+
</p>
16+
17+
Constructive's supply-chain policy **data** for [`pnpm-policy`](https://www.npmjs.com/package/pnpm-policy): our config plus the generated inventory of everything we publish on npm. No code, no bin, no install scripts — it is inert by construction, because a package whose whole job is to say "these names skip the quarantine" should not also be able to run anything.
18+
19+
The tool lives in [`pnpm-policy`](../pnpm-policy). This package is the answer to "who is Constructive?" so our repos do not each maintain their own copy.
20+
21+
## Use it in a workspace
22+
23+
```bash
24+
pnpm add -D pnpm-policy @constructive-io/pnpm-policy
25+
```
26+
27+
`pnpm-policy.yaml` at the workspace root:
28+
29+
```yaml
30+
minimumReleaseAge: 14d
31+
blockExoticSubdeps: true
32+
maintainers:
33+
- pyramation
34+
scopes:
35+
- "@constructive-io"
36+
- "@constructive-db"
37+
- "@launchql"
38+
- "@pgpm"
39+
- "@pgpmjs"
40+
- "@pgsql"
41+
inventory: "@constructive-io/pnpm-policy/inventory.json"
42+
allowBuilds:
43+
esbuild: native binary, downloaded at install time
44+
```
45+
46+
Then:
47+
48+
```bash
49+
pnpm pnpm-policy generate # patch pnpm-workspace.yaml
50+
pnpm pnpm-policy check # CI: fail on drift or an expired waiver
51+
```
52+
53+
**Pin it exactly.** A floating range on an exemption list re-introduces exactly the trust hole the quarantine closes — the list could widen under a repo with no visible change. A pnpm `configDependency` gives you version *and* integrity:
54+
55+
```yaml
56+
# pnpm-workspace.yaml
57+
configDependencies:
58+
"@constructive-io/pnpm-policy": "0.1.0+sha512-..."
59+
```
60+
61+
## What is in here
62+
63+
| File | Contents |
64+
| --- | --- |
65+
| `pnpm-policy.yaml` | Our policy: release age, claimed scopes, `allowBuilds`, exceptions. |
66+
| `inventory.json` | Generated: every package `maintainer:pyramation` publishes, compressed. |
67+
68+
```json
69+
{
70+
"generatedAt": "…",
71+
"maintainers": ["pyramation"],
72+
"scopes": ["@constructive-io", "@constructive-db", "@launchql", "@pgpm", "@pgpmjs", "@pgsql"],
73+
"packages": ["pgsql-parser", "libpg-query", "inquirerer", "…"]
74+
}
75+
```
76+
77+
Scopes become `@scope/*` globs, so a package published into one tomorrow is exempt without a refresh. They are claimed by hand in `pnpm-policy.yaml` rather than inferred: npm has no "list a scope" API, so nothing can prove a scope is exclusively ours. Everything else — the ~890 unscoped and other-scoped names — is listed individually, which is exact, and `pnpm-policy generate` intersects that list against the consuming workspace's lockfile so only the names a repo actually resolves are written out.
78+
79+
## Refreshing
80+
81+
```bash
82+
pnpm run refresh # pnpm-policy inventory --cwd .
83+
```
84+
85+
Five throttled requests, a couple of seconds. In CI this runs weekly and **opens a pull request** rather than committing ([`pnpm-policy-inventory.yml`](../../.github/workflows/pnpm-policy-inventory.yml)): a name appearing in the diff is a name that stops being quarantined, so it gets read by a human before it lands. The npm search endpoint is anonymous, so the job needs no registry token — only `contents: write` and `pull-requests: write` to open the PR.
86+
87+
Consuming repos pick the change up when they bump their pin, deliberately.

0 commit comments

Comments
 (0)