[feat] release: interactive release client, drop the CI linked-deps guard
`pnpm run release` (scripts/release.mjs, zx + enquirer + commander) replaces the hand sequence of bump, changelog, gate, tag, push. It picks packages from a list annotated with what npmjs already has, computes versions from the manifest, collects notes in $EDITOR seeded with the commits since the package's last tag, and prepends them to CHANGELOG.md in the format release.yml's parser expects. The gate (lint:ci -> typecheck -> test:coverage -> build -> verify-pack) runs against the bumped tree *before* the commit, so a failure leaves nothing to unpick -- it offers to restore instead. Tags go out dependency-first, and each version is polled on npmjs before the next tag is pushed. That polling is what lets release.yml lose its `check linked deps are released` step: the ordering is now enforced before CI ever sees a tag, rather than after. linked-deps.mjs stays as a hand-check. The accepted cost is that a tag pushed some other way is no longer caught. Three things found by running it rather than reading it: - Tags are annotated (`-a -m`). A lightweight tag is rejected outright under tag.forceSignAnnotated, which is set on the machine this was written on. - pnpm 11 forwards the `--` in `pnpm run release -- --dry-run` literally, and commander reads a bare `--` as "the rest are positionals". The script takes no positionals, so it strips it and both spellings work. - Prompts refuse with a message naming the flag that avoids them when stdin is not a TTY, instead of hanging as an unsettled top-level await. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DCzYTAm9QUhvLNr2EpdagJ
This commit is contained in:
co-authored by
Claude Opus 5
parent
0aa0be5542
commit
2019626618
@@ -142,13 +142,27 @@ One pass per invocation, `nopy.main.ts` orchestrating:
|
||||
variables (prompt, or read them back from the session on replay) → run
|
||||
`before` hooks → resolve `manifest.dependencies(vars)` (dynamic: it receives
|
||||
the *collected* variables) → emit the deploy call → run `after` hooks. There
|
||||
is no separate topological sort; ordering falls out of the recursion, and a
|
||||
`${cubeId}:${host}` set makes emission idempotent. Hooks get a `HookContext`
|
||||
is no separate topological sort; emission is post-order, so the ordering *is*
|
||||
topological without an algorithm computing it, and a `${cubeId}:${host}` set
|
||||
makes emission idempotent. Hooks get a `HookContext`
|
||||
whose `exec(id, vars)` re-enters `resolveCube`, so a hook can pull in a cube
|
||||
that is not a declared dependency.
|
||||
6. **`nopy.executor.ts`** — runs the built `pyinfra <host> -y --data K=V ... --chdir <cubeDir> <script>`
|
||||
Cycles are caught by a separate **resolution stack** — a (cube, host) pair
|
||||
re-entered while still resolving raises with the whole path named. It has to
|
||||
be separate from `resolvedCubes`, which is written *after* the descent and so
|
||||
never sees a cycle at all, and which cannot be widened into a "seen" set
|
||||
because re-entering a finished cube with different `param` overrides is
|
||||
exactly what a dependency or a hook is for.
|
||||
6. **`nopy.executor.ts`** — runs the built
|
||||
`pyinfra <host> -y [-vv] [--debug] --data K=V ... --chdir <cubeDir> <script>`
|
||||
commands through execa with inherited stdio, sequentially, stopping at the
|
||||
first failure unless `continueOnError`.
|
||||
first failure unless `continueOnError`. `DeployCall.command` is a true argv
|
||||
and is spawned **without a shell**: it used to be joined into one string and
|
||||
run through `execa({shell: true})`, which made every `--data` value shell
|
||||
syntax — a password or a variable holding `;` or `$(…)` was executed. The only
|
||||
thing that joins it back into a string is `maskCommand()`, for display, which
|
||||
shell-quotes as it goes so `--print-only` output stays pasteable. The
|
||||
verbosity/debug flags come from `config.log` through `logConfigToFlags()`.
|
||||
|
||||
### Variables
|
||||
|
||||
@@ -277,6 +291,14 @@ another edge to the publish order. Extract it if a third CLI appears.
|
||||
|
||||
Tag-driven, one package at a time; see `README.PUBLISH.md`.
|
||||
|
||||
`pnpm run release` (`scripts/release.mjs`, zx + enquirer + commander) is the
|
||||
front door: pick packages, pick versions, write notes into `CHANGELOG.md`, run
|
||||
the gate **against the bumped tree before committing** so a failure leaves
|
||||
nothing to unpick, then commit, tag and push. Tags go out dependency-first and it
|
||||
polls npmjs for each version before pushing the next — which is what replaced the
|
||||
CI-side linked-deps guard. Tags are annotated (`-a -m`), not lightweight: a
|
||||
lightweight tag is rejected outright under `tag.forceSignAnnotated`.
|
||||
|
||||
### Registry resolution
|
||||
|
||||
The repo commits a root `.npmrc` mapping `@bitsquare:registry` to the Gitea
|
||||
@@ -357,22 +379,28 @@ Three things the `workspace:*` links added, all of them non-obvious:
|
||||
`scripts/publish-order.mjs` topologically sorts over the `workspace:` edges;
|
||||
the snapshot workflow stamps *every* version first and only then publishes in
|
||||
that order, because `pnpm publish` reads the linked package's version at pack
|
||||
time. `release.yml` additionally refuses to ship a package whose linked
|
||||
dependency is not yet on npmjs (`scripts/linked-deps.mjs`) — npmjs is the
|
||||
registry you cannot take a mistake back from.
|
||||
time. `release.yml` used to additionally refuse to ship a package whose linked
|
||||
dependency was not yet on npmjs (`scripts/linked-deps.mjs`); that step is gone,
|
||||
and `scripts/release.mjs` enforces the same ordering earlier instead — it
|
||||
pushes tags dependency-first and polls npmjs for each version before pushing
|
||||
the next. `linked-deps.mjs` survives as a hand-check. A tag pushed some other
|
||||
way is no longer caught, which is the accepted cost.
|
||||
|
||||
## Known drift
|
||||
|
||||
`logConfigToFlags()` is exported and tested but nothing feeds its output into the
|
||||
built pyinfra command, so `log.verbosity` / `log.debug` in `.nopyrc.json`
|
||||
currently have no effect. Treat `docs/REFACTORING.md` as a plan, not a record.
|
||||
`logConfigToFlags()` is now consumed by `buildDeployCall`, so `log.verbosity` /
|
||||
`log.debug` in `.nopyrc.json` finally do what the README says. Note the
|
||||
consequence: `packages/nopy/.nopyrc.json` has always asked for
|
||||
`"verbosity": "trace", "debug": true`, and a run from that directory now actually
|
||||
gets `-vvv --debug`. Treat `docs/REFACTORING.md` as a plan, not a record.
|
||||
|
||||
The publish lane has now run against the Gitea registry: all four packages are
|
||||
there under `@main`, and `pnpm run try:snapshot` installs them into a throwaway
|
||||
project with npm and runs the binary. The npmjs lane has only ever published
|
||||
`@bitsquare/nopy`; `keyman`, `nopy-cubes` and `nopy-cubes-core` have never been
|
||||
released there, so the *check linked deps are released* guard in `release.yml`
|
||||
will stop the first `nopy` release until `nopy-cubes` ships.
|
||||
released there. That used to be caught by the *check linked deps are released*
|
||||
guard in `release.yml`; now it is `pnpm run release` that holds `nopy`'s tag back
|
||||
until `nopy-cubes` answers on npmjs.
|
||||
|
||||
Nothing checks that a bundle and the CLI reading it are compatible versions;
|
||||
`nopy.engines` was considered and deferred. `docs/CUBE-PACKAGES.md` is where all
|
||||
@@ -383,10 +411,8 @@ from the plan.
|
||||
`src/index.ts` plus the authoring package; its *Known gaps* section is the short
|
||||
list of behaviour that surprises a reader (`--json` printing nothing on success,
|
||||
`DeployCall.dependencies` always empty, `ExecutionResult.stdout` never populated,
|
||||
no cycle detection, and `self-update` reporting an empty dist-tag as an
|
||||
unreachable registry). `CubePackageRef` is referenced by the exported
|
||||
`NopyConfig` but is not itself re-exported, so a consumer cannot name the type —
|
||||
one line, not yet fixed. `DOCS-AUDIT.md` tracks the drift in the remaining
|
||||
and `self-update` reporting an empty dist-tag as an unreachable registry).
|
||||
`DOCS-AUDIT.md` tracks the drift in the remaining
|
||||
documents; §2.9 (the nopy README shipping yarn-workspace instructions to npmjs)
|
||||
and §2.10 (the keyman README describing four of nine operations and inventing a
|
||||
tenth) are both closed. The keyman README now quotes `helpText()` verbatim and a
|
||||
|
||||
Reference in New Issue
Block a user