Skip to content

Commit 115e1e7

Browse files
authored
build(webapi): fix alpha installs and release checks [Codex] (#397)
Makes the package installable with ReScript 13 alpha and checks the npm package that consumers will actually install. Improves the test runner, adds browser tests, and updates vulnerable development dependencies. Alpha releases use numbered versions and are checked before publishing. Pull request checks do not receive publishing permissions. This is the base for #399 and #398. A maintainer still needs to verify npm Trusted Publishing before publishing the v1 alpha (#385). Closes #384. Closes #396. Refs #383 and #385.
1 parent 43b56f2 commit 115e1e7

90 files changed

Lines changed: 2397 additions & 226 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ on:
55
branches:
66
- main
77
pull_request:
8-
branches:
9-
- main
108

119
permissions:
1210
contents: read
@@ -36,9 +34,21 @@ jobs:
3634
- name: Check public feature builds
3735
run: npm run check:features
3836

37+
- name: Verify packed consumers
38+
run: npm run check:consumer
39+
40+
- name: Run negative compiler consumers
41+
run: npm run test:negative
42+
3943
- name: Run tests
4044
run: npm test
4145

46+
- name: Install Chromium
47+
run: npx playwright install --with-deps chromium
48+
49+
- name: Run browser tests
50+
run: npm run test:browser
51+
4252
- name: Build documentation
4353
run: npm run build:docs
4454

‎.github/workflows/release.yml‎

Lines changed: 109 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,10 @@ name: Release
22

33
on:
44
pull_request:
5-
branches:
6-
- main
75
workflow_dispatch:
86
inputs:
97
dry_run:
10-
description: Run npm publish without publishing the package
8+
description: Validate and dry-run without publishing
119
required: true
1210
default: true
1311
type: boolean
@@ -16,11 +14,8 @@ permissions:
1614
contents: read
1715

1816
jobs:
19-
publish:
17+
validate:
2018
runs-on: ubuntu-latest
21-
permissions:
22-
contents: read
23-
id-token: write
2419

2520
steps:
2621
- name: Checkout repository
@@ -30,7 +25,6 @@ jobs:
3025
uses: actions/setup-node@v7
3126
with:
3227
node-version: "26"
33-
registry-url: https://registry.npmjs.org
3428
package-manager-cache: false
3529

3630
- name: Setup npm
@@ -48,39 +42,123 @@ jobs:
4842
- name: Check public feature builds
4943
run: npm run check:features
5044

45+
- name: Verify packed consumers
46+
run: npm run check:consumer
47+
48+
- name: Run negative compiler consumers
49+
run: npm run test:negative
50+
5151
- name: Run tests
5252
run: npm test
5353

54+
- name: Install Chromium
55+
run: npx playwright install --with-deps chromium
56+
57+
- name: Run browser tests
58+
run: npm run test:browser
59+
5460
- name: Build documentation
5561
run: npm run build:docs
5662

57-
- name: get-npm-version
58-
id: package-version
59-
uses: martinbeentjes/npm-get-version-action@v1.3.1
63+
- name: Validate alpha package metadata
64+
run: npm run check:release
6065

61-
- name: Get short commit hash
62-
id: vars
63-
run: echo "COMMIT_HASH=$(git rev-parse --short HEAD)" >> $GITHUB_ENV
66+
- name: Check unpublished release version
67+
if: github.event_name == 'workflow_dispatch'
68+
run: node scripts/check-release.mjs --check-unpublished
6469

65-
- name: Update package version to -<commit-hash>
66-
env:
67-
PACKAGE_VERSION: ${{ steps.package-version.outputs.current-version }}-alpha-${{ env.COMMIT_HASH }}
70+
- name: Package release artifact
71+
id: package
6872
run: |
69-
node <<'NODE'
70-
const fs = require("node:fs");
71-
const packageJsonPath = "package.json";
72-
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
73-
packageJson.version = process.env.PACKAGE_VERSION;
74-
fs.writeFileSync(packageJsonPath, `${JSON.stringify(packageJson, null, 2)}\n`);
75-
NODE
73+
mkdir -p .release
74+
npm pack --json --pack-destination .release > .release/metadata.json
75+
artifact="$(node -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(".release/metadata.json", "utf8"))[0].filename)')"
76+
echo "artifact=.release/$artifact" >> "$GITHUB_OUTPUT"
7677
77-
- name: Package npm package
78-
run: npm pack
78+
- name: Verify packed consumers and tarball contents
79+
run: npm run check:consumer -- "${{ steps.package.outputs.artifact }}"
7980

80-
- name: Dry-run npm publish
81-
if: github.event_name == 'pull_request' || inputs.dry_run
82-
run: npm publish ./rescript-webapi-*.tgz --access public --tag alpha --dry-run
81+
- name: Dry-run publish the validated artifact
82+
run: npm publish "${{ steps.package.outputs.artifact }}" --access public --tag alpha --dry-run --ignore-scripts
8383

84-
- name: Publish npm package
84+
- name: Reject real publication from non-main branches
85+
if: github.event_name == 'workflow_dispatch' && !inputs.dry_run && github.ref != 'refs/heads/main'
86+
run: |
87+
echo "Real alpha publication is permitted only from main."
88+
exit 1
89+
90+
- name: Upload validated release artifact
8591
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && !inputs.dry_run
86-
run: npm publish ./rescript-webapi-*.tgz --access public --tag alpha
92+
uses: actions/upload-artifact@v4
93+
with:
94+
name: alpha-release-artifact
95+
path: ${{ steps.package.outputs.artifact }}
96+
if-no-files-found: error
97+
retention-days: 1
98+
99+
publish:
100+
needs: validate
101+
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/main' && !inputs.dry_run
102+
runs-on: ubuntu-latest
103+
permissions:
104+
contents: read
105+
id-token: write
106+
107+
steps:
108+
- name: Checkout repository
109+
uses: actions/checkout@v7
110+
111+
- name: Setup Node.js
112+
uses: actions/setup-node@v7
113+
with:
114+
node-version: "26"
115+
registry-url: https://registry.npmjs.org
116+
package-manager-cache: false
117+
118+
- name: Setup npm
119+
run: npm install --global npm@11.5.1
120+
121+
- name: Download validated release artifact
122+
uses: actions/download-artifact@v4
123+
with:
124+
name: alpha-release-artifact
125+
path: .release
126+
127+
- name: Verify downloaded artifact metadata
128+
run: |
129+
artifact=".release/rescript-webapi-$(node -p 'require("./package.json").version').tgz"
130+
test -f "$artifact"
131+
node --input-type=module <<'NODE'
132+
import { execFileSync } from "node:child_process";
133+
import { readFileSync } from "node:fs";
134+
import { validateReleaseArtifact } from "./scripts/validate-release-artifact.mjs";
135+
const { name, version } = JSON.parse(readFileSync("package.json", "utf8"));
136+
const artifact = `.release/rescript-webapi-${version}.tgz`;
137+
const packed = JSON.parse(execFileSync("tar", ["-xOf", artifact, "package/package.json"], { encoding: "utf8" }));
138+
const errors = validateReleaseArtifact({ name, version }, packed);
139+
if (errors.length > 0) {
140+
throw new Error(`Downloaded artifact is not publishable:\n${errors.join("\n")}`);
141+
}
142+
console.log(`Verified ${name}@${version} artifact before publishing with the alpha tag.`);
143+
NODE
144+
145+
- name: Verify publication guard
146+
run: node scripts/check-release.mjs --publish
147+
148+
- name: Publish alpha artifact
149+
run: npm publish ".release/rescript-webapi-$(node -p 'require("./package.json").version').tgz" --access public --tag alpha --provenance --ignore-scripts
150+
151+
- name: Verify alpha dist-tag
152+
run: |
153+
node <<'NODE'
154+
const { execFileSync } = require("node:child_process");
155+
const { name, version } = require("./package.json");
156+
const tags = JSON.parse(execFileSync("npm", ["view", name, "dist-tags", "--json"], { encoding: "utf8" }));
157+
if (tags.alpha !== version) {
158+
throw new Error(`Expected alpha dist-tag ${version}, received ${tags.alpha ?? "missing"}.`);
159+
}
160+
if (tags.latest === version) {
161+
throw new Error(`Alpha publication unexpectedly advanced latest to ${version}.`);
162+
}
163+
console.log(`Verified ${name}@${version} is tagged alpha and not latest.`);
164+
NODE

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@
33
.bsb.lock
44
.astro
55
dist/
6+
.release/
67
tmp/
78
docs/public/llm.txt
9+
.playwright/
10+
/test-results/
11+
/playwright-report/
812
.npm-cache/
913
*.tgz
1014
*~

‎docs/content/docs/contributing/testing.mdx‎

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,27 @@ description: Learn more about testing the bindings for @rescript/webapi.
44
slug: "06-testing"
55
---
66

7-
import { Aside, FileTree } from "@astrojs/starlight/components";
7+
import { FileTree } from "@astrojs/starlight/components";
88

99
Adding some unit tests is a great way to ensure that the bindings work as expected.
1010
It illustrates how the bindings are supposed to be used and can help to catch regressions.
1111

1212
## Testing the bindings
1313

14-
Create a new help in the `test` folder with the same name as the module you want to test, followed by `_test.res`.
14+
Add a ReScript fixture under `tests/`, following the source module's directory structure. Register
15+
each fixture in exactly one category in `tests/fixtures.json`: `compileOnly`,
16+
`documentationExamples`, `nodeRuntime`, or `browserRuntime`. `npm test` fails if a fixture is missing
17+
from the manifest or appears in multiple categories, compiles all fixtures, and executes only the
18+
Node runtime modules. Compile-only fixtures and documentation examples are never executed.
1519

1620
<FileTree>
1721

1822
- src
19-
- DOMAPI.res
20-
- DOMAPI
21-
- HTMLCanvasElement.res
23+
- fetch
24+
- Headers.res
2225
- tests
23-
- DOMAPI
24-
- HTMLCanvasElement_test.res
26+
- FetchAPI
27+
- Headers\_\_test.res
2528

2629
</FileTree>
2730

@@ -33,12 +36,46 @@ git add tests
3336
npm test
3437
```
3538

36-
If any of the existing tests have different ouput JavaScript, you will see a diff in the output and the test will fail.
39+
Run the focused checks that match your fixture:
3740

38-
<Aside>
39-
Depending on your use-case, it might be okay to have slightly different output JavaScript. The
40-
maintainers will help you to decide if the change is acceptable.
41-
</Aside>
41+
```shell
42+
npm test # compile fixtures and execute Node-compatible runtime tests
43+
npm run test:browser # execute native-browser regressions in Chromium
44+
npm run test:negative # verify expected compiler failures and diagnostics
45+
```
46+
47+
Use the browser suite for APIs that require a real browser. Node's built-in Fetch support is useful
48+
for Node runtime coverage, but is not a substitute for Chromium validation; DOM emulators are not
49+
browser evidence either. Keep examples that need special setup, such as a GET request with a body,
50+
compile-only unless they are deliberately configured as a runtime fixture.
51+
Browser fixtures with asynchronous behavior must await all assertions and callback completion before
52+
the ReScript module finishes evaluating; the browser runner awaits module evaluation and treats
53+
rejections and uncaught page errors as failures.
54+
55+
The local browser runner starts its own server on port `4173` by default and never reuses an existing
56+
server. If another worktree or local service already uses that port, choose a free port with
57+
`WEBAPI_TEST_PORT`; the same value configures both the test server and browser base URL:
58+
59+
```shell
60+
WEBAPI_TEST_PORT=4192 npm run test:browser
61+
```
62+
63+
Invalid port values fail before the runner starts, and an occupied port fails instead of attaching
64+
to potentially stale generated files from another checkout.
65+
66+
Put expected-failure consumer sources under `scripts/fixtures/negative/`, outside the recursively
67+
compiled `tests/` tree. Register each in `scripts/negative-fixtures.json` with its feature set and a
68+
distinctive compiler diagnostic substring. The negative command must observe both compilation failure
69+
and that expected diagnostic; an unrelated compiler failure does not pass the check.
70+
71+
## Installed package consumers
72+
73+
Run `npm run check:consumer` to pack the current package, install that artifact with the supported
74+
ReScript compiler in a fresh temporary project, and compile representative feature consumers.
75+
This verifies the installed tarball rather than copied source files or local compiler links.
76+
The development compiler and the exact version exercised by this check are ReScript
77+
`13.0.0-alpha.5`. The peer range `>=13.0.0-alpha.5 <13.0.0` admits later `13.0.0` prereleases;
78+
it does not claim support for earlier compilers or stable `13.0.0`.
4279

4380
## Why add tests?
4481

‎docs/llm.js‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -145,11 +145,9 @@ files.sort();
145145
const pages = await Promise.all(files.map(processFile));
146146
const packageJson = await fs.readFile(path.join(import.meta.dirname, "../package.json"), "utf-8");
147147
const version = JSON.parse(packageJson).version;
148-
const sha = await execAsync("git rev-parse --short HEAD").then(({ stdout }) => stdout.trim());
149-
const fullVersion = `${version}-alpha-${sha}`;
150-
const header = `Experimental ReScript WebAPI Documentation ${fullVersion}
148+
const header = `Experimental ReScript WebAPI Documentation ${version}
151149
152-
This is the API documentation for the experimental WebAPI module version ${fullVersion}.
150+
This is the API documentation for the experimental WebAPI module version ${version}.
153151
More information can be found on https://rescript-lang.github.io/experimental-rescript-webapi/
154152
155153
`;

0 commit comments

Comments
 (0)