From f21159376d54e08541dbf1d76d6debe879aa455b Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Wed, 18 Dec 2024 21:18:38 +0100 Subject: [PATCH] dx: NPM Publishing Workflow (#44) --- .github/workflows/publish-to-npm.yml | 32 ++++++++++ package.json | 8 ++- packages/typed-binary/package.json | 1 + pnpm-lock.yaml | 22 ++++--- scripts/_colors.mjs | 43 +++++++++++++ scripts/publish-to-npm.mjs | 92 ++++++++++++++++++++++++++++ 6 files changed, 186 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/publish-to-npm.yml create mode 100644 scripts/_colors.mjs create mode 100644 scripts/publish-to-npm.mjs diff --git a/.github/workflows/publish-to-npm.yml b/.github/workflows/publish-to-npm.yml new file mode 100644 index 0000000..134687f --- /dev/null +++ b/.github/workflows/publish-to-npm.yml @@ -0,0 +1,32 @@ +name: Publish to NPM + +on: + release: + types: [published] + workflow_dispatch: + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: 20.x + registry-url: https://registry.npmjs.org/ + cache: 'pnpm' + + - name: Install dependencies + run: pnpm install --recursive --frozen-lockfile + + - run: pnpm publish-package -- --dry-run + working-directory: ./packages/typed-binary + env: + NODE_AUTH_TOKEN: ${{secrets.npm_token}} diff --git a/package.json b/package.json index ae0d65e..7768773 100644 --- a/package.json +++ b/package.json @@ -45,10 +45,12 @@ "cypress": "^13.13.1", "socket.io": "^4.7.5", "socket.io-client": "^4.7.5", - "typescript": "^5.5.2", - "tsx": "^4.16.2", "tsup": "8.1.0", + "tsx": "^4.16.2", + "typed-binary": "workspace:*", + "typescript": "^5.5.2", "vitest": "^1.6.0", - "typed-binary": "workspace:*" + "arg": "^5.0.2", + "zod": "^3.24.1" } } diff --git a/packages/typed-binary/package.json b/packages/typed-binary/package.json index 6e08322..1af2494 100644 --- a/packages/typed-binary/package.json +++ b/packages/typed-binary/package.json @@ -27,6 +27,7 @@ "build": "tsup", "prepublishOnly": "pnpm -w run check && pnpm -w run build", "dryPublish": "pnpm publish --dry-run", + "publish-package": "node ../../scripts/publish-to-npm.mjs", "check": "tsup" }, "keywords": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6aecb11..5d99690 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,6 +14,9 @@ importers: '@types/node': specifier: ^20.14.11 version: 20.14.11 + arg: + specifier: ^5.0.2 + version: 5.0.2 cypress: specifier: ^13.13.1 version: 13.13.1 @@ -38,6 +41,9 @@ importers: vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.14.11) + zod: + specifier: ^3.24.1 + version: 3.24.1 apps/examples: dependencies: @@ -219,7 +225,7 @@ packages: dependencies: sitemap: 7.1.2 stream-replace-string: 2.0.0 - zod: 3.23.8 + zod: 3.24.1 dev: false /@astrojs/starlight@0.25.1(astro@4.12.2): @@ -1840,7 +1846,6 @@ packages: /arg@5.0.2: resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - dev: false /argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} @@ -1966,8 +1971,8 @@ packages: vitefu: 0.2.5(vite@5.3.4) which-pm: 3.0.0 yargs-parser: 21.1.1 - zod: 3.23.8 - zod-to-json-schema: 3.23.1(zod@3.23.8) + zod: 3.24.1 + zod-to-json-schema: 3.23.1(zod@3.24.1) optionalDependencies: sharp: 0.33.4 transitivePeerDependencies: @@ -6576,17 +6581,16 @@ packages: resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==} engines: {node: '>=12.20'} - /zod-to-json-schema@3.23.1(zod@3.23.8): + /zod-to-json-schema@3.23.1(zod@3.24.1): resolution: {integrity: sha512-oT9INvydob1XV0v1d2IadrR74rLtDInLvDFfAa1CG0Pmg/vxATk7I2gSelfj271mbzeM4Da0uuDQE/Nkj3DWNw==} peerDependencies: zod: ^3.23.3 dependencies: - zod: 3.23.8 + zod: 3.24.1 dev: false - /zod@3.23.8: - resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==} - dev: false + /zod@3.24.1: + resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==} /zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} diff --git a/scripts/_colors.mjs b/scripts/_colors.mjs new file mode 100644 index 0000000..b0257c5 --- /dev/null +++ b/scripts/_colors.mjs @@ -0,0 +1,43 @@ +const colors = { + Reset: '\u001b[0m', + + Black: '\u001b[30m', + Red: '\u001b[31m', + Green: '\u001b[32m', + Yellow: '\u001b[33m', + Blue: '\u001b[34m', + Magenta: '\u001b[35m', + Cyan: '\u001b[36m', + White: '\u001b[37m', + + BrightBlack: '\u001b[30;1m', + BrightRed: '\u001b[31;1m', + BrightGreen: '\u001b[32;1m', + BrightYellow: '\u001b[33;1m', + BrightBlue: '\u001b[34;1m', + BrightMagenta: '\u001b[35;1m', + BrightCyan: '\u001b[36;1m', + BrightWhite: '\u001b[37;1m', + + BgBlack: '\u001b[40m', + BgRed: '\u001b[41m', + BgGreen: '\u001b[42m', + BgYellow: '\u001b[43m', + BgBlue: '\u001b[44m', + BgMagenta: '\u001b[45m', + BgCyan: '\u001b[46m', + BgWhite: '\u001b[47m', + + BgBrightBlack: '\u001b[40;1m', + BgBrightRed: '\u001b[41;1m', + BgBrightGreen: '\u001b[42;1m', + BgBrightYellow: '\u001b[43;1m', + BgBrightBlue: '\u001b[44;1m', + BgBrightMagenta: '\u001b[45;1m', + BgBrightCyan: '\u001b[46;1m', + BgBrightWhite: '\u001b[47;1m', + + Bold: '\u001b[1m', +}; + +export default colors; diff --git a/scripts/publish-to-npm.mjs b/scripts/publish-to-npm.mjs new file mode 100644 index 0000000..2bd406a --- /dev/null +++ b/scripts/publish-to-npm.mjs @@ -0,0 +1,92 @@ +// @ts-check +import { spawn } from 'node:child_process'; +import fs from 'node:fs/promises'; +import process from 'node:process'; +import arg from 'arg'; +import z from 'zod'; +import colors from './_colors.mjs'; + +const ReleaseChannel = z.enum(['alpha']); + +/** @typedef {z.infer} ReleaseChannel */ + +const args = arg({}); +const cwd = new URL(`file:${process.cwd()}/`); +const packageJsonUrl = new URL('./package.json', cwd); + +const pkg = JSON.parse(await fs.readFile(packageJsonUrl, 'utf-8')); + +/** + * @param {string} version + * @returns {z.infer | null} + */ +function extractChannel(version) { + if (/[a-zA-Z]/.test(version)) { + const channel = Object.values(ReleaseChannel.Values).find((c) => + version.includes(c), + ); + + if (!channel) { + throw new Error(`Unrecognized channel: ${channel}`); + } + + return channel; + } + + return null; +} + +const channel = extractChannel(pkg.version); + +/** + * @param {string} command The command to run + * @param {string[]} params The command to run + * @returns {Promise} The exit code of the process + */ +function promiseSpawn(command, params) { + return new Promise((resolve, reject) => { + const childProcess = spawn(command, params); + + childProcess.on('close', (code) => { + if (code === undefined || code !== 0) { + reject(code); + } else { + resolve(code ?? 1); + } + }); + + childProcess.stdout?.pipe(process.stdout); + childProcess.stderr?.pipe(process.stderr); + }); +} + +async function main() { + console.log( + `\ +Release channel: ${colors.Cyan}${channel ?? ''}${colors.Reset} +`, + ); + + try { + await promiseSpawn('pnpm', [ + 'publish', + '--provenance', + ...(channel ? ['--tag', channel] : []), + ...args._, + ]); + } catch (e) { + console.error('pnpm publish error code:', e); + process.exit(1); + } + + console.log( + ` + +------------------------------------------------------------------------- + +Package published! +`, + ); +} + +main();