Files
ansiblings/scripts/linked-deps.mjs
T
Benjamin DiedrichsenandClaude Opus 5 2019626618 [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
2026-09-01 12:31:17 +02:00

57 lines
2.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Prints the workspace packages a package links to, as `<name> <version>` lines.
*
* The version is the one the linked package declares *right now*, which is
* exactly what `pnpm publish` will substitute for `workspace:*` when it packs.
* So this is the list of versions that must already be on the registry before
* shipping the package, or the tarball points at something nobody can install.
*
* node scripts/linked-deps.mjs packages/nopy
*
* `release.yml` no longer runs this as a gate. `scripts/release.mjs` enforces
* the same ordering earlier and more cheaply, by pushing tags dependency-first
* and waiting for each version to appear on npmjs before pushing the next. This
* stays as the hand-check for when you want to see the list yourself.
*/
import fs from 'node:fs';
import path from 'node:path';
const PACKAGES_DIR = 'packages';
const RANGE_FIELDS = ['dependencies', 'peerDependencies', 'optionalDependencies'];
const target = process.argv[2];
if (!target) {
console.error('Usage: node scripts/linked-deps.mjs <package-dir>');
process.exit(2);
}
const read = (dir) => JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf-8'));
// Resolved by name rather than by directory: nothing guarantees that
// `@bitsquare/nopy-cubes` lives in `packages/nopy-cubes`.
const versionByName = new Map(
fs
.readdirSync(PACKAGES_DIR)
.map((name) => path.join(PACKAGES_DIR, name))
.filter((dir) => fs.existsSync(path.join(dir, 'package.json')))
.map((dir) => read(dir))
.map((manifest) => [manifest.name, manifest.version])
);
const manifest = read(target);
const linked = RANGE_FIELDS.flatMap((field) => Object.entries(manifest[field] ?? {}))
.filter(([, range]) => range.startsWith('workspace:'))
.map(([name]) => name);
for (const name of linked) {
const version = versionByName.get(name);
if (version === undefined) {
console.error(`${manifest.name} links to ${name}, which is not in ${PACKAGES_DIR}/.`);
process.exit(1);
}
console.log(`${name} ${version}`);
}