From ffb58b1ad24486bee193d6dd150cc5baae18acf1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 18 Mar 2026 01:59:03 +0000 Subject: [PATCH] build(publish): add JSR and npm publishing setup Co-authored-by: okikio <17222836+okikio@users.noreply.github.com> --- .github/workflow/ci.yml | 21 -------- .github/workflows/ci.yml | 38 ++++++++++++++ .github/workflows/publish.yml | 51 +++++++++++++++++++ .gitignore | 3 +- README.md | 21 +++++--- deno.jsonc | 13 +++++ scripts/build_npm.ts | 93 ++++++++++++++++++++++++++++++++++ tests/publishing_setup_test.ts | 39 ++++++++++++++ 8 files changed, 251 insertions(+), 28 deletions(-) delete mode 100644 .github/workflow/ci.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/publish.yml create mode 100644 scripts/build_npm.ts create mode 100644 tests/publishing_setup_test.ts diff --git a/.github/workflow/ci.yml b/.github/workflow/ci.yml deleted file mode 100644 index 7fa0cba..0000000 --- a/.github/workflow/ci.yml +++ /dev/null @@ -1,21 +0,0 @@ -on: - push: - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: denoland/setup-deno@v2 - with: - deno-version: 2.x - - run: deno test - - uses: oven-sh/setup-bun@v1 - with: - bun-version: 1.x - - run: bun test - - uses: actions/setup-node@v4 - with: - node-version-file: '.nvmrc' - - run: npx tsx --test \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..e5548e7 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + pull_request: + +permissions: + contents: read + +jobs: + validate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: denoland/setup-deno@v2 + + - name: Check formatting + run: deno fmt --check + + - name: Lint + run: deno lint + + - name: Type check + run: deno check **/*.ts + + - name: Test + run: deno task test + + build-npm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: denoland/setup-deno@v2 + + - name: Build npm package + run: deno task build:npm diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..bde4f92 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,51 @@ +# +# One-time setup before this workflow can publish: +# - Link `okikio/observables` in the JSR package settings so `deno publish` +# can exchange GitHub OIDC credentials for a short-lived publish token. +# - Configure npm trusted publishing for the `publish.yml` workflow so +# `npm publish` can authenticate without a long-lived npm token. +name: Publish + +on: + release: + types: [published] + workflow_dispatch: + +permissions: + contents: read + id-token: write + +jobs: + publish-jsr: + name: Publish to JSR + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: denoland/setup-deno@v2 + + - name: Publish to JSR + run: deno publish + + publish-npm: + name: Publish to npm + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - uses: denoland/setup-deno@v2 + + - name: Build npm package + run: deno task build:npm + + - uses: actions/setup-node@v6 + with: + node-version: '24' + registry-url: 'https://registry.npmjs.org' + + - name: Publish to npm + # npm trusted publishing uses the GitHub OIDC token minted from + # `id-token: write`, so no NODE_AUTH_TOKEN secret is required here. + run: | + cd npm + npm publish diff --git a/.gitignore b/.gitignore index bffe456..5beece6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ /.vscode/ /node_modules +/npm .env *.orig @@ -228,4 +229,4 @@ $RECYCLE.BIN/ # Docs docs -# End of https://www.toptal.com/developers/gitignore/api/deno,node,macos,windows,linux \ No newline at end of file +# End of https://www.toptal.com/developers/gitignore/api/deno,node,macos,windows,linux diff --git a/README.md b/README.md index 4e9d52c..3c3e583 100644 --- a/README.md +++ b/README.md @@ -111,27 +111,36 @@ Or deno add jsr:@okikio/observables ``` -### Node.js +### Node.js and Bun ```bash -npx jsr add @okikio/observables +npm install @okikio/observables +# pnpm add @okikio/observables +# yarn add @okikio/observables +# bun add @okikio/observables ``` -Or +If you prefer to install through the JSR bridge instead of the npm registry: ```bash -pnpm install jsr:@okikio/observables +npx jsr add @okikio/observables ```
Others ```bash -yarn add @okikio/observables@jsr:latest +pnpm add jsr:@okikio/observables ``` Or +```bash +yarn add @okikio/observables@jsr:latest +``` + +Or + ```bash bunx jsr add @okikio/observables ``` @@ -459,4 +468,4 @@ deno task bench ## Licence -See the [LICENSE](./LICENSE) file for license rights and limitations (MIT). \ No newline at end of file +See the [LICENSE](./LICENSE) file for license rights and limitations (MIT). diff --git a/deno.jsonc b/deno.jsonc index b0aae27..fa51e24 100644 --- a/deno.jsonc +++ b/deno.jsonc @@ -28,10 +28,23 @@ "lint": "deno lint", "fmt": "deno fmt", "check": "deno check **/*.ts", + "build:npm": "deno run -A scripts/build_npm.ts", "publish:dry": "deno publish --dry-run", "publish": "deno publish" }, "license": "MIT", + "publish": { + "exclude": [ + ".devcontainer/", + ".github/", + ".vscode/", + "bench/", + "coverage/", + "npm/", + "scripts/", + "tests/" + ] + }, "imports": { "@std/assert": "jsr:@std/assert@^1.0.14", "@std/async": "jsr:@std/async@^1.0.14", diff --git a/scripts/build_npm.ts b/scripts/build_npm.ts new file mode 100644 index 0000000..35f885b --- /dev/null +++ b/scripts/build_npm.ts @@ -0,0 +1,93 @@ +// deno-lint-ignore-file no-import-prefix no-unversioned-import +/** + * Builds the npm package from the Deno source using `@deno/dnt`. + * + * Run with: + * deno task build:npm + * + * Output is written to ./npm/ and is gitignored so publish artifacts never + * leak back into source control. + */ +import { build, emptyDir } from 'jsr:@deno/dnt'; +import { parse } from 'jsr:@std/jsonc'; + +const deno_config_path = new URL('../deno.jsonc', import.meta.url); +const deno_config_text = await Deno.readTextFile(deno_config_path); +const deno_config = parse(deno_config_text) as Record; + +function readConfigString(key: string): string { + const value = deno_config[key]; + + if (typeof value !== 'string') { + throw new Error(`Unable to find "${key}" in deno.jsonc.`); + } + + return value; +} + +await emptyDir('./npm'); + +await build({ + entryPoints: [ + './mod.ts', + { name: './error', path: './error.ts' }, + { name: './types', path: './_types.ts' }, + { name: './events', path: './events.ts' }, + { name: './queue', path: './queue.ts' }, + { name: './observable', path: './observable.ts' }, + { name: './operators', path: './helpers/mod.ts' }, + { name: './operations', path: './helpers/operations/mod.ts' }, + { name: './operations/batch', path: './helpers/operations/batch.ts' }, + { name: './operations/combination', path: './helpers/operations/combination.ts' }, + { name: './operations/conditional', path: './helpers/operations/conditional.ts' }, + { name: './operations/errors', path: './helpers/operations/errors.ts' }, + { name: './operations/timing', path: './helpers/operations/timing.ts' }, + { name: './operations/core', path: './helpers/operations/core.ts' }, + ], + outDir: './npm', + shims: { deno: false }, + typeCheck: 'both', + + // The Deno test suite imports jsr:@std/testing and other Deno-specific test + // utilities. Running those files through Node would pull Deno-only types into + // the npm build graph and fail type-checking for reasons unrelated to the + // published library surface. + test: false, + + package: { + name: readConfigString('name'), + version: readConfigString('version'), + description: readConfigString('description'), + license: readConfigString('license'), + keywords: [ + 'observable', + 'observables', + 'reactive', + 'streams', + 'tc39', + 'web-streams', + 'deno', + 'node', + 'bun', + ], + repository: { + type: 'git', + url: 'git+https://github.com/okikio/observables.git', + }, + bugs: { + url: 'https://github.com/okikio/observables/issues', + }, + homepage: 'https://jsr.io/@okikio/observables', + + // dnt generates main/module/types/exports from the entry points above. + sideEffects: false, + engines: { + node: '>=20', + }, + }, + + postBuild() { + Deno.copyFileSync('LICENSE', 'npm/LICENSE'); + Deno.copyFileSync('README.md', 'npm/README.md'); + }, +}); diff --git a/tests/publishing_setup_test.ts b/tests/publishing_setup_test.ts new file mode 100644 index 0000000..2f936c3 --- /dev/null +++ b/tests/publishing_setup_test.ts @@ -0,0 +1,39 @@ +import { describe, it } from '@std/testing/bdd'; +import { expect } from '@std/expect'; + +const repo_root = new URL('../', import.meta.url); + +function readRepoFile(path: string): string { + return Deno.readTextFileSync(new URL(path, repo_root)); +} + +describe('publishing setup', () => { + it('exposes the npm build task and JSR publish filtering', () => { + const deno_config = readRepoFile('deno.jsonc'); + + expect(deno_config).toContain('"build:npm": "deno run -A scripts/build_npm.ts"'); + expect(deno_config).toContain('"publish": {'); + expect(deno_config).toContain('"npm/"'); + expect(deno_config).toContain('"scripts/"'); + }); + + it('stores publishing files in their expected repository locations', () => { + for (const path of [ + '.github/workflows/ci.yml', + '.github/workflows/publish.yml', + 'scripts/build_npm.ts', + ]) { + const stat = Deno.statSync(new URL(path, repo_root)); + expect(stat.isFile).toBe(true); + } + }); + + it('documents npm install before the JSR bridge fallback', () => { + const readme = readRepoFile('README.md'); + + expect(readme).toContain('npm install @okikio/observables'); + expect(readme).toContain( + 'If you prefer to install through the JSR bridge instead of the npm registry:', + ); + }); +}); -- 2.51.2