mirror of
https://github.com/Kaelio/ktx.git
synced 2026-06-10 08:05:14 +02:00
release: add guarded npm publish script
This commit is contained in:
parent
81674c3017
commit
0861436f81
3 changed files with 197 additions and 0 deletions
|
|
@ -24,6 +24,7 @@
|
|||
"setup:dev": "node scripts/setup-dev.mjs",
|
||||
"release:published-smoke": "node scripts/published-package-smoke.mjs --require-config",
|
||||
"release:local-embeddings-smoke": "node scripts/local-embeddings-runtime-smoke.mjs --require-opt-in",
|
||||
"release:npm-publish": "node scripts/publish-public-npm-package.mjs",
|
||||
"release:readiness": "node scripts/release-readiness.mjs",
|
||||
"relationships:acquire-public-fixtures": "node scripts/acquire-public-benchmark-fixtures.mjs",
|
||||
"relationships:rebuild-public-snapshots": "node scripts/build-benchmark-snapshot.mjs --rebuild-all",
|
||||
|
|
|
|||
87
scripts/publish-public-npm-package.mjs
Normal file
87
scripts/publish-public-npm-package.mjs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
import { execFile } from 'node:child_process';
|
||||
import { access } from 'node:fs/promises';
|
||||
import { pathToFileURL } from 'node:url';
|
||||
import { promisify } from 'node:util';
|
||||
|
||||
import { packageArtifactLayout } from './package-artifacts.mjs';
|
||||
import { releaseReadinessReport } from './release-readiness.mjs';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export function resolvePublishMode(args = process.argv.slice(2)) {
|
||||
return { live: args.includes('--publish') };
|
||||
}
|
||||
|
||||
export function requireNpmPublicReleaseReady(report) {
|
||||
if (report.releaseMode !== 'npm-public-release-ready' || report.npmPublishEnabled !== true || !report.npmPublish) {
|
||||
throw new Error('release-policy.json must use npm-public-release-ready before publishing');
|
||||
}
|
||||
return report.npmPublish;
|
||||
}
|
||||
|
||||
export function buildNpmPublishCommand(tarballPath, publish, mode) {
|
||||
return {
|
||||
command: 'pnpm',
|
||||
args: [
|
||||
'publish',
|
||||
tarballPath,
|
||||
'--access',
|
||||
publish.access,
|
||||
'--tag',
|
||||
publish.tag,
|
||||
...(mode.live ? [] : ['--dry-run', '--no-git-checks']),
|
||||
],
|
||||
env: publish.registry ? { npm_config_registry: publish.registry } : {},
|
||||
};
|
||||
}
|
||||
|
||||
async function assertFileExists(path) {
|
||||
try {
|
||||
await access(path);
|
||||
} catch {
|
||||
throw new Error(`Missing npm tarball: ${path}. Run pnpm run artifacts:check first.`);
|
||||
}
|
||||
}
|
||||
|
||||
async function runPublishCommand(command) {
|
||||
process.stdout.write(`$ ${command.command} ${command.args.join(' ')}\n`);
|
||||
await execFileAsync(command.command, command.args, {
|
||||
env: { ...process.env, ...command.env },
|
||||
encoding: 'utf8',
|
||||
maxBuffer: 1024 * 1024 * 20,
|
||||
});
|
||||
}
|
||||
|
||||
export async function publishPublicNpmPackage(options = {}) {
|
||||
const rootDir = options.rootDir;
|
||||
const mode = options.mode ?? resolvePublishMode(options.args);
|
||||
const report = await releaseReadinessReport(rootDir);
|
||||
const publish = requireNpmPublicReleaseReady(report);
|
||||
const layout = packageArtifactLayout(rootDir);
|
||||
const tarballPath = layout.cliTarball;
|
||||
|
||||
await assertFileExists(tarballPath);
|
||||
const command = buildNpmPublishCommand(tarballPath, publish, mode);
|
||||
await runPublishCommand(command);
|
||||
|
||||
process.stdout.write(
|
||||
mode.live
|
||||
? `Published ${publish.packageName}@${publish.version} with tag ${publish.tag}\n`
|
||||
: `Dry-run verified ${publish.packageName}@${publish.version} with tag ${publish.tag}\n`,
|
||||
);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await publishPublicNpmPackage({ args: process.argv.slice(2) });
|
||||
}
|
||||
|
||||
if (import.meta.url === pathToFileURL(process.argv[1] ?? '').href) {
|
||||
try {
|
||||
await main();
|
||||
} catch (error) {
|
||||
process.stderr.write(`${error instanceof Error ? error.stack : String(error)}\n`);
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
109
scripts/publish-public-npm-package.test.mjs
Normal file
109
scripts/publish-public-npm-package.test.mjs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { describe, it } from 'node:test';
|
||||
|
||||
import {
|
||||
buildNpmPublishCommand,
|
||||
requireNpmPublicReleaseReady,
|
||||
resolvePublishMode,
|
||||
} from './publish-public-npm-package.mjs';
|
||||
|
||||
const readyReport = {
|
||||
releaseMode: 'npm-public-release-ready',
|
||||
npmPublishEnabled: true,
|
||||
npmPublish: {
|
||||
packageName: '@kaelio/ktx',
|
||||
version: '0.1.0',
|
||||
access: 'public',
|
||||
tag: 'latest',
|
||||
registry: null,
|
||||
},
|
||||
};
|
||||
|
||||
describe('resolvePublishMode', () => {
|
||||
it('dry-runs by default', () => {
|
||||
assert.deepEqual(resolvePublishMode([]), { live: false });
|
||||
});
|
||||
|
||||
it('requires an explicit flag for live publish', () => {
|
||||
assert.deepEqual(resolvePublishMode(['--publish']), { live: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('requireNpmPublicReleaseReady', () => {
|
||||
it('accepts the npm public release ready report', () => {
|
||||
assert.equal(requireNpmPublicReleaseReady(readyReport), readyReport.npmPublish);
|
||||
});
|
||||
|
||||
it('rejects artifact-only reports', () => {
|
||||
assert.throws(
|
||||
() =>
|
||||
requireNpmPublicReleaseReady({
|
||||
releaseMode: 'ci-artifact-only',
|
||||
npmPublishEnabled: false,
|
||||
npmPublish: null,
|
||||
}),
|
||||
/release-policy.json must use npm-public-release-ready before publishing/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildNpmPublishCommand', () => {
|
||||
it('builds a dry-run pnpm publish command by default', () => {
|
||||
assert.deepEqual(
|
||||
buildNpmPublishCommand('/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz', readyReport.npmPublish, {
|
||||
live: false,
|
||||
}),
|
||||
{
|
||||
command: 'pnpm',
|
||||
args: [
|
||||
'publish',
|
||||
'/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz',
|
||||
'--access',
|
||||
'public',
|
||||
'--tag',
|
||||
'latest',
|
||||
'--dry-run',
|
||||
'--no-git-checks',
|
||||
],
|
||||
env: {},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
it('omits dry-run only for explicit live publish', () => {
|
||||
assert.deepEqual(
|
||||
buildNpmPublishCommand('/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz', readyReport.npmPublish, {
|
||||
live: true,
|
||||
}).args,
|
||||
[
|
||||
'publish',
|
||||
'/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz',
|
||||
'--access',
|
||||
'public',
|
||||
'--tag',
|
||||
'latest',
|
||||
],
|
||||
);
|
||||
});
|
||||
|
||||
it('uses npm_config_registry when a registry is configured', () => {
|
||||
const publish = {
|
||||
...readyReport.npmPublish,
|
||||
registry: 'https://registry.npmjs.org/',
|
||||
};
|
||||
|
||||
assert.deepEqual(
|
||||
buildNpmPublishCommand('/repo/ktx/dist/artifacts/npm/kaelio-ktx-0.1.0.tgz', publish, { live: false }).env,
|
||||
{ npm_config_registry: 'https://registry.npmjs.org/' },
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('package script', () => {
|
||||
it('registers release:npm-publish', async () => {
|
||||
const packageJson = JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8'));
|
||||
|
||||
assert.equal(packageJson.scripts['release:npm-publish'], 'node scripts/publish-public-npm-package.mjs');
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue