diff --git a/.changeset/better-sqlite3-peer-record-remeasured.md b/.changeset/better-sqlite3-peer-record-remeasured.md new file mode 100644 index 0000000000..52ba213f8a --- /dev/null +++ b/.changeset/better-sqlite3-peer-record-remeasured.md @@ -0,0 +1,44 @@ +--- +"@objectstack/cli": patch +"create-objectstack": patch +--- + +fix(cli): re-measure the `better-auth` > `better-sqlite3` peer record, correct what it credits, and pin the declaration it justifies (#16813) + +A tree containing `@objectstack/cli` reports an unmet peer on every fresh +resolve — `better-auth` peers `better-sqlite3@^12.0.0`, the CLI declares +`^13.0.3` — and the reading that decides what to do about it lived only inside +the scaffold generator's prose. No range moves here and no resolution moves: +what changes is the recorded reason, which had two measured errors in it, plus +a gate that now holds the declaration to that reason. + +**The declaration is correct and stays at `^13`.** Three readings, taken rather +than inherited: + +- The peer is `optional`, and it governs exactly one configuration — a raw + better-sqlite3 `Database` passed to better-auth's `database` option. + `AuthManager.createDatabaseConfig()` returns an ObjectQL adapter factory, or + `undefined` for better-auth's in-memory adapter. Never a `Database`. +- better-auth cannot be incompatible with better-sqlite3 13, because it never + touches it: of the 464 files in the published `better-auth@1.7.2` tarball, + exactly one names better-sqlite3 — `package.json`, the peer declaration + itself — and no code file references it (positive control: `kysely` names 9). + It accepts a `Database` the caller constructs; its own sqlite test path uses + node's built-in `node:sqlite`. +- Pinning back to `^12` is not a neutral alternative. Measured on a bare + project depending on `@objectstack/cli@17.3.0`, it clears the report only by + resolving a **second** native better-sqlite3 (12.11.1 beside 13.0.3) that + nothing loads. The scaffold's existing `allowedVersions` entry clears the + same report with the lockfile byte-identical. + +**Two corrections to the record.** It credited `@objectstack/driver-sql` for +the 13.x copy; on the chain that actually reports +(`cli` → `runtime` → `plugin-auth` → `better-auth`) the binding copy is the +CLI's own `optionalDependencies` entry, which pnpm names in the warning itself. +And it was measured on better-auth 1.7.1 while the family has been pinned at +1.7.2 since — re-measured, with the empirical reading replaced by a structural +one. + +The scaffold's rendered `pnpm-workspace.yaml` comment changes wording in both +producers (`objectstack init` and the `create-objectstack` blank template); the +declarations, the widening entry and the resolution are untouched. diff --git a/packages/cli/src/commands/init.ts b/packages/cli/src/commands/init.ts index bdc57a5b8c..15bb78fee7 100644 --- a/packages/cli/src/commands/init.ts +++ b/packages/cli/src/commands/init.ts @@ -98,16 +98,44 @@ export const SCAFFOLD_BUILT_DEPENDENCIES = ['better-sqlite3', 'esbuild']; * the first thing a newcomer sees, on the one screen where they are deciding * whether this project is solid, and there is nothing they did to cause it. * - * - `better-auth>better-sqlite3` — better-auth 1.7.1 peers `^12.0.0` while the - * tree resolves 13.x (`@objectstack/driver-sql`'s optional dependency). The - * peer is OPTIONAL and governs one configuration only: a raw better-sqlite3 - * `Database` handed to better-auth's `database` option. ObjectStack never - * does that — `AuthManager.createDatabaseConfig()` passes an ObjectQL - * adapter factory. Measured on the configuration the range *does* govern - * (better-auth's own Kysely dialect: migrations, sign-up, sign-in, adapter - * find/update/delete), 1.7.1 behaves identically on better-sqlite3 13.0.3 - * and on 12.11.1. So the upstream range is stale and 13 is right — widening - * is the correct remedy, not pinning our own declaration back to 12. + * - `better-auth>better-sqlite3` — better-auth peers `^12.0.0` while the tree + * resolves 13.x. The peer is OPTIONAL and governs one configuration only: a + * raw better-sqlite3 `Database` handed to better-auth's `database` option. + * ObjectStack never does that — `AuthManager.createDatabaseConfig()` passes + * an ObjectQL adapter factory (or `undefined`, better-auth's own in-memory + * adapter). So the upstream range is stale and 13 is right — widening is + * the correct remedy, not pinning our own declaration back to 12. + * + * RE-MEASURED on the pinned 1.7.2 (#16813). The original reading was taken + * on 1.7.1 (#10326) and was behavioural: better-auth's own Kysely dialect — + * migrations, sign-up, sign-in, adapter find/update/delete — behaves + * identically on better-sqlite3 13.0.3 and on 12.11.1. 1.7.2 makes that + * structural instead of empirical: of the 464 files in the published + * `better-auth@1.7.2` tarball, exactly ONE names better-sqlite3 — + * `package.json`, i.e. the peer declaration itself. Zero code files + * reference it (positive control: `kysely` names 9). better-auth never + * imports the package; it accepts a `Database` the CALLER constructs and + * hands it to Kysely, and its own sqlite test path uses node's built-in + * `node:sqlite` `DatabaseSync`. There is therefore no better-auth call site + * that could touch an API moved between better-sqlite3 12 and 13 — the + * range is a statement about an instance we never supply. + * + * ⚠️ TWO CORRECTIONS to what this entry used to say, both measured: + * • the 13.x copy better-auth binds to is `@objectstack/cli`'s OWN + * `optionalDependencies` entry, NOT `@objectstack/driver-sql`'s. On the + * chain that actually reports (`cli` → `runtime` → `plugin-auth` → + * `better-auth`) the CLI is the ancestor, so its copy is the one pnpm + * resolves the peer against — pnpm names it in the warning itself + * ("found 13.0.3 in @objectstack/cli"). Editing driver-sql alone would + * not move this line. + * • pinning the CLI back to `^12` is not a neutral alternative. Measured + * on a bare project depending on `@objectstack/cli@17.3.0`, it clears + * the report only by installing a SECOND native better-sqlite3 + * (12.11.1 alongside 13.0.3), and the 12 copy is dead weight — the CLI + * loads better-sqlite3 itself (`src/utils/sqlite-occupancy.ts`) and + * knex resolves 13.x through driver-sql regardless. This + * `allowedVersions` entry clears the same report with the resolution + * byte-identical (0 lines of lockfile diff). * * - RETIRED (#3653): `@better-auth/scim>better-call` — the rc.1-era scim pin * peered an exact `better-call@1.3.7` against the host's 1.4.0, and this @@ -474,11 +502,16 @@ export function renderPnpmWorkspaceYaml( '# package states, and that pnpm reports on a first install. None is a', '# real incompatibility:', '#', - '# better-auth peers better-sqlite3 ^12.0.0 while the tree resolves 13.x.', - '# That peer is optional and covers handing better-auth a raw', - '# better-sqlite3 `Database`; ObjectStack hands it an ObjectQL adapter', - '# instead. Measured on the configuration the range does cover,', - '# better-auth 1.7.1 behaves identically on 13.0.3 and on 12.11.1.', + '# better-auth peers better-sqlite3 ^12.0.0 while the tree resolves 13.x', + '# (the copy @objectstack/cli declares for its own sqlite tooling). That', + '# peer is optional and covers handing better-auth a raw better-sqlite3', + '# `Database`; ObjectStack hands it an ObjectQL adapter instead, so', + '# nothing here goes down that path. Re-measured on better-auth 1.7.2:', + '# no file in the published package references better-sqlite3 at all —', + '# it only accepts a Database you construct — so there is no call site', + '# that could depend on what changed between 12 and 13. The upstream', + '# range is stale; pinning back to 12 would just install a second,', + '# unused native copy.', '#', '# @better-auth/scim (held at a release candidate deliberately) peers an', '# exact better-call 1.3.7, while better-auth itself depends on 1.4.0. A', diff --git a/packages/cli/test/better-sqlite3-peer-declaration.pin.test.ts b/packages/cli/test/better-sqlite3-peer-declaration.pin.test.ts new file mode 100644 index 0000000000..4224bf1b08 --- /dev/null +++ b/packages/cli/test/better-sqlite3-peer-declaration.pin.test.ts @@ -0,0 +1,132 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +/** + * **The pin for `@objectstack/cli`'s own `better-sqlite3` declaration**, and + * for its agreement with the scaffold widening that exists because of it + * (#16813). + * + * ## The report this is about + * + * A consumer that installs `@objectstack/cli` sees, whenever pnpm actually + * runs the resolution step: + * + * └─┬ @objectstack/cli 17.3.0 + * └─┬ @objectstack/runtime 17.4.0 + * └─┬ @objectstack/plugin-auth 17.4.0 + * └─┬ better-auth 1.7.2 + * └── ✕ unmet peer better-sqlite3@^12.0.0: found 13.0.3 + * + * ⚠️ It is easy to conclude that this file's job is to stop, and that the fix + * is to move the declaration below back inside `^12`. It is not, and the + * measurement that says so is recorded here so the next reader does not have + * to re-take it: + * + * 1. **The peer is optional and governs a configuration we never use.** + * better-auth's `better-sqlite3` peer is + * `peerDependenciesMeta.better-sqlite3.optional === true`, and it covers + * one thing: a raw better-sqlite3 `Database` handed to better-auth's + * `database` option. `AuthManager.createDatabaseConfig()` returns an + * ObjectQL adapter factory, or `undefined` for better-auth's in-memory + * adapter — never a `Database`. + * 2. **better-auth cannot be incompatible with better-sqlite3 13, because it + * never touches it.** Of the 464 files in the published + * `better-auth@1.7.2` tarball, exactly one names better-sqlite3 — + * `package.json`, i.e. the peer declaration itself. Zero code files + * reference it (positive control: `kysely` names 9). It accepts a + * `Database` the caller constructs; its own sqlite test path uses node's + * built-in `node:sqlite`. + * 3. **Pinning back to `^12` costs a second native module and buys nothing.** + * Measured on a bare project depending on `@objectstack/cli@17.3.0`: + * rewriting only this declaration to `^12.11.1` does clear the report, but + * the resolved tree then carries better-sqlite3 **12.11.1 and 13.0.3**, + * and the 12 copy is never loaded — this package loads better-sqlite3 + * itself (`src/utils/sqlite-occupancy.ts`) and knex resolves 13.x through + * `@objectstack/driver-sql` either way. The scaffold's + * `allowedVersions` entry clears the same report with the resolution + * byte-identical (0 lines of lockfile diff). + * + * ⇒ 13 is the correct declaration and the upstream range is stale. What was + * missing was not a different range but a gate holding the range to the + * reasoning that justifies it, which is what this file is. + * + * ## Why the two assertions are one pin and not two + * + * `SCAFFOLD_ALLOWED_PEER_VERSIONS['better-auth>better-sqlite3']` widens + * better-auth's peer to a **major**, and the only reason that major is right + * is the declaration below. Held apart, either can move alone and stay green: + * a declaration lifted to `^14` leaves the scaffold suppressing a report it no + * longer matches (so the NEXT real skew arrives pre-silenced), and a + * declaration dropped to `^12` leaves a suppression with no skew behind it — + * the exact shape `init.test.ts` refuses for the retired + * `@better-auth/scim>better-call` entry. So the major is read out of the + * manifest and compared, rather than written down twice. + * + * `init.test.ts` pins the widening's literal value and its rendering; this + * file pins what the widening is ABOUT. Neither restates the other. + * + * The manifest read stays inside this package (`test/` → package root), so it + * is not a `check:cross-package-test-inputs` escape and needs no declaration. + */ + +import { describe, it, expect } from 'vitest'; +import { readFileSync } from 'node:fs'; +import { resolve } from 'node:path'; +import { fileURLToPath } from 'node:url'; +// `.js`, not extensionless: this package is `moduleResolution: NodeNext`. +import { SCAFFOLD_ALLOWED_PEER_VERSIONS } from '../src/commands/init.js'; + +const HERE = resolve(fileURLToPath(import.meta.url), '..'); + +// One line on purpose — `check:cross-package-test-inputs` reconstructs reads by +// source scan, and a split `resolve(HERE, …)` is a spelling it does not know. +const MANIFEST = resolve(HERE, '../package.json'); + +const manifest = JSON.parse(readFileSync(MANIFEST, 'utf8')) as { + optionalDependencies?: Record; + dependencies?: Record; +}; + +const WIDENING_KEY = 'better-auth>better-sqlite3'; + +/** The single major a caret/tilde range admits, or null if it admits more. */ +function soleMajor(range: string): string | null { + const m = /^[\^~]?(\d+)\.\d+\.\d+$/.exec(range.trim()); + return m ? m[1] : null; +} + +describe('@objectstack/cli better-sqlite3 declaration (#16813)', () => { + it('declares better-sqlite3 as an OPTIONAL dependency, not a hard one', () => { + // Optional is load-bearing: `objectstack serve` must start on a host where + // the native build failed, falling back to the wasm driver. A hard + // dependency turns that degraded start into a failed install. + expect(manifest.optionalDependencies?.['better-sqlite3']).toBeTypeOf('string'); + expect(manifest.dependencies?.['better-sqlite3']).toBeUndefined(); + }); + + it('keeps the declaration on one major, so the scaffold can widen to it', () => { + const declared = manifest.optionalDependencies?.['better-sqlite3'] ?? ''; + expect( + soleMajor(declared), + `better-sqlite3 is declared "${declared}"; this pin needs a range admitting exactly one major`, + ).not.toBeNull(); + }); + + it('widens better-auth\'s peer to the major this package actually declares', () => { + // ⛔ Not "to 13" — that is `init.test.ts`'s assertion. This one says the + // widening and the declaration are the SAME major, so neither can move + // without the other. + const declared = manifest.optionalDependencies?.['better-sqlite3'] ?? ''; + expect(SCAFFOLD_ALLOWED_PEER_VERSIONS[WIDENING_KEY]).toBe(soleMajor(declared)); + }); + + it('refuses a retreat to better-sqlite3 12 (the report is not the defect)', () => { + // The failure this guards is a well-meant one: reading the unmet-peer + // report as a defect and "fixing" it here. Measured, that clears the line + // only by installing a second native better-sqlite3 that nothing loads — + // see this file's header for the three readings. If better-auth ever + // WIDENS its peer upstream, the right change is to retire the scaffold + // entry, not to move this declaration. + const declared = manifest.optionalDependencies?.['better-sqlite3'] ?? ''; + expect(soleMajor(declared)).not.toBe('12'); + }); +}); diff --git a/packages/cli/test/init.test.ts b/packages/cli/test/init.test.ts index 0151ff3f5b..398755e8e2 100644 --- a/packages/cli/test/init.test.ts +++ b/packages/cli/test/init.test.ts @@ -323,12 +323,16 @@ describe('benign peer-skew declarations (#10326)', () => { const settings = renderPnpmWorkspaceYaml().replace(/^\s*#.*$/gm, ''); it('widens better-auth\'s stale better-sqlite3 peer rather than pinning ours back', () => { - // better-auth 1.7.1 peers `^12.0.0` while the tree resolves 13.x. The peer - // is OPTIONAL and governs one configuration only — a raw better-sqlite3 + // better-auth peers `^12.0.0` while the tree resolves 13.x. The peer is + // OPTIONAL and governs one configuration only — a raw better-sqlite3 // `Database` passed to better-auth's `database` option — which ObjectStack // never does (AuthManager passes an ObjectQL adapter factory). Measured on // the configuration it does govern, 1.7.1 behaves identically on 13.0.3 and - // 12.11.1, so 13 is right and the upstream range is stale. + // 12.11.1; re-measured on the pinned 1.7.2 (#16813), the published package + // does not reference better-sqlite3 in any file, so no call site of ours + // can reach an API that moved 12 -> 13. 13 is right, the range is stale. + // The declaration this widening is ABOUT is pinned in + // `test/better-sqlite3-peer-declaration.pin.test.ts`. expect(SCAFFOLD_ALLOWED_PEER_VERSIONS['better-auth>better-sqlite3']).toBe('13'); expect(settings).toMatch(/^ {4}'better-auth>better-sqlite3': '13'$/m); }); diff --git a/packages/create-objectstack/src/template-consistency.test.ts b/packages/create-objectstack/src/template-consistency.test.ts index fe0df2a334..6b3968ff57 100644 --- a/packages/create-objectstack/src/template-consistency.test.ts +++ b/packages/create-objectstack/src/template-consistency.test.ts @@ -503,11 +503,16 @@ describe('blank template peer-skew declarations (#10326)', () => { const allowed = /^ {2}allowedVersions:\n((?:[ \t]+.*\n?)*)/m.exec(settings)?.[1] ?? ''; it('declares the stale better-auth > better-sqlite3 peer', () => { - // better-auth 1.7.1 peers `^12.0.0`; @objectstack/driver-sql resolves 13.x. + // better-auth peers `^12.0.0`; the tree resolves 13.x — @objectstack/cli's + // own optionalDependency, which is the copy pnpm names in the report on the + // cli -> runtime -> plugin-auth -> better-auth chain (#16813; this comment + // used to credit @objectstack/driver-sql, which is not the binding copy). // The peer is optional and governs only a raw better-sqlite3 `Database` // handed to better-auth's `database` option — a path ObjectStack never // takes (AuthManager passes an ObjectQL adapter factory). Measured on the - // path it does govern, 1.7.1 behaves identically on 13.0.3 and 12.11.1. + // path it does govern, 1.7.1 behaves identically on 13.0.3 and 12.11.1; + // re-measured on 1.7.2, the published package references better-sqlite3 in + // no file at all, so there is no call site to be incompatible. expect( /^\s*'better-auth>better-sqlite3':\s*'13'\s*$/m.test(allowed), "allowedVersions must widen better-auth's stale better-sqlite3 peer to 13", diff --git a/packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml b/packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml index 2bb641d63f..da540ae634 100644 --- a/packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml +++ b/packages/create-objectstack/src/templates/blank/pnpm-workspace.yaml @@ -36,13 +36,18 @@ allowBuilds: # first thing a brand-new project shows you — and none is a real # incompatibility: # -# better-auth peers better-sqlite3 ^12.0.0 while the tree resolves 13.x. That -# peer is optional and covers handing better-auth a raw better-sqlite3 -# `Database`; ObjectStack hands it an ObjectQL adapter instead, so nothing -# here goes down that path. Measured on the configuration the range does -# cover — better-auth's own Kysely dialect, running its migrations, sign-up, -# sign-in and adapter find/update/delete — 1.7.1 behaves identically on -# better-sqlite3 13.0.3 and on 12.11.1. The upstream range is stale. +# better-auth peers better-sqlite3 ^12.0.0 while the tree resolves 13.x — the +# copy @objectstack/cli declares for its own sqlite tooling, and the one pnpm +# names in the report. That peer is optional and covers handing better-auth a +# raw better-sqlite3 `Database`; ObjectStack hands it an ObjectQL adapter +# instead, so nothing here goes down that path. First measured behaviourally +# on 1.7.1 — better-auth's own Kysely dialect, running its migrations, +# sign-up, sign-in and adapter find/update/delete, behaves identically on +# better-sqlite3 13.0.3 and on 12.11.1. Re-measured on the pinned 1.7.2 and +# now structural: no file in the published package references better-sqlite3 +# at all, so no better-auth call site can depend on what moved between 12 and +# 13. The upstream range is stale. Pinning our own declaration back to 12 +# would clear the report only by installing a second, unused native copy. # # (The '@better-auth/scim>better-call' entry that used to sit here retired # with the scim release-candidate pin — stable @better-auth/scim 1.7.1 peers