From b84f1643a95e3c47a2b2ab0cca6d7239040b65da Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Thu, 5 Nov 2026 15:49:19 -0500 Subject: [PATCH 1/7] docs: harden alpha docs and local link validation --- .github/workflows/ci.yml | 3 + .github/workflows/release.yml | 3 + CHANGELOG.md | 39 ++++ README.md | 31 ++- astro.config.mjs | 5 +- docs/components/signatureItem.astro | 16 +- docs/content/docs/api-surface.mdx | 15 +- .../contributing/api-module-structure.mdx | 90 +++------ .../docs/contributing/getting-started.mdx | 19 +- docs/content/docs/contributing/releasing.mdx | 62 ++++++ docs/content/docs/font-attribution.mdx | 17 ++ docs/content/docs/index.mdx | 28 ++- docs/content/docs/philosophy.mdx | 11 +- docs/content/docs/project-status.mdx | 54 ++++- docs/fonts/roboto-mono-400.woff2 | Bin 11004 -> 0 bytes docs/fonts/roboto-mono-700.woff2 | Bin 10736 -> 0 bytes docs/fonts/subset-Inter-Bold.woff2 | Bin 19696 -> 0 bytes docs/fonts/subset-Inter-Italic.woff2 | Bin 20304 -> 0 bytes docs/fonts/subset-Inter-Medium.woff2 | Bin 19816 -> 0 bytes docs/fonts/subset-Inter-Regular.woff2 | Bin 18856 -> 0 bytes docs/fonts/subset-Inter-SemiBold.woff2 | Bin 19756 -> 0 bytes docs/pages/examples.astro | 2 +- docs/styles/theme.css | 3 +- docs/typeLinks.js | 28 +++ docs/utils.js | 6 + package.json | 5 +- scripts/check-doc-links.mjs | 184 ++++++++++++++++++ scripts/check-doc-links.test.mjs | 71 +++++++ scripts/check-doc-type-links.test.mjs | 69 +++++++ 29 files changed, 646 insertions(+), 115 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 docs/content/docs/contributing/releasing.mdx create mode 100644 docs/content/docs/font-attribution.mdx delete mode 100644 docs/fonts/roboto-mono-400.woff2 delete mode 100644 docs/fonts/roboto-mono-700.woff2 delete mode 100644 docs/fonts/subset-Inter-Bold.woff2 delete mode 100644 docs/fonts/subset-Inter-Italic.woff2 delete mode 100644 docs/fonts/subset-Inter-Medium.woff2 delete mode 100644 docs/fonts/subset-Inter-Regular.woff2 delete mode 100644 docs/fonts/subset-Inter-SemiBold.woff2 create mode 100644 docs/typeLinks.js create mode 100644 scripts/check-doc-links.mjs create mode 100644 scripts/check-doc-links.test.mjs create mode 100644 scripts/check-doc-type-links.test.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7b832494..be36aac0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,9 @@ jobs: - name: Build documentation run: npm run build:docs + - name: Check documentation links + run: npm run check:docs-links + - name: Upload documentation uses: actions/upload-pages-artifact@v5 if: github.ref == 'refs/heads/main' && github.event_name == 'push' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ab386f25..129a8ef8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -58,6 +58,9 @@ jobs: - name: Build documentation run: npm run build:docs + - name: Check documentation links + run: npm run check:docs-links + - name: get-npm-version id: package-version uses: martinbeentjes/npm-get-version-action@v1.3.1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..2f0bcc31 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,39 @@ +# Changelog + +This file records user-visible changes. Alpha releases may contain breaking changes, and +changes may occur between alpha versions. Version numbers and publication-channel instructions +are maintained by the release workflow; see the [alpha release readiness tracker](https://github.com/rescript-lang/experimental-rescript-webapi/issues/382). + +## 1.0.0-alpha.1 (candidate) + +Planned first release in the `1.0.0-alpha.N` series. ReScript `13.0.0-alpha.5` is the tested +compiler baseline. This alpha is not a stable API; review the migration notes below before +upgrading from earlier prereleases. The release workflow is the source of truth for actual +versioning and publication. + +### Migration notes + +- `WebAPI.Request.bytes`, `WebAPI.Response.bytes`, `WebAPI.Blob.bytes`, + `WebAPI.Blob.stream`, and `WebAPI.SafeFetch.bytes` expose + native `Uint8Array.t` values or chunks instead of `array`. Use the typed-array API + directly; the bytes are not copied into a ReScript integer array. +- `ResizeObserver` callbacks now receive `(entries, observer)` as two arguments rather than a + curried callback. +- `Element.closest` returns `Null.t` and is no longer polymorphic. Narrow the + result with the relevant public classifier when a more specific HTML interface is required. +- WebCrypto signatures now reflect native results: `sign` and `digest` return `ArrayBuffer.t`, + `verify` returns `bool`, and `deriveKey` returns `WebAPI.SubtleCrypto.cryptoKey`. Key and + algorithm support types are exposed through `WebAPI.SubtleCrypto`, not the internal + `WebCryptoTypes` module. + DOMMatrix float32-array conversions use `Float32Array.t`. +- `DOMTree.element` no longer exposes a generic `className`. Specific HTML interfaces retain a + string `className`; `SVGElement.t.className` is `SVGElement.svgAnimatedString` with + `baseVal` and `animVal`. +- `CustomElementRegistry.define` takes a `DOM.customElementConstructor`; it does not take an + element instance. +- The `NotificationTypes.notification` type no longer contains a static permission field. + Read static permission through `WebAPI.Notification.permission`. + +For the intended installation, compiler range, runtime/browser checks, and known limitations, +see the [project status](https://rescript-lang.github.io/experimental-rescript-webapi/project-status/) +and [API surface guide](https://rescript-lang.github.io/experimental-rescript-webapi/api-surface/). diff --git a/README.md b/README.md index d19fa525..42655ab4 100644 --- a/README.md +++ b/README.md @@ -6,23 +6,28 @@ Experimental successor to [rescript-webapi](https://github.com/TheSpyder/rescrip ## Getting started -Install the package using your favorite package manager: +ReScript `13.0.0-alpha.5` is the tested compiler baseline. Library alpha releases may contain +breaking changes. See the [project status](https://rescript-lang.github.io/experimental-rescript-webapi/project-status/) +for the accepted peer range and its testing limits. + +Install the library and compiler in your application: ```shell -npm i @rescript/webapi@alpha +npm install @rescript/webapi@alpha +npm install --save-dev rescript@13.0.0-alpha.5 ``` -and add `@rescript/webapi` to your `rescript.json`: +Add a source directory and the package dependency to `rescript.json`: ```json { - "dependencies": [ - "@rescript/webapi" - ] + "name": "webapi-consumer", + "sources": [{ "dir": "src" }], + "dependencies": ["@rescript/webapi"] } ``` -## Usage +In `src/Main.res`: ```rescript let location = WebAPI.DOM.window->WebAPI.Window.location @@ -30,6 +35,16 @@ let href = location.href location->WebAPI.Location.reload ``` +Compile the consumer with: + +```shell +npx rescript build +``` + +The namespace is flat under `WebAPI`; internal implementation modules are not public API. + ## Documentation -More information can be found on https://rescript-lang.github.io/experimental-rescript-webapi/ +More information can be found on https://rescript-lang.github.io/experimental-rescript-webapi/. +See the [alpha release checklist](https://github.com/rescript-lang/experimental-rescript-webapi/issues/382) +and [migration notes](CHANGELOG.md) for known limitations and breaking changes. diff --git a/astro.config.mjs b/astro.config.mjs index 55d8d85b..27298e3d 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -44,6 +44,9 @@ export default defineConfig({ { slug: "api-surface", }, + { + slug: "font-attribution", + }, { label: "Examples", link: "examples", @@ -75,7 +78,7 @@ export default defineConfig({ }, }, ], - customCss: ["./docs/styles/fonts.css", "./docs/styles/theme.css"], + customCss: ["./docs/styles/theme.css"], expressiveCode: { shiki: { langs: [rescriptTM], diff --git a/docs/components/signatureItem.astro b/docs/components/signatureItem.astro index 1dc31574..9c020baa 100644 --- a/docs/components/signatureItem.astro +++ b/docs/components/signatureItem.astro @@ -1,5 +1,5 @@ --- -import { createAPIModuleLink } from "../utils.js"; +import { apiModules, createAPIModuleLink, publicTypeTargets } from "../utils.js"; const { item, typesInOwnModule } = Astro.props; let link; @@ -9,8 +9,18 @@ if (typesInOwnModule && typesInOwnModule.has(item.path)) { if (item.path.startsWith("WebAPI.")) { const paths = item.path.split("."); - if (paths.length === 3) { - link = `${import.meta.env.BASE_URL}/${createAPIModuleLink(paths[1])}#${paths[2]}`; + const moduleName = paths[1]; + if ( + paths.length === 3 && + moduleName && + apiModules.some((apiModule) => apiModule.moduleName === moduleName) + ) { + link = `${import.meta.env.BASE_URL}/${createAPIModuleLink(moduleName)}#${paths[2]}`; + } else if (paths.length === 3 && moduleName && paths[2]) { + const publicType = publicTypeTargets.get(`${moduleName}.${paths[2]}`); + if (publicType) { + link = `${import.meta.env.BASE_URL}/${createAPIModuleLink(publicType.moduleName)}#${publicType.typeName}`; + } } } diff --git a/docs/content/docs/api-surface.mdx b/docs/content/docs/api-surface.mdx index 7297f6aa..a43cccb1 100644 --- a/docs/content/docs/api-surface.mdx +++ b/docs/content/docs/api-surface.mdx @@ -4,11 +4,19 @@ description: A practical summary of how to use the public @rescript/webapi modul slug: "api-surface" --- -The package exposes browser APIs under the `WebAPI` namespace. In normal application code, -add the package to `rescript.json`: +The package exposes browser APIs under the `WebAPI` namespace. ReScript `13.0.0-alpha.5` is +the tested compiler baseline, and alpha releases may contain breaking changes. See +[project status](../project-status/) for the accepted peer range and its testing limits, and +the [migration notes](https://github.com/rescript-lang/experimental-rescript-webapi/blob/main/CHANGELOG.md) +for API changes. + +Install `@rescript/webapi@alpha` and `rescript@13.0.0-alpha.5`, then add the package dependency. +Feature bundles are optional when consumers need to restrict compiled modules: ```json { + "name": "webapi-consumer", + "sources": [{ "dir": "src" }], "dependencies": ["@rescript/webapi"] } ``` @@ -24,6 +32,9 @@ let href = location.href location->WebAPI.Location.reload ``` +For the compiler range, alpha compatibility contract, and breaking changes to byte, observer, +DOM, and crypto types, read the [alpha migration notes](https://github.com/rescript-lang/experimental-rescript-webapi/blob/main/CHANGELOG.md). + ## Public module shape The public API is flat under `WebAPI`. Use owner modules directly rather than generated diff --git a/docs/content/docs/contributing/api-module-structure.mdx b/docs/content/docs/contributing/api-module-structure.mdx index 45781e8c..c790e915 100644 --- a/docs/content/docs/contributing/api-module-structure.mdx +++ b/docs/content/docs/contributing/api-module-structure.mdx @@ -1,87 +1,47 @@ --- title: API Module Structure -description: Learn more about the API module structure of @rescript/webapi. +description: How the public API is organized and how source files map to it. slug: "02-api-module-structure" --- -import { Aside } from "@astrojs/starlight/components"; import { FileTree } from "@astrojs/starlight/components"; -The bindings are organized by the Web API they represent. Each API has its interfaces and auxiliary types in a module named after the API, suffixed with `API` to prevent collisions with the type module. +The package exposes a flat `WebAPI` namespace. Source files are grouped by API family in +lowercase directories, while ReScript module names are derived from their filenames. -- package.json - src - - DOMAPI.res - - DOMAPI - - HTMLElement.res + - fetch + - Request.res + - Response.res + - FetchTypes.res -Within the API module, the structure is roughly as follows: +Application code uses public modules such as `WebAPI.Request` and `WebAPI.Response`; it does +not use the internal `FetchTypes` module. The supported public module list and feature bundles +are described in the [API surface guide](../api-surface/). -- Enum Types -- Interfaces -- Auxiliary Types +## Interface values and operations -## Enum types - -Enum types are used to represent constants in the Web API. They are typically used as arguments to methods or properties. -In ReScript terms these are variants: - -```ReScript -type scrollBehavior = - | @as("auto") Auto - | @as("instant") Instant - | @as("smooth") Smooth -``` - -Enums come first as they are always self-contained and do not depend on other types. - -## Interfaces - -[Interfaces](https://developer.mozilla.org/en-US/docs/Web/API#interfaces) are modeled as record types and are the core of the bindings. -They represent the properties and methods of the Web API. If an interface inherits from another interface, the base interface is spread into the inheriting interface. +An interface's value type is exposed by its public owner module as `t`. Operations are +functions in the corresponding public module, so a value can be piped into its operation: ```ReScript -type htmlSpanElement = { - ...htmlElement, -} +let request = WebAPI.Request.fromURL("https://example.com") +let copiedRequest = request->WebAPI.Request.clone ``` - +Auxiliary public shapes are exposed by their owner module when consumers need to construct or +name them. Internal `*Types` modules hold implementation details and are not stable public +targets. Prefer public aliases or inferred types; do not add internal modules to the API just to +make an internal reference visible. -```ReScript -type rec node = { - nodeName: string - // ... more properties -} +## Platform interfaces -and element = { - // duplicated property from node - nodeName: string - // ... more properties -} -``` - -## Auxiliary Types - -Auxiliary types are used to represent types that are not directly related to the Web API but are used in the bindings. -These can occur both before interfaces and after interfaces, depending on the context. - -```ReScript -type eventListenerOptions = {mutable capture?: bool} - -// Model after the documentation of -// https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#options -type addEventListenerOptions = { - ...eventListenerOptions, - mutable passive?: bool, - mutable once?: bool, - mutable signal?: abortSignal, -} -``` +Web APIs often inherit interface members. Bindings expose the operations needed by public +interface modules and use explicit conversion helpers where the source API can safely be viewed +as a broader interface. Runtime narrowing belongs in the destination type's checked +`classify` helper, rather than an unchecked cast in consumer code. See the [API surface guide's +DOM section](../api-surface/#classifying-dom-values) for an example. diff --git a/docs/content/docs/contributing/getting-started.mdx b/docs/content/docs/contributing/getting-started.mdx index bc7a11e6..1972f802 100644 --- a/docs/content/docs/contributing/getting-started.mdx +++ b/docs/content/docs/contributing/getting-started.mdx @@ -23,33 +23,28 @@ We recommend the following overall workflow when developing for this repository: - Always work in your fork - Always keep your fork up to date -Before updating your fork, run this command: +Add the upstream repository once, if it is not already configured: ```shell git remote add upstream https://github.com/rescript-lang/experimental-rescript-webapi.git ``` -This will make management of multiple forks and your own work easier over time. +Check `git remote -v` first to avoid adding the same remote twice. ### Updating your fork -We recommend the following commands to update your fork: +Update `main` without deleting local files or untracked work: ```shell git checkout main -git clean -xdf git fetch upstream -git rebase upstream/main +git merge --ff-only upstream/main git push ``` -Or more succinctly: - -```shell -git checkout main && git clean -xdf && git fetch upstream && git rebase upstream/main && git push -``` - -This will update your fork with the latest from `rescript-lang/experimental-rescript-webapi` on your machine and push those updates to your remote fork. +The fast-forward-only merge stops rather than rewriting history if your fork's `main` has +diverged. Keep work on feature branches; commit or stash changes before switching branches. +This updates your local `main` from upstream and pushes it to your fork's configured `origin`. ## Initial build diff --git a/docs/content/docs/contributing/releasing.mdx b/docs/content/docs/contributing/releasing.mdx new file mode 100644 index 00000000..a6f850e1 --- /dev/null +++ b/docs/content/docs/contributing/releasing.mdx @@ -0,0 +1,62 @@ +--- +title: Alpha releases +description: Maintainer guidance for validating and releasing a numbered WebAPI alpha. +slug: "08-alpha-releases" +--- + +The release checklist for `@rescript/webapi` is tracked in +[#382](https://github.com/rescript-lang/experimental-rescript-webapi/issues/382). ReScript +`13.0.0-alpha.5` is the tested compiler baseline. A stable compiler is not a prerequisite; +library alpha releases may include breaking changes between versions. + +## Prepare a candidate + +The release version is the synchronized `version` in `package.json` and `package-lock.json`. +Use the repository's release validation before changing that version: + +```shell +npm run format:check +npm run build +npm run check:features +npm test +npm run test:browser +npm run build:docs +npm run check:docs-links +npm run check:consumer +npm run check:release +``` + +The browser suite runs Chromium only; it is not an exhaustive browser compatibility matrix. +The consumer check compiles the packed artifact in a fresh project. A successful source build +alone is not evidence that package installation works. + +Update `package.json` and `package-lock.json` together to the next `1.0.0-alpha.N` version. +Update this package's `CHANGELOG.md` with the user-visible changes and migration notes. Run +`npm run check:release` again to check that the exact version is not already published, then +create the matching Git tag `v1.0.0-alpha.N` from the validated release commit. + +## Validate and publish + +Use the Release workflow's `workflow_dispatch` with `dry_run: true` first. Review the workflow +results and the package archive checks. Only an authorized maintainer should dispatch a real +publication from `main` with `dry_run: false`; alpha publication uses the `alpha` dist-tag and +must not advance `latest`. + +After a real publish, verify the registry dist-tags: + +```shell +npm view @rescript/webapi dist-tags --json +``` + +The workflow requests npm provenance through GitHub Actions OIDC. Before the first real +publication, maintainers must configure and verify npm trusted publishing for this repository +and workflow in npm's publishing settings. The expected trusted-publisher tuple is: + +- Package: `@rescript/webapi` +- Repository: `rescript-lang/experimental-rescript-webapi` +- Workflow: `.github/workflows/release.yml` +- GitHub environment: none + +In npm's package settings, **Trusted publishing** should show that exact repository and workflow +with no environment configured; provenance should be enabled. These external npm account settings +have not been verified from the repository. The workflow cannot confirm them by itself. diff --git a/docs/content/docs/font-attribution.mdx b/docs/content/docs/font-attribution.mdx new file mode 100644 index 00000000..cc1467c8 --- /dev/null +++ b/docs/content/docs/font-attribution.mdx @@ -0,0 +1,17 @@ +--- +title: Font attribution +description: Font licensing and typography choices for the documentation site. +slug: "font-attribution" +--- + +## System fonts + +The documentation site uses system sans-serif and monospace font stacks. It does not +redistribute font binaries. The rendered typography may vary with fonts installed by the reader's +operating system. + +The previous bundled Inter and Roboto Mono WOFF2 files were removed because the repository +did not record their source releases, subset-generation process, or applicable license notices. +Font metadata in those binaries identified family and version numbers but did not establish +source provenance or licensing. Rather than infer a license from a family name or an upstream +project's current terms, the site now relies on fonts already available on the reader's system. diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index d02839a2..5a973e07 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -23,24 +23,36 @@ import { Tabs, TabItem, Code } from "@astrojs/starlight/components"; ## Installation -Install the package using your favorite package manager: +ReScript `13.0.0-alpha.5` is the tested compiler baseline, and library alpha releases may +include breaking changes. See [project status](./project-status/) for the accepted peer range +and its testing limits. + +Install the package and tested compiler: - + - + -and add `@rescript/webapi` to your `rescript.json`: +Add the source directory and package dependency to `rescript.json`: export const rescriptJson = ` { - "dependencies": [ - "@rescript/webapi" - ] + "name": "webapi-consumer", + "sources": [{ "dir": "src" }], + "dependencies": ["@rescript/webapi"] } `; @@ -81,3 +93,5 @@ export { + +Save the ReScript sample in `src/Main.res`, then compile it with `npx rescript build`. diff --git a/docs/content/docs/philosophy.mdx b/docs/content/docs/philosophy.mdx index 32a0f623..b7ab59d8 100644 --- a/docs/content/docs/philosophy.mdx +++ b/docs/content/docs/philosophy.mdx @@ -6,9 +6,14 @@ slug: "design-philosophy" The core idea of these ReScript bindings is that each interface is modeled as a record type. Where possible, inheritance is represented using record spreading, and methods are modeled as functions in a separate module. This design allows for greater familiarity with the underlying JavaScript APIs, making it more friendly for newcomers. -## ReScript v12 - -These bindings utilize new features introduced in ReScript v12; thus, they are not compatible with older versions of ReScript. +## ReScript 13 alpha compatibility + +This project currently supports ReScript `13.0.0-alpha.5`. ReScript 13 is still an alpha, and +the library may continue to publish `1.0.0-alpha.N` releases alongside it. Library alpha releases +may include breaking API changes; a stable ReScript compiler is not required to use or release +this library's alpha versions. See the [API surface](../api-surface/) for the public module layout +and the [release readiness tracker](https://github.com/rescript-lang/experimental-rescript-webapi/issues/382) +for the current release scope. ## Web APIs diff --git a/docs/content/docs/project-status.mdx b/docs/content/docs/project-status.mdx index 577184de..3dfbf4f0 100644 --- a/docs/content/docs/project-status.mdx +++ b/docs/content/docs/project-status.mdx @@ -4,22 +4,56 @@ description: Learn more about the current status of @rescript/webapi. slug: "project-status" --- -This project is highly experimental and subject to change. -It is a work in progress and might not cover every API. -The core idea is to prioritize ground coverage based on community needs. -We aim to focus on the most used APIs first and then expand from there. +This project is an experimental ReScript 13 alpha package, not a stable or exhaustive set of +browser bindings. It exposes a broad collection of Web API bindings, but implementation depth +varies by API and browser support is determined by the host runtime, not by whether a ReScript +module compiles. + +`@rescript/webapi` alpha versions can make breaking API changes between releases. ReScript +`13.0.0-alpha.5` is the tested compiler baseline. The package peer range accepts +`>=13.0.0-alpha.5 <13.0.0`, which allows later `13.0.0` prereleases but does not claim each one +is tested; stable ReScript is not currently included in that range or required to ship this +alpha. ## Contributions [Contributions](../01-getting-started) are welcome! Please [open an issue](https://github.com/rescript-lang/experimental-rescript-webapi/issues/new/choose) if you have a specific API you would like to see added. This really helps prioritize our work and ensures that the most important APIs are addressed first. -## Current Status +## Architecture and validation + +The source is organized into API-family directories, while consumers use the flat `WebAPI` +namespace and public modules such as `WebAPI.Request` and `WebAPI.Response`. Generated +implementation modules and internal type-holder modules are not part of that public surface. +Bindings are maintained and corrected by hand as well as generated; the generator is not a +reproducible source of truth for every checked-in binding. + +Some bindings still carry private-record TODOs; a blanket private-record conversion is deferred +because the current compiler reports mutation warning 111. Generator reproducibility is also +follow-up work, not an alpha release gate. + +CI uses Node 26 for ReScript compilation and Node runtime fixtures. Its Chromium suite currently +executes a compiled URL fixture; this is a smoke check, not broad browser-runtime coverage of the +bindings. There is no Firefox, WebKit, or browser-version matrix. API availability, +secure-context requirements, permissions, and user activation still depend on the browser and +execution context. + +## Historical backlog -All bindings were generated using a modified version of [TypeScript-DOM-lib-generator](https://github.com/microsoft/TypeScript-DOM-lib-generator). -The next step is to manual tweak these files so they can enhance the developer experience. +Several long-standing requests remain useful references but are not individually alpha release +gates. [#236 constructor naming](https://github.com/rescript-lang/experimental-rescript-webapi/issues/236) +and [#27 overload improvements](https://github.com/rescript-lang/experimental-rescript-webapi/issues/27) +remain incremental API cleanup. Several named constructor families now use semantic names, +including `FontFace`, `MediaStream`, `Path2D`, and `WebSocket`; the broader overload lists are +not complete. [#237 module/type simplification](https://github.com/rescript-lang/experimental-rescript-webapi/issues/237) +has completed groundwork and DOM/event/observer work, while fetch/file/canvas/URL/socket, +capability access, media, and final integration items remain tracked there. -## Future +[#23 API overview](https://github.com/rescript-lang/experimental-rescript-webapi/issues/23) is a +stale point-in-time MDN coverage checklist; use the current public API documentation and source +manifest instead. [#4 error modeling](https://github.com/rescript-lang/experimental-rescript-webapi/issues/4) +and [#5 extension helpers](https://github.com/rescript-lang/experimental-rescript-webapi/issues/5) +are open design proposals without a settled project-wide direction. None of these historical +issues is automatically closed or made a blocker by this status page. -Our goal is to make this a true community project. Consumers of the bindings should be able to contribute back to the project with minimal hassle. -This means that the project should be easy to understand and contribute to. Once a contribution is accepted, a new release will follow shortly. +For the current release checklist and explicitly deferred work, see [#382](https://github.com/rescript-lang/experimental-rescript-webapi/issues/382). diff --git a/docs/fonts/roboto-mono-400.woff2 b/docs/fonts/roboto-mono-400.woff2 deleted file mode 100644 index 0a6a0df147bfce0ed0bc2c488e714294e797670d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 11004 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 diff --git a/docs/fonts/roboto-mono-700.woff2 b/docs/fonts/roboto-mono-700.woff2 deleted file mode 100644 index b6d0cb17b6e6187d93a4cf87dcc5164a4a5f5517..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10736 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

w)VRyK`lm9Z#Q4T1}an)k?X$Rx)K-8wRJDq3QzA znlYl5N`YIajDTa~#v^)*C!vgDvfnC@vUFX$PVWt?4OU5I^<<@HPIIC1@ zz=k0qgq=`L!vvyPoHEUHz^ax-ij~S{id)rF%B+)m86HZ1G zYm@&mS{xAxB$7ljDWsA{IvM;02oxk31XKtZI0U3nC}k%WqdE*S$8ONvx!*f_X&_ymMR#3a&5Wss4}lqFjZg`ZRkNyLQ>%_$JqM=-O&T?8)uLUS4lBHH zDwaz|_;WWCXJlk}*%y~35{ZdnG&#a`XcYJVsNldZp+G1Euv_PQ)vvL}*V%1e*2QM9 ieBO!2I`@889xGU8m`LRCJ--*w==st-OFD^M9smH?gt?Fa diff --git a/docs/fonts/subset-Inter-Bold.woff2 b/docs/fonts/subset-Inter-Bold.woff2 deleted file mode 100644 index f5e2b21b941ccf240d2f576b1e6db74d21fbcd87..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19696 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 Dks9=I diff --git a/docs/fonts/subset-Inter-Italic.woff2 b/docs/fonts/subset-Inter-Italic.woff2 deleted file mode 100644 index e30211472cba0d9d1a89d94956875dbf89e504ad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 20304 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 SZQjCwII;8B6ztK)9RL9Me4b(e diff --git a/docs/fonts/subset-Inter-Medium.woff2 b/docs/fonts/subset-Inter-Medium.woff2 deleted file mode 100644 index 567f4b66089a24039acd391a86e7abdc91b1f8bb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19816 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

-DYfoDPW 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 I?LPnj0Pto-m;e9( diff --git a/docs/pages/examples.astro b/docs/pages/examples.astro index cdc4f385..1687da3c 100644 --- a/docs/pages/examples.astro +++ b/docs/pages/examples.astro @@ -17,7 +17,7 @@ const headings = testFiles.map(({ name }) => ({

The best example of usage of these bindings are the{" "} - tests.
+ tests.
These exercise the public bindings directly. Reusable Web API bindings and conversion helpers belong in the library rather than in individual examples.

diff --git a/docs/styles/theme.css b/docs/styles/theme.css index 413e55d4..3533161c 100644 --- a/docs/styles/theme.css +++ b/docs/styles/theme.css @@ -1,6 +1,7 @@ :root, ::backdrop { - --sl-font: "Inter", "Roboto Mono"; + --sl-font: system-ui, sans-serif; + --sl-font-mono: ui-monospace, "Cascadia Code", "Segoe UI Mono", monospace; --sl-sidebar-width: 23rem; --sl-text-3xl: 1.5rem; --sl-text-2xl: 1.3rem; diff --git a/docs/typeLinks.js b/docs/typeLinks.js new file mode 100644 index 00000000..b1606b5c --- /dev/null +++ b/docs/typeLinks.js @@ -0,0 +1,28 @@ +export const parsePublicTypeAliases = (moduleName, source) => + [ + ...source.matchAll( + /^\s*type\s+([a-zA-Z][a-zA-Z0-9_]*)(?:<[^>\r\n]*>)?\s*=\s*([A-Z][A-Za-z0-9_]*)\.([a-z][A-Za-z0-9_]*)\b/gm, + ), + ] + .map((match) => ({ + moduleName, + typeName: match[1] ?? "", + backingModule: match[2] ?? "", + backingType: match[3] ?? "", + })) + .filter( + ({ typeName, backingModule, backingType }) => + typeName !== "" && backingModule !== "" && backingType !== "", + ); + +export const createPublicTypeTargets = (aliases) => { + const owners = new Map(); + for (const alias of aliases) { + const key = `${alias.backingModule}.${alias.backingType}`; + const matches = owners.get(key) ?? []; + owners.set(key, [...matches, { moduleName: alias.moduleName, typeName: alias.typeName }]); + } + return new Map( + [...owners].flatMap(([key, matches]) => (matches.length === 1 ? [[key, matches[0]]] : [])), + ); +}; diff --git a/docs/utils.js b/docs/utils.js index 9613a062..b69e88b9 100644 --- a/docs/utils.js +++ b/docs/utils.js @@ -3,6 +3,7 @@ import { exec } from "node:child_process"; import { promisify } from "node:util"; import { readdirSync, existsSync, readFileSync } from "fs"; import { micromark } from "micromark"; +import { createPublicTypeTargets, parsePublicTypeAliases } from "./typeLinks.js"; const execAsync = promisify(exec); const rootDir = process.cwd(); @@ -44,6 +45,11 @@ export const apiModules = publicSourceEntries .filter(({ filePath }) => existsSync(filePath)) .sort((a, b) => a.moduleName.localeCompare(b.moduleName)); +const publicTypeAliases = apiModules.flatMap(({ filePath, moduleName }) => + parsePublicTypeAliases(moduleName, readFileSync(filePath, "utf8")), +); +export const publicTypeTargets = createPublicTypeTargets(publicTypeAliases); + async function getRescriptDoc(absoluteFilePath) { const { stdout, stderr } = await execAsync(`rescript-tools doc ${absoluteFilePath}`, { maxBuffer: 1024 * 1024 * 10, // Increase buffer to 10 MB diff --git a/package.json b/package.json index c61bc088..8e87c830 100644 --- a/package.json +++ b/package.json @@ -40,8 +40,9 @@ "test:browser": "playwright test", "build": "rescript", "check:features": "node --test scripts/check-features.test.mjs && node scripts/check-features.mjs", - "format": "rescript format && oxfmt ./tests/index.js ./tests/fixtures.json ./tests/browser/native-api.spec.mjs ./tests/browser/server.mjs ./playwright.config.mjs ./scripts/check-features.mjs ./scripts/check-features.test.mjs ./package.json ./docs && prettier --write ./docs/pages", - "format:check": "rescript format --check && oxfmt ./tests/index.js ./tests/fixtures.json ./tests/browser/native-api.spec.mjs ./tests/browser/server.mjs ./playwright.config.mjs ./scripts/check-features.mjs ./scripts/check-features.test.mjs ./package.json ./docs --check && prettier --check ./docs/pages", + "check:docs-links": "node --test scripts/check-doc-links.test.mjs scripts/check-doc-type-links.test.mjs && node scripts/check-doc-links.mjs", + "format": "rescript format && oxfmt ./tests/index.js ./tests/fixtures.json ./tests/browser/native-api.spec.mjs ./tests/browser/server.mjs ./playwright.config.mjs ./scripts/check-features.mjs ./scripts/check-features.test.mjs ./scripts/check-doc-links.mjs ./scripts/check-doc-links.test.mjs ./scripts/check-doc-type-links.test.mjs ./package.json ./README.md ./CHANGELOG.md ./docs && prettier --write ./docs/pages", + "format:check": "rescript format --check && oxfmt ./tests/index.js ./tests/fixtures.json ./tests/browser/native-api.spec.mjs ./tests/browser/server.mjs ./playwright.config.mjs ./scripts/check-features.mjs ./scripts/check-features.test.mjs ./scripts/check-doc-links.mjs ./scripts/check-doc-links.test.mjs ./scripts/check-doc-type-links.test.mjs ./package.json ./README.md ./CHANGELOG.md ./docs --check && prettier --check ./docs/pages", "docs": "astro dev", "prebuild:docs": "node docs/llm.js", "build:docs": "astro build" diff --git a/scripts/check-doc-links.mjs b/scripts/check-doc-links.mjs new file mode 100644 index 00000000..35658159 --- /dev/null +++ b/scripts/check-doc-links.mjs @@ -0,0 +1,184 @@ +import { readFileSync, readdirSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +const scriptPath = fileURLToPath(import.meta.url); +const repoRoot = path.resolve(path.dirname(scriptPath), ".."); + +const decodeHTML = (value) => + value.replaceAll("&", "&").replaceAll(""", '"').replaceAll("'", "'"); + +const isExternal = (url) => /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(url); + +const parseAttributes = (text) => { + const attributes = new Map(); + const pattern = /([^\s"'<>/=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+)))?/g; + for (const match of text.matchAll(pattern)) { + attributes.set( + (match[1] ?? "").toLowerCase(), + decodeHTML(match[2] ?? match[3] ?? match[4] ?? ""), + ); + } + return attributes; +}; + +const findTagEnd = (html, start) => { + let quote = ""; + for (let index = start; index < html.length; index += 1) { + const character = html[index]; + if (quote !== "") { + if (character === quote) quote = ""; + } else if (character === '"' || character === "'") { + quote = character; + } else if (character === ">") { + return index; + } + } + return html.length; +}; + +const parseElements = (html) => { + const elements = []; + const lowerHTML = html.toLowerCase(); + let cursor = 0; + let rawTextElement = ""; + + while (cursor < html.length) { + if (rawTextElement !== "") { + const closingTag = lowerHTML.indexOf(`", start + 4); + cursor = commentEnd === -1 ? html.length : commentEnd + 3; + continue; + } + if (html.startsWith("$/.exec(tag); + cursor = end + 1; + if (match === null) continue; + + const name = (match[1] ?? "").toLowerCase(); + const attributes = parseAttributes(match[2] ?? ""); + elements.push({ name, attributes }); + if (["script", "style", "textarea", "title"].includes(name)) rawTextElement = name; + } + return elements; +}; + +const attributesFrom = (elements) => + elements.flatMap(({ attributes }) => { + const directReferences = ["href", "src", "poster", "action", "data", "xlink:href"] + .map((name) => attributes.get(name)) + .filter((value) => value !== undefined); + const sourceSet = attributes.get("srcset") ?? ""; + const sourceSetReferences = sourceSet + .split(",") + .map((candidate) => candidate.trim().split(/\s+/, 1)[0]) + .filter(Boolean); + return [...directReferences, ...sourceSetReferences]; + }); + +const fragmentsFrom = (html) => + new Set( + parseElements(html).flatMap(({ name, attributes }) => [ + ...(attributes.has("id") ? [attributes.get("id")] : []), + ...(name === "a" && attributes.has("name") ? [attributes.get("name")] : []), + ]), + ); + +const normalizeBase = (base) => `/${base.split("/").filter(Boolean).join("/")}/`; + +const resolveTarget = (source, reference, base) => { + const basePath = normalizeBase(base); + const pageURL = `https://docs.invalid/${source.replace(/index\.html$/, "")}`; + const targetURL = new URL(reference, pageURL); + let targetPath = decodeURIComponent(targetURL.pathname); + if (targetPath.startsWith(basePath)) { + targetPath = targetPath.slice(basePath.length); + } else if (reference.startsWith("/")) { + return { _tag: "Failure", reason: `absolute link is outside base path ${basePath}` }; + } else { + targetPath = targetPath.replace(/^\//, ""); + } + const file = + targetPath === "" + ? "index.html" + : targetPath.endsWith("/") + ? `${targetPath}index.html` + : targetPath; + return { _tag: "Success", file, fragment: decodeURIComponent(targetURL.hash.slice(1)) }; +}; + +export const checkDocumentLinks = (documents, base) => { + const errors = []; + const fragmentCache = new Map(); + for (const [source, html] of documents) { + if (!source.endsWith(".html")) continue; + for (const reference of attributesFrom(parseElements(html))) { + if (reference === "" || isExternal(reference)) continue; + const target = resolveTarget(source, reference, base); + if (target._tag === "Failure") { + errors.push(`${source}: ${reference}: ${target.reason}`); + continue; + } + const routeFile = documents.has(target.file) + ? target.file + : !path.extname(target.file) && documents.has(`${target.file}/index.html`) + ? `${target.file}/index.html` + : target.file; + const targetHTML = documents.get(routeFile); + if (targetHTML === undefined) { + errors.push(`${source}: ${reference}: missing ${target.file}`); + continue; + } + if (target.fragment && routeFile.endsWith(".html")) { + const targetFragments = fragmentCache.get(routeFile) ?? fragmentsFrom(targetHTML); + fragmentCache.set(routeFile, targetFragments); + if (targetFragments.has(target.fragment)) continue; + errors.push( + `${source}: ${reference}: missing fragment #${target.fragment} in ${routeFile}`, + ); + } + } + } + return errors; +}; + +const filesIn = (directory, relative = "") => + readdirSync(path.join(directory, relative), { withFileTypes: true }).flatMap((entry) => { + const entryPath = path.join(relative, entry.name); + return entry.isDirectory() ? filesIn(directory, entryPath) : [entryPath]; + }); + +if (process.argv[1] !== undefined && path.resolve(process.argv[1]) === scriptPath) { + const outputDirectory = path.resolve(process.argv[2] ?? "dist"); + const base = process.argv[3] ?? "/experimental-rescript-webapi/"; + const documents = new Map( + filesIn(outputDirectory).map((file) => [ + file, + readFileSync(path.join(outputDirectory, file), "utf8"), + ]), + ); + const errors = checkDocumentLinks(documents, base); + if (errors.length > 0) { + console.error(errors.join("\n")); + process.exit(1); + } + console.log(`Validated local links, assets, and fragments in ${documents.size} built files.`); +} diff --git a/scripts/check-doc-links.test.mjs b/scripts/check-doc-links.test.mjs new file mode 100644 index 00000000..d42f76eb --- /dev/null +++ b/scripts/check-doc-links.test.mjs @@ -0,0 +1,71 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { checkDocumentLinks } from "./check-doc-links.mjs"; + +test("validates page, asset, and fragment links under a deployment base", () => { + const pages = new Map([ + [ + "index.html", + 'Headers', + ], + ["apidocs/fetch/index.html", '

Headers

'], + ["logo.svg", ""], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); +}); + +test("resolves relative guide links from their page and detects missing targets", () => { + const pages = new Map([ + ["examples/index.html", 'testsbad'], + ["06-testing/index.html", "
guide
"], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), [ + "examples/index.html: #missing: missing fragment #missing in examples/index.html", + ]); +}); + +test("rejects absolute site links that omit the deployment base", () => { + const pages = new Map([["index.html", 'Fetch']]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), [ + "index.html: /apidocs/fetch/: absolute link is outside base path /docs-site/", + ]); +}); + +test("does not treat external links as local routes", () => { + const pages = new Map([ + ["index.html", 'external'], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); +}); + +test("finds stale implementation-module links and accepts unlinked internal type text", () => { + const pages = new Map([ + [ + "apidocs/request/index.html", + '

FetchTypes.headers

headers', + ], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), [ + "apidocs/request/index.html: /docs-site/apidocs/fetch-types/#headers: missing apidocs/fetch-types/index.html", + ]); +}); + +test("ignores URL-like markup in code and script text", () => { + const pages = new Map([ + [ + "index.html", + `
<a href="/docs-site/not-a-route">
+ + guide`, + ], + ["guide/index.html", "

Guide

"], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); +}); diff --git a/scripts/check-doc-type-links.test.mjs b/scripts/check-doc-type-links.test.mjs new file mode 100644 index 00000000..dae1731c --- /dev/null +++ b/scripts/check-doc-type-links.test.mjs @@ -0,0 +1,69 @@ +import assert from "node:assert/strict"; +import test from "node:test"; + +import { createPublicTypeTargets, parsePublicTypeAliases } from "../docs/typeLinks.js"; + +test("parses public aliases to backing module types, including generic aliases", () => { + assert.deepEqual( + parsePublicTypeAliases( + "ReadableStream", + "type t<'r> = FileTypes.readableStream<'r>\ntype options = StreamTypes.options", + ), + [ + { + moduleName: "ReadableStream", + typeName: "t", + backingModule: "FileTypes", + backingType: "readableStream", + }, + { + moduleName: "ReadableStream", + typeName: "options", + backingModule: "StreamTypes", + backingType: "options", + }, + ], + ); +}); + +test("only links backing types with one public owner", () => { + const targets = createPublicTypeTargets([ + { + moduleName: "Request", + typeName: "t", + backingModule: "FetchTypes", + backingType: "request", + }, + { + moduleName: "Request", + typeName: "requestInit", + backingModule: "FetchTypes", + backingType: "requestInit", + }, + { + moduleName: "Response", + typeName: "t", + backingModule: "FetchTypes", + backingType: "response", + }, + { + moduleName: "Request", + typeName: "bodyInit", + backingModule: "FetchTypes", + backingType: "bodyInit", + }, + { + moduleName: "Response", + typeName: "bodyInit", + backingModule: "FetchTypes", + backingType: "bodyInit", + }, + ]); + + assert.deepEqual(targets.get("FetchTypes.request"), { moduleName: "Request", typeName: "t" }); + assert.deepEqual(targets.get("FetchTypes.requestInit"), { + moduleName: "Request", + typeName: "requestInit", + }); + assert.equal(targets.get("FetchTypes.bodyInit"), undefined); +}); From 36ec0c077f15377339b880a84ae22df24360ee04 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Thu, 5 Nov 2026 15:54:24 -0500 Subject: [PATCH 2/7] docs: clarify alpha crypto migrations and release checks --- CHANGELOG.md | 6 ++++++ docs/content/docs/contributing/releasing.mdx | 19 ++++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2f0bcc31..1cffd3bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,12 @@ versioning and publication. `verify` returns `bool`, and `deriveKey` returns `WebAPI.SubtleCrypto.cryptoKey`. Key and algorithm support types are exposed through `WebAPI.SubtleCrypto`, not the internal `WebCryptoTypes` module. +- Generic `SubtleCrypto.generateKey` and `generateKey2` now return `unknown` because the result + depends on the algorithm. Use `generateKeySymmetric` for HMAC keys + (`SubtleCrypto.cryptoKey`) or `generateEcdsaKeyPair` for ECDSA pairs + (`SubtleCrypto.cryptoKeyPair`). Generic `exportKey` also returns `unknown`; use + `exportKeyBinary` for `Raw`, `Pkcs8`, or `Spki` formats (`ArrayBuffer.t`), and + `exportKeyJwk` for `Jwk` format (`JSON.t`). DOMMatrix float32-array conversions use `Float32Array.t`. - `DOMTree.element` no longer exposes a generic `className`. Specific HTML interfaces retain a string `className`; `SVGElement.t.className` is `SVGElement.svgAnimatedString` with diff --git a/docs/content/docs/contributing/releasing.mdx b/docs/content/docs/contributing/releasing.mdx index a6f850e1..ca2358fc 100644 --- a/docs/content/docs/contributing/releasing.mdx +++ b/docs/content/docs/contributing/releasing.mdx @@ -42,6 +42,13 @@ results and the package archive checks. Only an authorized maintainer should dis publication from `main` with `dry_run: false`; alpha publication uses the `alpha` dist-tag and must not advance `latest`. +For a local publish dry-run, use the exact validated archive produced by the release workflow +(do not repack a different candidate): + +```shell +npm publish ./path/to/validated.tgz --access public --tag alpha --dry-run +``` + After a real publish, verify the registry dist-tags: ```shell @@ -53,10 +60,12 @@ publication, maintainers must configure and verify npm trusted publishing for th and workflow in npm's publishing settings. The expected trusted-publisher tuple is: - Package: `@rescript/webapi` -- Repository: `rescript-lang/experimental-rescript-webapi` -- Workflow: `.github/workflows/release.yml` +- Owner: `rescript-lang` +- Repository: `experimental-rescript-webapi` +- Workflow filename: `release.yml` - GitHub environment: none -In npm's package settings, **Trusted publishing** should show that exact repository and workflow -with no environment configured; provenance should be enabled. These external npm account settings -have not been verified from the repository. The workflow cannot confirm them by itself. +In npm's package settings, **Trusted publishing** should show those exact owner, repository, and +workflow filename fields, no GitHub environment, and allow direct `npm publish` actions. +Provenance should be enabled. These external npm account settings have not been verified from the +repository. The workflow cannot confirm them by itself. From bdff32eaa9eeea8cdd8ac9e00488f9c996cba593 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Thu, 5 Nov 2026 15:59:22 -0500 Subject: [PATCH 3/7] test(docs): validate stylesheet asset references --- scripts/check-doc-links.mjs | 27 ++++++++++++++++++++++++--- scripts/check-doc-links.test.mjs | 15 +++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/scripts/check-doc-links.mjs b/scripts/check-doc-links.mjs index 35658159..6a542fa4 100644 --- a/scripts/check-doc-links.mjs +++ b/scripts/check-doc-links.mjs @@ -47,6 +47,12 @@ const parseElements = (html) => { if (rawTextElement !== "") { const closingTag = lowerHTML.indexOf(` .split(",") .map((candidate) => candidate.trim().split(/\s+/, 1)[0]) .filter(Boolean); - return [...directReferences, ...sourceSetReferences]; + const styleReferences = [ + ...cssURLsFrom(attributes.get("style") ?? ""), + ...cssURLsFrom(attributes.get("style-text") ?? ""), + ]; + return [...directReferences, ...sourceSetReferences, ...styleReferences]; }); +const cssURLsFrom = (css) => [ + ...[...css.matchAll(/url\(\s*(['"]?)(.*?)\1\s*\)/gi)].map((match) => decodeHTML(match[2] ?? "")), + ...[...css.matchAll(/@import\s+(?:"([^"]+)"|'([^']+)')/gi)].map((match) => + decodeHTML(match[1] ?? match[2] ?? ""), + ), +]; + const fragmentsFrom = (html) => new Set( parseElements(html).flatMap(({ name, attributes }) => [ @@ -129,8 +146,12 @@ export const checkDocumentLinks = (documents, base) => { const errors = []; const fragmentCache = new Map(); for (const [source, html] of documents) { - if (!source.endsWith(".html")) continue; - for (const reference of attributesFrom(parseElements(html))) { + const references = source.endsWith(".html") + ? attributesFrom(parseElements(html)) + : source.endsWith(".css") + ? cssURLsFrom(html) + : []; + for (const reference of references) { if (reference === "" || isExternal(reference)) continue; const target = resolveTarget(source, reference, base); if (target._tag === "Failure") { diff --git a/scripts/check-doc-links.test.mjs b/scripts/check-doc-links.test.mjs index d42f76eb..a94f0d7d 100644 --- a/scripts/check-doc-links.test.mjs +++ b/scripts/check-doc-links.test.mjs @@ -69,3 +69,18 @@ test("ignores URL-like markup in code and script text", () => { assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); }); + +test("checks local CSS imports, url() assets, and inline styles", () => { + const pages = new Map([ + [ + "index.html", + '
', + ], + ["styles/site.css", '@import "../theme.css"; .icon { background: url("../assets/icon.svg"); }'], + ["theme.css", "body { color: black; }"], + ["assets/inline.svg", ""], + ["assets/icon.svg", ""], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); +}); From 92efd0aa2c21d64993802a7f52c35b1d45278554 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Thu, 5 Nov 2026 15:59:42 -0500 Subject: [PATCH 4/7] docs: name public notification permission type --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cffd3bf..86f3fc72 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,8 +37,8 @@ versioning and publication. `baseVal` and `animVal`. - `CustomElementRegistry.define` takes a `DOM.customElementConstructor`; it does not take an element instance. -- The `NotificationTypes.notification` type no longer contains a static permission field. - Read static permission through `WebAPI.Notification.permission`. +- `WebAPI.Notification.t` no longer contains a static permission field. Read that value through + `WebAPI.Notification.permission`. For the intended installation, compiler range, runtime/browser checks, and known limitations, see the [project status](https://rescript-lang.github.io/experimental-rescript-webapi/project-status/) From ee3273208360bb39d5dbe2001fb06e2d2c243ef8 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Fri, 6 Nov 2026 20:20:31 -0500 Subject: [PATCH 5/7] docs: close alpha review findings and migration gaps --- CHANGELOG.md | 15 ++ README.md | 4 + .../docs/contributing/api-modelling.mdx | 190 ++++------------ .../contributing/api-module-structure.mdx | 5 +- .../docs/contributing/code-generation.mdx | 89 ++------ .../contributing/module-type-structure.mdx | 86 +++---- docs/content/docs/contributing/releasing.mdx | 30 ++- docs/content/docs/index.mdx | 4 + docs/content/docs/project-status.mdx | 33 ++- docs/layouts/BaseLayout.astro | 3 - docs/styles/fonts.css | 106 --------- docs/typeLinks.js | 67 +++++- scripts/check-doc-links.mjs | 213 ++++++++++++++++-- scripts/check-doc-links.test.mjs | 73 +++++- scripts/check-doc-type-links.test.mjs | 23 ++ 15 files changed, 504 insertions(+), 437 deletions(-) delete mode 100644 docs/layouts/BaseLayout.astro delete mode 100644 docs/styles/fonts.css diff --git a/CHANGELOG.md b/CHANGELOG.md index 86f3fc72..51275686 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,12 +13,23 @@ versioning and publication. ### Migration notes +These candidate notes are provisional until the binding changes are merged and checked against +their final public signatures. In particular, reconcile the Web Audio typed-buffer contracts and +the empty, nullable, and IndexedDB result changes from #392 before treating this file as the +release changelog. + +- WebSocket sends and worker `Request` fetches now preserve their native receiver (`this`) rather + than calling receiver-bound methods unbound. - `WebAPI.Request.bytes`, `WebAPI.Response.bytes`, `WebAPI.Blob.bytes`, `WebAPI.Blob.stream`, and `WebAPI.SafeFetch.bytes` expose native `Uint8Array.t` values or chunks instead of `array`. Use the typed-array API directly; the bytes are not copied into a ReScript integer array. - `ResizeObserver` callbacks now receive `(entries, observer)` as two arguments rather than a curried callback. +- Web Audio analyser float buffers, `AudioBuffer` channel and copy APIs, and Biquad/IIR filter + frequency-response buffers use `Float32Array.t`; analyser byte buffers use `Uint8Array.t`. + DOMMatrix and DOMMatrixReadOnly float32 conversions also use `Float32Array.t`. Use native + typed-array values rather than ordinary ReScript arrays for these calls. - `Element.closest` returns `Null.t` and is no longer polymorphic. Narrow the result with the relevant public classifier when a more specific HTML interface is required. - WebCrypto signatures now reflect native results: `sign` and `digest` return `ArrayBuffer.t`, @@ -37,6 +48,10 @@ versioning and publication. `baseVal` and `animVal`. - `CustomElementRegistry.define` takes a `DOM.customElementConstructor`; it does not take an element instance. +- The request destination, referrer-policy, and media `canPlayType` result types now include + `Empty` for native empty-string values. `HTMLCanvasElement.toBlob` and an `ifAvailable` Web + Locks callback use nullable results; `IDBFactory.deleteDatabase` has an operation-specific + `unit` result rather than a database result. - `WebAPI.Notification.t` no longer contains a static permission field. Read that value through `WebAPI.Notification.permission`. diff --git a/README.md b/README.md index 42655ab4..cac5bd73 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,10 @@ npm install @rescript/webapi@alpha npm install --save-dev rescript@13.0.0-alpha.5 ``` +The `@alpha` command targets the published alpha dist-tag. The planned `1.0.0-alpha.1` candidate +is not published yet; see [project status](https://rescript-lang.github.io/experimental-rescript-webapi/project-status/) +for the current tag and note that candidate installation is verified from its packed tarball. + Add a source directory and the package dependency to `rescript.json`: ```json diff --git a/docs/content/docs/contributing/api-modelling.mdx b/docs/content/docs/contributing/api-modelling.mdx index 430cb2b0..f5119614 100644 --- a/docs/content/docs/contributing/api-modelling.mdx +++ b/docs/content/docs/contributing/api-modelling.mdx @@ -1,175 +1,65 @@ --- title: API Modelling -description: Learn more about the API modelling process of @rescript/webapi. +description: How to model Web API operations in the flat public ReScript API. slug: "05-api-modelling" --- -import { Aside, Code, Icon } from "@astrojs/starlight/components"; +Bindings follow the browser API where that gives consumers a useful, type-safe ReScript +interface. Source files are grouped by API family, but applications use flat public owner +modules such as `WebAPI.Fetch`, `WebAPI.Request`, and `WebAPI.Element`. Internal `*Types` +modules are not application-facing namespaces. -One of this projects goals is to provide a consistent and idiomatic API for the Web APIs. -The interopt story of ReScript is quite good, but it it has limitations. -JavaScript is a dynamic language and has a lot of flexibility. -ReScript is a statically typed language and has to model the dynamic parts of JavaScript in a static way. +## Method overloads -## Dynamic parameters and properties - -Some Web APIs have a parameter or property that can be multiple things. -In ReScript, you would model this as a variant type. This is an example wrapper around values with a key property to discriminate them. - -In JavaScript, this strictness is not enforced and you can pass a string where a number is expected. -There are multiple strategies to model this in ReScript and it depends on the specific API which one is the best. - -### Overloads - -One example is [addEventListener](https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener). -This can access either a boolean or an object as the third parameter. - -```js -addEventListener(type, listener); -addEventListener(type, listener, options); -addEventListener(type, listener, useCapture); -``` - -Because, this is a method, we can model this as an overloaded function in ReScript. -The first two overloads are the same, so we can merge them into one with an optional options parameter. +JavaScript can expose several signatures for one operation. ReScript cannot overload one value +name, so give variants distinct names that describe their input rather than appending numbers. +For example, `fetch` takes a URL and `fetchWithRequest` takes a `Request`: ```ReScript -@send -external addEventListener: ( - htmlButtonElement, - eventType, - eventListener<'event>, - ~options: addEventListenerOptions=?, -) => unit = "addEventListener" -``` +let response = await WebAPI.Fetch.fetch("https://example.com") -The third overload takes a boolean and is worth using when you want to change the default of the `useCapture` boolean parameter. -We can use [a fixed argument](https://rescript-lang.org/docs/manual/latest/bind-to-js-function#fixed-arguments) to model this. - -```ReScript -@send -external addEventListenerWithCapture: ( - htmlButtonElement, - ~type_: eventType, - ~callback: eventListener<'event>, - @as(json`true`) _, -) => unit = "addEventListener" +let request = WebAPI.Request.fromURL("https://example.com") +let responseFromRequest = await WebAPI.Fetch.fetchWithRequest(request) ``` -When naming an overloaded function, we can use the `With` suffix to indicate that it is an overloaded function. +When several signatures differ only by optional arguments, keep a single binding with an +optional labeled argument when the browser API treats it as one operation. -### Constructor overloads +## Constructor overloads -Constructors follow a different naming rule than methods. +Constructors use a distinct naming convention: -- Keep singleton constructors named `make`. -- Keep true no-argument default constructors named `make`, even when the constructor family also has typed overloads. -- When a constructor family does not have a default constructor, use `from*` names for every overload, including the first one. -- Base `from*` names on the source input type: `fromString`, `fromArrayBuffer`, `fromMediaStream`, `fromURLWithProtocols`, and so on. -- If an optional labeled argument hides a real default constructor, split that binding into `make()` plus typed `from*` overloads instead of keeping the optional argument on `make`. +- Keep singleton constructors and true default constructors named `make`. +- Name typed constructors `from*` after the source type, such as `fromString`, + `fromArrayBuffer`, or `fromURLWithProtocols`. +- Split a hidden default constructor into `make()` plus typed `from*` constructors rather than + leaving optional input on `make`. -For example, these constructor families are easier to understand than numbered overloads: +For example, a `DOMMatrix` has a true default constructor alongside string and array forms: ```ReScript -@new -external fromString: (~family: string, ~source: string) => fontFace = "FontFace" - -@new -external fromDataView: (~family: string, ~source: DataView.t) => fontFace = "FontFace" - -@new -external fromArrayBuffer: (~family: string, ~source: ArrayBuffer.t) => fontFace = "FontFace" +let identity = WebAPI.DOMMatrix.make() +let fromTransform = WebAPI.DOMMatrix.fromString("matrix(1, 0, 0, 1, 0, 0)") +let fromComponents = WebAPI.DOMMatrix.fromArray([1., 0., 0., 1., 0., 0.]) ``` -And if a constructor really does have a default form, split it instead of hiding it: - -```ReScript -@new -external make: unit => domMatrix = "DOMMatrix" - -@new -external fromString: string => domMatrix = "DOMMatrix" - -@new -external fromArray: array => domMatrix = "DOMMatrix" -``` - -Single-source constructor variants should take the source value directly without a label. - -Keep `makeWith*` names for non-default convenience constructors that are not part of a source-type overload family. +Keep comments on each binding specific to the input shape and include its source type, a +consumer example, and links to the relevant MDN or ReScript type documentation. See the +[documentation guide](../07-documentation/) for the comment format. -Constructor naming is currently verified with compile-coverage tests. Runtime verification for these constructor families will be added later when the Vitest and happy-dom harness lands. +## Dynamic browser values -### Decoded variants +Some browser properties are untagged unions in JavaScript. Model these as a small public +conversion/decoding API when consumers need to distinguish their variants; do not expose the +underlying implementation type just to make it nameable. For example, the public +`WebAPI.FillStyle` module provides conversion helpers and a decoder for Canvas fill styles. -We can be pragmatic with overloaded functions and use model them in various creative ways. -For properties, we **cannot do this unfortunately**. A propery can only be defined once and have a single type. +Keep null and empty states explicit when they are part of the browser contract. Use `Null.t` +for nullable results and variants for meaningful empty values; do not replace native results +with invented defaults or unchecked casts. -The strategy here is to use a decoded variant. - -Example for the [fillStyle](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle) property of the `CanvasRenderingContext2D` interface can be either a: - -- `string` -- `CanvasGradient` -- `CanvasPattern` - -These types are not all primitives and thus we cannot define it as [untagged variants](https://rescript-lang.org/docs/manual/latest/variant#untagged-variants). -What we can do instead is represent the type as an empty type and use a helper module to interact with this. - -export const fillStyleDef = ` -type fillStyle - -type canvasRenderingContext2D = { -// ... other propeties -mutable fillStyle: fillStyle -} -`; - - - -When we wish to read and write the `fillStyle` property, we can use a helper module to lift the type to an actual ReScript variant: - -export const fillStyleModule = ` -open WebAPI - -external fromString: string => fillStyle = "%identity" -external fromCanvasGradient: canvasGradient => fillStyle = "%identity" -external fromCanvasPattern: canvasGradient => fillStyle = "%identity" - -type decoded = -| String(string) -| CanvasGradient(canvasGradient) -| CanvasPattern(canvasPattern) - -let decode = (t: fillStyle): decoded => { -if CanvasGradient.isInstanceOf(t) { -CanvasGradient(unsafeConversation(t)) -} else if CanvasPattern.isInstanceOf(t) { -CanvasPattern(unsafeConversation(t)) -} else { -String(unsafeConversation(t)) -} -} -` - - - -We can now use `FillStyle.decode` to get the actual value of the `fillStyle` property. -And use `FillStyle.fromString`, `FillStyle.fromCanvasGradient`, and `FillStyle.fromCanvasPattern` to set the value. - -```ReScript -let ctx = myCanvas->HTMLCanvasElement.getContext_2D - -// Write -ctx.fillStyle = FillStyle.fromString("red") - -// Read -switch ctx.fillStyle->FillStyle.decode { -| FillStyle.String(color) => Console.log(`Color: ${color}`) -| FillStyle.CanvasGradient(_) => Console.log("CanvasGradient") -| FillStyle.CanvasPattern(_) => Console.log("CanvasPattern") -} -``` +## Verification - -Try and use `decoded` and `decode` as conventions for the type and function names. +Add compile-only, Node runtime, or browser runtime fixtures according to the API being modeled. +The [testing guide](../06-testing/) explains `tests/fixtures.json`, `npm test`, +`npm run test:browser`, and packed consumer checks. diff --git a/docs/content/docs/contributing/api-module-structure.mdx b/docs/content/docs/contributing/api-module-structure.mdx index c790e915..92a669f5 100644 --- a/docs/content/docs/contributing/api-module-structure.mdx +++ b/docs/content/docs/contributing/api-module-structure.mdx @@ -25,8 +25,9 @@ are described in the [API surface guide](../api-surface/). ## Interface values and operations -An interface's value type is exposed by its public owner module as `t`. Operations are -functions in the corresponding public module, so a value can be piped into its operation: +Concrete interface modules commonly expose their value type as `t`; helper or operation-only +modules may not have a standalone value type. Operations live on their public owner module, so +an interface value can be piped into an operation: ```ReScript let request = WebAPI.Request.fromURL("https://example.com") diff --git a/docs/content/docs/contributing/code-generation.mdx b/docs/content/docs/contributing/code-generation.mdx index 98d6d85a..22fad99c 100644 --- a/docs/content/docs/contributing/code-generation.mdx +++ b/docs/content/docs/contributing/code-generation.mdx @@ -1,83 +1,30 @@ --- title: Code Generation -description: Learn more about the code generation process for @rescript/webapi. +description: How generated material fits into the maintained WebAPI bindings. slug: "04-code-generation" --- -The original bindings were generated using a modified version of [TypeScript-DOM-lib-generator](https://github.com/microsoft/TypeScript-DOM-lib-generator). -These bindings were a great starting point, but they are not perfect. -It is more than likely that you will need to **tweak the generated bindings by hand** to make them more idiomatic to ReScript. +The repository contains a modified +[TypeScript-DOM-lib-generator](https://github.com/microsoft/TypeScript-DOM-lib-generator) under +`tools/TypeScript-DOM-lib-generator`. Generated output is a starting point, not the complete +source of truth: checked-in bindings are reviewed and maintained by hand. -For example the `window.fetch` function was generated as: +Source modules are grouped by API family, while consumers use the flat public `WebAPI` +namespace. For example, the public `WebAPI.Fetch` functions are maintained in +`src/fetch/Fetch.res`: ```ReScript -/** -[Read more on MDN](https://developer.mozilla.org/docs/Web/API/Window/fetch) -*/ -@send -external fetch: (window, ~input: request, ~init: requestInit=?) - => promise = "fetch" +external fetch: (string, ~init: Request.requestInit=?) => promise = "fetch" -/** -[Read more on MDN](https://developer.mozilla.org/docs/Web/API/Window/fetch) -*/ -@send -external fetch2: (window, ~input: string, ~init: requestInit=?) - => promise = "fetch" +external fetchWithRequest: (Request.t, ~init: Request.requestInit=?) => promise = + "fetch" ``` -While not that bad and usable, it can be improved: +The generator may produce numbered overloads or use generated type-holder names. Before +committing a binding, use the public owner modules, semantic overload names, accurate native +types, and ReScript-facing documentation. Do not hand-edit generated `docs/pages/apidocs` +output; the API pages are generated from the public module sources. -- Rename `fetch2` to `fetch` because it is the more common usage of the function. -- Rename `fetch` to `fetchWithRequest` for clarity that this is the "overload" with a `Request` object. -- Consider removing the named `~input` and `~init` arguments and use positional arguments instead. - Motivation: If the function does not have any parameters with the same type, it is more ergonomic to use positional arguments. - This heuristic is not set in stone and can be adjusted based on the specific function. -- The documentation can be improved. - -```ReScript -/** TODO: add better docs */ -@send -external fetch: (window, string, ~init: requestInit=?) - => promise = "fetch" - -/** TODO: add better docs */ -@send -external fetchWithRequest: (window, request, ~init: requestInit=?) - => promise = "fetch" -``` - -Once these changes are made, the bindings can be tested and then committed to the repository. -The generation does no longer happen automatically, so manual improvements will not be overwritten. - -## Sandboxed Code Generation - -Not every API was covered by the TypeScript-DOM-lib-generator. -Potentially, you want to add a new API to the bindings and star from code generation. - -In [emitter.ts](https://github.com/rescript-lang/experimental-rescript-webapi/blob/main/tools/TypeScript-DOM-lib-generator/src/build/emitter.ts), -you can override the `interfaceHierarchy` with any new interface or type you want to add. - -```typescript -interfaceHierarchy = [ - { - name: "Temp", - entries: [ - enums(["WebGLPowerPreference"]), - dictionaries(["ImageBitmapRenderingContextSettings", "WebGLContextAttributes"]), - ], - opens: [], - }, -]; -``` - -After running the generator (in `tools/TypeScript-DOM-lib-generator`): - -```shell -npm run build -``` - -All the generated files will be in the `tmp` folder. You can use this as inspiration for your own bindings. - -To figure out if you need `enums`, `dictionaries`, or `interfaces`, you can look at the [JSON dump](https://github.com/rescript-lang/experimental-rescript-webapi/blob/main/tools/TypeScript-DOM-lib-generator/dom-dump.json) file -and see how the TypeScript-DOM-lib-generator represents the shape you are after. +Not every hand-maintained change is reproducibly generated from the current tool. Treat the +checked-in source as canonical, and keep generated files and compile-coverage fixtures +consistent with the repository's documented test workflow. diff --git a/docs/content/docs/contributing/module-type-structure.mdx b/docs/content/docs/contributing/module-type-structure.mdx index bb5b58fc..db920d59 100644 --- a/docs/content/docs/contributing/module-type-structure.mdx +++ b/docs/content/docs/contributing/module-type-structure.mdx @@ -1,77 +1,49 @@ --- title: Module Type Structure -description: Learn more about the module structure of @rescript/webapi. +description: How public interface modules represent values and operations. slug: "03-module-type-structure" --- -import { Aside, FileTree, Code } from "@astrojs/starlight/components"; +import { FileTree } from "@astrojs/starlight/components"; -Every interface in a Web API module can potentially contain methods. These methods are modeled in a separate module named after the interface. - -The primary reason for this separation is to handle method overloads. -As explained in the [Design Philosophy](../design-philosophy) section, ReScript does not permit records to define the same properties more than once. -Therefore, methods with overloads cannot be modeled within the same record type. - -## Bindings - -Another advantage of having a separate file is that these bindings can utilize all types defined in the API module. -Under normal circumstances, the type module only contains `@send` bindings where the type is the first parameter. +The public package is flat under `WebAPI`. Concrete interface modules commonly keep the +interface's `t` type and its operations together in one source file. For example, the +`HTMLButtonElement` binding lives in the HTML family directory: -- DOMAPI - - HTMLButtonElement.res +- src + - html + - HTMLButtonElement.res +An interface value is passed as the first argument to its operation. Consumer code uses the +public owner module: + ```ReScript -/** -Returns whether a form will validate when it is submitted, without having to submit it. -[Read more on MDN]( - https://developer.mozilla.org/docs/Web/API/HTMLButtonElement/checkValidity) -*/ -@send -external checkValidity: htmlButtonElement => bool = "checkValidity" +let button: WebAPI.HTMLButtonElement.t = getButton() +let isValid = button->WebAPI.HTMLButtonElement.checkValidity ``` -## Inheritance - -When an interface inherits from another interface, the base interface methods can be [included](https://rescript-lang.org/syntax-lookup#include) into the inheriting interface. -All methods from [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement#instance_methods) should also be available on [HTMLButtonElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLButtonElement#instance_methods). - -export const htmlElementModule = ` -open DOMAPI - -// A concrete type for \`T.t\` is passed later using the \`include\` keyword. -module Impl = (T: { type t }) => { - -/\*_ -[Read more on MDN](https://developer.mozilla.org/docs/Web/API/HTMLElement/focus) -_/ -@send -external focus: (T.t, ~options: focusOptions=?) => unit = "focus" +The corresponding binding follows the same shape: +```ReScript +type rec t = { + ...DOMTree.htmlElement, + disabled: bool, } -include Impl({ type t = htmlElement }) -`; - - - -export const buttonModule = ` -open DOMAPI - -// Include all the methods from HTMLElement -include HTMLElement.Impl({ type t = htmlButtonElement }) - -// Add additional methods specific to HTMLButtonElement: - -/\*_ -Returns whether a form will validate when it is submitted, without having to submit it. -[Read more on MDN](https://developer.mozilla.org/docs/Web/API/HTMLButtonElement/checkValidity) -_/ @send -external checkValidity: htmlButtonElement => bool = "checkValidity" -`; +external checkValidity: t => bool = "checkValidity" +``` + +Some DOM bindings reuse inherited operations through existing `Impl` helpers, but those are +implementation details, not additional consumer namespaces. Do not add `include` directives +just to expose internal modules. Prefer a concrete public owner module and selectively expose +only the aliases consumers need. - +Not every public module represents an interface value or exports `t`; some modules provide +operations or auxiliary types only. Internal `*Types` modules are implementation details. See +the [API module structure](../02-api-module-structure/) and +[public API surface](../api-surface/) for the package's current module layout. diff --git a/docs/content/docs/contributing/releasing.mdx b/docs/content/docs/contributing/releasing.mdx index ca2358fc..9c34ba87 100644 --- a/docs/content/docs/contributing/releasing.mdx +++ b/docs/content/docs/contributing/releasing.mdx @@ -30,25 +30,31 @@ The browser suite runs Chromium only; it is not an exhaustive browser compatibil The consumer check compiles the packed artifact in a fresh project. A successful source build alone is not evidence that package installation works. -Update `package.json` and `package-lock.json` together to the next `1.0.0-alpha.N` version. -Update this package's `CHANGELOG.md` with the user-visible changes and migration notes. Run -`npm run check:release` again to check that the exact version is not already published, then -create the matching Git tag `v1.0.0-alpha.N` from the validated release commit. +The initial candidate is `1.0.0-alpha.1`, tagged `v1.0.0-alpha.1`. For each later alpha, update +`package.json` and `package-lock.json` together to the next `1.0.0-alpha.N` version, add the +user-visible changes and migration guidance to `CHANGELOG.md`, and create the matching Git tag +`v1.0.0-alpha.N` from the validated release commit. Run `npm run check:release` after the version +change; it rejects an already-published version and checks the expected alpha metadata. ## Validate and publish -Use the Release workflow's `workflow_dispatch` with `dry_run: true` first. Review the workflow -results and the package archive checks. Only an authorized maintainer should dispatch a real -publication from `main` with `dry_run: false`; alpha publication uses the `alpha` dist-tag and -must not advance `latest`. - -For a local publish dry-run, use the exact validated archive produced by the release workflow -(do not repack a different candidate): +The Release workflow's unprivileged `validate` job checks and packs the candidate once. It runs +`npm run check:consumer` and a publish dry-run against that exact `.tgz`; the PR and a manual +`workflow_dispatch` with `dry_run: true` use this validation path. The workflow does not expose +that runner-local archive for a later local download. If reproducing the artifact checks locally, +pack once and pass the same path to both commands: ```shell -npm publish ./path/to/validated.tgz --access public --tag alpha --dry-run +mkdir -p .release +npm pack --json --pack-destination .release > .release/metadata.json +artifact=".release/$(node -e 'process.stdout.write(JSON.parse(require("node:fs").readFileSync(".release/metadata.json", "utf8"))[0].filename)')" +npm run check:consumer -- "$artifact" +npm publish "$artifact" --access public --tag alpha --dry-run --ignore-scripts ``` +Only an authorized maintainer should start a real publication with `workflow_dispatch` from +`main` and `dry_run: false`. The separate publish job is main-only; it verifies and publishes the +validated archive with the `alpha` dist-tag and `--provenance`, and must not advance `latest`. After a real publish, verify the registry dist-tags: ```shell diff --git a/docs/content/docs/index.mdx b/docs/content/docs/index.mdx index 5a973e07..52a3628c 100644 --- a/docs/content/docs/index.mdx +++ b/docs/content/docs/index.mdx @@ -46,6 +46,10 @@ Install the package and tested compiler: +The `@alpha` install command targets the published alpha dist-tag. The planned `1.0.0-alpha.1` +candidate is not published yet; see [project status](./project-status/) for the current tag and +note that candidate installation is verified from its packed tarball. + Add the source directory and package dependency to `rescript.json`: export const rescriptJson = ` diff --git a/docs/content/docs/project-status.mdx b/docs/content/docs/project-status.mdx index 3dfbf4f0..b6ae6244 100644 --- a/docs/content/docs/project-status.mdx +++ b/docs/content/docs/project-status.mdx @@ -15,6 +15,11 @@ module compiles. is tested; stable ReScript is not currently included in that range or required to ship this alpha. +The install example uses the `alpha` dist-tag intended for `1.0.0-alpha.1`. At the time of this +candidate, npm's public `alpha` tag still resolves to `0.1.0-alpha-b1d58f9`; the candidate is +validated from its packed tarball, but has not been published. After an authorized publication, +the same `@alpha` install command will resolve to the new candidate. + ## Contributions [Contributions](../01-getting-started) are welcome! Please [open an issue](https://github.com/rescript-lang/experimental-rescript-webapi/issues/new/choose) if you have a specific API you would like to see added. @@ -42,18 +47,30 @@ execution context. Several long-standing requests remain useful references but are not individually alpha release gates. [#236 constructor naming](https://github.com/rescript-lang/experimental-rescript-webapi/issues/236) -and [#27 overload improvements](https://github.com/rescript-lang/experimental-rescript-webapi/issues/27) -remain incremental API cleanup. Several named constructor families now use semantic names, -including `FontFace`, `MediaStream`, `Path2D`, and `WebSocket`; the broader overload lists are -not complete. [#237 module/type simplification](https://github.com/rescript-lang/experimental-rescript-webapi/issues/237) -has completed groundwork and DOM/event/observer work, while fetch/file/canvas/URL/socket, -capability access, media, and final integration items remain tracked there. +remains open, although a maintainer comment says it may be ready to close. The listed constructor +families now use semantic names in `FontFace`, `VideoFrame`, `MediaStream`, `Path2D`, `DOMMatrix`, +`DOMMatrixReadOnly`, `WebSocket`, and `OfflineAudioContext`. `ReadableStream.fromUnderlyingSource` +still combines an optional strategy rather than splitting it into a separately named variant. +Do not treat the issue's old checklist or comment as a recorded closure. + +[#27 overload improvements](https://github.com/rescript-lang/experimental-rescript-webapi/issues/27) +also remains open. Its long checklist predates the current module layout and overlaps constructor +work in #236; numbered overloads such as `Path2D.roundRect2` and `roundRect3` still exist. This +is incremental cleanup, not an alpha gate. + +[#237 module/type simplification](https://github.com/rescript-lang/experimental-rescript-webapi/issues/237) +is still open, but its implementation slices #240, #241, #242, and #243 are closed (as are #238 +and #239). The current source has no root `src/*API.res` type-holder files or +`Global.window`/`Global.navigator` references. The parent checklist has not been refreshed; the +refactor appears complete in code, but its tracker remains open. [#23 API overview](https://github.com/rescript-lang/experimental-rescript-webapi/issues/23) is a stale point-in-time MDN coverage checklist; use the current public API documentation and source manifest instead. [#4 error modeling](https://github.com/rescript-lang/experimental-rescript-webapi/issues/4) -and [#5 extension helpers](https://github.com/rescript-lang/experimental-rescript-webapi/issues/5) -are open design proposals without a settled project-wide direction. None of these historical +remains an open design discussion: comments disagree about whether DOMException should be modeled +separately from `Error`, citing different TypeScript DOM declarations. [#5 extension helpers](https://github.com/rescript-lang/experimental-rescript-webapi/issues/5) +also remains open; comments differ on whether typed helper APIs belong in owner modules or +separate extension modules. Neither has a settled project-wide design. None of these historical issues is automatically closed or made a blocker by this status page. For the current release checklist and explicitly deferred work, see [#382](https://github.com/rescript-lang/experimental-rescript-webapi/issues/382). diff --git a/docs/layouts/BaseLayout.astro b/docs/layouts/BaseLayout.astro deleted file mode 100644 index e77ccc58..00000000 --- a/docs/layouts/BaseLayout.astro +++ /dev/null @@ -1,3 +0,0 @@ ---- -import '@fontsource-variable/inter'; ---- \ No newline at end of file diff --git a/docs/styles/fonts.css b/docs/styles/fonts.css deleted file mode 100644 index 1b3d817b..00000000 --- a/docs/styles/fonts.css +++ /dev/null @@ -1,106 +0,0 @@ -/* latin */ -@font-face { - font-family: "Inter"; - src: - local("Inter Bold"), - local("Inter-Bold"), - url("../fonts/subset-Inter-Bold.woff2") format("woff2"); - font-weight: 700; - font-style: normal; - font-display: swap; - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Inter"; - src: - local("Inter Italic"), - local("Inter-Italic"), - url("../fonts/subset-Inter-Italic.woff2") format("woff2"); - font-weight: 400; - font-style: italic; - font-display: swap; - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Inter"; - src: - local("Inter Medium"), - local("Inter-Medium"), - url("../fonts/subset-Inter-Medium.woff2") format("woff2"); - font-weight: 500; - font-style: normal; - font-display: swap; - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Inter"; - src: - local("Inter Semi Bold"), - local("Inter-SemiBold"), - url("../fonts/subset-Inter-SemiBold.woff2") format("woff2"); - font-weight: 600; - font-style: normal; - font-display: swap; - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Inter"; - src: - local("Inter Regular"), - local("Inter-Regular"), - url("../fonts/subset-Inter-Regular.woff2") format("woff2"); - font-weight: 400; - font-style: normal; - font-display: swap; - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* ROBOTO MONO */ - -/* latin */ -@font-face { - font-family: "Roboto Mono"; - font-style: normal; - font-weight: 400; - font-display: swap; - src: - local("Roboto Mono"), - local("RobotoMono-Regular"), - url("../fonts/roboto-mono-400.woff2") format("woff2"); - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} - -/* latin */ -@font-face { - font-family: "Roboto Mono"; - font-style: bold; - font-weight: 700; - font-display: swap; - src: - local("Roboto Mono Bold"), - local("RobotoMono-Bold"), - url("../fonts/roboto-mono-700.woff2") format("woff2"); - unicode-range: - U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, - U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD; -} diff --git a/docs/typeLinks.js b/docs/typeLinks.js index b1606b5c..7f0cdbfd 100644 --- a/docs/typeLinks.js +++ b/docs/typeLinks.js @@ -1,7 +1,70 @@ +const stripReScriptComments = (source) => { + const output = []; + let index = 0; + let quote = ""; + + while (index < source.length) { + if (quote !== "") { + const character = source[index] ?? ""; + output.push(character); + if (character === "\\" && index + 1 < source.length) { + output.push(source[index + 1] ?? ""); + index += 2; + } else { + if (character === quote) quote = ""; + index += 1; + } + } else if (source[index] === '"' || source[index] === "`") { + quote = source[index] ?? ""; + output.push(quote); + index += 1; + } else if (source.startsWith("//", index)) { + while (index < source.length && source[index] !== "\n") index += 1; + } else if (source.startsWith("/*", index)) { + index += 2; + while (index < source.length && !source.startsWith("*/", index)) { + if (source[index] === "\n") output.push("\n"); + index += 1; + } + index = Math.min(index + 2, source.length); + output.push(" "); + } else { + output.push(source[index] ?? ""); + index += 1; + } + } + return output.join(""); +}; + +const maskNestedDeclarations = (source) => { + const depth = { value: 0 }; + let quote = ""; + return source + .split("\n") + .map((line) => { + const result = depth.value === 0 ? line : ""; + for (let index = 0; index < line.length; index += 1) { + const character = line[index] ?? ""; + if (quote !== "") { + if (character === "\\") index += 1; + else if (character === quote) quote = ""; + } else if (character === '"' || character === "`") { + quote = character; + } else if (character === "{") { + depth.value += 1; + } else if (character === "}") { + depth.value = Math.max(0, depth.value - 1); + } + } + return result; + }) + .join("\n"); +}; + export const parsePublicTypeAliases = (moduleName, source) => [ - ...source.matchAll( - /^\s*type\s+([a-zA-Z][a-zA-Z0-9_]*)(?:<[^>\r\n]*>)?\s*=\s*([A-Z][A-Za-z0-9_]*)\.([a-z][A-Za-z0-9_]*)\b/gm, + ...maskNestedDeclarations(stripReScriptComments(source)).matchAll( + /^type[ \t]+([a-zA-Z][a-zA-Z0-9_]*)(?:<[^>\r\n]*>)?[ \t]*=[ \t]*([A-Z][A-Za-z0-9_]*)\.([a-z][A-Za-z0-9_]*)\b/gm, ), ] .map((match) => ({ diff --git a/scripts/check-doc-links.mjs b/scripts/check-doc-links.mjs index 6a542fa4..82a5dfc7 100644 --- a/scripts/check-doc-links.mjs +++ b/scripts/check-doc-links.mjs @@ -6,7 +6,21 @@ const scriptPath = fileURLToPath(import.meta.url); const repoRoot = path.resolve(path.dirname(scriptPath), ".."); const decodeHTML = (value) => - value.replaceAll("&", "&").replaceAll(""", '"').replaceAll("'", "'"); + value.replace(/&(#x[\da-f]+|#\d+|amp|quot|apos|lt|gt|nbsp);/gi, (entity, reference) => { + const normalized = reference.toLowerCase(); + if (normalized === "amp") return "&"; + if (normalized === "quot") return '"'; + if (normalized === "apos" || normalized === "#39") return "'"; + if (normalized === "lt") return "<"; + if (normalized === "gt") return ">"; + if (normalized === "nbsp") return "\u00a0"; + const codePoint = normalized.startsWith("#x") + ? Number.parseInt(normalized.slice(2), 16) + : Number.parseInt(normalized.slice(1), 10); + return codePoint > 0 && codePoint <= 0x10ffff && !(codePoint >= 0xd800 && codePoint <= 0xdfff) + ? String.fromCodePoint(codePoint) + : "\ufffd"; + }); const isExternal = (url) => /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(url); @@ -104,12 +118,143 @@ const attributesFrom = (elements) => return [...directReferences, ...sourceSetReferences, ...styleReferences]; }); -const cssURLsFrom = (css) => [ - ...[...css.matchAll(/url\(\s*(['"]?)(.*?)\1\s*\)/gi)].map((match) => decodeHTML(match[2] ?? "")), - ...[...css.matchAll(/@import\s+(?:"([^"]+)"|'([^']+)')/gi)].map((match) => - decodeHTML(match[1] ?? match[2] ?? ""), - ), -]; +const skipCSSWhitespaceAndComments = (css, start) => { + let index = start; + while (index < css.length) { + if (/\s/.test(css[index] ?? "")) { + index += 1; + } else if (css.startsWith("/*", index)) { + const commentEnd = css.indexOf("*/", index + 2); + index = commentEnd === -1 ? css.length : commentEnd + 2; + } else { + break; + } + } + return index; +}; + +const readCSSEscape = (css, start) => { + const escaped = css.slice(start + 1); + const hex = /^[0-9a-f]{1,6}/i.exec(escaped)?.[0]; + if (hex === undefined) { + return { value: escaped[0] ?? "", nextIndex: Math.min(start + 2, css.length) }; + } + const codePoint = Number.parseInt(hex, 16); + let nextIndex = start + 1 + hex.length; + if (/\s/.test(css[nextIndex] ?? "")) nextIndex += 1; + const safeCodePoint = + codePoint === 0 || codePoint > 0x10ffff || (codePoint >= 0xd800 && codePoint <= 0xdfff) + ? 0xfffd + : codePoint; + return { value: String.fromCodePoint(safeCodePoint), nextIndex }; +}; + +const readCSSString = (css, start) => { + const quote = css[start] ?? ""; + let value = ""; + let index = start + 1; + while (index < css.length && css[index] !== quote) { + if (css[index] === "\\") { + const escaped = readCSSEscape(css, index); + value += escaped.value; + index = escaped.nextIndex; + } else { + value += css[index] ?? ""; + index += 1; + } + } + return { value, nextIndex: Math.min(index + 1, css.length) }; +}; + +const readCSSIdentifier = (css, start) => { + let value = ""; + let index = start; + while (index < css.length) { + const character = css[index] ?? ""; + if (character === "\\") { + const escaped = readCSSEscape(css, index); + value += escaped.value; + index = escaped.nextIndex; + } else if (/[a-z0-9_-]/i.test(character) || character.charCodeAt(0) >= 0x80) { + value += character; + index += 1; + } else { + break; + } + } + return { value, nextIndex: index }; +}; + +const readCSSURL = (css, start) => { + const contentStart = skipCSSWhitespaceAndComments(css, start); + const quote = css[contentStart]; + if (quote === '"' || quote === "'") { + const parsed = readCSSString(css, contentStart); + const close = skipCSSWhitespaceAndComments(css, parsed.nextIndex); + return css[close] === ")" ? { ...parsed, nextIndex: close + 1 } : null; + } + + let value = ""; + let index = contentStart; + while (index < css.length && css[index] !== ")") { + if (css[index] === "\\") { + const escaped = readCSSEscape(css, index); + value += escaped.value; + index = escaped.nextIndex; + } else { + value += css[index] ?? ""; + index += 1; + } + } + return css[index] === ")" ? { value: value.trim(), nextIndex: index + 1 } : null; +}; + +const cssURLsFrom = (css) => { + const references = []; + let index = 0; + while (index < css.length) { + if (css.startsWith("/*", index)) { + const commentEnd = css.indexOf("*/", index + 2); + index = commentEnd === -1 ? css.length : commentEnd + 2; + continue; + } + + const character = css[index] ?? ""; + if (character === '"' || character === "'") { + index = readCSSString(css, index).nextIndex; + continue; + } + + if (css.slice(index, index + 7).toLowerCase() === "@import") { + const importValue = skipCSSWhitespaceAndComments(css, index + 7); + if (css[importValue] === '"' || css[importValue] === "'") { + const parsed = readCSSString(css, importValue); + references.push(parsed.value); + index = parsed.nextIndex; + continue; + } + } + + if (/[a-z_-]/i.test(character) || character === "\\") { + const identifier = readCSSIdentifier(css, index); + if ( + identifier.value.toLowerCase() === "url" && + css[identifier.nextIndex] === "(" + ) { + const parsed = readCSSURL(css, identifier.nextIndex + 1); + if (parsed !== null && !parsed.value.startsWith("var(")) { + references.push(parsed.value); + index = parsed.nextIndex; + continue; + } + } + index = Math.max(identifier.nextIndex, index + 1); + } else { + index += 1; + } + } + return references; +}; const fragmentsFrom = (html) => new Set( @@ -119,32 +264,52 @@ const fragmentsFrom = (html) => ]), ); -const normalizeBase = (base) => `/${base.split("/").filter(Boolean).join("/")}/`; +const normalizeBase = (base) => { + const segments = base.split("/").filter(Boolean); + return segments.length === 0 ? "/" : `/${segments.join("/")}/`; +}; + +const decodeFragment = (fragment) => { + try { + return decodeURIComponent(fragment); + } catch { + return fragment; + } +}; const resolveTarget = (source, reference, base) => { const basePath = normalizeBase(base); - const pageURL = `https://docs.invalid/${source.replace(/index\.html$/, "")}`; - const targetURL = new URL(reference, pageURL); - let targetPath = decodeURIComponent(targetURL.pathname); - if (targetPath.startsWith(basePath)) { - targetPath = targetPath.slice(basePath.length); - } else if (reference.startsWith("/")) { - return { _tag: "Failure", reason: `absolute link is outside base path ${basePath}` }; - } else { - targetPath = targetPath.replace(/^\//, ""); + try { + const pageURL = `https://docs.invalid${basePath}${source.replace(/index\.html$/, "")}`; + const targetURL = new URL(reference, pageURL); + const targetPath = decodeURIComponent(targetURL.pathname); + if (!targetPath.startsWith(basePath)) { + return { _tag: "Failure", reason: `link resolves outside base path ${basePath}` }; + } + const relativePath = targetPath.slice(basePath.length); + const file = + relativePath === "" + ? "index.html" + : relativePath.endsWith("/") + ? `${relativePath}index.html` + : relativePath; + return { + _tag: "Success", + file, + fragment: decodeFragment(targetURL.hash.slice(1)), + }; + } catch (error) { + const reason = error instanceof Error ? error.message : String(error); + return { _tag: "Failure", reason: `invalid URL or percent-encoding (${reason})` }; } - const file = - targetPath === "" - ? "index.html" - : targetPath.endsWith("/") - ? `${targetPath}index.html` - : targetPath; - return { _tag: "Success", file, fragment: decodeURIComponent(targetURL.hash.slice(1)) }; }; export const checkDocumentLinks = (documents, base) => { const errors = []; const fragmentCache = new Map(); + if (![...documents.keys()].some((source) => source.endsWith(".html"))) { + return ["No built HTML documents were found."]; + } for (const [source, html] of documents) { const references = source.endsWith(".html") ? attributesFrom(parseElements(html)) diff --git a/scripts/check-doc-links.test.mjs b/scripts/check-doc-links.test.mjs index a94f0d7d..47778fcc 100644 --- a/scripts/check-doc-links.test.mjs +++ b/scripts/check-doc-links.test.mjs @@ -31,7 +31,38 @@ test("rejects absolute site links that omit the deployment base", () => { const pages = new Map([["index.html", 'Fetch']]); assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), [ - "index.html: /apidocs/fetch/: absolute link is outside base path /docs-site/", + "index.html: /apidocs/fetch/: link resolves outside base path /docs-site/", + ]); +}); + +test("supports a root deployment base", () => { + const pages = new Map([ + ["index.html", 'guide'], + ["guide/index.html", "

Guide

"], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/"), []); +}); + +test("rejects relative links that escape the deployment base", () => { + const pages = new Map([ + ["index.html", 'guide'], + ["guide/index.html", "

Guide

"], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs/"), [ + "index.html: ../guide/: link resolves outside base path /docs/", + ]); +}); + +test("does not strip an accidentally duplicated base path from relative URLs", () => { + const pages = new Map([ + ["index.html", 'guide'], + ["guide/index.html", "

Guide

"], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs/"), [ + "index.html: docs/guide/: missing docs/guide/index.html", ]); }); @@ -70,17 +101,55 @@ test("ignores URL-like markup in code and script text", () => { assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); }); +test("reports malformed percent-encoding and rejects an empty build", () => { + const pages = new Map([["index.html", 'bad fragment']]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), [ + "index.html: #%E0%A4%A: missing fragment #%E0%A4%A in index.html", + ]); + assert.deepEqual(checkDocumentLinks(new Map(), "/docs-site/"), [ + "No built HTML documents were found.", + ]); +}); + +test("normalizes numeric HTML entities and literal percent fragments", () => { + const pages = new Map([ + [ + "index.html", + 'accententity textpercent', + ], + ["guide/index.html", '

'], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs/"), []); +}); + test("checks local CSS imports, url() assets, and inline styles", () => { const pages = new Map([ [ "index.html", '
', ], - ["styles/site.css", '@import "../theme.css"; .icon { background: url("../assets/icon.svg"); }'], + [ + "styles/site.css", + String.raw`/* old asset: url("../removed.woff2") */ @import "../theme.css"; .icon { content: "url(../not-an-asset.svg)"; background: url("../assets/icon.svg"); src: u\72l("../assets/escaped.svg"); }`, + ], ["theme.css", "body { color: black; }"], ["assets/inline.svg", ""], ["assets/icon.svg", ""], + ["assets/escaped.svg", ""], ]); assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); }); + +test("reports missing assets from actual CSS url() tokens", () => { + const pages = new Map([ + ["index.html", ''], + ["site.css", String.raw`/* url("/docs-site/comment.svg") */ .x{background:url("/docs-site/missing.svg")}`], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), [ + "site.css: /docs-site/missing.svg: missing missing.svg", + ]); +}); diff --git a/scripts/check-doc-type-links.test.mjs b/scripts/check-doc-type-links.test.mjs index dae1731c..5aef178b 100644 --- a/scripts/check-doc-type-links.test.mjs +++ b/scripts/check-doc-type-links.test.mjs @@ -26,6 +26,29 @@ test("parses public aliases to backing module types, including generic aliases", ); }); +test("ignores comment examples and nested-module aliases when finding public targets", () => { + assert.deepEqual( + parsePublicTypeAliases( + "PublicOwner", + `/** +type example = Internal.real +*/ +type t = Internal.real +module Impl = { +type hidden = Internal.secret +}`, + ), + [ + { + moduleName: "PublicOwner", + typeName: "t", + backingModule: "Internal", + backingType: "real", + }, + ], + ); +}); + test("only links backing types with one public owner", () => { const targets = createPublicTypeTargets([ { From 7bcac558f08cc7a9d73c86885b667b8a07f30cc9 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Fri, 6 Nov 2026 20:38:39 -0500 Subject: [PATCH 6/7] docs: finalize alpha contract and link validation --- CHANGELOG.md | 21 ++--- astro.config.mjs | 12 ++- .../docs/contributing/documentation.mdx | 45 +++++----- docs/content/docs/contributing/releasing.mdx | 1 + docs/content/docs/project-status.mdx | 13 +-- docs/siteConfig.js | 2 + package.json | 4 +- scripts/check-doc-links.mjs | 84 +++++++++++++++---- scripts/check-doc-links.test.mjs | 64 +++++++++++++- 9 files changed, 172 insertions(+), 74 deletions(-) create mode 100644 docs/siteConfig.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 51275686..2be3ba0f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,11 +13,6 @@ versioning and publication. ### Migration notes -These candidate notes are provisional until the binding changes are merged and checked against -their final public signatures. In particular, reconcile the Web Audio typed-buffer contracts and -the empty, nullable, and IndexedDB result changes from #392 before treating this file as the -release changelog. - - WebSocket sends and worker `Request` fetches now preserve their native receiver (`this`) rather than calling receiver-bound methods unbound. - `WebAPI.Request.bytes`, `WebAPI.Response.bytes`, `WebAPI.Blob.bytes`, @@ -28,30 +23,30 @@ release changelog. curried callback. - Web Audio analyser float buffers, `AudioBuffer` channel and copy APIs, and Biquad/IIR filter frequency-response buffers use `Float32Array.t`; analyser byte buffers use `Uint8Array.t`. - DOMMatrix and DOMMatrixReadOnly float32 conversions also use `Float32Array.t`. Use native + `DOMMatrix` and `DOMMatrixReadOnly` Float32 conversions also use `Float32Array.t`. Use native typed-array values rather than ordinary ReScript arrays for these calls. - `Element.closest` returns `Null.t` and is no longer polymorphic. Narrow the result with the relevant public classifier when a more specific HTML interface is required. - WebCrypto signatures now reflect native results: `sign` and `digest` return `ArrayBuffer.t`, `verify` returns `bool`, and `deriveKey` returns `WebAPI.SubtleCrypto.cryptoKey`. Key and - algorithm support types are exposed through `WebAPI.SubtleCrypto`, not the internal - `WebCryptoTypes` module. + algorithm support types are available through `WebAPI.SubtleCrypto`. - Generic `SubtleCrypto.generateKey` and `generateKey2` now return `unknown` because the result depends on the algorithm. Use `generateKeySymmetric` for HMAC keys (`SubtleCrypto.cryptoKey`) or `generateEcdsaKeyPair` for ECDSA pairs (`SubtleCrypto.cryptoKeyPair`). Generic `exportKey` also returns `unknown`; use `exportKeyBinary` for `Raw`, `Pkcs8`, or `Spki` formats (`ArrayBuffer.t`), and `exportKeyJwk` for `Jwk` format (`JSON.t`). - DOMMatrix float32-array conversions use `Float32Array.t`. - `DOMTree.element` no longer exposes a generic `className`. Specific HTML interfaces retain a string `className`; `SVGElement.t.className` is `SVGElement.svgAnimatedString` with `baseVal` and `animVal`. - `CustomElementRegistry.define` takes a `DOM.customElementConstructor`; it does not take an element instance. -- The request destination, referrer-policy, and media `canPlayType` result types now include - `Empty` for native empty-string values. `HTMLCanvasElement.toBlob` and an `ifAvailable` Web - Locks callback use nullable results; `IDBFactory.deleteDatabase` has an operation-specific - `unit` result rather than a database result. +- Request destination, referrer-policy, and media `canPlayType` result types now include `Empty` + for native empty-string values. `HTMLCanvasElement.toBlob` callbacks receive a + `Null.t` because the browser can return null (including for a zero-sized + canvas). An `ifAvailable` Web Locks callback receives a nullable lock when the lock is + unavailable. `IDBFactory.deleteDatabase` has an operation-specific `unit` result because the + browser's successful result is `undefined`, not a database. - `WebAPI.Notification.t` no longer contains a static permission field. Read that value through `WebAPI.Notification.permission`. diff --git a/astro.config.mjs b/astro.config.mjs index 27298e3d..e1cab123 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -2,10 +2,9 @@ import { readFileSync } from "node:fs"; import { defineConfig } from "astro/config"; import starlight from "@astrojs/starlight"; import { apiModules } from "./docs/utils"; +import { docsBase, docsSite } from "./docs/siteConfig.js"; -const rescriptTM = JSON.parse( - readFileSync("./docs/assets/rescript.tmLanguage.json", "utf-8"), -); +const rescriptTM = JSON.parse(readFileSync("./docs/assets/rescript.tmLanguage.json", "utf-8")); const apiSidebarItems = apiModules.map(({ moduleName, link }) => ({ label: moduleName, @@ -15,8 +14,8 @@ const apiSidebarItems = apiModules.map(({ moduleName, link }) => ({ export default defineConfig({ srcDir: "docs", publicDir: "docs/public", - site: "https://rescript-lang.github.io", - base: "experimental-rescript-webapi", + site: docsSite, + base: docsBase, integrations: [ starlight({ title: "Experimental WebAPI", @@ -31,8 +30,7 @@ export default defineConfig({ }, ], editLink: { - baseUrl: - "https://github.com/rescript-lang/experimental-rescript-webapi/edit/main/", + baseUrl: "https://github.com/rescript-lang/experimental-rescript-webapi/edit/main/", }, sidebar: [ { diff --git a/docs/content/docs/contributing/documentation.mdx b/docs/content/docs/contributing/documentation.mdx index 44333490..9b39655e 100644 --- a/docs/content/docs/contributing/documentation.mdx +++ b/docs/content/docs/contributing/documentation.mdx @@ -1,20 +1,12 @@ --- title: "Documentation" -description: Learn more about the relevance of adding documentation to @rescript/webapi. +description: Learn how to document public @rescript/webapi bindings. slug: "07-documentation" --- -After the bindings are generated, all you got was a link to the MDN documentation. -While again, not that bad, it can be improved by adding the core of the binding to the documentation. -And explain how it is supposed to be used. - -## Importance - -One could wonder how important documentation is in this case? -A link to MDN is enough, right? Well, not always. -When a method has multiple overloads, it can be hard to understand which one to use. -By adding an example usage, the user can see easily see which one to use. -It keeps the user inside the IDE and avoids context switching. +Bindings may begin as generated output, but the checked-in source and its ReScript comments are +maintained by hand. Generated API pages use those comments to explain how a public binding works +and link to the corresponding browser API. ## Structure @@ -22,10 +14,10 @@ The documentation for each binding should roughly follow this structure: - signature - short description focused on the ReScript side of the API -- example usage -- link to the MDN documentation +- example usage in the public `WebAPI` namespace +- links to MDN and any relevant source type -For example, the `window.fetch` function could be documented as: +For example, the public `WebAPI.Fetch.fetch` function can be documented in its source module: ````ReScript /* @@ -35,19 +27,16 @@ Starts the process of fetching a resource from the network, returning a promise that is fulfilled once the response is available. ```res -window->Window.fetch("https://rescript-lang.org") +let response = await WebAPI.Fetch.fetch("https://rescript-lang.org") ``` [Read more on MDN](https://developer.mozilla.org/docs/Web/API/Window/fetch) */ -@send -external fetch: (window, string, ~init: requestInit=?) - => promise = "fetch" +external fetch: (string, ~init: Request.requestInit=?) => promise = "fetch" ```` -Constructor overloads should use the same structure, but keep the description concise and let the signature and example show the input shape. -Use backticks for specific types and names. -If a constructor variant has a single source input, show it as a direct unlabeled argument in both the signature and example. +Keep overload examples specific to their argument shape. Constructor documentation uses +semantic names such as `make`, `fromString`, and `fromArray` rather than numbered overloads: ````ReScript /* @@ -56,13 +45,19 @@ If a constructor variant has a single source input, show it as a direct unlabele Creates a new `DOMMatrix` from an array of matrix component values. ```res -let matrix = DOMMatrix.fromArray([1., 0., 0., 1., 0., 0.]) +let matrix = WebAPI.DOMMatrix.fromArray([1., 0., 0., 1., 0., 0.]) ``` [Read more on MDN](https://developer.mozilla.org/docs/Web/API/DOMMatrix) */ @new -external fromArray: array => domMatrix = "DOMMatrix" +external fromArray: array => DOM.domMatrix = "DOMMatrix" ```` -For now, these examples stay compile-coverage oriented. Runtime verification of constructor behavior will be handled later via Vitest and happy-dom. +When an input is a public alias, link to its owner module. Do not link an internal type holder +unless it has a valid public owner target; prefer public aliases or inferred types. See the +[API module structure](../02-api-module-structure/) guide for the current module layout. + +Keep examples that are not runtime-safe as compile-only fixtures. Put browser-dependent behavior +checks in the `browserRuntime` fixture category and run them with the native Chromium suite. See +the [testing guide](../06-testing/) for fixture categories and commands. diff --git a/docs/content/docs/contributing/releasing.mdx b/docs/content/docs/contributing/releasing.mdx index 9c34ba87..fcf528ff 100644 --- a/docs/content/docs/contributing/releasing.mdx +++ b/docs/content/docs/contributing/releasing.mdx @@ -18,6 +18,7 @@ Use the repository's release validation before changing that version: npm run format:check npm run build npm run check:features +npm run test:negative npm test npm run test:browser npm run build:docs diff --git a/docs/content/docs/project-status.mdx b/docs/content/docs/project-status.mdx index b6ae6244..b7e97396 100644 --- a/docs/content/docs/project-status.mdx +++ b/docs/content/docs/project-status.mdx @@ -33,15 +33,16 @@ implementation modules and internal type-holder modules are not part of that pub Bindings are maintained and corrected by hand as well as generated; the generator is not a reproducible source of truth for every checked-in binding. -Some bindings still carry private-record TODOs; a blanket private-record conversion is deferred +Approximately 80 bindings still carry private-record TODOs; a blanket conversion is deferred because the current compiler reports mutation warning 111. Generator reproducibility is also follow-up work, not an alpha release gate. -CI uses Node 26 for ReScript compilation and Node runtime fixtures. Its Chromium suite currently -executes a compiled URL fixture; this is a smoke check, not broad browser-runtime coverage of the -bindings. There is no Firefox, WebKit, or browser-version matrix. API availability, -secure-context requirements, permissions, and user activation still depend on the browser and -execution context. +CI uses Node 26 for ReScript compilation and Node runtime fixtures. Its Playwright suite runs +compiled Chromium regressions for the confirmed alpha contracts, including typed byte/audio +buffers, DOM constructors and narrowing, nullable Canvas/Locks results, IndexedDB deletion, +WebSocket receiver behavior, and worker Fetch. These targeted checks do not cover every binding. +There is no Firefox, WebKit, or browser-version matrix. API availability, secure-context +requirements, permissions, and user activation still depend on the browser and execution context. ## Historical backlog diff --git a/docs/siteConfig.js b/docs/siteConfig.js new file mode 100644 index 00000000..26ceaf5f --- /dev/null +++ b/docs/siteConfig.js @@ -0,0 +1,2 @@ +export const docsSite = "https://rescript-lang.github.io"; +export const docsBase = "experimental-rescript-webapi"; diff --git a/package.json b/package.json index 57013534..ec5e1c4c 100644 --- a/package.json +++ b/package.json @@ -44,8 +44,8 @@ "check:consumer": "node scripts/check-consumer.mjs", "check:release": "node --test scripts/validate-release-artifact.test.mjs && node scripts/check-release.mjs", "check:docs-links": "node --test scripts/check-doc-links.test.mjs scripts/check-doc-type-links.test.mjs && node scripts/check-doc-links.mjs", - "format": "rescript format && oxfmt ./tests/index.js ./tests/fixtures.json ./tests/browser/native-api.spec.mjs ./tests/browser/describe-error.mjs ./tests/browser/server.mjs ./scripts/negative-fixtures.json ./scripts/validate-release-artifact.mjs ./scripts/validate-release-artifact.test.mjs ./playwright.config.mjs ./scripts/check-consumer.mjs ./scripts/check-release.mjs ./scripts/check-features.mjs ./scripts/check-features.test.mjs ./scripts/check-doc-links.mjs ./scripts/check-doc-links.test.mjs ./scripts/check-doc-type-links.test.mjs ./package.json ./README.md ./CHANGELOG.md ./docs && prettier --write .github/workflows/*.yml ./docs/pages", - "format:check": "rescript format --check && oxfmt ./tests/index.js ./tests/fixtures.json ./tests/browser/native-api.spec.mjs ./tests/browser/describe-error.mjs ./tests/browser/server.mjs ./scripts/negative-fixtures.json ./scripts/validate-release-artifact.mjs ./scripts/validate-release-artifact.test.mjs ./playwright.config.mjs ./scripts/check-consumer.mjs ./scripts/check-release.mjs ./scripts/check-features.mjs ./scripts/check-features.test.mjs ./scripts/check-doc-links.mjs ./scripts/check-doc-links.test.mjs ./scripts/check-doc-type-links.test.mjs ./package.json ./README.md ./CHANGELOG.md ./docs --check && prettier --check .github/workflows/*.yml ./docs/pages", + "format": "rescript format && oxfmt ./tests/index.js ./tests/fixtures.json ./tests/browser/native-api.spec.mjs ./tests/browser/describe-error.mjs ./tests/browser/server.mjs ./scripts/negative-fixtures.json ./scripts/validate-release-artifact.mjs ./scripts/validate-release-artifact.test.mjs ./playwright.config.mjs ./astro.config.mjs ./scripts/check-consumer.mjs ./scripts/check-release.mjs ./scripts/check-features.mjs ./scripts/check-features.test.mjs ./scripts/check-doc-links.mjs ./scripts/check-doc-links.test.mjs ./scripts/check-doc-type-links.test.mjs ./package.json ./README.md ./CHANGELOG.md ./docs && prettier --write .github/workflows/*.yml ./docs/pages", + "format:check": "rescript format --check && oxfmt ./tests/index.js ./tests/fixtures.json ./tests/browser/native-api.spec.mjs ./tests/browser/describe-error.mjs ./tests/browser/server.mjs ./scripts/negative-fixtures.json ./scripts/validate-release-artifact.mjs ./scripts/validate-release-artifact.test.mjs ./playwright.config.mjs ./astro.config.mjs ./scripts/check-consumer.mjs ./scripts/check-release.mjs ./scripts/check-features.mjs ./scripts/check-features.test.mjs ./scripts/check-doc-links.mjs ./scripts/check-doc-links.test.mjs ./scripts/check-doc-type-links.test.mjs ./package.json ./README.md ./CHANGELOG.md ./docs --check && prettier --check .github/workflows/*.yml ./docs/pages", "docs": "astro dev", "prebuild:docs": "node docs/llm.js", "build:docs": "astro build" diff --git a/scripts/check-doc-links.mjs b/scripts/check-doc-links.mjs index 82a5dfc7..5cdfd061 100644 --- a/scripts/check-doc-links.mjs +++ b/scripts/check-doc-links.mjs @@ -1,6 +1,7 @@ import { readFileSync, readdirSync } from "node:fs"; import path from "node:path"; import { fileURLToPath } from "node:url"; +import { docsBase, docsSite } from "../docs/siteConfig.js"; const scriptPath = fileURLToPath(import.meta.url); const repoRoot = path.resolve(path.dirname(scriptPath), ".."); @@ -22,7 +23,14 @@ const decodeHTML = (value) => : "\ufffd"; }); -const isExternal = (url) => /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(url); +const isExternal = (url, siteOrigin) => { + if (!/^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(url)) return false; + try { + return new URL(url, siteOrigin).origin !== siteOrigin; + } catch { + return true; + } +}; const parseAttributes = (text) => { const attributes = new Map(); @@ -101,16 +109,39 @@ const parseElements = (html) => { return elements; }; +const sourceSetReferencesFrom = (sourceSet) => { + const references = []; + let index = 0; + while (index < sourceSet.length) { + while (sourceSet[index] === "," || /\s/.test(sourceSet[index] ?? "")) index += 1; + if (index >= sourceSet.length) break; + + const isDataURL = sourceSet.slice(index, index + 5).toLowerCase() === "data:"; + let url = ""; + while ( + index < sourceSet.length && + !/\s/.test(sourceSet[index] ?? "") && + (isDataURL || sourceSet[index] !== ",") + ) { + url += sourceSet[index] ?? ""; + index += 1; + } + if (url !== "") references.push(url); + while (index < sourceSet.length && sourceSet[index] !== ",") index += 1; + if (sourceSet[index] === ",") index += 1; + } + return references; +}; + const attributesFrom = (elements) => - elements.flatMap(({ attributes }) => { + elements.flatMap(({ name, attributes }) => { + // Canonical URLs describe a deployment's preferred host/path, not navigation or assets. + if (name === "link" && attributes.get("rel")?.split(/\s+/).includes("canonical")) return []; const directReferences = ["href", "src", "poster", "action", "data", "xlink:href"] .map((name) => attributes.get(name)) .filter((value) => value !== undefined); const sourceSet = attributes.get("srcset") ?? ""; - const sourceSetReferences = sourceSet - .split(",") - .map((candidate) => candidate.trim().split(/\s+/, 1)[0]) - .filter(Boolean); + const sourceSetReferences = sourceSetReferencesFrom(sourceSet); const styleReferences = [ ...cssURLsFrom(attributes.get("style") ?? ""), ...cssURLsFrom(attributes.get("style-text") ?? ""), @@ -197,7 +228,10 @@ const readCSSURL = (css, start) => { let value = ""; let index = contentStart; while (index < css.length && css[index] !== ")") { - if (css[index] === "\\") { + if (css.startsWith("/*", index)) { + const commentEnd = css.indexOf("*/", index + 2); + index = commentEnd === -1 ? css.length : commentEnd + 2; + } else if (css[index] === "\\") { const escaped = readCSSEscape(css, index); value += escaped.value; index = escaped.nextIndex; @@ -237,12 +271,9 @@ const cssURLsFrom = (css) => { if (/[a-z_-]/i.test(character) || character === "\\") { const identifier = readCSSIdentifier(css, index); - if ( - identifier.value.toLowerCase() === "url" && - css[identifier.nextIndex] === "(" - ) { + if (identifier.value.toLowerCase() === "url" && css[identifier.nextIndex] === "(") { const parsed = readCSSURL(css, identifier.nextIndex + 1); - if (parsed !== null && !parsed.value.startsWith("var(")) { + if (parsed !== null && !parsed.value.toLowerCase().startsWith("var(")) { references.push(parsed.value); index = parsed.nextIndex; continue; @@ -304,7 +335,7 @@ const resolveTarget = (source, reference, base) => { } }; -export const checkDocumentLinks = (documents, base) => { +export const checkDocumentLinks = (documents, base, siteOrigin = "https://docs.invalid") => { const errors = []; const fragmentCache = new Map(); if (![...documents.keys()].some((source) => source.endsWith(".html"))) { @@ -317,7 +348,7 @@ export const checkDocumentLinks = (documents, base) => { ? cssURLsFrom(html) : []; for (const reference of references) { - if (reference === "" || isExternal(reference)) continue; + if (reference === "" || isExternal(reference, siteOrigin)) continue; const target = resolveTarget(source, reference, base); if (target._tag === "Failure") { errors.push(`${source}: ${reference}: ${target.reason}`); @@ -333,7 +364,7 @@ export const checkDocumentLinks = (documents, base) => { errors.push(`${source}: ${reference}: missing ${target.file}`); continue; } - if (target.fragment && routeFile.endsWith(".html")) { + if (target.fragment && /\.(?:html|svg)$/i.test(routeFile)) { const targetFragments = fragmentCache.get(routeFile) ?? fragmentsFrom(targetHTML); fragmentCache.set(routeFile, targetFragments); if (targetFragments.has(target.fragment)) continue; @@ -352,19 +383,36 @@ const filesIn = (directory, relative = "") => return entry.isDirectory() ? filesIn(directory, entryPath) : [entryPath]; }); +export const requiredDocPages = [ + "index.html", + "apidocs/index.html", + "examples/index.html", + "font-attribution/index.html", +]; + +export const missingRequiredDocPages = (documents) => + requiredDocPages.filter((page) => !documents.has(page)); + if (process.argv[1] !== undefined && path.resolve(process.argv[1]) === scriptPath) { const outputDirectory = path.resolve(process.argv[2] ?? "dist"); - const base = process.argv[3] ?? "/experimental-rescript-webapi/"; + const siteURL = new URL(docsBase, docsSite); + const base = process.argv[3] ?? siteURL.pathname; + const siteOrigin = process.argv[4] ?? new URL(docsSite).origin; const documents = new Map( filesIn(outputDirectory).map((file) => [ file, readFileSync(path.join(outputDirectory, file), "utf8"), ]), ); - const errors = checkDocumentLinks(documents, base); + const missingPages = missingRequiredDocPages(documents).map( + (page) => `Missing required docs page ${page}.`, + ); + const errors = [...missingPages, ...checkDocumentLinks(documents, base, siteOrigin)]; if (errors.length > 0) { console.error(errors.join("\n")); process.exit(1); } - console.log(`Validated local links, assets, and fragments in ${documents.size} built files.`); + console.log( + `Validated local links, assets, and fragments in ${documents.size} built files. External and canonical URLs were not checked.`, + ); } diff --git a/scripts/check-doc-links.test.mjs b/scripts/check-doc-links.test.mjs index 47778fcc..0b87628f 100644 --- a/scripts/check-doc-links.test.mjs +++ b/scripts/check-doc-links.test.mjs @@ -1,7 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { checkDocumentLinks } from "./check-doc-links.mjs"; +import { checkDocumentLinks, missingRequiredDocPages } from "./check-doc-links.mjs"; test("validates page, asset, and fragment links under a deployment base", () => { const pages = new Map([ @@ -74,6 +74,48 @@ test("does not treat external links as local routes", () => { assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); }); +test("validates absolute same-site links against built routes", () => { + const pages = new Map([ + ["index.html", 'guide'], + ["guide/index.html", '

Guide

'], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/", "https://docs.invalid"), []); +}); + +test("does not treat canonical metadata as a navigational route", () => { + const pages = new Map([ + [ + "index.html", + 'guide', + ], + ["guide/index.html", "

Guide

"], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/", "https://docs.invalid"), []); +}); + +test("validates SVG fragment targets as well as HTML fragments", () => { + const pages = new Map([ + ["index.html", 'icon'], + ["assets/icons.svg", ''], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); +}); + +test("does not split commas inside data URLs when checking srcset assets", () => { + const pages = new Map([ + [ + "index.html", + '', + ], + ["assets/second.svg", ""], + ]); + + assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), []); +}); + test("finds stale implementation-module links and accepts unlinked internal type text", () => { const pages = new Map([ [ @@ -102,16 +144,29 @@ test("ignores URL-like markup in code and script text", () => { }); test("reports malformed percent-encoding and rejects an empty build", () => { - const pages = new Map([["index.html", 'bad fragment']]); + const pages = new Map([ + ["index.html", 'bad fragmentbad path'], + ]); assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), [ "index.html: #%E0%A4%A: missing fragment #%E0%A4%A in index.html", + "index.html: bad%: invalid URL or percent-encoding (URI malformed)", ]); assert.deepEqual(checkDocumentLinks(new Map(), "/docs-site/"), [ "No built HTML documents were found.", ]); }); +test("requires the docs home, API index, examples, and font-attribution pages", () => { + const artifact = new Map([ + ["index.html", ""], + ["apidocs/index.html", ""], + ["examples/index.html", ""], + ]); + + assert.deepEqual(missingRequiredDocPages(artifact), ["font-attribution/index.html"]); +}); + test("normalizes numeric HTML entities and literal percent fragments", () => { const pages = new Map([ [ @@ -146,7 +201,10 @@ test("checks local CSS imports, url() assets, and inline styles", () => { test("reports missing assets from actual CSS url() tokens", () => { const pages = new Map([ ["index.html", ''], - ["site.css", String.raw`/* url("/docs-site/comment.svg") */ .x{background:url("/docs-site/missing.svg")}`], + [ + "site.css", + String.raw`/* url("/docs-site/comment.svg") */ .x{background:url("/docs-site/missing.svg")}`, + ], ]); assert.deepEqual(checkDocumentLinks(pages, "/docs-site/"), [ From 0b65debcc0a804be5967c673c7e5bd5ed47bf621 Mon Sep 17 00:00:00 2001 From: Josh Vlk Date: Fri, 6 Nov 2026 20:39:59 -0500 Subject: [PATCH 7/7] docs: clarify release artifact transfer path --- docs/content/docs/contributing/releasing.mdx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/contributing/releasing.mdx b/docs/content/docs/contributing/releasing.mdx index fcf528ff..c6521539 100644 --- a/docs/content/docs/contributing/releasing.mdx +++ b/docs/content/docs/contributing/releasing.mdx @@ -41,9 +41,10 @@ change; it rejects an already-published version and checks the expected alpha me The Release workflow's unprivileged `validate` job checks and packs the candidate once. It runs `npm run check:consumer` and a publish dry-run against that exact `.tgz`; the PR and a manual -`workflow_dispatch` with `dry_run: true` use this validation path. The workflow does not expose -that runner-local archive for a later local download. If reproducing the artifact checks locally, -pack once and pass the same path to both commands: +`workflow_dispatch` with `dry_run: true` use this validation path. The archive is not exposed for +a later local download on those paths; a real main-only publication transfers the same artifact +internally from `validate` to `publish`. If reproducing the artifact checks locally, pack once +and pass the same path to both commands: ```shell mkdir -p .release