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
21 changes: 16 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,13 @@ jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run check
- run: npm run package:artifact
- run: npm run release:check
- name: Upload packed artifact
uses: actions/upload-artifact@v7
with:
Expand All @@ -34,9 +33,10 @@ jobs:
include-hidden-files: true

node-22:
needs: quality
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 22
Expand All @@ -47,13 +47,14 @@ jobs:
- run: npm run test:package

browser:
needs: quality
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
browser: [chromium, firefox, webkit]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
Expand All @@ -62,3 +63,13 @@ jobs:
- run: npx playwright install --with-deps ${{ matrix.browser }}
- run: npm run build
- run: npm run test:browser -- --project=${{ matrix.browser }}
- name: Upload Playwright failure diagnostics
if: failure()
uses: actions/upload-artifact@v7
with:
name: playwright-${{ matrix.browser }}-failure
path: |
playwright-report/
test-results/
if-no-files-found: warn
retention-days: 7
62 changes: 49 additions & 13 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
type: choice
options: [next, latest]
confirmation:
description: Type publish VERSION with CHANNEL
description: Type publish 2.0.0 with latest
required: true

permissions:
Expand All @@ -22,20 +22,23 @@ jobs:
authorize:
runs-on: ubuntu-latest
steps:
- name: Require main and exact confirmation
- name: Require main and exact stable release confirmation
env:
ACTUAL_REF: ${{ github.ref }}
ACTUAL_VERSION: ${{ inputs.version }}
ACTUAL_CHANNEL: ${{ inputs.channel }}
ACTUAL_CONFIRMATION: ${{ inputs.confirmation }}
EXPECTED_CONFIRMATION: publish ${{ inputs.version }} with ${{ inputs.channel }}
run: |
test "$ACTUAL_REF" = "refs/heads/main"
test "$ACTUAL_CONFIRMATION" = "$EXPECTED_CONFIRMATION"
test "$ACTUAL_VERSION" = "2.0.0"
test "$ACTUAL_CHANNEL" = "latest"
test "$ACTUAL_CONFIRMATION" = "publish 2.0.0 with latest"

verify:
needs: authorize
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
Expand All @@ -50,7 +53,20 @@ jobs:
env:
REQUESTED_CHANNEL: ${{ inputs.channel }}
run: node scripts/verify-release-channel.mjs "$REQUESTED_CHANNEL"
- run: node scripts/verify-registry.mjs unpublished
- name: Verify 2.0.0 is absent from npm
run: node scripts/verify-registry.mjs unpublished
- name: Assert exactly one verified tarball
run: |
shopt -s nullglob
tarballs=(.artifacts/*.tgz)
test "${#tarballs[@]}" -eq 1
- name: Upload verified release artifact
uses: actions/upload-artifact@v7
with:
name: release-package-${{ github.sha }}
path: .artifacts/*.tgz
if-no-files-found: error
include-hidden-files: true

browser:
needs: verify
Expand All @@ -60,7 +76,7 @@ jobs:
matrix:
browser: [chromium, firefox, webkit]
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
Expand All @@ -78,19 +94,38 @@ jobs:
contents: read
id-token: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
registry-url: https://registry.npmjs.org
- run: npm ci
- name: Download verified release artifact
uses: actions/download-artifact@v8
with:
name: release-package-${{ github.sha }}
path: .artifacts
- name: Verify requested version
env:
REQUESTED_VERSION: ${{ inputs.version }}
run: test "$(node -p "require('./package.json').version")" = "$REQUESTED_VERSION"
- run: npm run release:check
- name: Publish through npm trusted publishing
run: npm publish --access public --tag ${{ inputs.channel }}
- name: Select the single verified tarball
run: |
shopt -s nullglob
tarballs=(.artifacts/*.tgz)
test "${#tarballs[@]}" -eq 1
printf 'VERIFIED_TARBALL=%s\n' "${tarballs[0]}" >> "$GITHUB_ENV"
- name: Reconfirm 2.0.0 is absent from npm
run: node scripts/verify-registry.mjs unpublished
- name: Publish verified tarball for initial package bootstrap
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
REQUESTED_CHANNEL: ${{ inputs.channel }}
run: |
test -n "$NODE_AUTH_TOKEN"
npm publish "$VERIFIED_TARBALL" \
--access public \
--tag "$REQUESTED_CHANNEL" \
--provenance
- name: Verify registry propagation
run: node scripts/verify-registry.mjs published

Expand All @@ -100,7 +135,7 @@ jobs:
permissions:
contents: write
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7
- name: Create GitHub release
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -109,5 +144,6 @@ jobs:
run: >-
gh release create "v$RELEASE_VERSION"
--title "ReadonlyView v$RELEASE_VERSION"
--target "$GITHUB_SHA"
--generate-notes
${{ inputs.channel == 'next' && '--prerelease' || '' }}
6 changes: 2 additions & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

## Unreleased

## 2.0.0 - 2026-09-03

### Breaking Changes

- Renamed `immuview` to `@nipe-solutions/readonly-view`.
Expand All @@ -27,7 +29,3 @@
### Documentation

- Rewritten around readonly-view ownership.

## 2.0.0

Prepared but not released. Promote Unreleased notes when publishing.
28 changes: 27 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Use Node.js 24 (`.nvmrc`) and npm.

```bash
npm ci
npm run check
npm run release:check
```

## Commands
Expand All @@ -18,3 +18,29 @@ npm run check
- `npm run build:website`: production documentation site.

Add a failing regression test before fixing a defect. Tests should name an observable break and exercise real behavior. Keep changes focused, document public behavior, and explain API, compatibility, size, or security impact in the pull request.

## Deployment and release changes

Vercel's Git integration creates previews for pull requests and deploys
documentation from `main` to the custom domain
`https://readonly-view.nipesolutions.com`. Changes to `vercel.json`, public URLs,
legal routes, DNS expectations, or release automation must describe their preview
and production verification in the pull request.

Publishing is restricted to the protected GitHub environment `npm`. The first
publication uses a short-lived granular `NPM_TOKEN` environment secret because
npm requires the package to exist before trusted-publisher setup; only the
publish job receives it, and it publishes the verified tarball with provenance.
Afterward, revoke the token, delete the secret, and configure the trusted-
publisher tuple `NIPE-Solutions/readonly-view`, `release.yml`, `npm` for future
releases. The stable release is dispatched from `main` with exactly
`version=2.0.0`, `channel=latest`, and
`confirmation=publish 2.0.0 with latest`. After publication, verify the npm
metadata and a fresh external consumer install, provenance, the SHA-targeted
`v2.0.0` GitHub release, and every production route; see
[the release runbook](docs/RELEASING.md) for the exact commands.

Never attempt to overwrite or republish an npm version. A package defect requires
a reviewed patch release (and a precise deprecation warning when appropriate); a
site defect should be rolled back in Vercel and corrected on `main`. Do not
deprecate or otherwise mutate the old `immuview` package.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ Initial wrapping creates one proxy and does not walk the graph. Nested proxies a

## Documentation

[Architecture](docs/architecture.md) · [API](docs/api.md) · [Supported types](docs/supported-types.md) · [Compatibility](docs/compatibility.md) · [Migration](docs/migration-v1-v2.md) · [Security](docs/security-and-trust.md) · [Contributing](CONTRIBUTING.md)
[Documentation site](https://readonly-view.nipesolutions.com) · [Architecture](docs/architecture.md) · [API](docs/api.md) · [Supported types](docs/supported-types.md) · [Compatibility](docs/compatibility.md) · [Migration](docs/migration-v1-v2.md) · [Security](docs/security-and-trust.md) · [Contributing](CONTRIBUTING.md)

## License

Expand Down
2 changes: 2 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

Security fixes are supported for the latest published major version. Report vulnerabilities through GitHub private vulnerability reporting for `NIPE-Solutions/readonly-view`; do not publish exploit details in an issue.

Canonical documentation URL: [readonly-view.nipesolutions.com](https://readonly-view.nipesolutions.com).

ReadonlyView is not a security sandbox. Unexpected source-reference escapes through supported paths, source mutation by the library, prototype pollution, or package-integrity failures are relevant reports. Existing source references, user function/getter side effects, and consumer-proxy behavior are outside the sandbox guarantee but may still reveal correctness defects.
122 changes: 108 additions & 14 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,110 @@
# Releasing

Releases are manual and protected.

1. Use Node 24 and run `npm ci`.
2. Update version and promote Unreleased notes.
3. Run `npm run release:check` and the browser matrix.
4. Review `npm pack --dry-run`, declarations, budgets, and license.
5. Configure npm trusted publishing for the `NIPE-Solutions/readonly-view`
repository and the `release.yml` workflow, with the `npm` GitHub environment
protected by required reviewers.
6. Dispatch Release from `main` with the exact version, the `next` or `latest`
channel, and the literal `publish VERSION with CHANNEL` confirmation.
7. Verify registry propagation and create the matching GitHub release.

Stable versions use `latest`; prereleases use `next`. Never publish during implementation.
Releases are manual, protected, and run only from `main`. The current workflow is
locked to the first stable release: `@nipe-solutions/[email protected]` on the
`latest` npm channel. Its inputs remain versioned so a future release can update
the explicit authorization gate without redesigning the workflow.

## Hosted documentation

Vercel's Git integration owns documentation deployments for the
`NIPE-Solutions/readonly-view` repository. Pull requests receive preview
deployments; a merge to the production branch, `main`, creates the production
deployment using the committed `vercel.json`. The production project must have
`readonly-view.nipesolutions.com` attached as its custom domain with valid DNS
and TLS before npm publication. Verify the home page, all four legal routes,
`/robots.txt`, and `/sitemap.xml` on that HTTPS origin.

If a documentation deployment is defective, restore the previous known-good
Vercel deployment immediately and follow with a reviewed revert or fix on
`main`. Do not publish a package merely to repair the hosted documentation.

## First-publication authentication

Create a protected GitHub environment named `npm`, restrict deployments to
`main`, and require a reviewer when the repository plan supports it. Because npm
does not allow trusted-publisher setup until the package exists, the initial
`2.0.0` publication uses a short-lived granular npm access token:

1. Immediately before dispatch, create a granular token with the shortest
practical expiry. Scope its package and organization permissions only as
narrowly as npm permits for the initial `@nipe-solutions/readonly-view`
publication, grant the required package write access, and select the token's
`Bypass 2FA` option. Bypassing 2FA is required for this non-interactive direct
publication because GitHub Actions cannot enter a one-time password.
2. Add it as an environment secret named `NPM_TOKEN` on the protected `npm`
environment, not as a repository-wide secret. Only the `publish` job reads
it.
3. After `2.0.0` exists and all checks below pass, configure the npm trusted
publisher with this exact tuple:

- repository owner/name: `NIPE-Solutions/readonly-view`
- workflow filename: `release.yml`
- GitHub environment: `npm`

4. Immediately after the trusted publisher is configured, delete the
`NPM_TOKEN` GitHub environment secret and revoke the granular token before
any further release work. For future versions, update the explicit release
authorization and remove `NODE_AUTH_TOKEN` from the publish step so npm
authenticates through the trusted publisher.

The initial workflow uses the token for registry authentication and the
publish job's OIDC permission for `--provenance`; it is not an OIDC-only
publication. Never use a long-lived npm token.

## Stable 2.0.0 procedure

1. On the reviewed `main` commit, use Node 24 and run `npm ci`,
`npm run release:check`, and `npm run test:browser`.
2. Review `npm pack --dry-run`, declarations, bundle budgets, license, and the
exact tarball. Confirm the production documentation checks above pass.
3. Confirm `npm view @nipe-solutions/[email protected] version` returns an npm
`E404`; an existing version must never be overwritten.
4. Dispatch the protected workflow with exactly these inputs:

```bash
gh workflow run release.yml --ref main \
-f version=2.0.0 \
-f channel=latest \
-f 'confirmation=publish 2.0.0 with latest'
```

5. Approve the `npm` environment deployment, then monitor the publish job. It
downloads the single tarball produced by the verified job, reconfirms
registry absence, publishes those exact bytes with provenance, waits for
registry propagation, and only then creates GitHub release `v2.0.0` at the
immutable workflow commit.
6. Verify the public result:

```bash
npm view @nipe-solutions/[email protected] name version dist-tags.latest repository homepage
test -n "$(npm view @nipe-solutions/[email protected] dist.attestations.url)"

consumer_dir="$(mktemp -d)"
(
cd "$consumer_dir"
npm init --yes >/dev/null
npm install --ignore-scripts --save-exact @nipe-solutions/[email protected]
node --input-type=module -e \
"import('@nipe-solutions/readonly-view').then(m => console.log(typeof m.readonlyView))"
)

for route in / /privacy/ /impressum/ /de/datenschutz/ /de/impressum/ /robots.txt /sitemap.xml; do
curl --fail --silent --show-error --location \
"https://readonly-view.nipesolutions.com${route}" >/dev/null
done

gh release view v2.0.0 --repo NIPE-Solutions/readonly-view \
--json tagName,targetCommitish,url
```

The metadata must report version `2.0.0`, `latest` must resolve to `2.0.0`, the
homepage must be `https://readonly-view.nipesolutions.com`, the import must print
`function` from the temporary consumer, the attestation URL must be nonempty,
all production requests must succeed over HTTPS, and the GitHub release target
must match the dispatched `main` SHA. Remove `consumer_dir` after inspection.

npm versions are immutable: never attempt to republish `2.0.0`. If the package
is defective, deprecate it with a precise warning when appropriate, publish a
reviewed patch version, and move `latest` only through the guarded workflow. Do
not deprecate or otherwise mutate the old `immuview` package.
2 changes: 2 additions & 0 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,6 @@

The emitted JavaScript targets ES2022. “Current browser” means the versions installed by the lockfile's Playwright release, not every historical browser with Proxy support.

Canonical documentation URL: [readonly-view.nipesolutions.com](https://readonly-view.nipesolutions.com).

On macOS 14, the current Playwright WebKit bundle has an upstream launch-protocol incompatibility. Local configuration omits that one project on that OS; the Linux CI matrix remains authoritative for WebKit support.
Loading