GeezerVisonThe World Wide Web, restored to 800 pixels of glory. | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Home|About|Gaming cabinet|Archives|How it works|Starter sites | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
GeezerVison is showing github.com — build: validate the published package with publint and are-the-types-wrong by JosunLP · Pull Request #236 · bQuery/bQuery Conversation
|
| Finding | Status |
|---|---|
23 × types ordered after import |
Fixed in #234 (confirmed clean here) |
315 × InternalResolutionError — extensionless imports in .d.ts |
New. Fixed in this PR |
repository.url not a full git URL |
Fixed here |
node10 resolution failures, CJS-resolves-to-ESM, false-esm |
Accepted policy, ignored with reasons |
Was stacked on #234, now targets
dev. #234 has merged, so this is a plaindevPR again.devis merged in.
package.json: publint + @arethetypeswrong/cli dev deps; check:publint, check:attw, check:publish scripts; check:publish added to prepublishOnly; repository.url → git+https://….scripts/postbuild-types.mjs, run from build:types. Rewrites extensionless relative specifiers in emitted declarations to explicit .js, resolving each against what is actually in dist/ and leaving anything unresolvable alone. Rewrote 628 specifiers across 211 of 284 files..github/workflows/test.yml: new package job..github/workflows/npm-publish.yml: check:publish in the build job and before npm publish.tests/postbuild-types.test.ts — 12 tests over the specifier resolver and the rewriter.docs/contributing/release-process.md: a check:publish section including the ignored-rules table with reasons.bun run lint — bunx eslint . cleanbun run build — rebuilt from cleanbun test — 3339 pass, 1 skip, 0 failbun run check:publish exits 0; bun run check, format:check, lint:types, test:types passThe declaration fix, measured against a real consumer (moduleResolution: nodenext, skipLibCheck: false):
before: dist/index.d.ts(10,15): error TS2834: Relative import paths need explicit
file extensions ... (×12 shown, tsc exit 2)
after: tsc exit 0
attw problem counts on the packed tarball: InternalResolutionError 315 → 0. Runtime re-verified after every change: require('@bquery/bquery') → 638 exports, ESM root and sub-path imports work, require('@bquery/bquery/package.json') resolves.
Why a declaration fix is in a "add tooling" PR. Adding attw and leaving 315 problems in place would mean either a permanently red gate or ignoring internal-resolution-error — which would make the whole exercise ceremonial. The problem is real and reproduces outside attw, so it is fixed.
Why post-process rather than fix the source. The correct fix is .js extensions on relative imports throughout src/, which touches nearly every file. Only the declarations need it — Vite resolves the runtime bundles fine. The rewrite is conservative: it only appends an extension when the target actually exists in dist/.
The .d.cts question is settled, and not the way this PR originally said. An earlier revision of this description claimed a .d.cts twin produced green tools over broken types, and I left false-esm ignored on that basis. That conclusion came from testing a single copied declaration, which does push TS1479 one level down. A full .d.cts tree with every specifier resolved to .cjs type-checks cleanly — 0 errors both with and without skipLibCheck. That tree is what #234 shipped as scripts/postbuild-cts.mjs, so --ignore-rules false-esm is gone from this PR and publint now reports "All good!".
publint runs without --strict. --strict would turn remaining warnings fatal; suppressing them entirely would hide them.
The package job and #233's quality job coexist in test.yml, as do #241's coverage job. The merge of dev here keeps all three.
🤖 Generated with Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…irst
Investigating the reported `exports` map problems turned up a worse one:
`require('@bquery/bquery')` resolved but returned an empty object. The
package is `"type": "module"` and the UMD bundle shipped as
`dist/full.umd.js`, so Node read it as ESM. The UMD wrapper's CommonJS
branch never ran, and `require()` handed back an empty module namespace
instead of the 638 exports — silently, with no error to go on.
Emit the UMD bundle as `dist/full.umd.cjs` so Node reads it as CommonJS,
and point `main` and both `require` conditions at it. The `unpkg` and
`jsdelivr` fields now point at `dist/full.iife.js`, which is the build
meant for script tags; `dist/full.umd.js` is gone, and the README and
`src/full.ts` examples use the IIFE URL.
Also apply the three fixes the issue asks for: `types` now comes first in
all 23 sub-path entries, `./package.json` is exported, and the ESM-only
policy for sub-paths is documented in the getting-started guide with the
three CommonJS workarounds.
`tests/package-exports.test.ts` pins all of it, including that no
`require` condition may point at a bare `.js` file again.
Closes #219
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
…wrong
Add both tools as `check:publint` / `check:attw` / `check:publish`, and run
them from the PR gate, the publish workflow and `prepublishOnly`.
Running them found a third problem beyond the two already known: every
emitted declaration used extensionless relative imports. `tsc` re-emits
specifiers exactly as the source writes them, and this is an ESM package,
so under `moduleResolution: node16`/`nodenext` a consumer with
`skipLibCheck: false` gets a wall of TS2834 — 315 problems by attw's
count. Most projects run `skipLibCheck: true`, which is why it went
unnoticed. `scripts/postbuild-types.mjs` now rewrites the specifiers as
part of `build:types`, taking 315 to 0; a strict `nodenext` consumer with
`skipLibCheck: false` type-checks clean.
Three attw results are ignored on purpose, all following from the
documented ESM-only policy: node10 resolution (predates `exports`),
CJS-resolves-to-ESM on sub-paths, and false-esm on the root. The last one
is a real gap — `require('@bquery/bquery')` works at runtime but its types
are ESM-shaped — and closing it needs a parallel `.d.cts` declaration
tree, not a copied `.d.cts`, which does not typecheck because its
re-exports still point at ESM declarations. publint keeps printing it as a
warning so it stays visible. All of this is recorded in
docs/contributing/release-process.md.
Also fixes publint's one remaining suggestion: `repository.url` is now a
full `git+https://` URL.
Closes #218
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
There was an error while loading. Please reload this page.
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Advanced Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…test `test:types` builds the test tree against tsconfig.component-test.json, whose lib predates ES2021, so `replaceAll` fails with TS2550. Uses split/join instead, matching how the repo already avoids newer built-ins in the test tree. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
The base branch picked up `dev`, which added `check:theme-links` on the same lines this branch extends. Keep both: the `postbuild-types` step on `build:types`, `check:theme-links` in the `check` chain, and this branch's publint/attw scripts. `check:publish` still passes. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Code review of #236 (publint + attw gate, declaration-extension rewrite).
I verified the change end to end locally against the built dist/: bun run check:publint exits 0 (one deliberate false-esm warning), bun run check:attw exits 0, bun test tests/postbuild-types.test.ts 10/10 pass, bun run test:types clean, eslint and prettier --check clean on the touched files. No extensionless relative specifiers remain in any dist/**/*.d.ts statement, the lockfile has both new dev deps, and there is no prepare/prepack script, so attw's internal npm pack inside prepublishOnly cannot recurse. The resolver's file-before-directory preference matches what moduleResolution: Bundler did in the source, and there are no foo.d.ts + foo/index.d.ts collisions in dist/. Good approach overall.
4 findings, all low severity — one confirmed latent bug in the rewriter, one silent-success gap, one gate/doc mismatch, one doc off-by-one.
Generated by Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| const distDir = join(repoRoot, 'dist'); | ||
|
|
||
| /** `from '…'`, `import('…')` and bare `import '…'`, single or double quoted. */ | ||
| const SPECIFIER = /(\bfrom\s*|\bimport\s*\(\s*|\bimport\s+)(['"])(\.{1,2}\/[^'"]*)\2/g; |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
The rewriter is comment-blind — it already edits JSDoc text.
SPECIFIER is run over the raw declaration source, so a relative specifier inside a comment is rewritten exactly like a real import.
This is not hypothetical; it happens in the current build. src/component/types.ts:264 has
* Can be either a plain string or a {@link import('./css').ComponentStyles}
and the emitted dist/component/types.d.ts:200 now reads {@link import('./css.js').ComponentStyles}. (Harmless there — arguably an improvement, since @link import types do resolve.)
The failure mode is @example blocks that show consumer-facing code. A doc comment emitted into dist/ containing import { $ } from './core' becomes './core/index.js', because dist/core/index.d.ts exists — the published example then points at bQuery's internal dist layout, which is wrong for the reader and shows up in IDE hover docs. The repo already has plenty of @example blocks with relative specifiers (src/router/index.ts:25, src/router/router.ts:72, src/i18n/index.ts:55); today none of them happen to collide with a real dist/ path, so this is latent rather than broken.
Cheapest mitigation: only match at statement positions (e.g. require the match to start at the beginning of a line after optional whitespace, which excludes * ... JSDoc continuation lines), or strip/skip comment ranges before rewriting.
Generated by Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| return { fileCount: files.length, filesChanged, specifiersChanged }; | ||
| } | ||
|
|
||
| export async function main() { |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Silent success when zero declarations are found.
main() guards only that dist/ exists. If collectDeclarations() comes back empty, the script still prints the ✓ line and exits 0.
Concrete way to trip it: change build:types to emit into dist/types, or reorder build so a vite build with emptyOutDir runs after build:types. The script then prints ✓ Declaration imports: rewrote 0 specifier(s) across 0 of 0 .d.ts files., bun run build is green locally, and the published declarations silently go back to the extensionless imports this PR exists to fix. Only the separate package CI job would notice.
That also cuts against the rule this PR's own doc restates two sections below for check:full-bundle: "If a module barrel cannot be read or parsed, that is also treated as a failure — silently skipping is no longer allowed."
| export async function main() { | |
| export async function main() { | |
| if (!existsSync(distDir)) { | |
| console.error('✗ No dist/ directory — run the type build first.'); | |
| process.exit(1); | |
| } | |
| const { fileCount, filesChanged, specifiersChanged } = await fixDeclarationExtensions(); | |
| if (fileCount === 0) { | |
| console.error('✗ No .d.ts files under dist/ — the type build emitted nothing to rewrite.'); | |
| process.exit(1); | |
| } | |
Generated by Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| "check:doc-exports": "bun scripts/check-doc-exports.mjs", | ||
| "check:theme-links": "bun scripts/check-theme-links.mjs", | ||
| "check:publint": "publint", | ||
| "check:attw": "attw --pack . --profile esm-only --ignore-rules false-esm", |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
--ignore-rules false-esm is package-wide, not root-only.
attw applies --ignore-rules to every entry point, but the new release doc presents it as an exemption for the root only ("false-esm on the root"). That gap will hide a real regression:
if someone later adds a require condition to a sub-path — say "./core": { "require": "./dist/core.umd.cjs" } — while types still resolves into the ESM .d.ts tree, attw reports FalseESM for @bquery/bquery/core, this flag drops it, check:publish exits 0, and the broken sub-path ships. The maintainer, reading the doc, believes only the root is exempt.
Either split the run (one --entrypoints . invocation with the ignore, one for the rest without it), or reword the false-esm row in docs/contributing/release-process.md to say the ignore is package-wide and that a sub-path FalseESM would be masked too.
Generated by Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
| workflow (#218): | ||
|
|
||
| - **[`publint`](https://publint.dev)** — checks the `exports` map and the | ||
| tarball's contents. The map has 25 entries and is hand-edited on every new |
There was a problem hiding this comment.
The reason will be displayed to describe this comment to others. Learn more.
Off-by-one: the map has 26 entries on this branch, not 25.
$ node -e "console.log(Object.keys(require('./package.json').exports).length)"
26
A hard-coded count in prose will drift again on the next sub-path; consider dropping the number, or fixing it to 26.
Generated by Claude Code
Sorry, something went wrong.
There was an error while loading. Please reload this page.
…ur doc/test gaps Review findings on #219: - **Blocking.** `require` resolved to real CommonJS but its *types* still resolved to the ESM `dist/index.d.ts`, so a `module: node16` CJS consumer failed with TS1479 — reproduced against a real consumer package. `types` now lives inside each condition branch, and `scripts/postbuild-cts.mjs` emits a `.d.cts` tree beside the ESM one during `build:types`. A single copied `.d.cts` is not enough: it pushes the same error onto its own relative imports (24 errors with `skipLibCheck: false`). The whole tree has to exist, with specifiers resolved file-before-directory to the `.cjs` twin — `./utils` is a file in one module and a directory in another, and appending `.cjs` blindly yields TS2307. Measured on a real consumer: 24 errors before, 0 after, with `skipLibCheck` both on and off. The ESM consumer and `require()`'s 638 runtime exports are unchanged. - Documented CJS workaround 1 used top-level `await`, which is a SyntaxError in a `.cjs` file. It is now inside an async function. - Workaround 2 sat directly under the `require('@bquery/bquery/server')` failure, so it read as the fix for it — but the UMD bundle stubs Node built-ins, so `server`/`ssr` APIs throw there. Scoped explicitly. - The README's "Module format" link pointed at `bquery.dev`; the site is `bquery.js.org`. - The regression guard checked the strings in the map but never that the targets exist, so reverting `vite.umd.config.ts`'s `fileName` alone kept all five tests green while `require()` failed with MODULE_NOT_FOUND. It now resolves every target against `dist/`, and asserts `require` types are `.d.cts`. Both new assertions fail against the reverted filename. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
…ewrite Merges the updated base (#219), which now emits a `.d.cts` tree, and builds on it. Review findings on #218: - `--ignore-rules false-esm` was package-wide while the release doc presented it as a root-only exemption, so a FalseESM on a sub-path that later grew a `require` condition would have been masked. With the `.d.cts` tree in place the ignore is no longer needed at all: `attw --profile esm-only` reports `node16 (from CJS): 🟢 (CJS)` for the root and publint prints "All good!", so the flag is removed rather than re-scoped. - The rewriter was comment-blind and already edited JSDoc: `{@link import('./css')}` shipped as `'./css.js'`. Harmless there, but an `@example` showing consumer code would be rewritten to bQuery's internal dist layout and land in IDE hover docs. Comment lines are now skipped — 625 specifiers rewritten instead of 626, the difference being exactly that `{@link}`. - `main()` printed success and exited 0 when no declarations were found, so an `outDir` change or a build reorder would silently ship unrewritten declarations with a green build. That is now a failure, matching the rule the same doc states for `check:full-bundle`. - The doc said the map has 25 entries; it has 26. The count is dropped rather than corrected, since it drifts on every new sub-path. `build:types` runs both post-build steps: `postbuild-types` makes the ESM specifiers explicit, then `postbuild-cts` mirrors the tree. Verified against real consumer packages — 0 errors from both a `module: node16` CJS and an ESM consumer with `skipLibCheck: false`. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
#231 landed on `dev` as a squash, so the `check` chain there gained `check:workflow-branches` while this branch still carried the pre-squash merge — git cannot tell the two are the same content, hence the conflict. Resolved to keep both: `dev`'s full check chain and this branch's `postbuild-cts` step on `build:types`. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
The base picked up `dev`, which gained `check:workflow-branches` from #231's squash merge. Resolved to keep every script: both post-build steps on `build:types`, the full check chain, and this branch's publint/attw gate. `check:publish` still exits 0 with publint reporting "All good!". Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
dev now carries #234 as a squash, so the branch's merge of fix/219-exports-map conflicts with it. Resolved by keeping both sides everywhere they diverge: - package.json: build:types runs postbuild-types.mjs and postbuild-cts.mjs; check:publint/attw/publish sit beside check:package and check:coverage; prepublishOnly runs both. - test.yml: the quality, coverage and package jobs coexist. - npm-publish.yml: check:package then check:publish. - release-process.md: the pre-publish list names both. Co-Authored-By: Claude Opus 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
There was an error while loading. Please reload this page.
…wrong (#236) Co-authored-by: Claude Opus 5 <[email protected]> Closes #218
Both postbuild rewriters skipped comment text by splitting the source into
lines and matching each one on its own. `SPECIFIER` matches `\s*`/`\s+`
between the keyword and the quote, so a wrapped specifier —
export type X = import(
'./css'
).Css;
only matches against the whole text. Per line it matched nothing, so the
extension was never added and the `.d.ts`/`.d.cts` shipped an unresolvable
import with the build still green. A rewrite that fails silently is worse
than one that over-reaches.
The guard now records the offsets of comment lines up front and the match
runs over the whole source, checking each hit's index against those ranges.
Comment text is still skipped, wrapped specifiers are rewritten again.
No multiline specifier is in the current `dist/` output, so nothing shipped
broken — but `postbuild-types.mjs` has carried this since #236 and
`prettier`'s print width decides when it stops being latent.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz
## Summary - Release PR for **1.17.0**, cutting everything on `dev` since `1.16.1` into `main`. - Feature release for `server` and `security`, plus the packaging fixes that make the published tarball resolve correctly for the first time. - **One user-visible break:** `dist/full.umd.js` no longer exists. Details under [Upgrading](#upgrading). > **You asked for 1.7.0.** `v1.7.0` is already tagged and the package is on `1.16.1`, so that number would be a downgrade. Cut as **1.17.0** per your confirmation. ## Motivation `dev` is 14 commits ahead of `main` and carries four new features, one lifecycle change, one reactive fix and the packaging work. Closes the release cycle for #210, #211, #212, #213, #215, #216, #218, #219, #220, #222, #223 and #229 — all twelve issues are closed and their PRs merged. ## What this PR's diff actually contains > **Correction, kept in place.** This section originally read "No source changes. The diff is 8 files plus the new release-notes page." That was true of the release commit alone and has not been true of the PR for some time. Left standing rather than quietly deleted, because the same staleness is what the note below guards against. The PR targets `main`, so GitHub shows the **whole `1.16.1 → 1.17.0` delta: 73 files, +9985/−555** — all of `serveStatic`, `rateLimit`, both sanitizer backends, the reactive scheduler and the packaging work. Review it as a feature release, not as a version bump. Measured against `dev`, the delta is **35 files, +1766/−258**: the version bump, the release notes, and eleven source fixes pushed onto this branch during review that are **not on `dev`**: | Commit | Change | | --- | --- | | `1c6b5fb` | SSR validates tag and attribute names at every serialization site — **this is the CodeQL `js/xss-through-dom` fix** | | `f0c64b5` | `rateLimit` keys on the rightmost (proxy-added) `X-Forwarded-For` hop | | `407a6c5` | `trustProxy` reads one proxy-controlled header instead of whichever turns up | | `9e02000` | `postbuild-cts`/`postbuild-types` keep rewriting specifiers that wrap across lines | | `e668d14` | One text-extraction rule across both sanitizer backends; the HTML-sink path escapes | | `29dbcbf` | `serveStatic` can revalidate a precompressed asset, and ranks encodings by q-value | | `1192a87` | Limiters sharing a `SessionStore` serialize against each other | | `786bd4d` | A trailing slash no longer closes an element HTML will not let close itself; entities decode on the DOM text-only path | | `4d10b5e` | Character references decode by the tokenizer's rules (case, `;`, legacy prefixes, attribute rule) from an HTML 4.01 + ASCII + legacy table; **raises five bundle budgets by ~1.8 kB** | | `4c269a4` | A precompressed sidecar's `ETag`/`Last-Modified` come from the sidecar; its `304` no longer offers ranges | | `0fc9849` | The workflow branch guard fails on unreadable triggers even when it has no branch list | ## Version and metadata - `package.json`: `1.16.1` → `1.17.0`. - `AGENT.md`, `llms.txt`, `.cursorrules`, `.clinerules`, `.github/copilot-instructions.md`: version baseline and a new highlights section each. `bun run check:ai-guidance` enforces these and passes. **Changelog** - `## [Unreleased]` → `## [1.17.0] - 2026-09-21`, section headings and the table of contents renamed to match. - **Three Changed entries added that were missing**, plus a Fixed entry per review-round fix. See the note below. **Docs** - New `docs/release-notes/1.17.md` — the release process requires a page per minor. - Row added to `docs/release-notes/index.md`. ## The changelog gap I had to close first The `Unreleased` block on `dev` covered only #205, #210, #222, #223 and #229. The packaging work had merged with **no changelog entry at all**: | Merged | Missing from the changelog | | --- | --- | | #219 (PR #234) | `dist/full.umd.js` → `.cjs`, CDN fields → `full.iife.js`, `require()` fixed, `types` first, `./package.json` export, `.d.cts` tree | | #218 (PR #236) | Extensionless declaration imports fixed (315 × `TS2834` → 0) | | #220 (PR #235) | JS source maps dropped from the tarball | The first row is this release's only breaking change. A release that ships a 404 for pinned CDN URLs without a changelog line is how users find out the hard way, so those three entries are now written up under `### Changed (1.17.0)`. The remaining merged work (#211, #212, #213, #215, #216, #243, #244) is CI and repo tooling with no effect on consumers, so it stays out of the changelog by design. ## Validation Against `0fc9849`: - [x] `bun run lint` — clean - [x] `bun run build` - [x] `bun test` — **3518 pass, 1 skip, 0 fail** - [x] `npx tsc --noEmit` — clean | Gate | Result | | --- | --- | | `check:ai-guidance` | in sync at 1.17.0 | | `check` (full-bundle, stability, doc-exports, theme-links, workflow-branches) | pass | | `format:check` | pass | | `check:package` | 1.38 MB packed / 5.39 MB unpacked / 1240 files | | `check:size` | 25/25 within budget, largest `./full` at 133.4 kB gzipped | | `check:publish` | publint + attw exit 0 | | `check:coverage` | 89.27% lines / 87.88% functions | | `build:docs` | VitePress builds with the new release-notes page | The entity decoder was also checked outside the test suite: differential-tested against Chromium's `DOMParser` on 60,046 generated text and attribute inputs, with 0 differences outside names the table deliberately leaves out (the previous decoder differed on 9,240). **One unexplained result, recorded rather than rounded off.** An earlier full-suite run reported `3497 pass / 1 fail`. It has not recurred in 23 further full-suite runs or 25 targeted runs of the changed files, every test file passes in isolation, and the output gave only the counts — so I do not have the name of the test that failed. Calling it a flake would be a guess; it is listed here because an unreproduced failure is worth a reviewer knowing about. ## Checklist - [x] I followed the contributing guidelines. - [x] I added or updated tests where behavior changed. - [x] I updated documentation when public behavior or developer workflow changed. - [x] I confirmed the change does not introduce security-sensitive regressions. ## Upgrading Package specifiers (`@bquery/bquery`, `@bquery/bquery/*`) are unaffected and resolve better than before. One path changed: | Before | After | | --- | --- | | `dist/full.umd.js` | `dist/full.umd.cjs` | | `unpkg` / `jsdelivr` → UMD build | → `dist/full.iife.js` | A pinned `unpkg.com/@bquery/bquery@1/dist/full.umd.js` will **404**. `full.iife.js` sets the same `window.bQuery` global. Behaviour changes worth checking: - `app.use()` middleware now runs for requests matching no route. - `batch()` runs effects only after derivations settle, so code relying on observing an intermediate state inside a batch sees the settled state. - SSR drops attribute and tag names outside the validated ASCII patterns, so a non-ASCII name such as `<div é="x">` is no longer serialized. - **In the browser**, `stripTags()` no longer surfaces `<script>`/`<style>` source as text — `stripTags('<script>alert(1)</script>')` is now `''` rather than `'alert(1)'`, matching the server. - **In the browser**, `sanitizeHtml(html, { stripAllTags: true })` now escapes its output. It is branded `SanitizedHtml` and callers assign it to HTML sinks, so returning entity-decoded text raw handed back live markup. `stripTags()` is unaffected and still returns raw plain text. - The sanitizer decodes character references like a browser: `café` is `café` on the server and on the browser's text-only path, names are case-sensitive, and `href="?a=1©=2"` is left intact. HTML5-only names outside the table (e.g. `☆`) stay literal on the string backend. Every entry that sanitizes (`core`, `component`, `security`, `view`, `storybook`, and the umbrella entries) grows by about **1.8 kB gzipped**. ## Notes for reviewers **Why 1.17.0 and not 2.0.0.** There is a defensible major case, and it got slightly stronger during review: the UMD path removal, the `app.use()` lifecycle change, the `batch()` ordering change and now two browser-visible sanitizer changes are all observable. I still read them as minor because no exported API was removed or changed shape, the UMD break is a deep file path rather than a package specifier, the sanitizer changes remove a leak rather than alter a contract, and #242 deliberately kept `'sync'` the default scheduler for all of 1.x precisely to avoid forcing a major. If you weigh the CDN path break higher, say so and I will recut as 2.0.0 — it is a re-run of the same mechanical edits. **The entity table is a size trade-off you may want to overrule.** It is every HTML 4.01 name plus every ASCII-valued and legacy-prefixed name: ~1.3 kB of the ~1.8 kB. The full HTML5 table would be ~7.4 kB; dropping the Greek and math symbols would save ~0.6 kB at the cost of `α`, `≤`, `∞` and friends diverging between backends. Either direction is a small follow-up. **Branch choice, and why the back-merge is mandatory.** This is `release/1.17.0` off `dev` rather than `dev → main` directly, so `dev` stays at `1.16.1` if you defer the release. That was the only cost when this PR opened. It is not any more: the eleven fixes listed above live **only on this branch**. Merging to `main` without back-merging `main` into `dev` would leave `dev` without the CodeQL fix, both rate-limiter hardening commits, the shared-store lock and the sanitizer fixes — and the next release cut from `dev` would reintroduce all of them as regressions. So after merge, either back-merge `main` into `dev`, or say the word and I will redo this as a straight `dev → main` PR, which removes the follow-up entirely. **`main` has one commit `dev` does not** (`fe51cb5`, the squash of #208). The merge is conflict-free — verified with `git merge-tree` — but that squash is why `dev` and `main` diverge rather than fast-forward. Note also that `dev`'s squash-merges carry different SHAs than the same content on this branch, so compare the two by content (`git diff origin/dev origin/release/1.17.0`), not by commit list. **Not yet done, deliberately:** no git tag and no `npm publish`. Tagging `v1.17.0` belongs on the merge commit, and publishing is maintainer-only per the release process. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz --- _Generated by [Claude Code](https://claude.ai/code/session_01Y2dPuUeGeK7mJvar9uLqKz)_ <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added static file serving with caching, range requests, compressed assets, directory indexes, and traversal protection. * Added fixed-window rate limiting with standard headers and `429` responses; a key function is required unless trusted proxy headers are used. * Added DOM-free HTML sanitization with configurable backends. * Added opt-in batched reactive scheduling, scheduler configuration, and synchronous flushing. * Global middleware now runs for unmatched routes. * **Changed** * Renamed the CommonJS bundle from `full.umd.js` to `full.umd.cjs`; browser scripts use `full.iife.js`. * Improved package type declarations and source-map handling. * Reactive updates now settle derived values before effects run. * **Bug Fixes** * Hardened server-rendered HTML by rejecting invalid tag and attribute names. * Improved compressed-asset revalidation and sanitizer handling of HTML entities. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Claude Opus 5 <[email protected]>
There was an error while loading. Please reload this page.
Successfully merging this pull request may close these issues.
There was an error while loading. Please reload this page.