@smoothbricks/cli 0.1.0 → 0.1.1
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/README.md +208 -83
- package/dist/cli.js +50 -20
- package/dist/lib/devenv.d.ts +6 -0
- package/dist/lib/devenv.d.ts.map +1 -0
- package/dist/lib/devenv.js +82 -0
- package/dist/lib/workspace.d.ts +5 -0
- package/dist/lib/workspace.d.ts.map +1 -1
- package/dist/lib/workspace.js +62 -5
- package/dist/monorepo/index.d.ts +6 -1
- package/dist/monorepo/index.d.ts.map +1 -1
- package/dist/monorepo/index.js +38 -5
- package/dist/monorepo/lockfile.d.ts +5 -1
- package/dist/monorepo/lockfile.d.ts.map +1 -1
- package/dist/monorepo/lockfile.js +45 -5
- package/dist/monorepo/managed-files.d.ts +1 -1
- package/dist/monorepo/managed-files.d.ts.map +1 -1
- package/dist/monorepo/managed-files.js +27 -10
- package/dist/monorepo/package-hygiene.js +1 -1
- package/dist/monorepo/package-policy.d.ts +3 -0
- package/dist/monorepo/package-policy.d.ts.map +1 -1
- package/dist/monorepo/package-policy.js +122 -19
- package/dist/monorepo/packed-manifest.d.ts +4 -0
- package/dist/monorepo/packed-manifest.d.ts.map +1 -0
- package/dist/monorepo/packed-manifest.js +51 -0
- package/dist/monorepo/packed-package.d.ts.map +1 -1
- package/dist/monorepo/packed-package.js +6 -0
- package/dist/monorepo/packs/index.d.ts.map +1 -1
- package/dist/monorepo/packs/index.js +4 -1
- package/dist/monorepo/publish-workflow.d.ts +86 -0
- package/dist/monorepo/publish-workflow.d.ts.map +1 -0
- package/dist/monorepo/publish-workflow.js +343 -0
- package/dist/nx-version-actions.cjs +25 -0
- package/dist/nx-version-actions.d.cts +6 -0
- package/dist/nx-version-actions.d.cts.map +1 -0
- package/dist/release/core.d.ts +50 -0
- package/dist/release/core.d.ts.map +1 -0
- package/dist/release/core.js +97 -0
- package/dist/release/github-release.d.ts +46 -0
- package/dist/release/github-release.d.ts.map +1 -0
- package/dist/release/github-release.js +97 -0
- package/dist/release/index.d.ts +14 -5
- package/dist/release/index.d.ts.map +1 -1
- package/dist/release/index.js +544 -109
- package/dist/release/npm-auth.d.ts +17 -0
- package/dist/release/npm-auth.d.ts.map +1 -0
- package/dist/release/npm-auth.js +51 -0
- package/dist/release/orchestration.d.ts +48 -0
- package/dist/release/orchestration.d.ts.map +1 -0
- package/dist/release/orchestration.js +105 -0
- package/dist/release/publish-plan.d.ts +17 -0
- package/dist/release/publish-plan.d.ts.map +1 -0
- package/dist/release/publish-plan.js +15 -0
- package/dist/release/retag-unpublished.d.ts +34 -0
- package/dist/release/retag-unpublished.d.ts.map +1 -0
- package/dist/release/retag-unpublished.js +77 -0
- package/managed/raw/tooling/git-hooks/pre-commit.sh +2 -7
- package/managed/templates/github/workflows/ci.yml +2 -0
- package/package.json +24 -3
- package/src/cli.ts +64 -25
- package/src/lib/devenv.test.ts +28 -0
- package/src/lib/devenv.ts +89 -0
- package/src/lib/workspace.ts +76 -5
- package/src/monorepo/__tests__/nx-version-actions.test.ts +75 -0
- package/src/monorepo/__tests__/publish-workflow.test.ts +315 -0
- package/src/monorepo/index.ts +45 -5
- package/src/monorepo/lockfile.ts +52 -7
- package/src/monorepo/managed-files.ts +42 -12
- package/src/monorepo/package-hygiene.ts +1 -1
- package/src/monorepo/package-policy.ts +134 -18
- package/src/monorepo/packed-manifest.ts +67 -0
- package/src/monorepo/packed-package.ts +7 -0
- package/src/monorepo/packs/index.ts +4 -0
- package/src/monorepo/publish-workflow.ts +430 -0
- package/src/nx-version-actions.cts +36 -0
- package/src/release/__tests__/core-properties.test.ts +149 -0
- package/src/release/__tests__/core-scenarios.test.ts +177 -0
- package/src/release/__tests__/core.test.ts +73 -0
- package/src/release/__tests__/fixture-repo.test.ts +305 -0
- package/src/release/__tests__/github-release.test.ts +144 -0
- package/src/release/__tests__/helpers/fixture-repo.ts +113 -0
- package/src/release/__tests__/npm-auth.test.ts +129 -0
- package/src/release/__tests__/orchestration.test.ts +202 -0
- package/src/release/__tests__/publish-plan.test.ts +61 -0
- package/src/release/__tests__/retag-unpublished.test.ts +160 -0
- package/src/release/core.ts +170 -0
- package/src/release/github-release.ts +134 -0
- package/src/release/index.ts +662 -116
- package/src/release/npm-auth.ts +122 -0
- package/src/release/orchestration.ts +186 -0
- package/src/release/publish-plan.ts +37 -0
- package/src/release/retag-unpublished.ts +122 -0
- package/managed/templates/github/workflows/publish.yml +0 -136
package/README.md
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
`@smoothbricks/cli` provides `smoo`, the SmoothBricks monorepo automation CLI. It is the control plane for shared CI,
|
|
4
4
|
release, Git hook, package metadata, and publish validation conventions across SmoothBricks-style repositories.
|
|
5
5
|
|
|
6
|
-
The tool is intentionally convention-over-configuration. SmoothBricks repos use Nx, Bun, Nix, devenv, and
|
|
7
|
-
`smoo` assumes those pieces exist instead of adding another local config file. Repos should be made correct
|
|
8
|
-
the mutating initialization path, then kept correct by the read-only validation path.
|
|
6
|
+
The tool is intentionally convention-over-configuration. SmoothBricks repos use [Nx], [Bun], [Nix], [devenv], and
|
|
7
|
+
[direnv], so `smoo` assumes those pieces exist instead of adding another local config file. Repos should be made correct
|
|
8
|
+
by running the mutating initialization path, then kept correct by the read-only validation path.
|
|
9
9
|
|
|
10
10
|
## Install
|
|
11
11
|
|
|
@@ -28,19 +28,20 @@ directly. Published installs use the package binary in `bin/smoo`, which imports
|
|
|
28
28
|
|
|
29
29
|
```bash
|
|
30
30
|
smoo monorepo init [--runtime-only] [--sync-runtime]
|
|
31
|
-
smoo monorepo validate
|
|
31
|
+
smoo monorepo validate [--fail-fast] [--only-if-new-workspace-package]
|
|
32
32
|
smoo monorepo update
|
|
33
33
|
smoo monorepo check
|
|
34
34
|
smoo monorepo diff
|
|
35
35
|
smoo monorepo validate-commit-msg <commit-msg-file>
|
|
36
36
|
smoo monorepo sync-bun-lockfile-versions
|
|
37
|
-
smoo monorepo list-
|
|
37
|
+
smoo monorepo list-release-packages [--fail-empty] [--github-output <path>]
|
|
38
38
|
smoo monorepo validate-public-tags
|
|
39
|
-
smoo monorepo release-state
|
|
40
39
|
|
|
41
|
-
smoo release
|
|
42
|
-
smoo release
|
|
43
|
-
smoo release
|
|
40
|
+
smoo release npm-status
|
|
41
|
+
smoo release repair-pending [--dry-run]
|
|
42
|
+
smoo release version --bump <auto|patch|minor|major|prerelease> [--dry-run] [--github-output <path>]
|
|
43
|
+
smoo release publish --bump <auto|patch|minor|major|prerelease> [--dry-run]
|
|
44
|
+
smoo release retag-unpublished <tag...> [--to <ref>] [--push] [--dispatch] [--remote <remote>] [--branch <branch>] [--dry-run]
|
|
44
45
|
smoo release trust-publisher [--dry-run] [--otp <code>] [--skip-login]
|
|
45
46
|
|
|
46
47
|
smoo github-ci cleanup-cache
|
|
@@ -55,12 +56,13 @@ only reporting drift.
|
|
|
55
56
|
|
|
56
57
|
It currently:
|
|
57
58
|
|
|
58
|
-
- Updates managed CI, release, hook, and formatting files.
|
|
59
|
+
- Updates managed CI, release, hook, and formatting files. The publish workflow is only written when the repo has owned
|
|
60
|
+
release packages.
|
|
59
61
|
- Ensures the local `tooling/smoo` source shim is executable when present.
|
|
60
62
|
- Synchronizes root runtime versions inside devenv, or when `--sync-runtime` is passed.
|
|
61
|
-
- Applies publish metadata defaults to `npm:public` packages.
|
|
63
|
+
- Applies safe publish metadata defaults to `npm:public` packages without inferring package ownership.
|
|
62
64
|
- Normalizes internal workspace dependency ranges to `workspace:*`.
|
|
63
|
-
- Runs `sherif --fix --select highest` for broad monorepo package hygiene.
|
|
65
|
+
- Runs [`sherif --fix --select highest`][sherif] for broad monorepo package hygiene.
|
|
64
66
|
- Normalizes conditional export ordering so `types` comes first and `default` comes last.
|
|
65
67
|
- Adds `src` to package `files` when development-only exports intentionally point at source files.
|
|
66
68
|
|
|
@@ -86,24 +88,32 @@ It checks:
|
|
|
86
88
|
- Managed file drift.
|
|
87
89
|
- Root package policy.
|
|
88
90
|
- Root Bun type version matches the Bun package manager version.
|
|
89
|
-
- Nx release policy
|
|
91
|
+
- Nx release policy, including project package release tags, project-level GitHub Release changelogs, and the temporary
|
|
92
|
+
Bun lockfile versionActions hook.
|
|
93
|
+
- `bun.lock` workspace versions match package manifests.
|
|
90
94
|
- Public package tag policy.
|
|
91
95
|
- Public package metadata.
|
|
92
96
|
- Workspace dependency ranges.
|
|
93
|
-
- `sherif` package hygiene.
|
|
94
|
-
- Packed public package artifacts with `publint
|
|
95
|
-
- Packed public package type resolution with the `attw` CLI.
|
|
97
|
+
- [`sherif`] package hygiene, with warnings treated as validation failures.
|
|
98
|
+
- Packed public package artifacts with [`publint`].
|
|
99
|
+
- Packed public package type resolution with the [`attw`][are-the-types-wrong] CLI.
|
|
100
|
+
|
|
101
|
+
The packed-package checks validate what [npm] users will install, not only the source tree. `smoo` packs each
|
|
102
|
+
`npm:public` package with [Bun], runs [`publint`] on the tarball, then runs [`attw`][are-the-types-wrong] on the same
|
|
103
|
+
tarball.
|
|
96
104
|
|
|
97
|
-
The
|
|
98
|
-
|
|
105
|
+
The [`attw`][are-the-types-wrong] check uses the `node16` profile and ignores the CJS-to-ESM warning. SmoothBricks
|
|
106
|
+
packages are [ESM]-first, so [CommonJS] consumers can use dynamic import. Node 10-only subpath failures are
|
|
107
|
+
intentionally ignored because [Node.js] 10 is not part of the supported package contract.
|
|
99
108
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
`smoo monorepo validate --only-if-new-workspace-package` first checks the staged git diff for newly added workspace
|
|
110
|
+
package manifests. If none are staged, it exits successfully without running the full validator. The generated
|
|
111
|
+
pre-commit hook uses this mode so adding a package rechecks conditional managed files, including whether the publish
|
|
112
|
+
workflow is now required, without making every commit pay for full validation.
|
|
103
113
|
|
|
104
114
|
## Publishable Packages
|
|
105
115
|
|
|
106
|
-
Publishability is declared with an Nx tag:
|
|
116
|
+
Publishability is declared with an [Nx] tag:
|
|
107
117
|
|
|
108
118
|
```json
|
|
109
119
|
{
|
|
@@ -113,23 +123,39 @@ Publishability is declared with an Nx tag:
|
|
|
113
123
|
}
|
|
114
124
|
```
|
|
115
125
|
|
|
116
|
-
This tag is the source of truth for
|
|
117
|
-
|
|
126
|
+
This tag is the source of truth for public npm metadata and publish artifact validation. Release selection adds one more
|
|
127
|
+
convention: a package is released by the current repository only when its `repository.url` exactly matches the root
|
|
128
|
+
`package.json` `repository.url`. Equivalent-but-different spellings, such as `git+https` vs SSH for the same GitHub
|
|
129
|
+
repo, fail validation because ownership should be explicit and visually obvious. This lets a workspace mirror public
|
|
130
|
+
packages from another repository without publishing them from the mirror.
|
|
118
131
|
|
|
119
132
|
Rules:
|
|
120
133
|
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
- Every non-private workspace package must have `npm:public`.
|
|
134
|
+
- `npm:public` packages must not be `private: true`.
|
|
135
|
+
- `private: true` packages must not have `npm:public`.
|
|
124
136
|
- Public packages must define license metadata.
|
|
125
137
|
- Public packages must publish with `publishConfig.access = "public"`.
|
|
126
|
-
- Public
|
|
138
|
+
- Public packages must define `repository.type`, `repository.url`, and `repository.directory`.
|
|
127
139
|
- Public packages must define `files`.
|
|
128
140
|
- Public library packages must define `types`.
|
|
129
141
|
- Public packages must define either `exports` or `bin`.
|
|
130
142
|
|
|
131
|
-
|
|
132
|
-
license
|
|
143
|
+
Owned public packages may inherit the root license when the root license is not `UNLICENSED`. Mirrored public packages
|
|
144
|
+
must carry their own license. `smoo monorepo init` does not copy the root `repository.url` into packages; a missing
|
|
145
|
+
package `repository.url` is a validation failure so new packages must consciously choose whether they are owned by the
|
|
146
|
+
current repository or mirrored from another one. Init still sets `publishConfig.access = "public"`, repository type,
|
|
147
|
+
repository directory, export ordering, and source-file publish entries when those can be derived safely.
|
|
148
|
+
|
|
149
|
+
`smoo monorepo list-release-packages` prints the comma-separated package names that are both `npm:public` and owned by
|
|
150
|
+
the current repository. Release commands, trusted-publisher setup, and the managed publish workflow use this owned
|
|
151
|
+
release package list instead of every public package in the workspace.
|
|
152
|
+
|
|
153
|
+
For [GitHub Actions], `smoo monorepo list-release-packages --fail-empty --github-output "$GITHUB_OUTPUT"` appends the
|
|
154
|
+
`projects=<package-list>` output expected by the managed publish workflow and fails with a clear error when no owned
|
|
155
|
+
release packages exist.
|
|
156
|
+
|
|
157
|
+
`smoo release npm-status` shows whether each owned release package's current `name@version` already exists on npm. It is
|
|
158
|
+
an npm registry check, not a full release workflow status check.
|
|
133
159
|
|
|
134
160
|
## Managed Files
|
|
135
161
|
|
|
@@ -137,15 +163,18 @@ license and repository URL, then sets each package's `repository.directory` from
|
|
|
137
163
|
|
|
138
164
|
Managed files include:
|
|
139
165
|
|
|
140
|
-
- `.git-format-staged.yml`
|
|
166
|
+
- [`.git-format-staged.yml`][git-format-staged]
|
|
141
167
|
- Git hook scripts under `tooling/git-hooks`
|
|
142
|
-
- direnv/GitHub Actions bootstrap scripts under `tooling/direnv`
|
|
143
|
-
- GitHub Actions workflows under `.github/workflows`
|
|
144
|
-
- Local composite GitHub Actions under `.github/actions`
|
|
168
|
+
- [direnv]/[GitHub Actions] bootstrap scripts under `tooling/direnv`
|
|
169
|
+
- [GitHub Actions] workflows under `.github/workflows`
|
|
170
|
+
- Local composite [GitHub Actions] under `.github/actions`
|
|
145
171
|
|
|
146
172
|
When a managed target is a symlink, `smoo` leaves it alone. SmoothBricks uses symlinks back to `packages/cli/managed` so
|
|
147
173
|
changes to the CLI package are tested immediately. Downstream repos receive ordinary committed copies.
|
|
148
174
|
|
|
175
|
+
The publish workflow is conditional. Repositories with no owned release packages skip `.github/workflows/publish.yml` in
|
|
176
|
+
`init`, `check`, and `diff`; adding a new owned package makes the workflow required on the next validation run.
|
|
177
|
+
|
|
149
178
|
Use:
|
|
150
179
|
|
|
151
180
|
```bash
|
|
@@ -158,8 +187,12 @@ smoo monorepo diff
|
|
|
158
187
|
|
|
159
188
|
## Git Hooks
|
|
160
189
|
|
|
161
|
-
The generated pre-commit hook runs `git-format-staged` from the repository root with `tooling`,
|
|
162
|
-
the devenv profile on `PATH`.
|
|
190
|
+
The generated pre-commit hook runs [`git-format-staged`][git-format-staged] from the repository root with `tooling`,
|
|
191
|
+
`node_modules/.bin`, and the [devenv] profile on `PATH`.
|
|
192
|
+
|
|
193
|
+
After formatting, the hook runs `smoo monorepo validate --fail-fast --only-if-new-workspace-package`. This keeps normal
|
|
194
|
+
commits fast while still catching incomplete package setup and conditional managed-file drift when a new workspace
|
|
195
|
+
package manifest is staged.
|
|
163
196
|
|
|
164
197
|
The generated commit-msg hook delegates conventional commit validation to:
|
|
165
198
|
|
|
@@ -171,21 +204,22 @@ This keeps hook behavior consistent with CI and avoids duplicating commit messag
|
|
|
171
204
|
|
|
172
205
|
## GitHub Actions
|
|
173
206
|
|
|
174
|
-
The generated workflows keep readable YAML and named top-level steps, while larger logic lives in
|
|
175
|
-
post-checkout composite actions, or the small pre-smoo bootstrap script. Checkout stays inline in each
|
|
176
|
-
repository-local composite actions do not exist until `actions/checkout` has populated the working
|
|
207
|
+
The generated [GitHub Actions] workflows keep readable YAML and named top-level steps, while larger logic lives in
|
|
208
|
+
`smoo` commands, post-checkout composite actions, or the small pre-smoo bootstrap script. Checkout stays inline in each
|
|
209
|
+
workflow because repository-local composite actions do not exist until `actions/checkout` has populated the working
|
|
210
|
+
tree.
|
|
177
211
|
|
|
178
|
-
CI uses explicit lint, test, and build phases. The publish workflow does the same
|
|
179
|
-
|
|
212
|
+
CI uses explicit lint, test, and build phases. The publish workflow does the same after versioning so GitHub output
|
|
213
|
+
stays readable and validation happens on the exact release commit.
|
|
180
214
|
|
|
181
|
-
CI status deeplinks depend on GitHub'
|
|
182
|
-
next to each top-level step, and the `smoo github-ci nx-smart --step <number>` values for lint, test, and build
|
|
183
|
-
stay synchronized with those comments. Composite action internals do not change the top-level step numbers.
|
|
215
|
+
CI status deeplinks depend on [GitHub Actions]' top-level job step anchors. The generated CI workflow keeps `# Step N`
|
|
216
|
+
comments next to each top-level step, and the `smoo github-ci nx-smart --step <number>` values for lint, test, and build
|
|
217
|
+
must stay synchronized with those comments. Composite action internals do not change the top-level step numbers.
|
|
184
218
|
|
|
185
219
|
Managed CI setup is split across local composite actions:
|
|
186
220
|
|
|
187
|
-
- `setup-devenv` installs Nix, restores the Nix cache segment, imports the store NAR, restores `.devenv`/`.direnv`
|
|
188
|
-
after an exact Nix cache hit, enables Cachix, installs devenv, restores `node_modules`, and builds the shell.
|
|
221
|
+
- `setup-devenv` installs [Nix], restores the Nix cache segment, imports the store NAR, restores `.devenv`/`.direnv`
|
|
222
|
+
only after an exact Nix cache hit, enables [Cachix], installs [devenv], restores `node_modules`, and builds the shell.
|
|
189
223
|
- `save-nix-devenv` runs under `always()`, calls `smoo github-ci cleanup-cache`, and explicitly saves cache segments
|
|
190
224
|
only when cleanup reports `cache-ready=true`.
|
|
191
225
|
- `cache-nix-devenv` is the shared restore/save primitive for the separate `nix` and `devenv` cache segments.
|
|
@@ -208,64 +242,132 @@ The bootstrap script is intentionally small. It only handles work required befor
|
|
|
208
242
|
|
|
209
243
|
## Releases
|
|
210
244
|
|
|
211
|
-
Release commands wrap Nx release but keep SmoothBricks policy in one place.
|
|
245
|
+
Release commands wrap [Nx Release][nx-release] but keep SmoothBricks policy in one place.
|
|
212
246
|
|
|
213
247
|
Versioning:
|
|
214
248
|
|
|
215
|
-
- `--bump auto` uses Nx
|
|
249
|
+
- `--bump auto` uses [Nx Release][nx-release] [Conventional Commits] versioning.
|
|
216
250
|
- `--bump patch|minor|major|prerelease` forces the release specifier.
|
|
217
|
-
- Release
|
|
218
|
-
- Nx release config must use `currentVersionResolver: "git-tag"` with
|
|
219
|
-
Conventional-commit versioning requires git tags as the primary source,
|
|
220
|
-
releases before package tags exist.
|
|
221
|
-
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
251
|
+
- Release packages are discovered from `npm:public` packages whose `repository.url` exactly matches the root package.
|
|
252
|
+
- [Nx Release][nx-release] config must use `currentVersionResolver: "git-tag"` with
|
|
253
|
+
`fallbackCurrentVersionResolver: "disk"`. Conventional-commit versioning requires git tags as the primary source,
|
|
254
|
+
while the disk fallback supports initial releases before package tags exist.
|
|
255
|
+
- [Nx Release][nx-release] config must use `versionActions: "@smoothbricks/cli/nx-version-actions"`. This wraps Nx's JS
|
|
256
|
+
version actions and temporarily syncs `bun.lock` workspace versions after Nx runs `bun install --lockfile-only`.
|
|
257
|
+
- [Nx Release][nx-release] `preVersionCommand` is intentionally not used. smoo builds exactly the packages that still
|
|
258
|
+
need npm publish immediately before packing them, while the managed workflow separately builds, lints, tests, and
|
|
259
|
+
validates newly created release commits.
|
|
260
|
+
- `smoo release repair-pending` runs before the normal publish flow. It repairs older remote release tags whose npm
|
|
261
|
+
package version or GitHub Release is missing, while leaving the current `HEAD` release target to `version` and
|
|
262
|
+
`publish`.
|
|
263
|
+
- `smoo release version` selects the current release target before validation by running [Nx Release][nx-release]
|
|
264
|
+
versioning. npm registry state is not used to decide whether versioning should run. If `HEAD` is already a release
|
|
265
|
+
target, versioning returns `mode=none` and leaves idempotent completion to `smoo release publish`.
|
|
266
|
+
- `smoo release version --github-output "$GITHUB_OUTPUT"` appends `mode=new|none` and `projects=<comma-list>`. The
|
|
267
|
+
managed publish workflow uses `mode != "none"` to build, lint, test, and validate exactly the commit that
|
|
268
|
+
`smoo release publish` will publish. The validation and publish step names include the selected mode so the [GitHub
|
|
269
|
+
Actions] run shows whether it is creating a new release or recording a no-op.
|
|
270
|
+
- Explicit bumps are mandatory when `HEAD` is not already a release target: after pending releases are repaired,
|
|
271
|
+
`bump=patch|minor|major|prerelease` must make Nx create a new release commit. smoo fails if Nx returns without moving
|
|
272
|
+
`HEAD`; `auto` may no-op when there are no releasable conventional commits.
|
|
273
|
+
- `--dry-run` previews versioning and completion without pushing refs, publishing npm packages, or writing GitHub
|
|
274
|
+
Releases.
|
|
275
|
+
- The `nx-version-actions` hook is a temporary Bun workaround. Bun currently leaves `bun.lock` workspace versions stale
|
|
276
|
+
after package manifest bumps, and `bun pm pack` rewrites `workspace:*` dependencies using those stale lockfile
|
|
277
|
+
versions. Keep the hook until supported Bun versions resolve these issues:
|
|
278
|
+
[oven-sh/bun#18906](https://github.com/oven-sh/bun/issues/18906),
|
|
279
|
+
[oven-sh/bun#20477](https://github.com/oven-sh/bun/issues/20477), and
|
|
280
|
+
[oven-sh/bun#20829](https://github.com/oven-sh/bun/issues/20829).
|
|
281
|
+
- Package release tags must use `{projectName}@{version}`. smoo derives release package/version pairs from that tag
|
|
282
|
+
shape and recreates missing local tags for Nx release commits before repairing remote state.
|
|
283
|
+
- `smoo release retag-unpublished <tag...>` is a break-glass recovery command for the case where Nx already committed a
|
|
284
|
+
version bump but npm publish failed before the package version became durable. It moves exact owned release tags to
|
|
285
|
+
`HEAD` by default without bumping package manifests again. It refuses to move a tag when `package@version` already
|
|
286
|
+
exists on npm, when the GitHub Release exists, or when the target ref's package manifest does not contain the tagged
|
|
287
|
+
version. Pass `--push` to update remote tags with `--force-with-lease`; pass `--dispatch` to also start `publish.yml`
|
|
288
|
+
with `bump=auto`. Dispatch validates that the target ref is already the remote branch head so the workflow will
|
|
289
|
+
publish the same commit that was retagged.
|
|
290
|
+
|
|
291
|
+
### Repair Process
|
|
292
|
+
|
|
293
|
+
`repair-pending` is tag-driven, not history-driven. It starts from fetched remote release tags because those tags are
|
|
294
|
+
the durable record that a package version was selected for release. It only checks npm and GitHub Release state to
|
|
295
|
+
decide which tags still need work; it does not walk normal commits looking for release-shaped changes.
|
|
296
|
+
|
|
297
|
+
1. Collect owned release tags from the fetched remote tag set, sorted newest-first by annotated tag `creatordate`. Only
|
|
298
|
+
tags matching owned package release names are considered, and each tag is peeled to the commit it releases.
|
|
299
|
+
2. Classify each owned release tag before grouping by commit. A tag needs npm repair when `package@version` is missing
|
|
300
|
+
from npm, and it needs GitHub repair when the GitHub Release for that tag is missing. Tags needing neither are
|
|
301
|
+
filtered out immediately.
|
|
302
|
+
3. Group only repair-needed tags by peeled commit. Empty commits disappear because their tags were already complete.
|
|
303
|
+
Exclude `HEAD` because the current release target is handled by `smoo release version` and `smoo release publish`,
|
|
304
|
+
not by the older-release repair loop.
|
|
305
|
+
4. Sort the remaining repair commits oldest-to-newest. Only after this sorted non-HEAD repair list exists does smoo
|
|
306
|
+
start checking out commits.
|
|
307
|
+
5. For each repair commit, check out the commit once and load that checkout's direnv environment once. If any grouped
|
|
308
|
+
tag still needs npm publish, run `nx run-many -t build --projects=<comma-separated npm-missing packages>` once, then
|
|
309
|
+
publish those packages using the npm dist-tag implied by each package version. If the commit only needs GitHub
|
|
310
|
+
Releases, skip the build. Finally, create the missing GitHub Releases for the grouped tags that need them.
|
|
311
|
+
|
|
312
|
+
Pending release state should be a suffix of the release-target timeline because `repair-pending` runs before every
|
|
313
|
+
publish. Once a complete release target is reached, older targets are assumed complete; an observed gap in repair state
|
|
314
|
+
violates the workflow invariant and should fail loudly instead of silently repairing history out of order.
|
|
228
315
|
|
|
229
316
|
Publishing:
|
|
230
317
|
|
|
231
318
|
- `prerelease` publishes with npm dist-tag `next`.
|
|
232
319
|
- Stable bumps publish with npm dist-tag `latest`.
|
|
233
|
-
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
320
|
+
- `smoo release publish` pushes missing branch/tag refs, publishes missing npm versions, creates or updates GitHub
|
|
321
|
+
Releases, and writes a GitHub Step Summary. Already published npm versions are skipped, so reruns after auth or
|
|
322
|
+
network failures retry only the package versions npm does not have yet.
|
|
323
|
+
- npm registry state gates publish idempotency only. It decides which already-versioned package tarballs still need to
|
|
324
|
+
be published during a real release retry, not whether versioning should run or whether the workflow has a release to
|
|
325
|
+
publish.
|
|
326
|
+
- Before npm publish, smoo runs `nx run-many -t build --projects=<comma-separated npm-missing packages>` for exactly the
|
|
327
|
+
packages whose `name@version` is not on npm yet. Nx cache makes this cheap when the managed workflow already built the
|
|
328
|
+
same projects, and it keeps reruns self-sufficient when repairing a previously selected `HEAD` release target.
|
|
237
329
|
- Publish uses `bun pm pack` to create package tarballs, then publishes those tarballs with latest npm CLI and
|
|
238
|
-
`--provenance`.
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
330
|
+
`--provenance`. Each package uses the npm dist-tag implied by its own version (`next` for prereleases, `latest` for
|
|
331
|
+
stable versions). Bun pack resolves internal `workspace:*` dependency ranges to real versions in the tarball manifest;
|
|
332
|
+
smoo fails before publish if a packed manifest still contains `workspace:` or if an internal dependency does not match
|
|
333
|
+
the current workspace package version.
|
|
334
|
+
- [npm CLI][npm] owns publish authentication. Existing packages use [trusted publishing][npm-trusted-publishing] with
|
|
335
|
+
[GitHub Actions OIDC][github-actions-oidc] from the workflow's `id-token: write` permission. Package names that do not
|
|
336
|
+
exist on npm yet use `secrets.NPM_TOKEN` as `NODE_AUTH_TOKEN` for the first publish, because trusted publishing can
|
|
337
|
+
only be configured after the package exists. Once `smoo release trust-publisher` succeeds for a package, future
|
|
338
|
+
publishes for that package use OIDC even if the bootstrap token is still present in repository secrets.
|
|
339
|
+
- `smoo release trust-publisher` configures [npm trusted publishing][npm-trusted-publishing] for every owned release
|
|
340
|
+
package. It uses the root `package.json` `repository.url` as the GitHub `owner/repo`, uses `publish.yml` as the
|
|
341
|
+
trusted workflow, and runs `npm trust` through `nix shell nixpkgs#nodejs_latest` because the Lambda-pinned Node 24/npm
|
|
342
|
+
toolchain may lag the npm CLI feature. By default it runs `npm login --auth-type=web` first so npm can open a browser
|
|
343
|
+
login; pass `--skip-login` when the current npm session is already authenticated. Packages must already exist on npm
|
|
344
|
+
before trust can be configured. npm may still require operation-level 2FA for `npm trust`; smoo prompts for a hidden
|
|
345
|
+
OTP per package, or you can pass `--otp <code>` for non-interactive use.
|
|
250
346
|
|
|
251
347
|
GitHub Releases:
|
|
252
348
|
|
|
253
|
-
- `smoo release
|
|
254
|
-
|
|
349
|
+
- `smoo release publish` delegates to `nx release changelog` once per owned package whose current package release tag is
|
|
350
|
+
at `HEAD`, passing that package's version explicitly for independent releases after npm publish succeeds.
|
|
351
|
+
- Nx project changelogs are configured to create or update GitHub Releases, not local changelog files.
|
|
352
|
+
- Generated release notes are package-scoped Conventional Commit changelogs. Init defaults author rendering and GitHub
|
|
353
|
+
username lookup on, while validation allows repos to override those render options.
|
|
255
354
|
|
|
256
|
-
The release flow is designed to be rerun after partial failure. Nx owns local version/tag behavior,
|
|
257
|
-
|
|
355
|
+
The release flow is designed to be rerun after partial failure. Nx owns local version/tag behavior, while smoo derives
|
|
356
|
+
durable completion state from the remote branch, release tags, npm registry versions, and GitHub Releases. Repeated
|
|
357
|
+
Publish runs converge without self-spawning another workflow run.
|
|
258
358
|
|
|
259
359
|
## Why This Shape
|
|
260
360
|
|
|
261
361
|
The important design goal is one source of truth per convention:
|
|
262
362
|
|
|
263
|
-
- Nx `npm:public` tags decide what
|
|
363
|
+
- [Nx] `npm:public` tags decide what has a public npm package contract.
|
|
364
|
+
- Matching root/package `repository.url` values decide which public packages are released by the current repo.
|
|
264
365
|
- Managed files decide what generated CI and hooks should look like.
|
|
265
|
-
- Root package metadata provides defaults for public packages.
|
|
366
|
+
- Root package metadata provides defaults only for owned public packages.
|
|
266
367
|
- Actual workspace package names decide which dependency ranges become `workspace:*`.
|
|
267
|
-
-
|
|
268
|
-
- `
|
|
368
|
+
- Package manifests decide Bun lockfile workspace versions until Bun stops leaving them stale during releases.
|
|
369
|
+
- [`sherif`] handles broad package hygiene.
|
|
370
|
+
- [`publint`] and [`attw`][are-the-types-wrong] validate real packed artifacts.
|
|
269
371
|
|
|
270
372
|
This keeps `smoo` small where external tools already do the job, but keeps SmoothBricks-specific policy native where
|
|
271
373
|
generic tools do not know the repo contract. In particular, `sherif` is useful for package hygiene, but it does not know
|
|
@@ -287,3 +389,26 @@ If validating packages with Zig build steps from outside the devenv shell, add Z
|
|
|
287
389
|
```bash
|
|
288
390
|
nix shell nixpkgs#zig -c nx run-many -t build --projects=<public-projects>
|
|
289
391
|
```
|
|
392
|
+
|
|
393
|
+
## Links
|
|
394
|
+
|
|
395
|
+
[are-the-types-wrong]: https://github.com/arethetypeswrong/arethetypeswrong.github.io/tree/main/packages/cli
|
|
396
|
+
[Bun]: https://bun.sh/
|
|
397
|
+
[Cachix]: https://www.cachix.org/
|
|
398
|
+
[CommonJS]: https://nodejs.org/api/modules.html
|
|
399
|
+
[Conventional Commits]: https://www.conventionalcommits.org/
|
|
400
|
+
[devenv]: https://devenv.sh/
|
|
401
|
+
[direnv]: https://direnv.net/
|
|
402
|
+
[ESM]: https://nodejs.org/api/esm.html
|
|
403
|
+
[git-format-staged]: https://github.com/smoothbricks/git-format-staged
|
|
404
|
+
[GitHub Actions]: https://docs.github.com/actions
|
|
405
|
+
[github-actions-oidc]:
|
|
406
|
+
https://docs.github.com/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect
|
|
407
|
+
[Nix]: https://nixos.org/
|
|
408
|
+
[Node.js]: https://nodejs.org/
|
|
409
|
+
[npm]: https://www.npmjs.com/
|
|
410
|
+
[npm-trusted-publishing]: https://docs.npmjs.com/trusted-publishers
|
|
411
|
+
[Nx]: https://nx.dev/
|
|
412
|
+
[nx-release]: https://nx.dev/features/manage-releases
|
|
413
|
+
[`publint`]: https://publint.dev/
|
|
414
|
+
[`sherif`]: https://github.com/QuiiBz/sherif
|
package/dist/cli.js
CHANGED
|
@@ -32,6 +32,7 @@ function buildProgram() {
|
|
|
32
32
|
monorepo
|
|
33
33
|
.command('validate')
|
|
34
34
|
.option('--fail-fast', 'stop after the first failing validation pack')
|
|
35
|
+
.option('--only-if-new-workspace-package', 'skip validation unless a new workspace package manifest is staged')
|
|
35
36
|
.action(async (options) => {
|
|
36
37
|
const { validateMonorepo } = await import('./monorepo/index.js');
|
|
37
38
|
await validateMonorepo(await findRepoRoot(), options);
|
|
@@ -56,51 +57,80 @@ function buildProgram() {
|
|
|
56
57
|
const { syncBunLockfileVersions } = await import('./monorepo/index.js');
|
|
57
58
|
syncBunLockfileVersions(await findRepoRoot());
|
|
58
59
|
});
|
|
59
|
-
monorepo
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
monorepo
|
|
61
|
+
.command('list-release-packages')
|
|
62
|
+
.option('--fail-empty', 'fail when no owned release packages are found')
|
|
63
|
+
.option('--github-output <path>', 'append projects=<packages> to a GitHub Actions output file')
|
|
64
|
+
.action(async (options) => {
|
|
65
|
+
const { listReleasePackagesForNx } = await import('./monorepo/index.js');
|
|
66
|
+
const packages = listReleasePackagesForNx(await findRepoRoot(), options);
|
|
67
|
+
if (!options.githubOutput) {
|
|
68
|
+
console.log(packages);
|
|
69
|
+
}
|
|
62
70
|
});
|
|
63
71
|
monorepo.command('validate-public-tags').action(async () => {
|
|
64
72
|
const { validatePublicPackageTags } = await import('./monorepo/index.js');
|
|
65
73
|
validatePublicPackageTags(await findRepoRoot());
|
|
66
74
|
});
|
|
67
|
-
|
|
75
|
+
const release = program.command('release').description('Version, publish, and create GitHub Releases');
|
|
76
|
+
release.command('npm-status').action(async () => {
|
|
68
77
|
const { printReleaseState } = await import('./release/index.js');
|
|
69
78
|
await printReleaseState(await findRepoRoot());
|
|
70
79
|
});
|
|
71
|
-
|
|
80
|
+
release
|
|
81
|
+
.command('repair-pending')
|
|
82
|
+
.description('Repair incomplete older release commits before releasing the current HEAD')
|
|
83
|
+
.option('--dry-run [dryRun]', 'run without pushing, publishing, or writing GitHub Releases')
|
|
84
|
+
.action(async (options) => {
|
|
85
|
+
const { releaseRepairPending } = await import('./release/index.js');
|
|
86
|
+
await releaseRepairPending(await findRepoRoot(), { ...options, dryRun: booleanOption(options.dryRun) });
|
|
87
|
+
});
|
|
72
88
|
release
|
|
73
89
|
.command('version')
|
|
74
90
|
.option('--bump <bump>', 'auto, patch, minor, major, or prerelease', 'auto')
|
|
75
|
-
.option('--dry-run [dryRun]', 'run without writing versions
|
|
91
|
+
.option('--dry-run [dryRun]', 'run without writing versions or tags')
|
|
92
|
+
.option('--github-output <path>', 'append mode=<mode> and projects=<packages> to a GitHub Actions output file')
|
|
76
93
|
.action(async (options) => {
|
|
77
94
|
const { releaseVersion } = await import('./release/index.js');
|
|
78
|
-
await releaseVersion(await findRepoRoot(), {
|
|
95
|
+
await releaseVersion(await findRepoRoot(), {
|
|
96
|
+
bump: options.bump,
|
|
97
|
+
dryRun: booleanOption(options.dryRun),
|
|
98
|
+
githubOutput: options.githubOutput,
|
|
99
|
+
});
|
|
79
100
|
});
|
|
80
101
|
release
|
|
81
102
|
.command('publish')
|
|
82
103
|
.option('--bump <bump>', 'auto, patch, minor, major, or prerelease', 'auto')
|
|
83
|
-
.option('--
|
|
84
|
-
.option('--npm-tag <npmTag>', 'explicit npm dist-tag; must match bump-derived tag')
|
|
85
|
-
.option('--dry-run [dryRun]', 'run without publishing')
|
|
104
|
+
.option('--dry-run [dryRun]', 'run without pushing, publishing, or writing GitHub Releases')
|
|
86
105
|
.action(async (options) => {
|
|
87
106
|
const { releasePublish } = await import('./release/index.js');
|
|
88
107
|
await releasePublish(await findRepoRoot(), { ...options, dryRun: booleanOption(options.dryRun) });
|
|
89
108
|
});
|
|
90
109
|
release
|
|
91
|
-
.command('
|
|
92
|
-
.
|
|
93
|
-
.
|
|
94
|
-
.option('--
|
|
95
|
-
.option('--
|
|
96
|
-
.option('--
|
|
97
|
-
.
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
.command('retag-unpublished')
|
|
111
|
+
.description('Move unpublished owned release tags to a later commit without bumping versions')
|
|
112
|
+
.argument('<tag...>', 'owned release tags to move, for example @scope/pkg@1.2.3')
|
|
113
|
+
.option('--to <ref>', 'commit or ref to move tags to', 'HEAD')
|
|
114
|
+
.option('--push', 'push moved tags with force-with-lease')
|
|
115
|
+
.option('--dispatch', 'push moved tags and start publish.yml with bump=auto')
|
|
116
|
+
.option('--remote <remote>', 'git remote used for pushed tags')
|
|
117
|
+
.option('--branch <branch>', 'branch used for publish workflow dispatch')
|
|
118
|
+
.option('--dry-run [dryRun]', 'validate and print the retag operation without mutating refs')
|
|
119
|
+
.action(async (tags, options) => {
|
|
120
|
+
const { releaseRetagUnpublished } = await import('./release/index.js');
|
|
121
|
+
await releaseRetagUnpublished(await findRepoRoot(), {
|
|
122
|
+
tags,
|
|
123
|
+
to: options.to,
|
|
124
|
+
push: options.push === true,
|
|
125
|
+
dispatch: options.dispatch === true,
|
|
126
|
+
remote: options.remote,
|
|
127
|
+
branch: options.branch,
|
|
128
|
+
dryRun: booleanOption(options.dryRun),
|
|
129
|
+
});
|
|
100
130
|
});
|
|
101
131
|
release
|
|
102
132
|
.command('trust-publisher')
|
|
103
|
-
.description('Configure npm trusted publishing for
|
|
133
|
+
.description('Configure npm trusted publishing for owned release packages')
|
|
104
134
|
.option('--dry-run [dryRun]', 'show npm trust changes without saving them')
|
|
105
135
|
.option('--otp <otp>', 'npm one-time password for trust operations')
|
|
106
136
|
.option('--skip-login', 'skip npm browser login before configuring trust')
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
type EnvSnapshot = Record<string, string>;
|
|
2
|
+
export declare function withDevenvEnv<T>(root: string, runWithEnv: () => Promise<T>): Promise<T>;
|
|
3
|
+
export declare function parseNulEnv(bytes: Uint8Array): EnvSnapshot;
|
|
4
|
+
export declare function mergeDevenvEnv(baseEnv: EnvSnapshot, devenvEnv: EnvSnapshot): EnvSnapshot;
|
|
5
|
+
export {};
|
|
6
|
+
//# sourceMappingURL=devenv.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devenv.d.ts","sourceRoot":"","sources":["../../src/lib/devenv.ts"],"names":[],"mappings":"AAMA,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1C,wBAAsB,aAAa,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAS7F;AAmBD,wBAAgB,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,WAAW,CAoB1D;AAED,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,WAAW,GAAG,WAAW,CAExF"}
|