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
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ jobs:
if: matrix.os == 'ubuntu-latest'
shell: pwsh
run: |
$version = (Select-String -Path eng/Versions.props -Pattern '<SnapshotPackageVersion>(.*?)</SnapshotPackageVersion>').Matches.Groups[1].Value
$version = (Select-String -Path eng/Versions.props -Pattern '<SnapshotCliPackageVersion>(.*?)</SnapshotCliPackageVersion>').Matches.Groups[1].Value
dotnet tool install Snapshot.Cli --version $version --tool-path artifacts/tool-test --add-source artifacts/packages --ignore-failed-sources
./artifacts/tool-test/snapshot help
- name: Upload pack diagnostics
Expand Down
15 changes: 6 additions & 9 deletions .github/workflows/publish-npm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ on:
push:
branches: [release]
paths:
- 'client/**'
- 'README.md'
- 'LICENSE'
- '128x128_compressed.png'
- 'client/src/**'
- 'client/package.json'
- 'client/package-lock.json'
- 'client/build.mjs'
workflow_dispatch:

permissions:
contents: read
id-token: write

jobs:
publish:
Expand All @@ -21,10 +22,8 @@ jobs:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 22
node-version: 24
registry-url: https://registry.npmjs.org
cache: npm
cache-dependency-path: client/package-lock.json
- name: Test and build
working-directory: client
run: |
Expand All @@ -34,8 +33,6 @@ jobs:
- name: Publish when version is new
working-directory: client
shell: bash
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_ACCESS_TOKEN }}
run: |
set -euo pipefail
name="$(node -p 'require("./package.json").name')"
Expand Down
56 changes: 34 additions & 22 deletions .github/workflows/publish-nuget.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ on:
- 'eng/Versions.props'
- 'Directory.Build.props'
- 'Directory.Packages.props'
- 'README.md'
- 'LICENSE'
- '128x128_compressed.png'
workflow_dispatch:

permissions:
Expand Down Expand Up @@ -70,36 +67,51 @@ jobs:
- name: Validate packed CLI installation
shell: pwsh
run: |
$version = (Select-String -Path eng/Versions.props -Pattern '<SnapshotPackageVersion>(.*?)</SnapshotPackageVersion>').Matches.Groups[1].Value
$version = (Select-String -Path eng/Versions.props -Pattern '<SnapshotCliPackageVersion>(.*?)</SnapshotCliPackageVersion>').Matches.Groups[1].Value
dotnet tool install Snapshot.Cli --version $version --tool-path artifacts/tool-test --add-source artifacts/packages --ignore-failed-sources
./artifacts/tool-test/snapshot help
- name: NuGet login using trusted publishing
uses: NuGet/login@v1
id: login
with:
user: ${{ secrets.NUGET_USER }}
- name: Publish packages
- name: Publish only new package versions
shell: bash
run: |
set -euo pipefail
version="$(sed -n 's:.*<SnapshotPackageVersion>\(.*\)</SnapshotPackageVersion>.*:\1:p' eng/Versions.props)"

dotnet nuget push "artifacts/packages/Snapshot.Protocol.${version}.nupkg" \
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
read_version() {
local property="$1"
sed -n "s:.*<${property}>\(.*\)</${property}>.*:\1:p" eng/Versions.props
}

publish_if_new() {
local package_id="$1"
local version="$2"
local package_path="artifacts/packages/${package_id}.${version}.nupkg"
local lower_id="${package_id,,}"
local lower_version="${version,,}"
local versions_url="https://api.nuget.org/v3-flatcontainer/${lower_id}/index.json"

dotnet nuget push "artifacts/packages/Snapshot.Processing.${version}.nupkg" \
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
if [[ ! -f "$package_path" ]]; then
echo "Expected package was not produced: ${package_path}"
exit 1
fi

dotnet nuget push "artifacts/packages/Snapshot.Playwright.${version}.nupkg" \
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
if versions_json="$(curl -fsSL "$versions_url" 2>/dev/null)" && \
jq -e --arg version "$lower_version" '.versions | index($version) != null' <<<"$versions_json" >/dev/null; then
echo "${package_id}@${version} is already published; skipping."
return
fi

echo "Publishing ${package_id}@${version}."
dotnet nuget push "$package_path" \
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
}

dotnet nuget push "artifacts/packages/Snapshot.Cli.${version}.nupkg" \
--api-key "${{ steps.login.outputs.NUGET_API_KEY }}" \
--source https://api.nuget.org/v3/index.json \
--skip-duplicate
publish_if_new 'Snapshot.Protocol' "$(read_version SnapshotProtocolPackageVersion)"
publish_if_new 'Snapshot.Processing' "$(read_version SnapshotProcessingPackageVersion)"
publish_if_new 'Snapshot.Playwright' "$(read_version SnapshotPlaywrightPackageVersion)"
publish_if_new 'Snapshot.Cli' "$(read_version SnapshotCliPackageVersion)"
47 changes: 39 additions & 8 deletions .github/workflows/release-guard.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,28 +14,59 @@ jobs:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Require version bumps for publishable changes
- name: Require package-specific version bumps
shell: bash
run: |
set -euo pipefail
base='${{ github.event.pull_request.base.sha }}'
head='${{ github.event.pull_request.head.sha }}'
changed="$(git diff --name-only "$base" "$head")"

if grep -Eq '^(src/|eng/Versions\.props|Directory\.(Build|Packages)\.props|README\.md$|LICENSE$|128x128_compressed\.png$)' <<<"$changed"; then
old_version="$(git show "${base}:eng/Versions.props" 2>/dev/null | sed -n 's:.*<SnapshotPackageVersion>\(.*\)</SnapshotPackageVersion>.*:\1:p' || true)"
new_version="$(sed -n 's:.*<SnapshotPackageVersion>\(.*\)</SnapshotPackageVersion>.*:\1:p' eng/Versions.props)"
read_version() {
local source="$1"
local property="$2"
sed -n "s:.*<${property}>\(.*\)</${property}>.*:\1:p" <<<"$source"
}

require_dotnet_bump() {
local path_pattern="$1"
local property="$2"
local package_id="$3"

if ! grep -Eq "$path_pattern" <<<"$changed"; then
return
fi

local old_props
local new_props
local old_version
local new_version
old_props="$(git show "${base}:eng/Versions.props" 2>/dev/null || true)"
new_props="$(cat eng/Versions.props)"
old_version="$(read_version "$old_props" "$property")"
new_version="$(read_version "$new_props" "$property")"

if [[ -z "$new_version" ]]; then
echo "${property} is missing from eng/Versions.props."
exit 1
fi

if [[ -n "$old_version" && "$old_version" == "$new_version" ]]; then
echo "Publishable .NET files changed without bumping SnapshotPackageVersion ($new_version)."
echo "${package_id} changed without bumping ${property} (${new_version})."
exit 1
fi
fi
}

require_dotnet_bump '^src/Snapshot\.Protocol/' 'SnapshotProtocolPackageVersion' 'Snapshot.Protocol'
require_dotnet_bump '^src/Snapshot\.Processing/' 'SnapshotProcessingPackageVersion' 'Snapshot.Processing'
require_dotnet_bump '^src/Snapshot\.Playwright/' 'SnapshotPlaywrightPackageVersion' 'Snapshot.Playwright'
require_dotnet_bump '^src/Snapshot\.Cli/' 'SnapshotCliPackageVersion' 'Snapshot.Cli'

if grep -Eq '^(client/(src/|package\.json|build\.mjs)|README\.md$|LICENSE$|128x128_compressed\.png$)' <<<"$changed"; then
if grep -Eq '^(client/(src/|package\.json$|package-lock\.json$|build\.mjs$))' <<<"$changed"; then
old_version="$(git show "${base}:client/package.json" 2>/dev/null | jq -r .version 2>/dev/null || true)"
new_version="$(jq -r .version client/package.json)"
if [[ -n "$old_version" && "$old_version" == "$new_version" ]]; then
echo "The browser client changed without bumping client/package.json ($new_version)."
echo "The browser client changed without bumping client/package.json (${new_version})."
exit 1
fi
fi
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@ Add the browser client to your application loader. Sites that deliberately want
</script>
```

The minified npm build above is the recommended browser script for most applications. `@latest` follows the newest published client release. Deployments that require a permanently pinned client can replace it with an exact version such as:

```text
https://cdn.jsdelivr.net/npm/@magiccodingman/snapshot-protocol@0.1.0/dist/snapshot-protocol.min.js
```

For debugging, use the readable distribution file by replacing `snapshot-protocol.min.js` with `snapshot-protocol.js`. See [Browser client](docs/client-script.md) for the complete client contract and CDN options.

When a route has reached its final state, render:

```html
Expand All @@ -55,9 +63,12 @@ When a route has reached its final state, render:
</snapshot-ready>
```

Install the CLI:
### .NET prerequisite and CLI

Snapshot currently targets .NET 10. Install the matching .NET SDK or runtime before installing and running the CLI. The repository's authoritative target framework is defined in [`Directory.Build.props`](Directory.Build.props), while project-specific requirements live in the corresponding `.csproj` files. Check those files if this README has not yet been updated for a newer target.

```bash
dotnet --version
dotnet tool install --global Snapshot.Cli
snapshot browser install
snapshot build ./publish/wwwroot --output ./site.snapshot.zip
Expand Down Expand Up @@ -218,9 +229,13 @@ IPFS subdomain gateways and DNSLink provide a proper application origin. Legacy

- `main` is active upstream development.
- `release` is the protected stable source.
- NuGet and npm publishing workflows run only after promotion to `release`.
- npm publishing runs only for publishable browser-client inputs (`client/src/**`, package metadata, and the build script) and uses npm Trusted Publishing through GitHub OIDC. The client version in `client/package.json` determines whether a new package is published.
- `Snapshot.Protocol`, `Snapshot.Processing`, `Snapshot.Playwright`, and `Snapshot.Cli` have independent versions in `eng/Versions.props`. A release publishes only package versions that do not already exist on NuGet.org.
- README, license, and logo edits do not automatically create package releases. They are included the next time an intentionally versioned package is published; manual workflow runs are available for rerunning that release automation when needed.
- GitHub releases and immutable tags remain manually authored archival milestones.

See [Release process](docs/release-process.md) for the exact version properties, trusted-publishing setup, and workflow behavior.

## Documentation

- [Protocol and message contract](docs/protocol.md)
Expand Down
7 changes: 6 additions & 1 deletion docs/cli.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
# CLI reference

Install:
## .NET prerequisite

Snapshot currently targets .NET 10. Install the matching .NET SDK or runtime before installing and running the global tool:

```bash
dotnet --version
dotnet tool install --global Snapshot.Cli
```

The repository's authoritative shared target framework is defined in [`Directory.Build.props`](../Directory.Build.props). Project-specific requirements are defined in the corresponding `.csproj` files, and [`global.json`](../global.json) selects the SDK used to build the repository. Check those files if this documentation has not yet been updated for a newer target.

Main commands:

```text
Expand Down
23 changes: 23 additions & 0 deletions docs/client-script.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,29 @@ The npm package is `@magiccodingman/snapshot-protocol`.

The readable source is maintained in `client/src/snapshot-protocol.js`. Release automation produces an unminified distribution file, a conservatively minified file, and a source map.

## CDN usage

The minified npm build is the recommended browser client for most applications:

```html
<script
id="snapshot-protocol"
data-site-version="1"
data-force-origin="false"
src="https://cdn.jsdelivr.net/npm/@magiccodingman/snapshot-protocol@latest/dist/snapshot-protocol.min.js">
</script>
```

`@latest` follows the newest published npm release. Use an exact version when the deployed site must remain pinned until you deliberately update it:

```text
https://cdn.jsdelivr.net/npm/@magiccodingman/snapshot-protocol@0.1.0/dist/snapshot-protocol.min.js
```

The readable debugging build is available at the same URL with `snapshot-protocol.js` instead of `snapshot-protocol.min.js`. The source map is `snapshot-protocol.min.js.map`.

npm-backed jsDelivr URLs are preferred over a moving GitHub branch URL because each published npm version is an immutable release. The package also declares the minified build as its jsDelivr and UNPKG entry point, but the explicit `/dist/snapshot-protocol.min.js` path is retained in examples so the loaded artifact is obvious.

## Script attributes

- `data-site-version`: developer-controlled version used to invalidate stale snapshots.
Expand Down
76 changes: 70 additions & 6 deletions docs/release-process.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,76 @@
# Release process

`main` is upstream development. `release` is the protected stable source.
`main` is upstream development. `release` is the protected stable source. A pull request from `main` to `release` runs release guards, and publishing begins only after that promotion is merged.

Before promotion, bump `eng/Versions.props` when publishable .NET code changes and bump `client/package.json` when the browser client changes. Update `CHANGELOG.md` in the same promotion work.
## npm browser client

A pull request from `main` to `release` runs release guards. After merge, path-aware workflows publish only the affected packages.
The browser package is `@magiccodingman/snapshot-protocol`. Its release version is the `version` field in `client/package.json`.

- `publish-nuget.yml` uses NuGet trusted publishing with the `release` environment and `NUGET_USER`.
- `publish-npm.yml` uses `NPM_ACCESS_TOKEN` and publishes the built client package.
The release guard requires a client version bump when publishable client inputs change:

GitHub releases and tags are created manually when a stable commit deserves an archival milestone and curated release notes.
- `client/src/**`
- `client/package.json`
- `client/package-lock.json`
- `client/build.mjs`

The npm workflow is triggered only by those publishable client inputs on `release`, or manually with `workflow_dispatch`. Test-only edits and root README, license, or logo changes do not start publishing. The root files are copied into the package during its next intentional client release.

Before publishing, the workflow installs dependencies, tests, and builds the client. It checks whether the exact package version already exists and skips it when present. New versions publish through npm Trusted Publishing and GitHub OIDC; no long-lived npm publish token is stored.

Configure the npm trusted publisher with:

```text
GitHub owner: magiccodingman
Repository: Snapshot
Workflow filename: publish-npm.yml
Environment: release
Allowed action: npm publish
```

The workflow uses Node 24 because npm trusted publishing requires a current Node and npm CLI. After the trusted publisher is verified, package settings may disallow traditional publish tokens without blocking OIDC publishing.

The recommended CDN URL for most applications follows the newest published release:

```text
https://cdn.jsdelivr.net/npm/@magiccodingman/snapshot-protocol@latest/dist/snapshot-protocol.min.js
```

Use an exact package version instead of `@latest` when a deployment must remain permanently pinned.

## NuGet packages

The four .NET packages release independently. Their versions are declared in `eng/Versions.props`:

| Package | Version property |
| --- | --- |
| `Snapshot.Protocol` | `SnapshotProtocolPackageVersion` |
| `Snapshot.Processing` | `SnapshotProcessingPackageVersion` |
| `Snapshot.Playwright` | `SnapshotPlaywrightPackageVersion` |
| `Snapshot.Cli` | `SnapshotCliPackageVersion` |

A source change under one package directory requires only that package's version to be bumped:

```text
src/Snapshot.Protocol/** -> SnapshotProtocolPackageVersion
src/Snapshot.Processing/** -> SnapshotProcessingPackageVersion
src/Snapshot.Playwright/** -> SnapshotPlaywrightPackageVersion
src/Snapshot.Cli/** -> SnapshotCliPackageVersion
```

The NuGet workflow still restores, tests, and packs the complete package set so project-reference and CLI installation problems are caught together. Publishing is independent: it checks each package ID and version on NuGet.org and pushes only versions that do not already exist. Bumping one package does not manufacture new releases for the other three.

Changes to `Directory.Build.props`, `Directory.Packages.props`, or other shared build inputs run the workflow for validation, but they do not automatically force every package version to change. Bump only the package versions that should be released. README, license, and logo edits do not trigger NuGet publishing; they are included with the next intentionally versioned release. `workflow_dispatch` is available when the release automation itself needs to be rerun.

`publish-nuget.yml` uses NuGet Trusted Publishing through the `release` GitHub environment. Configure the NuGet.org policy with the repository owner, repository, workflow filename `publish-nuget.yml`, and environment `release`. The `NUGET_USER` GitHub secret contains only the NuGet.org profile name; `NuGet/login@v1` exchanges GitHub OIDC for a short-lived API key during the job.

## Promotion checklist

Before promoting `main` to `release`:

1. Bump `client/package.json` only when publishing a new browser-client release.
2. Bump only the NuGet package version properties whose packages should publish.
3. Update `CHANGELOG.md` for the intended releases.
4. Confirm CI and the release guard pass.
5. Merge the promotion PR into `release`.

GitHub releases and immutable tags remain manually authored archival milestones with curated release notes.
5 changes: 4 additions & 1 deletion eng/Versions.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<Project>
<PropertyGroup>
<SnapshotPackageVersion>0.1.0</SnapshotPackageVersion>
<SnapshotProtocolPackageVersion>0.1.0</SnapshotProtocolPackageVersion>
<SnapshotProcessingPackageVersion>0.1.0</SnapshotProcessingPackageVersion>
<SnapshotPlaywrightPackageVersion>0.1.0</SnapshotPlaywrightPackageVersion>
<SnapshotCliPackageVersion>0.1.0</SnapshotCliPackageVersion>
</PropertyGroup>
</Project>
2 changes: 1 addition & 1 deletion src/Snapshot.Cli/Snapshot.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<AssemblyName>Snapshot.Cli</AssemblyName>
<RootNamespace>Snapshot.Cli</RootNamespace>
<PackageId>Snapshot.Cli</PackageId>
<PackageVersion>$(SnapshotPackageVersion)</PackageVersion>
<PackageVersion>$(SnapshotCliPackageVersion)</PackageVersion>
<Description>Cross-platform command-line tool for building, inspecting, extracting, validating, and safely processing Snapshot Protocol ZIP artifacts.</Description>
<PackageTags>snapshot;cli;playwright;spa;wasm;seo;zip;minify</PackageTags>
<IsPackable>true</IsPackable>
Expand Down
Loading
Loading