Skip to content

Commit 3a4fd44

Browse files
authored
feat: add monorepo support (#26)
1 parent 2c16e0b commit 3a4fd44

13 files changed

Lines changed: 1844 additions & 84 deletions

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ jobs:
3232
- name: 📦 Install dependencies
3333
run: pnpm install --frozen-lockfile --ignore-scripts
3434

35+
- name: 🧪 Test
36+
run: pnpm test
37+
3538
- name: 💪 Test types
3639
run: pnpm test:types
3740

README.md

Lines changed: 51 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ The aim of **uppt** is to make a very simple, secure release workflow for mainta
1010

1111
### Set up your package for trusted publishing on npmjs.com
1212

13-
1. Visit `https://npmjs.com/<package-name>/settings` and add a new trusted publisher entry, pointing at your repo and the `release.yml` workflow, with the `npm stage publish` permission chip. Set the 'Environment name' to 'npm'.
13+
1. Visit `https://npmjs.com/<package-name>/settings` and add a new trusted publisher entry, pointing at your repo and the `release.yml` workflow, with the `npm stage publish` permission chip. Set the 'Environment name' to 'npm'. In a monorepo, repeat this once per published package, pointing each entry at the same workflow and environment.
1414

1515
> [!NOTE]
1616
> [Staged publishing](https://docs.npmjs.com/staged-publishing/) requires you to approve the publish before it goes live.
@@ -81,10 +81,8 @@ jobs:
8181
# The chained dispatch from `release` lands here as a `workflow_dispatch`
8282
# event on a `vX.Y.Z` tag ref. The `pack` job installs deps, runs
8383
# `pnpm pack` (or `npm pack`), and uploads the tarball as a workflow
84-
# artifact. Lifecycle scripts (`prepack`, `prepare`, `postpack`) run
85-
# here, in a job with `permissions: {}` and no `npm` environment.
86-
# Manual recovery uses the same path.
87-
# (Run workflow -> pick a `v*` tag).
84+
# artifact. See "Lifecycle scripts" below for what runs where. Manual
85+
# recovery uses the same path (Run workflow -> pick a `v*` tag).
8886
pack:
8987
if: github.event_name == 'workflow_dispatch' && startsWith(github.ref, 'refs/tags/v')
9088
runs-on: ubuntu-latest
@@ -137,6 +135,7 @@ Whenever you push to the default branch, this action parses conventional commits
137135
| `base-branch` | default branch | Base branch for the release PR. |
138136
| `node-version` | `24` | Node version for the scripts. Needs `--experimental-strip-types` (Node 22.6+, 24+ recommended). |
139137
| `checkout` | `true` | Set to `false` if the caller has already checked out with `fetch-depth: 0`. |
138+
| `packages` | _(unset)_ | Newline-separated list of publishable workspace directories (paths or globs, e.g. `packages/*`). When set, uppt operates in monorepo lockstep mode. See [Monorepo support](#monorepo-support). |
140139

141140
### Creates a release (`danielroe/uppt/release`)
142141

@@ -158,6 +157,7 @@ This subaction installs the package's dependencies, runs `pnpm pack --json` (if
158157
| `node-version` | `24` | Node version for the scripts. Needs `--experimental-strip-types` (Node 22.6+, 24+ recommended). Ignored when `install` is `false`. |
159158
| `checkout` | `true` | Set to `false` if the caller has already checked out the tag ref. |
160159
| `install` | `true` | Set to `false` to handle `actions/setup-node` and dependency installation yourself. Useful when you want a pinned package manager version, a cached `node_modules`, or a hardened install policy. When `false`, the caller must put `node`, `npm`, and any package manager on PATH before `uppt/pack` runs. |
160+
| `packages` | _(unset)_ | Newline-separated list of publishable workspace directories (paths or globs). Must match the value passed to `uppt/pr`. See [Monorepo support](#monorepo-support). |
161161

162162
| Output | Description |
163163
| --- | --- |
@@ -167,15 +167,58 @@ This subaction installs the package's dependencies, runs `pnpm pack --json` (if
167167

168168
This subaction downloads the tarball uploaded by `uppt/pack` in the same workflow run and runs `npm stage publish ./<tarball>.tgz` with OIDC authentication. The staged version then needs to be approved by a maintainer with 2FA on npmjs.com before it goes live.
169169

170-
> [!IMPORTANT]
171-
> `prepublishOnly` is **not** invoked: `uppt/publish` publishes the prebuilt tarball with `--ignore-scripts`. Move any logic you previously had in `prepublishOnly` into `prepack` so it runs during `uppt/pack` and the output lands in the tarball.
172-
173170
| Input | Default | Description |
174171
| --- | --- | --- |
175172
| `node-version` | `24` | Node version for the scripts and for `npm stage publish`. Needs `--experimental-strip-types` (Node 22.6+, 24+ recommended). |
176173
| `npm-access` | `public` | npm access level (`public` or `restricted`). |
177174
| `files` | _(scan artifact)_ | Optional JSON array of tarball filenames to publish, as emitted by `uppt/pack`'s `files` output. When omitted, every `*.tgz` in the downloaded artifact is published. |
178175

176+
## Lifecycle scripts
177+
178+
uppt runs your package's lifecycle scripts at one specific point and skips them everywhere else. The aim is to keep the runner that produces the tarball from executing more third-party code than it has to.
179+
180+
- **During install** (inside `uppt/pack`): runs with `--ignore-scripts`. Your dependencies' `preinstall` / `install` / `postinstall` hooks do **not** fire, and neither does your own repo's `prepare`. This is deliberate: it's why a compromised transitive dependency can't run code on the publish runner. If your build genuinely needs a dependency's `postinstall` to have run, set `install: false` on `uppt/pack` and install yourself before the action runs.
181+
- **During pack** (inside `uppt/pack`, after install): `prepack`, `prepare`, and `postpack` run. This is where your build belongs.
182+
- **During publish** (inside `uppt/publish`): nothing runs. `prepublishOnly` is **not** invoked; the prebuilt tarball is published with `--ignore-scripts`. Move any logic you previously had in `prepublishOnly` into `prepack` so it runs during `uppt/pack` and the output lands in the tarball.
183+
184+
## Monorepo support
185+
186+
uppt supports lockstep monorepos: every publishable package shares a single version, gets bumped together, lands under one `vX.Y.Z` tag, and is staged in one workflow run.
187+
188+
Declare the publishable workspaces by passing the same `packages:` input to both `uppt/pr` and `uppt/pack`. Each line is a directory path or a glob; `!`-prefixed entries are excluded; workspaces whose `package.json` has `"private": true` are silently skipped (even when listed by an exact path), so playgrounds and example apps stay out of npm.
189+
190+
```yaml
191+
pr:
192+
# ...
193+
steps:
194+
- uses: danielroe/uppt/pr@<sha>
195+
with:
196+
token: ${{ secrets.GITHUB_TOKEN }}
197+
packages: |
198+
packages/*
199+
!packages/playground
200+
201+
pack:
202+
# ...
203+
steps:
204+
- uses: danielroe/uppt/pack@<sha>
205+
with:
206+
packages: |
207+
packages/*
208+
!packages/playground
209+
```
210+
211+
The lockstep version comes from the workspaces themselves: every listed package must agree on a single semver `version`, and that's the version uppt bumps from. The root `package.json#version` (if present) is only bumped when it already matches the lockstep version, so a `0.0.0` or absent root version is left untouched.
212+
213+
> [!IMPORTANT]
214+
> The `packages:` value on `uppt/pr` and `uppt/pack` must match. If they diverge, the release PR and the published tarballs will cover different sets of packages.
215+
216+
> [!IMPORTANT]
217+
> If you use pnpm, every workspace you list under `packages:` must also be listed in your `pnpm-workspace.yaml`. `pnpm pack` resolves `workspace:` and `catalog:` specifiers via the workspace graph, so a directory missing from `pnpm-workspace.yaml` will produce a tarball with unresolved specifiers (or fail outright).
218+
219+
> [!NOTE]
220+
> Independent versioning (per-package tags and cadence) is not yet supported. Track [#9](https://github.com/danielroe/uppt/issues/9) if you need it.
221+
179222
## Prerequisites
180223

181224
For `pr` to work you need:

pack/action.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ inputs:
1919
description: 'Whether the action should install the package''s dependencies itself. Set to `false` if the caller has already installed (e.g. with a pinned package manager version, a cached `node_modules`, or a hardened install policy). When `false`, the action will not run `actions/setup-node` either; the caller is responsible for putting `node`, `npm`, and any package manager on PATH before `uppt/pack` runs.'
2020
required: false
2121
default: 'true'
22+
packages:
23+
description: 'Newline-separated list of publishable workspace directories, relative to the repo root. Each line is a path or glob (e.g. `packages/*`); `!`-prefixed entries are excluded; workspaces with `"private": true` are skipped. Must match the value passed to `uppt/pr`. Omit for single-package repos.'
24+
required: false
25+
default: ''
2226

2327
outputs:
2428
files:
@@ -99,6 +103,7 @@ runs:
99103
shell: bash
100104
env:
101105
PACK_OUT_DIR: ${{ runner.temp }}/uppt-pack
106+
PACKAGES: ${{ inputs.packages }}
102107
run: node --experimental-strip-types ${{ github.action_path }}/../scripts/pack.ts
103108

104109
- name: Upload tarball artifact

package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,15 @@
55
"description": "Composite GitHub Action to release PRs from conventional commits, tag on merge, publish to npm via OIDC.",
66
"author": "Daniel Roe <daniel@roe.dev>",
77
"license": "MIT",
8+
"type": "module",
89
"scripts": {
10+
"test": "vitest run",
911
"test:types": "tsgo --noEmit"
1012
},
1113
"devDependencies": {
1214
"@types/node": "24.12.4",
13-
"@typescript/native-preview": "7.0.0-dev.20260527.2"
15+
"@typescript/native-preview": "7.0.0-dev.20260527.2",
16+
"vitest": "^3.2.4"
1417
},
1518
"packageManager": "pnpm@11.5.0"
1619
}

0 commit comments

Comments
 (0)