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
81 changes: 67 additions & 14 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
@@ -1,22 +1,13 @@
name: Docs

# The playground runs the checkout's own generator (lib/ wrapper + a wasm build
# of src/), so every push and PR builds the site. Production only updates for a
# release commit or a docs-only change; other pushes upload a preview version.
on:
workflow_dispatch:
push:
branches: [main]
paths:
- 'website/**'
- '.github/workflows/docs.yml'
- 'bun.lock'
- 'bunfig.toml'
- 'stackblitz/petstore.openapi.yaml'
pull_request:
paths:
- 'website/**'
- '.github/workflows/docs.yml'
- 'bun.lock'
- 'bunfig.toml'
- 'stackblitz/petstore.openapi.yaml'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
Expand All @@ -32,6 +23,9 @@ jobs:
pull-requests: write
steps:
- uses: actions/checkout@v7
with:
# the deploy gate diffs the pushed range
fetch-depth: 0
- name: Setup bun
uses: oven-sh/setup-bun@v2
with:
Expand All @@ -40,11 +34,61 @@ jobs:
uses: actions/setup-node@v7
with:
node-version: 22
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
with:
targets: wasm32-wasip1-threads
- name: Cache cargo
uses: actions/cache@v6
with:
path: |
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
~/.napi-rs
.cargo-cache
target/
# same key as the CI matrix entry so both workflows share the cache
key: wasm32-wasip1-threads-cargo-ubuntu-latest
- name: Install dependencies
run: bun ci
- name: Build wasm engine
run: bun run build --target wasm32-wasip1-threads
# `napi build` regenerates the committed entry files; the site aliases lib/.
- name: Restore committed entry files
run: git checkout -- native.js browser.js index.d.ts
- name: Decide deploy target
id: gate
env:
BEFORE: ${{ github.event.before }}
run: |
set -eu
# wrangler caps version tags at 25 characters
echo "short=$(git rev-parse --short=7 HEAD)" >> "$GITHUB_OUTPUT"
if [ "${{ github.event_name }}" = "pull_request" ]; then
echo "production=false" >> "$GITHUB_OUTPUT"; echo "pull request: preview only"; exit 0
fi
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "production=true" >> "$GITHUB_OUTPUT"; echo "manual run"; exit 0
fi
# HEAD only, same as the publish job in CI.yml: a release commit that is
# not the push HEAD reaches neither npm nor production.
if git log -1 --pretty=%B | grep -q '^v\?[0-9]\+\.[0-9]\+\.[0-9]\+$'; then
echo "production=true" >> "$GITHUB_OUTPUT"; echo "release commit"; exit 0
fi
# a force-push or new branch has no usable `before`
if [ -n "$BEFORE" ] && ! echo "$BEFORE" | grep -q '^0*$' && git cat-file -e "$BEFORE" 2>/dev/null; then
if ! git diff --name-only "$BEFORE" HEAD | grep -qv '^\(website/\|\.github/workflows/docs\.yml\|bun\.lock\|bunfig\.toml\|stackblitz/\)'; then
echo "production=true" >> "$GITHUB_OUTPUT"; echo "docs-only change"; exit 0
fi
fi
echo "production=false" >> "$GITHUB_OUTPUT"; echo "unreleased generator change: preview only"
- name: Build site
working-directory: website
run: bun run build
env:
# previews label the engine with the commit they run
ENGINE_VERSION_SUFFIX: ${{ steps.gate.outputs.production != 'true' && format('+{0}', steps.gate.outputs.short) || '' }}
- name: Type-check
working-directory: website
run: bun run check
Expand All @@ -55,15 +99,24 @@ jobs:
working-directory: website
run: bun run test
- name: Deploy to production
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
if: steps.gate.outputs.production == 'true'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
workingDirectory: website
command: deploy
- name: Upload preview version
- name: Upload preview version (main)
if: github.event_name == 'push' && steps.gate.outputs.production == 'false'
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
gitHubToken: ${{ secrets.GITHUB_TOKEN }}
workingDirectory: website
command: versions upload --tag main-${{ steps.gate.outputs.short }} --message "main@${{ steps.gate.outputs.short }}"
- name: Upload preview version (PR)
if: github.event_name == 'pull_request'
uses: cloudflare/wrangler-action@v3
with:
Expand Down
20 changes: 0 additions & 20 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 11 additions & 40 deletions website/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,48 +10,19 @@ bun run --filter @avsystem/openapi-ng-website build # production build
bun run --filter @avsystem/openapi-ng-website test # vitest + playwright
```

## Running the playground against the local build
## The playground engine

The site pins the published `@avsystem/openapi-ng` and `@avsystem/openapi-ng-wasm32-wasi`
packages in `package.json`, and `scripts/bundle-engine.mjs` copies the wasm loader out of
`node_modules` on every `dev`/`build`. To try unreleased generator changes in the playground,
link both packages to the local build. From the repo root:
The playground runs the generator from this checkout, not a published package:
`astro.config.mjs` aliases `@avsystem/openapi-ng/browser` to the root `lib/browser.js`, and
`scripts/bundle-engine.mjs` copies the wasm loader from the repo root on every `dev`/`build`.
Build the engine once before starting the site, and again after any Rust or template change:

```bash
bun run build --target wasm32-wasip1-threads # wasm engine; writes openapi-ng.wasi* and *.wasm at the root
git checkout native.js # the build rewrites the committed entry file
bunx napi create-npm-dirs # npm/<target>/package.json for every target (gitignored)
cp openapi-ng.wasm32-wasi.wasm openapi-ng.wasi.cjs openapi-ng.wasi.d.cts \
openapi-ng.wasi-browser.js wasi-worker.mjs wasi-worker-browser.mjs npm/wasm32-wasi/
bun run build --target wasm32-wasip1-threads # writes openapi-ng.wasi* and *.wasm at the root
git checkout -- native.js browser.js index.d.ts # the build regenerates the committed entries
```

Then register both local packages with bun and point the site at them. Bun's `link:` protocol
refers to packages registered via `bun link` (a relative path there is not supported, and
`workspace:*` cannot name the workspace root):

```bash
bun link # registers @avsystem/openapi-ng (repo root)
(cd npm/wasm32-wasi && bun link) # registers @avsystem/openapi-ng-wasm32-wasi
```

In `website/package.json` set

```json
"@avsystem/openapi-ng": "link:@avsystem/openapi-ng",
"@avsystem/openapi-ng-wasm32-wasi": "link:@avsystem/openapi-ng-wasm32-wasi"
```

and run `bun install && bun run --filter @avsystem/openapi-ng-website dev`. Both packages must be
linked: the JS wrapper validates option keys, so a published wrapper rejects options the local
engine adds. The links are symlinks, so after further Rust or template changes just rebuild the
wasm, copy the six files again and restart `dev` so `predev` re-bundles the engine; no reinstall
is needed. Avoid `file:..` for the root package: bun copies `file:` dependencies, and that copies
the whole repo (including `target/`) into `node_modules`.

Before committing, revert `website/package.json` and the root `bun.lock`:

```bash
git checkout -- website/package.json bun.lock && bun install
```

Bump the pins only once the new version is on npm.
Restart `dev` afterwards so `predev` re-bundles the engine. The deployed site is built the same
way by `.github/workflows/docs.yml`, so a release commit ships the docs and playground for that
exact version; the workflow deploys to production only for a release commit or a docs-only
change and uploads a preview version for everything else.
16 changes: 15 additions & 1 deletion website/astro.config.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { defineConfig } from 'astro/config';
import starlight from '@astrojs/starlight';
import { fileURLToPath } from 'node:url';

// Forward slashes: Vite module ids and the picomatch glob below use them on every OS.
const repoRoot = fileURLToPath(new URL('..', import.meta.url)).replace(/\\/g, '/');

// `astro dev` ignores public/_headers; mirror its COOP/COEP scope so the
// playground page is cross-origin isolated and its wasm worker inherits COEP.
Expand Down Expand Up @@ -77,13 +81,23 @@ export default defineConfig({
}),
],
vite: {
build: { target: 'es2022' },
// The playground runs the checkout's own wrapper (CommonJS under lib/),
// not a published package; see scripts/bundle-engine.mjs for the engine.
resolve: {
alias: { '@avsystem/openapi-ng/browser': `${repoRoot}lib/browser.js` },
},
build: {
target: 'es2022',
commonjsOptions: { include: [/node_modules/, `${repoRoot}lib/*.js`] },
},
server: { fs: { allow: ['..'] } },
plugins: [playgroundHeaders],
// The playground's imports are only reachable through src/playground/*.ts, so
// dev discovers them late and re-optimizes, which 504s already-served modules.
optimizeDeps: {
include: [
// Resolved through the alias above; pre-bundling is what turns the
// CommonJS wrapper into ESM in dev, so this entry stays.
'@avsystem/openapi-ng/browser',
'codemirror',
'@codemirror/state',
Expand Down
2 changes: 0 additions & 2 deletions website/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,6 @@
},
"dependencies": {
"@astrojs/starlight": "0.41.7",
"@avsystem/openapi-ng": "0.6.0",
"@avsystem/openapi-ng-wasm32-wasi": "0.6.0",
"@codemirror/lang-javascript": "6.2.5",
"@codemirror/lang-json": "6.0.2",
"@codemirror/lang-yaml": "6.1.3",
Expand Down
35 changes: 25 additions & 10 deletions website/scripts/bundle-engine.mjs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
#!/usr/bin/env node
// Pre-bundles the napi-rs WASI browser loader so the page can import it
// from /playground-engine/ without Vite processing node_modules internals.
// The engine comes from the repo checkout: `napi build --target
// wasm32-wasip1-threads` at the repo root writes the loader, worker and wasm
// next to package.json, so the playground always runs the commit it is built from.
import { build } from 'esbuild';
import fs from 'node:fs';
import { createRequire } from 'node:module';
Expand All @@ -9,10 +12,21 @@ import { fileURLToPath } from 'node:url';

const require = createRequire(import.meta.url);
const websiteRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const repoRoot = path.resolve(websiteRoot, '..');
const outDir = path.join(websiteRoot, 'public', 'playground-engine');
const pkgDir = path.dirname(
require.resolve('@avsystem/openapi-ng-wasm32-wasi/package.json'),
);

const ENGINE_FILES = [
'openapi-ng.wasi-browser.js',
'wasi-worker-browser.mjs',
'openapi-ng.wasm32-wasi.wasm',
];
const missing = ENGINE_FILES.filter(file => !fs.existsSync(path.join(repoRoot, file)));
if (missing.length) {
throw new Error(
`bundle-engine: missing ${missing.join(', ')} at the repo root; run ` +
'`bun run build --target wasm32-wasip1-threads` there first.',
);
}

const PACKAGE_WORKER_URL =
"new URL('@avsystem/openapi-ng-wasm32-wasi/wasi-worker-browser.mjs', import.meta.url)";
Expand All @@ -22,11 +36,11 @@ fs.rmSync(outDir, { recursive: true, force: true });
fs.mkdirSync(outDir, { recursive: true });

const loaderSource = fs.readFileSync(
path.join(pkgDir, 'openapi-ng.wasi-browser.js'),
path.join(repoRoot, 'openapi-ng.wasi-browser.js'),
'utf8',
);
// `napi artifacts` rewrites the published loader to the bare specifier; a local
// `napi build` already emits the relative one, so the replace below is a no-op.
// `napi build` emits the relative worker URL; `napi artifacts` rewrites it to
// the bare specifier, so accept both.
if (
!loaderSource.includes(PACKAGE_WORKER_URL) &&
!loaderSource.includes(LOCAL_WORKER_URL)
Expand All @@ -39,7 +53,7 @@ if (
await build({
stdin: {
contents: loaderSource.replace(PACKAGE_WORKER_URL, LOCAL_WORKER_URL),
resolveDir: pkgDir,
resolveDir: repoRoot,
sourcefile: 'openapi-ng.wasi-browser.js',
loader: 'js',
},
Expand All @@ -52,7 +66,7 @@ await build({
});

await build({
entryPoints: [path.join(pkgDir, 'wasi-worker-browser.mjs')],
entryPoints: [path.join(repoRoot, 'wasi-worker-browser.mjs')],
bundle: true,
format: 'esm',
platform: 'browser',
Expand All @@ -62,10 +76,11 @@ await build({
});

fs.copyFileSync(
path.join(pkgDir, 'openapi-ng.wasm32-wasi.wasm'),
path.join(repoRoot, 'openapi-ng.wasm32-wasi.wasm'),
path.join(outDir, 'openapi-ng.wasm32-wasi.wasm'),
);

const { version } = require('@avsystem/openapi-ng-wasm32-wasi/package.json');
const version =
require(path.join(repoRoot, 'package.json')).version + (process.env.ENGINE_VERSION_SUFFIX ?? '');
fs.writeFileSync(path.join(outDir, 'version.json'), JSON.stringify({ version }));
console.log(`bundle-engine: wrote ${outDir} (v${version})`);
5 changes: 4 additions & 1 deletion website/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist", "public"]
"exclude": ["dist", "public"],
"compilerOptions": {
"paths": { "@avsystem/openapi-ng/browser": ["../browser.d.ts"] }
}
}
Loading