basalt-ui 2.0.4 → 2.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## [2.0.5](https://github.com/jkrumm/basalt-ui/compare/v2.0.4...v2.0.5) (2026-10-14)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * fail loudly on a missing subpath peer ([74593b1](https://github.com/jkrumm/basalt-ui/commit/74593b10672e9f294032126598765029600ad937))
7
+
1
8
  ## [2.0.4](https://github.com/jkrumm/basalt-ui/compare/v2.0.3...v2.0.4) (2026-10-14)
2
9
 
3
10
 
package/MIGRATING.md CHANGED
@@ -66,18 +66,18 @@ There is one row per SURFACE, not per export, and a surface earns a row once any
66
66
  shipped lefthook preset both changed in 1.30.0 with no row that owned them — which is how a
67
67
  consumer read this index, found nothing, and diffed a KPI row that had silently gone 4-up → 2-up.
68
68
 
69
- | Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | 1.32.0 | 1.32.1 | 1.33.0 | 1.34.0 | 1.34.1 | 1.35.0 | 1.36.0 | 1.36.1 | 1.37.0 | 1.37.1 | 1.38.0 | 1.38.1 | 1.39.0 | 1.40.0 | 1.40.1 | 1.40.2 | 1.40.3 | 1.41.0 | 2.0.0 | 2.0.1 | 2.0.2 | 2.0.3 | 2.0.4 | Unreleased |
70
- | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------- | ------ | ------ | -------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ------ | ------ | ----------------------------------------------- | ------ | ------ | ------ | ------ | ------------------------------------------------------------ | ---------- | --- | --- | --- | --- | --- | --- |
71
- | **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | `CONTAINER_GRID_BREAKPOINTS`; `raw-breakpoint` trusts it (§ 1.33.0) | `CONTAINER_KEYS` (§ 1.34.0) | — | — | — | — | — | — | — | | `appShellInset` 20 → 16 (§ 1.38.1) | — | — | — | — | — | — | — | — | — | — | — | — |
72
- | **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ 1.31.0) | — | `ResponsiveChartHeight` `sm` → key (§ Container recipes) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
73
- | **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ 1.31.0) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 1.32.0) | shell-less `shell/` gutter, `AppShell` breakpoints, `useSizeClass` for overlays (§§ Shell-less, `useSizeClass`) | — | `useBreakpoint` removed (§ 1.34.0) | — | — | `useCoarsePointer()` (§ `useCoarsePointer()`) | — | — | — | — | | content gutter 16, block 8 from `sm` (§ 1.38.1) | — | — | — | — | — | — | — | — | — | — | — | — |
74
- | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | `NumberFilter clearable`; `createSearchStore` `resets` (§ State and controls) | — | — | — | `FormSection`/`FormRow`/`FormGroup`/`FormActions` move here (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
75
- | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | `FormGroup` row no longer stretches (§ Fixes) | — | — | — | — | the layout + `useForm`/`schemaResolver` deprecated, removed 1.37.0 (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
76
- | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | `renderCard` documented (§ `renderCard`) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
77
- | **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
78
- | **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
79
- | **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | the managed block moves `CLAUDE.md` → `AGENTS.md` (§ 1.41.0) | — | — | — | — | — | — |
80
- | **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ 1.32.0) | `FormRow`/`FormGroup` homes in both lanes; `basalt-main` dropped (§ Fixes) | C1 + breakpoint pairs → `error`; `unknown-vx-token` `warn` (§ Guards) | — | — | — | `unknown-vx-token` → `error`; the token factories throw (§ Guards) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
69
+ | Surface | 1.27.0 | 1.28.0 | 1.29.0 | 1.30.0 | 1.30.1 | 1.30.2 | 1.31.0 | 1.32.0 | 1.32.1 | 1.33.0 | 1.34.0 | 1.34.1 | 1.35.0 | 1.36.0 | 1.36.1 | 1.37.0 | 1.37.1 | 1.38.0 | 1.38.1 | 1.39.0 | 1.40.0 | 1.40.1 | 1.40.2 | 1.40.3 | 1.41.0 | 2.0.0 | 2.0.1 | 2.0.2 | 2.0.3 | 2.0.4 | 2.0.5 | Unreleased |
70
+ | ----------------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------ | ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ---------------------------------- | ------ | ------ | -------------------------------------------------------------------------------------------- | ------ | ------ | ------ | ------ | ------ | ----------------------------------------------- | ------ | ------ | ------ | ------ | ------------------------------------------------------------ | ---------- | --- | --- | --- | --- | --- | --- | --- |
71
+ | **tokens** (`./tokens`) | — | `--vx-divider` → `rgba()` on both schemes (§ Shell) | `--vx-space-touch-target` added (§ Additions) | eight spacing numbers (§ Chrome) | — | — | `--vx-hit` replaces the touch vars; `SIZE_CLASSES` (§ 1.31.0) | — | `SIZE_CLASSES`/`CONTAINER_CLASSES` actually exported (§ Fixes) | `CONTAINER_GRID_BREAKPOINTS`; `raw-breakpoint` trusts it (§ 1.33.0) | `CONTAINER_KEYS` (§ 1.34.0) | — | — | — | — | — | — | — | | `appShellInset` 20 → 16 (§ 1.38.1) | — | — | — | — | — | — | — | — | — | — | — | — | — |
72
+ | **charts** (`./charts`) | — | phone tier, `curve`, formatters, `state`, log axis (§ preamble) | `ChartState.empty: string`, `AxisBottomNumeric` (§ Additions) | responsive `height`, `legend.maxRows` (§ Charts) | `height` reaches the kinds; `maxRows` beats the `fill` fit (§ Charts) | — | container-class tier + `height` keys (§ 1.31.0) | — | `ResponsiveChartHeight` `sm` → key (§ Container recipes) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
73
+ | **shell** (`.`) | — | scrollport, region seams, **the brand left the sidebar** (§§ scrollport, brand, Shell) | `PageTitle`, `BasaltDevDock`, `useBreakpoint` (§ Additions) | More popover, aside pill, the responsive gutter, `useBasaltSpacing` (§ Chrome) | — | — | `theme.breakpoints` derived, `useSizeClass`, `data-basalt-host`, `useBreakpoint` on notice (§ 1.31.0) | `useBreakpoint` removal → 1.33.0; `useSizeClass` is shell-only (§ 1.32.0) | shell-less `shell/` gutter, `AppShell` breakpoints, `useSizeClass` for overlays (§§ Shell-less, `useSizeClass`) | — | `useBreakpoint` removed (§ 1.34.0) | — | — | `useCoarsePointer()` (§ `useCoarsePointer()`) | — | — | — | — | | content gutter 16, block 8 from `sm` (§ 1.38.1) | — | — | — | — | — | — | — | — | — | — | — | — | — |
74
+ | **controls** (`./controls`) | `NumberFilter`, `field.number` (§ Controls) | sheet renders panel rows; `PanelRow`, `SliderControl` (§ `basalt-ui/controls`) | — | — | — | — | — | — | — | `NumberFilter clearable`; `createSearchStore` `resets` (§ State and controls) | — | — | — | `FormSection`/`FormRow`/`FormGroup`/`FormActions` move here (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
75
+ | **forms** (`./forms`) | — | `field` → `inputProps`, **`inputProps` drops `key`**, the layout tier (§ `inputProps`) | `field` DELETED — and the `--fix` recipe no longer works (§ Consolidation) | — | — | — | — | — | `FormGroup` row no longer stretches (§ Fixes) | — | — | — | — | the layout + `useForm`/`schemaResolver` deprecated, removed 1.37.0 (§ The form layout moves) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
76
+ | **data** (`./data/table`) | — | root is a `<div>`, row selection, `getItemKey` required (§ `basalt-ui/data`) | `./data` + `./query` + `./connectivity` dropped (§ Consolidation) | — | — | — | — | — | `renderCard` documented (§ `renderCard`) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
77
+ | **dashboard** (`.`) | `StatCard`: `unit`, `breakdown`, delta format (§ `StatCard`) | `StatGroup`, `WidgetGrid`, query-aware `StatCard` — additive | — | **the column law keys on the CONTAINER; `cols={4}` is 2-up at `sm`** (§ Dashboard) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
78
+ | **toolchain** (`./configs/*`) | — | — | the shipped lefthook preset's `check-theme` BROKE — the CLI resolver stopped ascending (§ CLI) | the preset's default bin, and the `root:` recipe (§ Toolchain) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
79
+ | **CLI** (`basalt-ui`) | — | — | **resolver stops relocating; `doctor` loses 6 of 10 checks** (§ CLI) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | the managed block moves `CLAUDE.md` → `AGENTS.md` (§ 1.41.0) | — | — | — | — | — | — | — |
80
+ | **guards** | five rules → `error` (§ Guards) | six new ids at `warn` (§ preamble) | `query-dual-import` retired in 1.29.1 (§ Guards) | four rules → `error` (§ Guards) | — | — | — | `raw-breakpoint`: the container-grid answer, `error` at 1.33.0 (§ 1.32.0) | `FormRow`/`FormGroup` homes in both lanes; `basalt-main` dropped (§ Fixes) | C1 + breakpoint pairs → `error`; `unknown-vx-token` `warn` (§ Guards) | — | — | — | `unknown-vx-token` → `error`; the token factories throw (§ Guards) | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — | — |
81
81
 
82
82
  ---
83
83
 
@@ -992,6 +992,13 @@ optional, subpath-required peers.
992
992
  _Nothing yet. Author the next section under this heading as `## Unreleased — <title>`; the
993
993
  release renames it and opens a new one here._
994
994
 
995
+ ## 2.0.5 — subpath peers
996
+
997
+ - `doctor` gains `subpath-peers`: when your source imports a basalt-ui subpath whose required peers
998
+ are not installed (e.g. `shiki`, `@shikijs/langs`, `@shikijs/themes` for `basalt-ui/content`) it
999
+ fails with the `bun add` line to run. Without them the build succeeded and the chunk threw
1000
+ "Could not resolve" at load. `CodeBlock` also warns once in dev when the highlighter cannot load.
1001
+
995
1002
  ## 2.0.4 — argo table and peer friction
996
1003
 
997
1004
  - `BasaltDataTable` `stickyHeader` inside `BasaltShell` now pins flush under the app chrome. The
@@ -1003,10 +1010,6 @@ release renames it and opens a new one here._
1003
1010
  smaller than its label — that is a column width, not a table default.
1004
1011
  - The column-fold toggle column's header cell carries a visually hidden `Details` label instead of
1005
1012
  an empty `aria-hidden` cell.
1006
- - `doctor` gains `subpath-peers`: when your source imports a basalt-ui subpath whose required peers
1007
- are not installed (e.g. `shiki`, `@shikijs/langs`, `@shikijs/themes` for `basalt-ui/content`) it
1008
- fails with the `bun add` line to run. Without them the build succeeded and the chunk threw
1009
- "Could not resolve" at load. `CodeBlock` also warns once in dev when the highlighter cannot load.
1010
1013
 
1011
1014
  ## 2.0.3 — argo shell and CI friction
1012
1015
 
@@ -40,4 +40,15 @@ export declare function unknownConfigKeysCheck(cwd: string): Check;
40
40
  * that is not installed is not this check's business (optional peers are legitimately absent).
41
41
  */
42
42
  export declare function exactPeersCheck(cwd: string, install: BasaltInstall): Check;
43
+ /**
44
+ * The peers a subpath needs installed: V2-SPEC § 1's subpath-required class plus `./content`'s
45
+ * shiki trio, which a bundler cannot resolve without them (argo friction row 15). The test pins it
46
+ * to `package.json` and `surfaces.ts`.
47
+ */
48
+ export declare const SUBPATH_REQUIRED_PEERS: Readonly<Record<string, readonly string[]>>;
49
+ /**
50
+ * `subpath-peers`: every `basalt-ui/<subpath>` the app imports has its required peers installed. A
51
+ * missing one still builds, then throws "Could not resolve" when the chunk loads.
52
+ */
53
+ export declare function subpathPeersCheck(cwd: string, cfg: BasaltConfig, install: BasaltInstall): Check;
43
54
  //# sourceMappingURL=doctor-upgrade.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"doctor-upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-upgrade.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,MAAM,KAAK,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AAgB9D,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,KAAG,MACc,CAAA;AA+CvD,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAqBpD;AA+CD,4FAA4F;AAC5F,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAyBhF;AA4CD,mGAAmG;AACnG,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,aAAa,GACrB,KAAK,CA8BP;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC9D;AAgGD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAkCjD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAezD;AAaD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAwB1E"}
1
+ {"version":3,"file":"doctor-upgrade.d.ts","sourceRoot":"","sources":["../../src/cli/doctor-upgrade.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAA;AAE5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAE5C,MAAM,MAAM,KAAK,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAA;AA6B9D,wEAAwE;AACxE,eAAO,MAAM,YAAY,GAAI,GAAG,MAAM,KAAG,MACc,CAAA;AA+CvD,uFAAuF;AACvF,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAqBpD;AA+CD,4FAA4F;AAC5F,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAsBhF;AAsDD,mGAAmG;AACnG,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,aAAa,GACrB,KAAK,CAsBP;AAUD;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAkC9D;AAgGD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAkCjD;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,MAAM,GAAG,KAAK,CAczD;AAkBD;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAgB1E;AAED;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAQ9E,CAAA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,GAAG,KAAK,CAsB/F"}
@@ -1,8 +1,8 @@
1
- import { existsSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, writeFileSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import { dirname, relative, resolve } from "node:path";
4
4
  import { BASALT_CONFIG_KEYS, RETIRED_CONFIG_KEYS } from "./config.js";
5
- import { parseJsonc, readIfExists, scannableFiles } from "./index.js";
5
+ import { parseJsonc, readIfExists, readPackageJson, scannableFiles } from "./index.js";
6
6
  const guarded = (id, run) => {
7
7
  try {
8
8
  return run();
@@ -10,6 +10,10 @@ const guarded = (id, run) => {
10
10
  return { skip: `${id}: cannot run (${error.message}).` };
11
11
  }
12
12
  };
13
+ const withInstall = (id, install, reads, run) => {
14
+ const { dir } = install;
15
+ return dir === null ? { skip: `${id}: basalt-ui does not resolve, so its ${reads} cannot be read.` } : guarded(id, () => run(dir));
16
+ };
13
17
  const TSCONFIG_PRESET = /(?:^|\/)basalt-ui\/configs\/tsconfig\.(?:base|react-app)\.json$/;
14
18
  const TSCONFIG_PRESET_ID = "basalt-ui/configs/tsconfig.react-app.json";
15
19
  const TSCONFIG_FIX = `"extends": "${TSCONFIG_PRESET_ID}" (tsconfig.base.json for a package without a DOM)`;
@@ -104,10 +108,7 @@ function basaltRuleIds(file, seen = /* @__PURE__ */ new Set()) {
104
108
  }
105
109
  function retiredLintRulesCheck(cwd, install) {
106
110
  const id = "retired-lint-rules";
107
- const dir = install.dir;
108
- if (dir === null)
109
- return { skip: `${id}: basalt-ui does not resolve, so its plugin cannot be read.` };
110
- return guarded(id, () => {
111
+ return withInstall(id, install, "plugin", (dir) => {
111
112
  const ids = [...new Set(basaltRuleIds(resolve(cwd, ".oxlintrc.json")))];
112
113
  if (ids.length === 0) return null;
113
114
  const pluginPath = resolve(dir, "configs/oxlint-plugin.js");
@@ -146,26 +147,23 @@ function basaltImports(src) {
146
147
  const { code, inLiteral } = tokenize(src);
147
148
  return [...code.matchAll(SUBPATH_IMPORT)].filter((m) => !inLiteral(m.index)).map((m) => [m[2], code.slice(0, m.index + m[0].length).split("\n").length]);
148
149
  }
150
+ const appImports = (cwd, cfg) => scannableFiles(cwd, cfg).filter((f) => /\.(?:[cm]?[jt]sx?|vue|astro)$/.test(f)).flatMap(
151
+ (rel) => basaltImports(readIfExists(resolve(cwd, rel)) ?? "").map(
152
+ ([sub, line]) => [sub, rel, line]
153
+ )
154
+ );
149
155
  function droppedSubpathsCheck(cwd, cfg, install) {
150
156
  const id = "dropped-subpaths";
151
- const dir = install.dir;
152
- if (dir === null)
153
- return { skip: `${id}: basalt-ui does not resolve, so its exports cannot be read.` };
154
- return guarded(id, () => {
155
- const pkg = JSON.parse(readFileSync(resolve(dir, "package.json"), "utf8"));
156
- if (pkg.exports === void 0) return null;
157
- const keys = Object.keys(pkg.exports);
157
+ return withInstall(id, install, "exports", (dir) => {
158
+ const pkg = readPackageJson(dir);
159
+ if (pkg === null) throw new Error(`${dir}/package.json is unreadable`);
160
+ const exportsMap = pkg.exports;
161
+ if (exportsMap === void 0) return null;
162
+ const keys = Object.keys(exportsMap);
158
163
  const has = (sub) => keys.some(
159
164
  (k) => k === `./${sub}` || k.endsWith("*") && `./${sub}`.startsWith(k.slice(0, -1))
160
165
  );
161
- const hits = [];
162
- for (const rel of scannableFiles(cwd, cfg).filter(
163
- (f) => /\.(?:[cm]?[jt]sx?|vue|astro)$/.test(f)
164
- )) {
165
- for (const [sub, line] of basaltImports(readIfExists(resolve(cwd, rel)) ?? "")) {
166
- if (!has(sub)) hits.push(`${rel}:${line} imports basalt-ui/${sub}`);
167
- }
168
- }
166
+ const hits = appImports(cwd, cfg).filter(([sub]) => !has(sub)).map(([sub, rel, line]) => `${rel}:${line} imports basalt-ui/${sub}`);
169
167
  if (hits.length === 0) return null;
170
168
  const more = hits.length > 10 ? ` (+${hits.length - 10} more)` : "";
171
169
  return {
@@ -297,8 +295,7 @@ function appWiringCheck(cwd) {
297
295
  }
298
296
  function unknownConfigKeysCheck(cwd) {
299
297
  return guarded("config-keys", () => {
300
- const raw = readIfExists(resolve(cwd, "package.json"));
301
- const block = raw === null ? null : JSON.parse(raw).basalt;
298
+ const block = readPackageJson(cwd)?.basalt;
302
299
  if (typeof block !== "object" || block === null || Array.isArray(block)) return null;
303
300
  const known = new Set(BASALT_CONFIG_KEYS);
304
301
  const unknown = Object.keys(block).filter((k) => !known.has(k));
@@ -311,23 +308,21 @@ function unknownConfigKeysCheck(cwd) {
311
308
  };
312
309
  });
313
310
  }
311
+ function peerRanges(dir) {
312
+ return readPackageJson(dir)?.peerDependencies ?? {};
313
+ }
314
314
  const EXACT_VERSION = /^\d+\.\d+\.\d+$/;
315
315
  function installedVersion(cwd, pkg) {
316
316
  for (let dir = cwd; ; dir = dirname(dir)) {
317
- const raw = readIfExists(resolve(dir, "node_modules", pkg, "package.json"));
318
- if (raw !== null) return JSON.parse(raw).version ?? null;
317
+ const manifest = readPackageJson(resolve(dir, "node_modules", pkg));
318
+ if (manifest !== null) return manifest.version ?? null;
319
319
  if (dirname(dir) === dir) return null;
320
320
  }
321
321
  }
322
322
  function exactPeersCheck(cwd, install) {
323
323
  const id = "exact-peers";
324
- if (install.dir === null)
325
- return { skip: `${id}: basalt-ui does not resolve, so its peers cannot be read.` };
326
- const dir = install.dir;
327
- return guarded(id, () => {
328
- const raw = readIfExists(resolve(dir, "package.json"));
329
- const peers = (raw === null ? {} : JSON.parse(raw).peerDependencies) ?? {};
330
- const off = Object.entries(peers).filter(([, range]) => EXACT_VERSION.test(range)).flatMap(([pkg, pin]) => {
324
+ return withInstall(id, install, "peers", (dir) => {
325
+ const off = Object.entries(peerRanges(dir)).filter(([, range]) => EXACT_VERSION.test(range)).flatMap(([pkg, pin]) => {
331
326
  const have = installedVersion(cwd, pkg);
332
327
  return have === null || have === pin ? [] : [`${pkg}@${have} (basalt-ui pins ${pin}): \`bun add ${pkg}@${pin}\``];
333
328
  });
@@ -337,12 +332,44 @@ function exactPeersCheck(cwd, install) {
337
332
  };
338
333
  });
339
334
  }
335
+ const SUBPATH_REQUIRED_PEERS = {
336
+ agent: ["ai", "@ai-sdk/provider", "motion"],
337
+ charts: ["@tanstack/charts", "d3-scale"],
338
+ commands: ["@tanstack/react-hotkeys"],
339
+ content: ["react-markdown", "shiki", "@shikijs/langs", "@shikijs/themes"],
340
+ "controls-dates": ["@mantine/dates", "dayjs"],
341
+ forms: ["@mantine/form"],
342
+ table: ["@tanstack/react-table"]
343
+ };
344
+ function subpathPeersCheck(cwd, cfg, install) {
345
+ const id = "subpath-peers";
346
+ return withInstall(id, install, "peers", (dir) => {
347
+ const missing = /* @__PURE__ */ new Map();
348
+ for (const [sub, file] of new Map(
349
+ appImports(cwd, cfg).toReversed().map(([s, f]) => [s, f])
350
+ )) {
351
+ for (const peer of SUBPATH_REQUIRED_PEERS[sub] ?? []) {
352
+ if (installedVersion(cwd, peer) !== null) continue;
353
+ missing.set(peer, [...missing.get(peer) ?? [], `basalt-ui/${sub} (${file})`]);
354
+ }
355
+ }
356
+ if (missing.size === 0) return null;
357
+ const ranges = peerRanges(dir);
358
+ const named = [...missing].map(([peer, by]) => `${peer} for ${by.join(", ")}`);
359
+ const add = [...missing.keys()].map((peer) => ranges[peer] ? `${peer}@${ranges[peer]}` : peer);
360
+ return {
361
+ fail: `${id}: peers a used basalt-ui subpath requires are not installed: ${named.join("; ")}. Fix: \`bun add ${add.join(" ")}\`.`
362
+ };
363
+ });
364
+ }
340
365
  export {
366
+ SUBPATH_REQUIRED_PEERS,
341
367
  appWiringCheck,
342
368
  droppedSubpathsCheck,
343
369
  exactPeersCheck,
344
370
  retiredLintRulesCheck,
345
371
  seedTsconfigExtends,
372
+ subpathPeersCheck,
346
373
  tsconfigBaseCheck,
347
374
  tsconfigFile,
348
375
  unknownConfigKeysCheck
@@ -22,8 +22,8 @@ export type DoctorResult = {
22
22
  * 4. `oxlint-preset`: `.oxlintrc.json` extends the shipped preset AND that path resolves.
23
23
  * 7. `ai-major-parity`: the `ai` major agrees across this package.json's dependency fields
24
24
  * (exempt via `basalt.aiMajorSkewReason`; a stale reason warns).
25
- * 8-13. `tsconfig-base`, `retired-lint-rules`, `dropped-subpaths`, `app-wiring`, `exact-peers`,
26
- * `config-keys` (./doctor-upgrade);
25
+ * 8-14. `tsconfig-base`, `retired-lint-rules`, `dropped-subpaths`, `app-wiring`, `exact-peers`,
26
+ * `subpath-peers`, `config-keys` (./doctor-upgrade);
27
27
  * a check that cannot run is SKIPPED and exits non-zero.
28
28
  *
29
29
  * Warning (non-fatal):
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAyDA,MAAM,MAAM,YAAY,GAAG;IACzB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAA;IACpB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AA8CD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,aAAa,GAAE,MAAsB,EAAE,KAAK,GAAE,MAAM,EAAO,GAAG,MAAM,CAsU1F"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AA0DA,MAAM,MAAM,YAAY,GAAG;IACzB,+CAA+C;IAC/C,YAAY,EAAE,MAAM,CAAA;IACpB,+CAA+C;IAC/C,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;OAKG;IACH,OAAO,EAAE,MAAM,CAAA;CAChB,CAAA;AA2BD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,MAAM,CAAC,aAAa,GAAE,MAAsB,EAAE,KAAK,GAAE,MAAM,EAAO,GAAG,MAAM,CAkU1F"}
@@ -19,6 +19,7 @@ import {
19
19
  readBasaltConfig,
20
20
  readIfExists,
21
21
  readManifest,
22
+ readPackageJson,
22
23
  resolveAppDir,
23
24
  resolveProjectDir,
24
25
  ROUTER_SEED_DESTS,
@@ -33,6 +34,7 @@ import {
33
34
  droppedSubpathsCheck,
34
35
  exactPeersCheck,
35
36
  retiredLintRulesCheck,
37
+ subpathPeersCheck,
36
38
  tsconfigBaseCheck,
37
39
  unknownConfigKeysCheck
38
40
  } from "./doctor-upgrade.js";
@@ -51,27 +53,13 @@ function majorOf(range) {
51
53
  }
52
54
  const AI_MAJOR_FIELDS = ["dependencies", "devDependencies", "peerDependencies"];
53
55
  function aiMajorsAt(dir) {
54
- const raw = readIfExists(resolve(dir, "package.json"));
55
- if (raw === null) return {};
56
- try {
57
- const pkg = JSON.parse(raw);
58
- const out = {};
59
- for (const field of AI_MAJOR_FIELDS) {
60
- const major = majorOf(pkg[field]?.["ai"]);
61
- if (major !== null) out[field] = major;
62
- }
63
- return out;
64
- } catch {
65
- return {};
56
+ const pkg = readPackageJson(dir) ?? {};
57
+ const out = {};
58
+ for (const field of AI_MAJOR_FIELDS) {
59
+ const major = majorOf(pkg[field]?.["ai"]);
60
+ if (major !== null) out[field] = major;
66
61
  }
67
- }
68
- function resolveAiMajorSkewReason(cfg) {
69
- const raw = cfg.aiMajorSkewReason;
70
- if (raw === void 0) return { reason: null, presentButInvalid: false };
71
- if (typeof raw === "string" && raw.trim().length > 0) {
72
- return { reason: raw, presentButInvalid: false };
73
- }
74
- return { reason: null, presentButInvalid: true };
62
+ return out;
75
63
  }
76
64
  function doctor(invocationCwd = process.cwd(), flags = []) {
77
65
  if (conflictingProfileFlags("doctor", flags)) return 1;
@@ -87,21 +75,10 @@ function doctor(invocationCwd = process.cwd(), flags = []) {
87
75
  const lines = [`
88
76
  basalt-ui doctor \u2014 ${cwd}
89
77
  `];
90
- function pass(msg) {
91
- lines.push(` \u2713 ${msg}`);
92
- }
93
- function warn(msg) {
94
- lines.push(` \u26A0 ${msg}`);
95
- result.warnings++;
96
- }
97
- function fail(msg) {
98
- lines.push(` \u2716 ${msg}`);
99
- result.hardFailures++;
100
- }
101
- function skip(msg) {
102
- lines.push(` ? SKIPPED ${msg}`);
103
- result.skipped++;
104
- }
78
+ const pass = (msg) => lines.push(` \u2713 ${msg}`);
79
+ const warn = (msg) => lines.push(` \u26A0 ${msg}`) && result.warnings++;
80
+ const fail = (msg) => lines.push(` \u2716 ${msg}`) && result.hardFailures++;
81
+ const skip = (msg) => lines.push(` ? SKIPPED ${msg}`) && result.skipped++;
105
82
  if (project.relocatedFrom !== null) {
106
83
  lines.push(` \u2192 BASALT_CWD relocated from ${project.relocatedFrom} to ${cwd}.
107
84
  `);
@@ -243,7 +220,9 @@ basalt-ui doctor \u2014 ${cwd}
243
220
  const aiEntries = AI_MAJOR_FIELDS.filter((field) => aiMajors[field] !== void 0).map(
244
221
  (field) => ({ field, major: aiMajors[field] })
245
222
  );
246
- const { reason: aiMajorSkewReason, presentButInvalid: aiMajorSkewReasonInvalid } = resolveAiMajorSkewReason(cfg);
223
+ const rawSkewReason = cfg.aiMajorSkewReason;
224
+ const aiMajorSkewReason = typeof rawSkewReason === "string" && rawSkewReason.trim().length > 0 ? rawSkewReason : null;
225
+ const aiMajorSkewReasonInvalid = aiMajorSkewReason === null && rawSkewReason !== void 0;
247
226
  if (aiEntries.length > 0) {
248
227
  const distinctMajors = new Set(aiEntries.map((entry) => entry.major));
249
228
  if (distinctMajors.size > 1) {
@@ -294,6 +273,10 @@ basalt-ui doctor \u2014 ${cwd}
294
273
  appWiringCheck(appDir)
295
274
  ],
296
275
  ["exact-peers: installed exact-pinned peers match basalt-ui", exactPeersCheck(cwd, install)],
276
+ [
277
+ "subpath-peers: every imported basalt-ui subpath has its required peers installed",
278
+ subpathPeersCheck(cwd, cfg, install)
279
+ ],
297
280
  [
298
281
  "config-keys: the basalt config block sets only keys basalt-ui reads",
299
282
  unknownConfigKeysCheck(cwd)
@@ -162,6 +162,8 @@ export declare const AGENTS_MD = "AGENTS.md";
162
162
  export declare const LEGACY_BLOCK_HOST = "CLAUDE.md";
163
163
  export declare function sha256(text: string): string;
164
164
  export declare function readIfExists(abs: string): string | null;
165
+ /** `<dir>/package.json` parsed as `T`; null when absent or not JSON. */
166
+ export declare function readPackageJson<T>(dir: string): T | null;
165
167
  /** Read a shipped source asset from the package root; null if the sibling hasn't authored it yet. */
166
168
  export declare function readSource(pkgRoot: string, source: string): string | null;
167
169
  /** The framework's own published version, read from the package root package.json. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
package/dist/cli/index.js CHANGED
@@ -91,20 +91,10 @@ function sanitizeSeverity(cfg) {
91
91
  return { ...cfg, severity: kept };
92
92
  }
93
93
  function hasBasaltKey(cwd) {
94
- const raw = readIfExists(resolve(cwd, "package.json"));
95
- try {
96
- return raw !== null && JSON.parse(raw).basalt !== void 0;
97
- } catch {
98
- return false;
99
- }
94
+ return readPackageJson(cwd)?.basalt !== void 0;
100
95
  }
101
96
  function readBasaltConfig(cwd) {
102
- try {
103
- const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf8"));
104
- return sanitizeSeverity(pkg.basalt ?? {});
105
- } catch {
106
- return {};
107
- }
97
+ return sanitizeSeverity(readPackageJson(cwd)?.basalt ?? {});
108
98
  }
109
99
  const SCANNABLE_EXT = /\.(?:tsx?|jsx|[cm][jt]s|astro|vue|css|html?|webmanifest)$/;
110
100
  function walkSourceFiles(dir, out = []) {
@@ -136,14 +126,11 @@ function relativePosix(fromDir, toPath) {
136
126
  const UNRESOLVED_INSTALL = { dir: null, version: null, how: null };
137
127
  function basaltInstallAt(dir) {
138
128
  const installDir = resolve(dir, "node_modules", "basalt-ui");
139
- const raw = readIfExists(resolve(installDir, "package.json"));
140
- if (raw === null) return null;
141
- try {
142
- const pkg = JSON.parse(raw);
143
- return { dir: installDir, version: pkg.version ?? null };
144
- } catch {
145
- return { dir: installDir, version: null };
146
- }
129
+ if (readIfExists(resolve(installDir, "package.json")) === null) return null;
130
+ return {
131
+ dir: installDir,
132
+ version: readPackageJson(installDir)?.version ?? null
133
+ };
147
134
  }
148
135
  function findBasaltInstall(cwd) {
149
136
  const own = basaltInstallAt(cwd);
@@ -244,24 +231,24 @@ function readIfExists(abs) {
244
231
  return null;
245
232
  }
246
233
  }
234
+ function readPackageJson(dir) {
235
+ const raw = readIfExists(resolve(dir, "package.json"));
236
+ try {
237
+ return raw === null ? null : JSON.parse(raw);
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
247
242
  function readSource(pkgRoot, source) {
248
243
  return readIfExists(resolve(pkgRoot, source));
249
244
  }
250
245
  function readPackageName(cwd) {
251
- try {
252
- const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf8"));
253
- if (pkg.name && pkg.name.length > 0) return pkg.name;
254
- } catch {
255
- }
246
+ const name = readPackageJson(cwd)?.name;
247
+ if (name && name.length > 0) return name;
256
248
  return cwd.split("/").filter(Boolean).pop() ?? "app";
257
249
  }
258
250
  function readFrameworkVersion(pkgRoot) {
259
- try {
260
- const pkg = JSON.parse(readFileSync(resolve(pkgRoot, "package.json"), "utf8"));
261
- return pkg.version ?? "0.0.0";
262
- } catch {
263
- return "0.0.0";
264
- }
251
+ return readPackageJson(pkgRoot)?.version ?? "0.0.0";
265
252
  }
266
253
  function basaltBinCommand(install, cwd) {
267
254
  if (install.dir === null) return "bunx basalt-ui";
@@ -397,14 +384,10 @@ function resolveAppDir(root) {
397
384
  };
398
385
  }
399
386
  function hasDependency(cwd, pkgName) {
400
- try {
401
- const pkg = JSON.parse(readFileSync(resolve(cwd, "package.json"), "utf8"));
402
- return Boolean(
403
- pkg.dependencies?.[pkgName] ?? pkg.devDependencies?.[pkgName] ?? pkg.peerDependencies?.[pkgName]
404
- );
405
- } catch {
406
- return false;
407
- }
387
+ const pkg = readPackageJson(cwd);
388
+ return Boolean(
389
+ pkg?.dependencies?.[pkgName] ?? pkg?.devDependencies?.[pkgName] ?? pkg?.peerDependencies?.[pkgName]
390
+ );
408
391
  }
409
392
  const ROOT_PLACEMENT = {
410
393
  isPackageRepoRoot: true,
@@ -694,14 +677,8 @@ function renderContext(cwd) {
694
677
  function patchPackageJson(cwd, mutate) {
695
678
  const abs = resolve(cwd, "package.json");
696
679
  const raw = readIfExists(abs);
697
- if (raw === null) return false;
698
- let pkg;
699
- try {
700
- pkg = JSON.parse(raw);
701
- } catch {
702
- return false;
703
- }
704
- if (!mutate(pkg)) return false;
680
+ const pkg = readPackageJson(cwd);
681
+ if (raw === null || pkg === null || !mutate(pkg)) return false;
705
682
  const indent = /\n([ \t]+)"/.exec(raw)?.[1] ?? " ";
706
683
  writeFileSync(abs, JSON.stringify(pkg, null, indent) + (raw.endsWith("\n") ? "\n" : ""));
707
684
  return true;
@@ -926,14 +903,8 @@ function expandWorkspaceGlobs(cwd, patterns) {
926
903
  return dirs;
927
904
  }
928
905
  function collectWorkspacePackages(cwd) {
929
- const raw = readIfExists(resolve(cwd, "package.json"));
930
- if (raw === null) return { packages: [], hasWorkspacesField: false };
931
- let parsed;
932
- try {
933
- parsed = JSON.parse(raw);
934
- } catch {
935
- return { packages: [], hasWorkspacesField: false };
936
- }
906
+ const parsed = readPackageJson(cwd);
907
+ if (parsed === null) return { packages: [], hasWorkspacesField: false };
937
908
  const patterns = isStringArray(parsed.workspaces) ? parsed.workspaces : parsed.workspaces !== null && typeof parsed.workspaces === "object" && isStringArray(parsed.workspaces.packages) ? parsed.workspaces.packages : null;
938
909
  if (patterns === null) return { packages: [], hasWorkspacesField: false };
939
910
  const rootName = parsed.name && parsed.name.length > 0 ? `${parsed.name} (root)` : "(root)";
@@ -1048,6 +1019,7 @@ export {
1048
1019
  readFrameworkVersion,
1049
1020
  readIfExists,
1050
1021
  readManifest,
1022
+ readPackageJson,
1051
1023
  readSource,
1052
1024
  reconcileRoots,
1053
1025
  relativePosix,
@@ -1 +1 @@
1
- {"version":3,"file":"highlighter.d.ts","sourceRoot":"","sources":["../../src/content/highlighter.ts"],"names":[],"mappings":"AAkGA;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBtF"}
1
+ {"version":3,"file":"highlighter.d.ts","sourceRoot":"","sources":["../../src/content/highlighter.ts"],"names":[],"mappings":"AAkHA;;;;;GAKG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAkBtF"}
@@ -37,6 +37,14 @@ const LANG_ALIASES = {
37
37
  docker: "dockerfile"
38
38
  };
39
39
  let highlighterPromise = null;
40
+ let warnedMissingPeer = false;
41
+ function warnMissingPeer(error) {
42
+ if (warnedMissingPeer || process.env["NODE_ENV"] === "production") return;
43
+ warnedMissingPeer = true;
44
+ console.warn(
45
+ `[basalt] CodeBlock: syntax highlighting is off \u2014 a peer of basalt-ui/content did not load (${error instanceof Error ? error.message : String(error)}). Code renders unhighlighted. Install them with: bun add shiki @shikijs/langs @shikijs/themes`
46
+ );
47
+ }
40
48
  async function loadHighlighter() {
41
49
  const [{ createHighlighterCore }, { createJavaScriptRegexEngine }] = await Promise.all([
42
50
  import("shiki/core"),
@@ -49,7 +57,10 @@ async function loadHighlighter() {
49
57
  });
50
58
  }
51
59
  function getHighlighter() {
52
- highlighterPromise ??= loadHighlighter().catch(() => null);
60
+ highlighterPromise ??= loadHighlighter().catch((error) => {
61
+ warnMissingPeer(error);
62
+ return null;
63
+ });
53
64
  return highlighterPromise;
54
65
  }
55
66
  async function ensureLanguage(highlighter, lang) {
@@ -59,7 +70,8 @@ async function ensureLanguage(highlighter, lang) {
59
70
  try {
60
71
  await highlighter.loadLanguage(loadLang());
61
72
  return true;
62
- } catch {
73
+ } catch (error) {
74
+ warnMissingPeer(error);
63
75
  return false;
64
76
  }
65
77
  }
package/llms.txt CHANGED
@@ -1,4 +1,4 @@
1
- # basalt-ui v2.0.4
1
+ # basalt-ui v2.0.5
2
2
  # Opinionated framework for Mantine-based React apps — theme, app shell, TanStack Charts, toolchain presets
3
3
  #
4
4
  # Entry points: use the exact import specifier below; do not guess.
@@ -13,7 +13,7 @@
13
13
  # A subpath's `Install:` line is its required peers; `OptionalPeers:` are import()-only, install on use.
14
14
 
15
15
  Package: basalt-ui
16
- Version: 2.0.4
16
+ Version: 2.0.5
17
17
 
18
18
  ## basalt-ui
19
19
  Import: import { ... } from 'basalt-ui'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "basalt-ui",
3
- "version": "2.0.4",
3
+ "version": "2.0.5",
4
4
  "description": "Opinionated framework for Mantine-based React apps — theme, app shell, TanStack Charts, toolchain presets",
5
5
  "keywords": [
6
6
  "charts",
@@ -2,13 +2,13 @@
2
2
  * `doctor`'s 2.0 strictness and upgrade checks. Each returns null (pass), `{ fail }` (the problem
3
3
  * plus its fix) or `{ skip }` (the check could not run: a check that cannot run is not a pass).
4
4
  */
5
- import { existsSync, readFileSync, writeFileSync } from 'node:fs'
5
+ import { existsSync, writeFileSync } from 'node:fs'
6
6
  import { createRequire } from 'node:module'
7
7
  import { dirname, relative, resolve } from 'node:path'
8
8
 
9
9
  import { BASALT_CONFIG_KEYS, RETIRED_CONFIG_KEYS } from './config'
10
10
  import type { BasaltConfig } from './config'
11
- import { parseJsonc, readIfExists, scannableFiles } from './index'
11
+ import { parseJsonc, readIfExists, readPackageJson, scannableFiles } from './index'
12
12
  import type { BasaltInstall } from './index'
13
13
 
14
14
  export type Check = { fail: string } | { skip: string } | null
@@ -22,6 +22,19 @@ const guarded = (id: string, run: () => Check): Check => {
22
22
  }
23
23
  }
24
24
 
25
+ /** A check that reads the resolved install: skips when basalt-ui does not resolve. */
26
+ const withInstall = (
27
+ id: string,
28
+ install: BasaltInstall,
29
+ reads: string,
30
+ run: (dir: string) => Check,
31
+ ): Check => {
32
+ const { dir } = install
33
+ return dir === null
34
+ ? { skip: `${id}: basalt-ui does not resolve, so its ${reads} cannot be read.` }
35
+ : guarded(id, () => run(dir))
36
+ }
37
+
25
38
  const TSCONFIG_PRESET = /(?:^|\/)basalt-ui\/configs\/tsconfig\.(?:base|react-app)\.json$/
26
39
  const TSCONFIG_PRESET_ID = 'basalt-ui/configs/tsconfig.react-app.json'
27
40
  const TSCONFIG_FIX = `"extends": "${TSCONFIG_PRESET_ID}" (tsconfig.base.json for a package without a DOM)`
@@ -148,10 +161,7 @@ function basaltRuleIds(file: string, seen = new Set<string>()): string[] {
148
161
  /** `retired-lint-rules`: `basalt/<id>` entries the installed plugin no longer registers. */
149
162
  export function retiredLintRulesCheck(cwd: string, install: BasaltInstall): Check {
150
163
  const id = 'retired-lint-rules'
151
- const dir = install.dir
152
- if (dir === null)
153
- return { skip: `${id}: basalt-ui does not resolve, so its plugin cannot be read.` }
154
- return guarded(id, () => {
164
+ return withInstall(id, install, 'plugin', (dir) => {
155
165
  const ids = [...new Set(basaltRuleIds(resolve(cwd, '.oxlintrc.json')))]
156
166
  if (ids.length === 0) return null
157
167
  const pluginPath = resolve(dir, 'configs/oxlint-plugin.js')
@@ -215,6 +225,16 @@ function basaltImports(src: string): [string, number][] {
215
225
  .map((m) => [m[2] as string, code.slice(0, m.index + m[0].length).split('\n').length])
216
226
  }
217
227
 
228
+ /** `[subpath, file, line]` per `basalt-ui/<subpath>` import in the app's source files. */
229
+ const appImports = (cwd: string, cfg: BasaltConfig): [string, string, number][] =>
230
+ scannableFiles(cwd, cfg)
231
+ .filter((f) => /\.(?:[cm]?[jt]sx?|vue|astro)$/.test(f))
232
+ .flatMap((rel) =>
233
+ basaltImports(readIfExists(resolve(cwd, rel)) ?? '').map(
234
+ ([sub, line]): [string, string, number] => [sub, rel, line],
235
+ ),
236
+ )
237
+
218
238
  /** `dropped-subpaths`: `basalt-ui/<subpath>` imports the installed `exports` map does not have. */
219
239
  export function droppedSubpathsCheck(
220
240
  cwd: string,
@@ -222,28 +242,20 @@ export function droppedSubpathsCheck(
222
242
  install: BasaltInstall,
223
243
  ): Check {
224
244
  const id = 'dropped-subpaths'
225
- const dir = install.dir
226
- if (dir === null)
227
- return { skip: `${id}: basalt-ui does not resolve, so its exports cannot be read.` }
228
- return guarded(id, () => {
229
- const pkg = JSON.parse(readFileSync(resolve(dir, 'package.json'), 'utf8')) as {
230
- exports?: Record<string, unknown>
231
- }
245
+ return withInstall(id, install, 'exports', (dir) => {
246
+ const pkg = readPackageJson<{ exports?: Record<string, unknown> }>(dir)
247
+ if (pkg === null) throw new Error(`${dir}/package.json is unreadable`)
248
+ const exportsMap = pkg.exports
232
249
  // No `exports` field means nothing restricts the deep imports.
233
- if (pkg.exports === undefined) return null
234
- const keys = Object.keys(pkg.exports)
250
+ if (exportsMap === undefined) return null
251
+ const keys = Object.keys(exportsMap)
235
252
  const has = (sub: string): boolean =>
236
253
  keys.some(
237
254
  (k) => k === `./${sub}` || (k.endsWith('*') && `./${sub}`.startsWith(k.slice(0, -1))),
238
255
  )
239
- const hits: string[] = []
240
- for (const rel of scannableFiles(cwd, cfg).filter((f) =>
241
- /\.(?:[cm]?[jt]sx?|vue|astro)$/.test(f),
242
- )) {
243
- for (const [sub, line] of basaltImports(readIfExists(resolve(cwd, rel)) ?? '')) {
244
- if (!has(sub)) hits.push(`${rel}:${line} imports basalt-ui/${sub}`)
245
- }
246
- }
256
+ const hits = appImports(cwd, cfg)
257
+ .filter(([sub]) => !has(sub))
258
+ .map(([sub, rel, line]) => `${rel}:${line} imports basalt-ui/${sub}`)
247
259
  if (hits.length === 0) return null
248
260
  const more = hits.length > 10 ? ` (+${hits.length - 10} more)` : ''
249
261
  return {
@@ -446,8 +458,7 @@ export function appWiringCheck(cwd: string): Check {
446
458
  */
447
459
  export function unknownConfigKeysCheck(cwd: string): Check {
448
460
  return guarded('config-keys', () => {
449
- const raw = readIfExists(resolve(cwd, 'package.json'))
450
- const block = raw === null ? null : (JSON.parse(raw) as { basalt?: unknown }).basalt
461
+ const block = readPackageJson<{ basalt?: unknown }>(cwd)?.basalt
451
462
  if (typeof block !== 'object' || block === null || Array.isArray(block)) return null
452
463
  const known = new Set<string>(BASALT_CONFIG_KEYS)
453
464
  const unknown = Object.keys(block).filter((k) => !known.has(k))
@@ -461,13 +472,18 @@ export function unknownConfigKeysCheck(cwd: string): Check {
461
472
  })
462
473
  }
463
474
 
475
+ /** The `peerDependencies` ranges declared by the package installed at `dir`. */
476
+ function peerRanges(dir: string): Record<string, string> {
477
+ return readPackageJson<{ peerDependencies?: Record<string, string> }>(dir)?.peerDependencies ?? {}
478
+ }
479
+
464
480
  const EXACT_VERSION = /^\d+\.\d+\.\d+$/
465
481
 
466
482
  /** `node_modules/<pkg>/package.json`'s version, searching `cwd` then every ancestor. */
467
483
  function installedVersion(cwd: string, pkg: string): string | null {
468
484
  for (let dir = cwd; ; dir = dirname(dir)) {
469
- const raw = readIfExists(resolve(dir, 'node_modules', pkg, 'package.json'))
470
- if (raw !== null) return (JSON.parse(raw) as { version?: string }).version ?? null
485
+ const manifest = readPackageJson<{ version?: string }>(resolve(dir, 'node_modules', pkg))
486
+ if (manifest !== null) return manifest.version ?? null
471
487
  if (dirname(dir) === dir) return null
472
488
  }
473
489
  }
@@ -479,16 +495,8 @@ function installedVersion(cwd: string, pkg: string): string | null {
479
495
  */
480
496
  export function exactPeersCheck(cwd: string, install: BasaltInstall): Check {
481
497
  const id = 'exact-peers'
482
- if (install.dir === null)
483
- return { skip: `${id}: basalt-ui does not resolve, so its peers cannot be read.` }
484
- const dir = install.dir
485
- return guarded(id, () => {
486
- const raw = readIfExists(resolve(dir, 'package.json'))
487
- const peers =
488
- (raw === null
489
- ? {}
490
- : (JSON.parse(raw) as { peerDependencies?: Record<string, string> }).peerDependencies) ?? {}
491
- const off = Object.entries(peers)
498
+ return withInstall(id, install, 'peers', (dir) => {
499
+ const off = Object.entries(peerRanges(dir))
492
500
  .filter(([, range]) => EXACT_VERSION.test(range))
493
501
  .flatMap(([pkg, pin]) => {
494
502
  const have = installedVersion(cwd, pkg)
@@ -502,3 +510,46 @@ export function exactPeersCheck(cwd: string, install: BasaltInstall): Check {
502
510
  }
503
511
  })
504
512
  }
513
+
514
+ /**
515
+ * The peers a subpath needs installed: V2-SPEC § 1's subpath-required class plus `./content`'s
516
+ * shiki trio, which a bundler cannot resolve without them (argo friction row 15). The test pins it
517
+ * to `package.json` and `surfaces.ts`.
518
+ */
519
+ export const SUBPATH_REQUIRED_PEERS: Readonly<Record<string, readonly string[]>> = {
520
+ agent: ['ai', '@ai-sdk/provider', 'motion'],
521
+ charts: ['@tanstack/charts', 'd3-scale'],
522
+ commands: ['@tanstack/react-hotkeys'],
523
+ content: ['react-markdown', 'shiki', '@shikijs/langs', '@shikijs/themes'],
524
+ 'controls-dates': ['@mantine/dates', 'dayjs'],
525
+ forms: ['@mantine/form'],
526
+ table: ['@tanstack/react-table'],
527
+ }
528
+
529
+ /**
530
+ * `subpath-peers`: every `basalt-ui/<subpath>` the app imports has its required peers installed. A
531
+ * missing one still builds, then throws "Could not resolve" when the chunk loads.
532
+ */
533
+ export function subpathPeersCheck(cwd: string, cfg: BasaltConfig, install: BasaltInstall): Check {
534
+ const id = 'subpath-peers'
535
+ return withInstall(id, install, 'peers', (dir) => {
536
+ const missing = new Map<string, string[]>()
537
+ for (const [sub, file] of new Map(
538
+ appImports(cwd, cfg)
539
+ .toReversed()
540
+ .map(([s, f]) => [s, f]),
541
+ )) {
542
+ for (const peer of SUBPATH_REQUIRED_PEERS[sub] ?? []) {
543
+ if (installedVersion(cwd, peer) !== null) continue
544
+ missing.set(peer, [...(missing.get(peer) ?? []), `basalt-ui/${sub} (${file})`])
545
+ }
546
+ }
547
+ if (missing.size === 0) return null
548
+ const ranges = peerRanges(dir)
549
+ const named = [...missing].map(([peer, by]) => `${peer} for ${by.join(', ')}`)
550
+ const add = [...missing.keys()].map((peer) => (ranges[peer] ? `${peer}@${ranges[peer]}` : peer))
551
+ return {
552
+ fail: `${id}: peers a used basalt-ui subpath requires are not installed: ${named.join('; ')}. Fix: \`bun add ${add.join(' ')}\`.`,
553
+ }
554
+ })
555
+ }
package/src/cli/doctor.ts CHANGED
@@ -6,7 +6,6 @@
6
6
  import { existsSync } from 'node:fs'
7
7
  import { relative, resolve } from 'node:path'
8
8
 
9
- import type { BasaltConfig } from './config'
10
9
  import {
11
10
  AGENTS_MD,
12
11
  LEGACY_BLOCK_HOST,
@@ -26,6 +25,7 @@ import {
26
25
  readBasaltConfig,
27
26
  readIfExists,
28
27
  readManifest,
28
+ readPackageJson,
29
29
  resolveAppDir,
30
30
  resolveProjectDir,
31
31
  ROUTER_SEED_DESTS,
@@ -40,6 +40,7 @@ import {
40
40
  droppedSubpathsCheck,
41
41
  exactPeersCheck,
42
42
  retiredLintRulesCheck,
43
+ subpathPeersCheck,
43
44
  tsconfigBaseCheck,
44
45
  unknownConfigKeysCheck,
45
46
  } from './doctor-upgrade'
@@ -85,32 +86,13 @@ type AiMajorField = (typeof AI_MAJOR_FIELDS)[number]
85
86
 
86
87
  /** The `ai` major per dependency field of THIS package.json (not walked across workspaces). */
87
88
  function aiMajorsAt(dir: string): Partial<Record<AiMajorField, number>> {
88
- const raw = readIfExists(resolve(dir, 'package.json'))
89
- if (raw === null) return {}
90
- try {
91
- const pkg = JSON.parse(raw) as Partial<Record<AiMajorField, Record<string, string>>>
92
- const out: Partial<Record<AiMajorField, number>> = {}
93
- for (const field of AI_MAJOR_FIELDS) {
94
- const major = majorOf(pkg[field]?.['ai'])
95
- if (major !== null) out[field] = major
96
- }
97
- return out
98
- } catch {
99
- return {}
100
- }
101
- }
102
-
103
- /** `basalt.aiMajorSkewReason` as a validated reason, plus whether the key was present but invalid. */
104
- function resolveAiMajorSkewReason(cfg: BasaltConfig): {
105
- reason: string | null
106
- presentButInvalid: boolean
107
- } {
108
- const raw = cfg.aiMajorSkewReason
109
- if (raw === undefined) return { reason: null, presentButInvalid: false }
110
- if (typeof raw === 'string' && raw.trim().length > 0) {
111
- return { reason: raw, presentButInvalid: false }
89
+ const pkg = readPackageJson<Partial<Record<AiMajorField, Record<string, string>>>>(dir) ?? {}
90
+ const out: Partial<Record<AiMajorField, number>> = {}
91
+ for (const field of AI_MAJOR_FIELDS) {
92
+ const major = majorOf(pkg[field]?.['ai'])
93
+ if (major !== null) out[field] = major
112
94
  }
113
- return { reason: null, presentButInvalid: true }
95
+ return out
114
96
  }
115
97
 
116
98
  /**
@@ -124,8 +106,8 @@ function resolveAiMajorSkewReason(cfg: BasaltConfig): {
124
106
  * 4. `oxlint-preset`: `.oxlintrc.json` extends the shipped preset AND that path resolves.
125
107
  * 7. `ai-major-parity`: the `ai` major agrees across this package.json's dependency fields
126
108
  * (exempt via `basalt.aiMajorSkewReason`; a stale reason warns).
127
- * 8-13. `tsconfig-base`, `retired-lint-rules`, `dropped-subpaths`, `app-wiring`, `exact-peers`,
128
- * `config-keys` (./doctor-upgrade);
109
+ * 8-14. `tsconfig-base`, `retired-lint-rules`, `dropped-subpaths`, `app-wiring`, `exact-peers`,
110
+ * `subpath-peers`, `config-keys` (./doctor-upgrade);
129
111
  * a check that cannot run is SKIPPED and exits non-zero.
130
112
  *
131
113
  * Warning (non-fatal):
@@ -153,21 +135,10 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
153
135
  const result: DoctorResult = { hardFailures: 0, warnings: 0, skipped: 0 }
154
136
  const lines: string[] = [`\nbasalt-ui doctor — ${cwd}\n`]
155
137
 
156
- function pass(msg: string): void {
157
- lines.push(` ✓ ${msg}`)
158
- }
159
- function warn(msg: string): void {
160
- lines.push(` ⚠ ${msg}`)
161
- result.warnings++
162
- }
163
- function fail(msg: string): void {
164
- lines.push(` ✖ ${msg}`)
165
- result.hardFailures++
166
- }
167
- function skip(msg: string): void {
168
- lines.push(` ? SKIPPED ${msg}`)
169
- result.skipped++
170
- }
138
+ const pass = (msg: string): number => lines.push(` ✓ ${msg}`)
139
+ const warn = (msg: string): number => lines.push(` ⚠ ${msg}`) && result.warnings++
140
+ const fail = (msg: string): number => lines.push(` ✖ ${msg}`) && result.hardFailures++
141
+ const skip = (msg: string): number => lines.push(` ? SKIPPED ${msg}`) && result.skipped++
171
142
 
172
143
  if (project.relocatedFrom !== null) {
173
144
  lines.push(` → BASALT_CWD relocated from ${project.relocatedFrom} to ${cwd}.\n`)
@@ -368,8 +339,11 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
368
339
  const aiEntries = AI_MAJOR_FIELDS.filter((field) => aiMajors[field] !== undefined).map(
369
340
  (field) => ({ field, major: aiMajors[field] as number }),
370
341
  )
371
- const { reason: aiMajorSkewReason, presentButInvalid: aiMajorSkewReasonInvalid } =
372
- resolveAiMajorSkewReason(cfg)
342
+ // `basalt.aiMajorSkewReason`: a non-blank string exempts a skew; any other present value is invalid.
343
+ const rawSkewReason = cfg.aiMajorSkewReason
344
+ const aiMajorSkewReason =
345
+ typeof rawSkewReason === 'string' && rawSkewReason.trim().length > 0 ? rawSkewReason : null
346
+ const aiMajorSkewReasonInvalid = aiMajorSkewReason === null && rawSkewReason !== undefined
373
347
  if (aiEntries.length > 0) {
374
348
  const distinctMajors = new Set(aiEntries.map((entry) => entry.major))
375
349
  if (distinctMajors.size > 1) {
@@ -428,6 +402,10 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
428
402
  appWiringCheck(appDir),
429
403
  ],
430
404
  ['exact-peers: installed exact-pinned peers match basalt-ui', exactPeersCheck(cwd, install)],
405
+ [
406
+ 'subpath-peers: every imported basalt-ui subpath has its required peers installed',
407
+ subpathPeersCheck(cwd, cfg, install),
408
+ ],
431
409
  [
432
410
  'config-keys: the basalt config block sets only keys basalt-ui reads',
433
411
  unknownConfigKeysCheck(cwd),
package/src/cli/index.ts CHANGED
@@ -148,23 +148,11 @@ function sanitizeSeverity(cfg: BasaltConfig): BasaltConfig {
148
148
  /** Does `cwd`'s own `package.json` carry a `basalt` key? — "misconfigured roots" vs "wrong
149
149
  * directory", the failure a root-invoked hook hits since 1.29.0's single resolver. Never a walk. */
150
150
  export function hasBasaltKey(cwd: string): boolean {
151
- const raw = readIfExists(resolve(cwd, 'package.json'))
152
- try {
153
- return raw !== null && (JSON.parse(raw) as { basalt?: unknown }).basalt !== undefined
154
- } catch {
155
- return false
156
- }
151
+ return readPackageJson<{ basalt?: unknown }>(cwd)?.basalt !== undefined
157
152
  }
158
153
 
159
154
  export function readBasaltConfig(cwd: string): BasaltConfig {
160
- try {
161
- const pkg = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf8')) as {
162
- basalt?: BasaltConfig
163
- }
164
- return sanitizeSeverity(pkg.basalt ?? {})
165
- } catch {
166
- return {}
167
- }
155
+ return sanitizeSeverity(readPackageJson<{ basalt?: BasaltConfig }>(cwd)?.basalt ?? {})
168
156
  }
169
157
 
170
158
  /**
@@ -237,13 +225,10 @@ const UNRESOLVED_INSTALL: BasaltInstall = { dir: null, version: null, how: null
237
225
  /** `<dir>/node_modules/basalt-ui` when it holds a readable package.json, else null. */
238
226
  function basaltInstallAt(dir: string): { dir: string; version: string | null } | null {
239
227
  const installDir = resolve(dir, 'node_modules', 'basalt-ui')
240
- const raw = readIfExists(resolve(installDir, 'package.json'))
241
- if (raw === null) return null
242
- try {
243
- const pkg = JSON.parse(raw) as { version?: string }
244
- return { dir: installDir, version: pkg.version ?? null }
245
- } catch {
246
- return { dir: installDir, version: null }
228
+ if (readIfExists(resolve(installDir, 'package.json')) === null) return null
229
+ return {
230
+ dir: installDir,
231
+ version: readPackageJson<{ version?: string }>(installDir)?.version ?? null,
247
232
  }
248
233
  }
249
234
 
@@ -500,31 +485,30 @@ export function readIfExists(abs: string): string | null {
500
485
  }
501
486
  }
502
487
 
488
+ /** `<dir>/package.json` parsed as `T`; null when absent or not JSON. */
489
+ export function readPackageJson<T>(dir: string): T | null {
490
+ const raw = readIfExists(resolve(dir, 'package.json'))
491
+ try {
492
+ return raw === null ? null : (JSON.parse(raw) as T)
493
+ } catch {
494
+ return null
495
+ }
496
+ }
497
+
503
498
  /** Read a shipped source asset from the package root; null if the sibling hasn't authored it yet. */
504
499
  export function readSource(pkgRoot: string, source: string): string | null {
505
500
  return readIfExists(resolve(pkgRoot, source))
506
501
  }
507
502
 
508
503
  function readPackageName(cwd: string): string {
509
- try {
510
- const pkg = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf8')) as { name?: string }
511
- if (pkg.name && pkg.name.length > 0) return pkg.name
512
- } catch {
513
- // fall through to dir name
514
- }
504
+ const name = readPackageJson<{ name?: string }>(cwd)?.name
505
+ if (name && name.length > 0) return name
515
506
  return cwd.split('/').filter(Boolean).pop() ?? 'app'
516
507
  }
517
508
 
518
509
  /** The framework's own published version, read from the package root package.json. */
519
510
  export function readFrameworkVersion(pkgRoot: string): string {
520
- try {
521
- const pkg = JSON.parse(readFileSync(resolve(pkgRoot, 'package.json'), 'utf8')) as {
522
- version?: string
523
- }
524
- return pkg.version ?? '0.0.0'
525
- } catch {
526
- return '0.0.0'
527
- }
511
+ return readPackageJson<{ version?: string }>(pkgRoot)?.version ?? '0.0.0'
528
512
  }
529
513
 
530
514
  /**
@@ -733,20 +717,18 @@ export function resolveAppDir(root: string): AppResolution {
733
717
 
734
718
  /** True when `pkgName` appears in the consumer's dependencies, devDependencies, or peerDependencies. */
735
719
  function hasDependency(cwd: string, pkgName: string): boolean {
736
- try {
737
- const pkg = JSON.parse(readFileSync(resolve(cwd, 'package.json'), 'utf8')) as {
738
- dependencies?: Record<string, string>
739
- devDependencies?: Record<string, string>
740
- peerDependencies?: Record<string, string>
741
- }
742
- return Boolean(
743
- pkg.dependencies?.[pkgName] ??
744
- pkg.devDependencies?.[pkgName] ??
745
- pkg.peerDependencies?.[pkgName],
746
- )
747
- } catch {
748
- return false
749
- }
720
+ const pkg =
721
+ readPackageJson<
722
+ Record<
723
+ 'dependencies' | 'devDependencies' | 'peerDependencies',
724
+ Record<string, string> | undefined
725
+ >
726
+ >(cwd)
727
+ return Boolean(
728
+ pkg?.dependencies?.[pkgName] ??
729
+ pkg?.devDependencies?.[pkgName] ??
730
+ pkg?.peerDependencies?.[pkgName],
731
+ )
750
732
  }
751
733
 
752
734
  /**
@@ -1275,14 +1257,8 @@ export function patchPackageJson(
1275
1257
  ): boolean {
1276
1258
  const abs = resolve(cwd, 'package.json')
1277
1259
  const raw = readIfExists(abs)
1278
- if (raw === null) return false
1279
- let pkg: Record<string, unknown>
1280
- try {
1281
- pkg = JSON.parse(raw) as Record<string, unknown>
1282
- } catch {
1283
- return false
1284
- }
1285
- if (!mutate(pkg)) return false
1260
+ const pkg = readPackageJson<Record<string, unknown>>(cwd)
1261
+ if (raw === null || pkg === null || !mutate(pkg)) return false
1286
1262
  const indent = /\n([ \t]+)"/.exec(raw)?.[1] ?? ' '
1287
1263
  writeFileSync(abs, JSON.stringify(pkg, null, indent) + (raw.endsWith('\n') ? '\n' : ''))
1288
1264
  return true
@@ -1689,15 +1665,8 @@ function expandWorkspaceGlobs(cwd: string, patterns: readonly string[]): string[
1689
1665
  * an unrecognized `workspaces` shape.
1690
1666
  */
1691
1667
  function collectWorkspacePackages(cwd: string): WorkspaceWalk {
1692
- const raw = readIfExists(resolve(cwd, 'package.json'))
1693
- if (raw === null) return { packages: [], hasWorkspacesField: false }
1694
-
1695
- let parsed: { workspaces?: unknown; name?: string }
1696
- try {
1697
- parsed = JSON.parse(raw) as { workspaces?: unknown; name?: string }
1698
- } catch {
1699
- return { packages: [], hasWorkspacesField: false }
1700
- }
1668
+ const parsed = readPackageJson<{ workspaces?: unknown; name?: string }>(cwd)
1669
+ if (parsed === null) return { packages: [], hasWorkspacesField: false }
1701
1670
 
1702
1671
  const patterns = isStringArray(parsed.workspaces)
1703
1672
  ? parsed.workspaces
@@ -64,6 +64,18 @@ const LANG_ALIASES: Record<string, string> = {
64
64
  }
65
65
 
66
66
  let highlighterPromise: Promise<HighlighterCore | null> | null = null
67
+ let warnedMissingPeer = false
68
+
69
+ /** Dev-only, once per process: the highlighter peers did not resolve, so code stays unhighlighted. */
70
+ function warnMissingPeer(error: unknown): void {
71
+ if (warnedMissingPeer || process.env['NODE_ENV'] === 'production') return
72
+ warnedMissingPeer = true
73
+ console.warn(
74
+ '[basalt] CodeBlock: syntax highlighting is off — a peer of basalt-ui/content did not load ' +
75
+ `(${error instanceof Error ? error.message : String(error)}). Code renders unhighlighted. ` +
76
+ 'Install them with: bun add shiki @shikijs/langs @shikijs/themes',
77
+ )
78
+ }
67
79
 
68
80
  async function loadHighlighter(): Promise<HighlighterCore | null> {
69
81
  const [{ createHighlighterCore }, { createJavaScriptRegexEngine }] = await Promise.all([
@@ -79,7 +91,10 @@ async function loadHighlighter(): Promise<HighlighterCore | null> {
79
91
 
80
92
  /** Module-level singleton — resolves once; every caller shares the same highlighter instance. */
81
93
  function getHighlighter(): Promise<HighlighterCore | null> {
82
- highlighterPromise ??= loadHighlighter().catch(() => null)
94
+ highlighterPromise ??= loadHighlighter().catch((error: unknown) => {
95
+ warnMissingPeer(error)
96
+ return null
97
+ })
83
98
  return highlighterPromise
84
99
  }
85
100
 
@@ -91,7 +106,8 @@ async function ensureLanguage(highlighter: HighlighterCore, lang: string): Promi
91
106
  try {
92
107
  await highlighter.loadLanguage(loadLang() as LanguageInput)
93
108
  return true
94
- } catch {
109
+ } catch (error) {
110
+ warnMissingPeer(error)
95
111
  return false
96
112
  }
97
113
  }