Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/pnpm-policy-inventory.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: Refresh pnpm-policy inventory

# The inventory is an exemption list: a package landing in it stops being
# quarantined by minimumReleaseAge. So this opens a pull request for review
# rather than committing to main — one human glance at "these 3 names became
# exempt" is the whole point of keeping the file in git.

on:
schedule:
# Mondays, 07:00 UTC.
- cron: '0 7 * * 1'
workflow_dispatch:
inputs:
throttle:
description: Milliseconds between registry requests
required: false
default: '1000'

permissions:
contents: write
pull-requests: write

concurrency:
group: pnpm-policy-inventory
cancel-in-progress: false

jobs:
refresh:
runs-on: ubuntu-latest

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@v2
with:
version: 10.12.2

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20.x"
cache: "pnpm"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build pnpm-policy
run: pnpm --filter pnpm-policy run build

# The npm search endpoint is anonymous — no registry token needed — but it
# rate-limits bursts, so requests are spaced out.
- name: Query npm for what we publish
run: pnpm --filter @constructive-io/pnpm-policy exec pnpm-policy inventory --cwd . --throttle "${{ inputs.throttle || '1000' }}"

- name: Open a pull request if the inventory moved
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
file=packages/constructive-pnpm-policy/inventory.json

if git diff --quiet -- "$file"; then
echo "Inventory unchanged."
exit 0
fi

# `generatedAt` changes on every run, so a timestamp-only diff is not a
# change worth a pull request.
if [ "$(git diff -U0 -- "$file" | grep -c '^[+-][^+-]')" -le 2 ] \
&& [ -z "$(git diff -U0 -- "$file" | grep '^[+-][^+-]' | grep -v generatedAt)" ]; then
echo "Only generatedAt changed."
exit 0
fi

branch="chore/pnpm-policy-inventory-$(date -u +%Y%m%d)"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git checkout -b "$branch"
git add "$file"
git commit -m "chore(constructive-pnpm-policy): refresh first-party inventory"
git push -f origin "$branch"

if gh pr list --head "$branch" --state open --json number --jq 'length' | grep -qv '^0$'; then
echo "Pull request already open for $branch."
exit 0
fi

{
echo "Automated refresh of the first-party inventory from the npm maintainer search."
echo
echo "**This is an exemption list.** Every name added here stops waiting out"
echo "\`minimumReleaseAge\`, so read the diff before merging: a name you do not"
echo "recognise means an account in \`maintainers:\` published something new."
echo
echo '```diff'
git diff HEAD~1 -- "$file" | head -c 60000
echo '```'
} > /tmp/pr-body.md

gh pr create \
--base "${{ github.ref_name }}" \
--head "$branch" \
--title "chore(constructive-pnpm-policy): refresh first-party inventory" \
--body-file /tmp/pr-body.md
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ A comprehensive collection of TypeScript utilities for working with schemas, JSO
| **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 |
| **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 |
| **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 |
| **@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 |
| **@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 |
| **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 |
| **@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 |
Expand Down
87 changes: 87 additions & 0 deletions packages/constructive-pnpm-policy/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
# @constructive-io/pnpm-policy

<p align="center" width="100%">
<img height="250" src="https://raw.githubusercontent.com/constructive-io/constructive/refs/heads/main/assets/outline-logo.svg" />
</p>

<p align="center" width="100%">
<a href="https://github.com/constructive-io/dev-utils/actions/workflows/ci.yml">
<img height="20" src="https://github.com/constructive-io/dev-utils/actions/workflows/ci.yml/badge.svg" />
</a>
<a href="https://github.com/constructive-io/dev-utils/blob/main/LICENSE">
<img height="20" src="https://img.shields.io/badge/license-MIT-blue.svg"/>
</a>
<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>
</p>

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.

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.

## Use it in a workspace

```bash
pnpm add -D pnpm-policy @constructive-io/pnpm-policy
```

`pnpm-policy.yaml` at the workspace root:

```yaml
minimumReleaseAge: 14d
blockExoticSubdeps: true
maintainers:
- pyramation
scopes:
- "@constructive-io"
- "@constructive-db"
- "@launchql"
- "@pgpm"
- "@pgpmjs"
- "@pgsql"
inventory: "@constructive-io/pnpm-policy/inventory.json"
allowBuilds:
esbuild: native binary, downloaded at install time
```

Then:

```bash
pnpm pnpm-policy generate # patch pnpm-workspace.yaml
pnpm pnpm-policy check # CI: fail on drift or an expired waiver
```

**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:

```yaml
# pnpm-workspace.yaml
configDependencies:
"@constructive-io/pnpm-policy": "0.1.0+sha512-..."
```

## What is in here

| File | Contents |
| --- | --- |
| `pnpm-policy.yaml` | Our policy: release age, claimed scopes, `allowBuilds`, exceptions. |
| `inventory.json` | Generated: every package `maintainer:pyramation` publishes, compressed. |

```json
{
"generatedAt": "…",
"maintainers": ["pyramation"],
"scopes": ["@constructive-io", "@constructive-db", "@launchql", "@pgpm", "@pgpmjs", "@pgsql"],
"packages": ["pgsql-parser", "libpg-query", "inquirerer", "…"]
}
```

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.

## Refreshing

```bash
pnpm run refresh # pnpm-policy inventory --cwd .
```

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.

Consuming repos pick the change up when they bump their pin, deliberately.
Loading
Loading