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
1 change: 1 addition & 0 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ permissions:
contents: read
concurrency:
group: cli-npm-release
# The release in progress always finishes. One run waits behind it, and a newer push replaces that waiting run, so the newest commit publishes next.
cancel-in-progress: false
jobs:
publish:
Expand Down
2 changes: 1 addition & 1 deletion docs/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ The committed stable version, initially `0.4.0`, sets the minimum release versio

The job updates the version only in its temporary build checkout. Both npm manifests contain `dealmachineRelease.sourceSha`; the workflow does not create a source commit or Git tag. The source version is a floor, so it can differ from an installed package's actual runtime version. The receipt and published metadata identify the exact source and package versions.

Releases run serially. A job whose source is no longer current `master` stops before publication. Re-running the current commit discovers any package already published for that SHA, rebuilds and compares its integrity, then publishes only the missing package. A conflicting source, changed bytes, multiple versions for one source, or a newer published stable version stops the retry. The workflow never overwrites immutable npm versions or silently moves `latest` backward.
Releases run serially in one concurrency group: the release in progress always finishes, one release waits behind it, and a newer `master` push replaces that waiting release. A job starts only when its source is the current `master`, so a rerun of an older commit never publishes. Once started, it publishes even if newer merges land during its checks; the newest merge publishes next as a higher patch version. Re-running the current commit discovers any package already published for that SHA, rebuilds and compares its integrity, then publishes only the missing package. A conflicting source, changed bytes, multiple versions for one source, or a newer published stable version stops the retry. The workflow never overwrites immutable npm versions or silently moves `latest` backward.

After an upload, verification waits up to three minutes for npm to expose the version and its distribution tag. Registry reads retry temporary connection failures and use fresh connections after the build. Package identity, source, archive integrity, alias dependency and channel checks still have to pass before the release succeeds.

Expand Down
9 changes: 4 additions & 5 deletions scripts/release-auto.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ assert(['dry-run', 'publish'].includes(mode) && !extra.length, 'Use release:auto
const execute = (command, args, options = {}) => execFileSync(command, args, { cwd: root, stdio: 'inherit', ...options });
const git = (...args) => execute('git', args, { encoding: 'utf8', stdio: 'pipe' }).trim();
const sourceSha = git('rev-parse', 'HEAD');
const assertCurrentMaster = () => {
assert.equal(git('ls-remote', 'origin', 'refs/heads/master').split(/\s/)[0], sourceSha, 'Source is no longer current master. Let the newest master run publish.');
};
if (mode === 'publish') {
assert.equal(process.env.GITHUB_ACTIONS, 'true', 'Automatic publishing runs only in GitHub Actions; use release:publish for an authorized local release.');
assert.equal(process.env.GITHUB_REPOSITORY, 'DealMachine/dealmachine-cli');
assert.equal(process.env.GITHUB_REF, 'refs/heads/master');
assert.equal(process.env.GITHUB_SHA, sourceSha);
assert(['push', 'workflow_dispatch'].includes(process.env.GITHUB_EVENT_NAME), 'Only master push and recovery dispatch may publish.');
assert.equal(git('status', '--porcelain'), '', 'Start from the clean committed release source.');
assertCurrentMaster();
// Only the current master may start a release, so a rerun of an older commit never publishes. Once started, the
// release finishes even if newer merges land during its checks: the workflow runs it in one concurrency group that
// never cancels a run in progress, so the newer merge waits and publishes next.
assert.equal(git('ls-remote', 'origin', 'refs/heads/master').split(/\s/)[0], sourceSha, 'Source is no longer current master. Let the newest master run publish.');
}

const registries = Object.fromEntries(await Promise.all(releasePackages.map(async name => [name, await registry(name)])));
Expand All @@ -49,7 +49,6 @@ try {
for (const artifact of record.artifacts) {
if (registries[artifact.name].versions?.[plan.version]) verifyPublishedPackage(artifact, registries[artifact.name], plan, { requireTag: true });
}
if (mode === 'publish') assertCurrentMaster();
for (const artifact of record.artifacts) {
if (registries[artifact.name].versions?.[plan.version]) {
console.log(`Verified existing ${artifact.name}@${plan.version}; resuming without republishing it.`);
Expand Down
Loading