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
35 changes: 35 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,11 @@ jobs:
ios=true
quality=true
;;
packages/web/**|scripts/build-web-package.mjs|scripts/check-web-package-contract.mjs|scripts/test-web-package*.mjs|scripts/web-package-*.mjs|.github/workflows/web-npm-publish.yml)
web=true
quality=true
site=true
;;
web/**|fixtures/**|scripts/build-web-wasm.sh|scripts/test-web*.mjs|scripts/web-*)
android=true
ios=true
Expand Down Expand Up @@ -471,6 +476,35 @@ jobs:
- name: Verify npm package contents
run: npm pack --dry-run --ignore-scripts

web-package-test:
name: Standalone Web Package Consumers
needs: changes
if: needs.changes.outputs.web == 'true'
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Set up release Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '24'
package-manager-cache: false

- name: Install package managers and dependencies
run: |
corepack enable
npm install --global npm@12.0.1
yarn install --immutable

- name: Test standalone package and publication guards
env:
CHROME_PATH: /usr/bin/google-chrome
run: |
yarn test:web:registry
yarn test:web:package

quality:
name: Lint, TypeScript, and Jest
needs: changes
Expand Down Expand Up @@ -528,6 +562,7 @@ jobs:
ios-build-test,
ios-rn-compatibility,
web-test,
web-package-test,
site-test,
]
if: always()
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ concurrency:
jobs:
publish-npm:
name: Publish to npm with OIDC
if: github.event_name != 'release' || startsWith(github.event.release.tag_name, 'v')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
Expand Down
23 changes: 23 additions & 0 deletions .github/workflows/registry-canary.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,12 @@ on:
default: react-native-bs-diff-patch@latest
type: string

web_package_spec:
description: Standalone Web npm package spec to validate
required: false
default: bs-diff-patch-web@latest
type: string

permissions:
contents: read

Expand Down Expand Up @@ -41,3 +47,20 @@ jobs:
env:
PACKAGE_SPEC: ${{ inputs.package_spec || 'react-native-bs-diff-patch@latest' }}
run: node scripts/test-registry-consumers.mjs ${{ matrix.consumer }}

standalone-web:
name: Standalone Web registry consumer
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Checkout the code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7

- name: Setup Node.js and dependencies
uses: ./.github/actions/setup

- name: Test published standalone Web package
env:
PACKAGE_SPEC: ${{ inputs.web_package_spec || 'bs-diff-patch-web@latest' }}
CHROME_PATH: /usr/bin/google-chrome
run: yarn test:web:package
178 changes: 178 additions & 0 deletions .github/workflows/web-npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
name: Publish standalone Web npm package

on:
release:
types: [published]
workflow_dispatch:
inputs:
release_tag:
description: Published Web release tag to publish or verify (web-v0.5.0)
required: true
type: string

permissions:
contents: read
id-token: write

concurrency:
group: web-npm-publish-${{ inputs.release_tag || github.event.release.tag_name }}
cancel-in-progress: false

jobs:
publish-web:
name: Publish or verify Web package
if: github.event_name != 'release' || startsWith(github.event.release.tag_name, 'web-v')
runs-on: ubuntu-latest
timeout-minutes: 30
env:
RELEASE_TAG: ${{ inputs.release_tag || github.event.release.tag_name }}
CHROME_PATH: /usr/bin/google-chrome
steps:
- name: Validate release request
env:
GH_TOKEN: ${{ github.token }}
run: |
if [[ "$GITHUB_EVENT_NAME" == workflow_dispatch && "$GITHUB_REF" != refs/heads/main ]]; then
echo 'Manual publication must use the main workflow.' >&2
exit 1
fi
if [[ ! "$RELEASE_TAG" =~ ^web-v[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
echo 'A web-v version tag is required.' >&2
exit 1
fi
gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RELEASE_TAG" > "$RUNNER_TEMP/release.json"
jq -e --arg tag "$RELEASE_TAG" '.draft == false and .published_at != null and .tag_name == $tag' "$RUNNER_TEMP/release.json" > /dev/null

- name: Checkout exact release source
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7
with:
ref: refs/tags/${{ env.RELEASE_TAG }}
fetch-depth: 0
persist-credentials: false

- name: Setup Node.js for trusted publishing
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7
with:
node-version: '24'
registry-url: https://registry.npmjs.org/
package-manager-cache: false

- name: Install package managers and dependencies
run: |
corepack enable
npm install --global npm@12.0.1
yarn install --immutable

- name: Verify release identity
id: release
run: |
tag_commit="$(git rev-parse "refs/tags/$RELEASE_TAG^{commit}")"
test "$(git rev-parse HEAD)" = "$tag_commit"
git merge-base --is-ancestor HEAD origin/main
package_version="$(node -p "require('./packages/web/package.json').version")"
test "$RELEASE_TAG" = "web-v$package_version"
if [[ "$package_version" == *-* ]]; then
prerelease="${package_version#*-}"
dist_tag="${prerelease%%.*}"
else
dist_tag=latest
fi
if [[ ! "$dist_tag" =~ ^[a-z][a-z0-9-]*$ ]]; then
echo 'Invalid npm dist-tag derived from package version.' >&2
exit 1
fi
echo "version=$package_version" >> "$GITHUB_OUTPUT"
echo "dist_tag=$dist_tag" >> "$GITHUB_OUTPUT"

- name: Run release quality gates
run: |
yarn prepare
yarn typecheck
yarn lint
yarn test --runInBand
yarn test:web
yarn test:web:browser
yarn test:web:metro
yarn test:toolkit
yarn test:node
yarn test:action
yarn test:package
yarn test:sdk
yarn test:web:registry
yarn test:web:package
git diff --exit-code HEAD

- name: Pack immutable candidate
id: candidate
run: |
cd build/web-package
npm pack --json --pack-destination "$RUNNER_TEMP" > "$RUNNER_TEMP/web-pack.json"
filename="$(node -e 'const m=require(process.argv[1]); const e=Array.isArray(m)?m:Object.values(m); if(e.length!==1)process.exit(1); process.stdout.write(e[0].filename)' "$RUNNER_TEMP/web-pack.json")"
echo "tarball=$RUNNER_TEMP/$filename" >> "$GITHUB_OUTPUT"

- name: Test exact candidate and check registry
id: registry
env:
PACKAGE_TARBALL: ${{ steps.candidate.outputs.tarball }}
run: |
yarn test:web:package
node scripts/web-package-registry.mjs status "$PACKAGE_TARBALL" > "$RUNNER_TEMP/web-registry-status.json"
echo "published=$(jq -r '.published' "$RUNNER_TEMP/web-registry-status.json")" >> "$GITHUB_OUTPUT"

- name: Publish new version using OIDC
if: steps.registry.outputs.published == 'false'
env:
PACKAGE_TARBALL: ${{ steps.candidate.outputs.tarball }}
DIST_TAG: ${{ steps.release.outputs.dist_tag }}
run: npm publish "$PACKAGE_TARBALL" --provenance --access public --tag "$DIST_TAG" --registry=https://registry.npmjs.org/

- name: Verify registry bytes and provenance policy
env:
PACKAGE_TARBALL: ${{ steps.candidate.outputs.tarball }}
ALREADY_PUBLISHED: ${{ steps.registry.outputs.published }}
run: |
provenance=()
if [[ "$ALREADY_PUBLISHED" != true ]]; then
provenance+=(--require-provenance)
fi
verified=false
for attempt in $(seq 1 12); do
if node scripts/web-package-registry.mjs verify "$PACKAGE_TARBALL" "${provenance[@]}" > "$RUNNER_TEMP/web-registry-verification.json"; then
verified=true
break
fi
echo "Registry verification pending (attempt $attempt)."
sleep 5
done
test "$verified" = true
cat "$RUNNER_TEMP/web-registry-verification.json"
if [[ "$ALREADY_PUBLISHED" == true ]]; then
echo 'Existing identical release verified; no upload or retroactive provenance claim.' >> "$GITHUB_STEP_SUMMARY"
fi

- name: Smoke test official registry package
env:
PACKAGE_SPEC: bs-diff-patch-web@${{ steps.release.outputs.version }}
run: yarn test:web:package

- name: Verify npm registry signatures
env:
PACKAGE_VERSION: ${{ steps.release.outputs.version }}
run: |
mkdir "$RUNNER_TEMP/web-signatures"
cd "$RUNNER_TEMP/web-signatures"
npm init --yes
npm install --ignore-scripts --no-audit --no-fund --registry=https://registry.npmjs.org/ "bs-diff-patch-web@$PACKAGE_VERSION"
npm audit signatures --registry=https://registry.npmjs.org/

- name: Archive publication evidence
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: web-package-publication
path: |
${{ runner.temp }}/bs-diff-patch-web-*.tgz
${{ runner.temp }}/web-pack.json
${{ runner.temp }}/web-registry-*.json
if-no-files-found: ignore
retention-days: 30
16 changes: 13 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,8 @@ yarn test:web:metro
yarn test:sdk
```

`test:sdk` installs the prepared package tarball into an isolated consumer and
checks the public `/web` and `/toolkit` ESM entries, the production Vite
`test:sdk` installs the prepared React Native package tarball into an isolated
consumer and checks its `/web` and `/toolkit` ESM entries, the production Vite
resource graph, and real byte round trips. It does not use a workspace link or
the registry's older package.

Expand Down Expand Up @@ -121,6 +121,13 @@ starts `.github/workflows/npm-publish.yml`, which publishes to npm through OIDC
Trusted Publishing and verifies the package provenance. No long-lived npm token
is stored in GitHub.

The flow in this section is for the existing `react-native-bs-diff-patch`
package. The standalone Web package `bs-diff-patch-web` has its own release and
tag namespace (`web-v0.5.0`) and does not replace, deprecate, or republish the
React Native package. Keep Node filesystem operations, the CLI, and related
release tooling on `react-native-bs-diff-patch`. See the [standalone Web package
design and first-publication procedure](./docs/standalone-web-package-design.md).

Maintainers should run the quality gates, then create the release:

```sh
Expand Down Expand Up @@ -196,8 +203,11 @@ The `package.json` file contains various scripts for common tasks:
- `yarn test:web`: verify the WebAssembly patch format and round trip.
- `yarn test:web:browser`: exercise the public Web Worker API in Chrome.
- `yarn test:web:metro`: verify Metro resolves the React Native Web entry.
- `yarn test:sdk`: install the prepared tarball and verify `/web` and `/toolkit`
- `yarn test:sdk`: install the prepared RN tarball and verify `/web` and `/toolkit`
from an isolated Vite consumer.
- `yarn test:web:package`: build and check the independent Web tarball, then verify
its root and `/toolkit` from an isolated consumer without RN dependencies.
- `yarn test:web:registry`: verify the registry publication safety guards.
- `node scripts/check-package-contract.mjs`: verify exports, declarations,
packed assets, and the Node-free browser resource graph.
- `yarn site:build`: render public Markdown and static site assets into `site-dist/`.
Expand Down
33 changes: 19 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,20 +88,23 @@ patch application uses the bounded streaming core. Try the workflow in the brows

## Install

For React Native, Node.js, and CLI consumers:

```sh
npm install react-native-bs-diff-patch@^0.5.0
```

The explicit `/web` and `/toolkit` entries are part of 0.5.0. For pre-release
verification of a locally prepared package, the same entries can be tested from
its tarball instead:
For standalone browser and desktop WebView consumers:

```sh
npm install ./react-native-bs-diff-patch-0.5.0.tgz
npm install bs-diff-patch-web@^0.5.0
```

The registry's 0.4.x package predates these subpaths. See the [Web and desktop
WebView SDK guide](./docs/web-sdk.md) for the resource graph and consumer checks.
For pre-release verification of either locally prepared package, substitute its
tarball in a clean consumer. The standalone Web package uses
`bs-diff-patch-web-0.5.0.tgz`; the React Native package keeps its own tarball.
See the [Web and desktop WebView SDK guide](./docs/web-sdk.md) for the resource
graph, migration, and consumer checks.

For iOS, install Pods and rebuild the native application:

Expand Down Expand Up @@ -156,14 +159,15 @@ try {

## Web: first round trip

Standalone browser, Vite, and Tauri consumers should import the explicit ESM
entry `react-native-bs-diff-patch/web`; it exposes byte APIs and does not
require React Native. The root package keeps its conditional React Native and
browser resolution for existing applications. See the [Web and desktop WebView
SDK guide](./docs/web-sdk.md) for the published resource graph and CSP.
Standalone browser, Vite, and Tauri consumers should install
`bs-diff-patch-web` and import its ESM root; it exposes byte APIs without
React Native, Node.js, or a Node sidecar. Existing applications can keep using
`react-native-bs-diff-patch/web`, which remains supported. See the [Web and
desktop WebView SDK guide](./docs/web-sdk.md) for the resource graph, migration,
and CSP.

```ts
import { diffBytes, patchBytes } from 'react-native-bs-diff-patch/web';
import { diffBytes, patchBytes } from 'bs-diff-patch-web';

const patchBytesValue = await diffBytes(oldFile, newFile, {
signal: abortController.signal,
Expand Down Expand Up @@ -254,8 +258,9 @@ TypeScript resolution from the real npm package shape.
## Documentation

- [Web and desktop WebView SDK](./docs/web-sdk.md) — use the explicit ESM
`/web` and `/toolkit` entries from Vite or Tauri without React Native or a
Node sidecar.
`bs-diff-patch-web` and its `/toolkit` entry from Vite or Tauri without React
Native or a Node sidecar. Existing `react-native-bs-diff-patch/web` and
`/toolkit` consumers remain supported.
- [Getting started](./docs/getting-started.md)
- [API reference](./docs/api-reference.md)
- [Production recipes](./docs/recipes.md)
Expand Down
Loading