basalt-ui 2.0.4 → 2.1.0
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 +19 -0
- package/MIGRATING.md +31 -16
- package/dist/agent/error.d.ts +16 -1
- package/dist/agent/error.d.ts.map +1 -1
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +2 -0
- package/dist/cli/doctor-upgrade.d.ts +11 -0
- package/dist/cli/doctor-upgrade.d.ts.map +1 -1
- package/dist/cli/doctor-upgrade.js +59 -32
- package/dist/cli/doctor.d.ts +2 -2
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +19 -36
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +27 -55
- package/dist/content/highlighter.d.ts.map +1 -1
- package/dist/content/highlighter.js +14 -2
- package/dist/shell/app-main.module.css +5 -3
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +1 -1
- package/dist/table/data-table.module.css +4 -10
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/agent/error.ts +16 -1
- package/src/agent/index.ts +1 -0
- package/src/cli/doctor-upgrade.ts +88 -37
- package/src/cli/doctor.ts +23 -45
- package/src/cli/index.ts +35 -66
- package/src/content/highlighter.ts +18 -2
- package/src/shell/app-main.module.css +5 -3
- package/src/shell/app-sidebar.tsx +3 -1
- package/src/table/data-table.module.css +4 -10
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
510
|
-
|
|
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
|
-
|
|
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
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
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
|
-
|
|
1279
|
-
|
|
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
|
|
1693
|
-
if (
|
|
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(() =>
|
|
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
|
}
|
|
@@ -103,9 +103,11 @@
|
|
|
103
103
|
* Block is breathing room under the band's single `--vx-divider` line — nothing aligns to it.
|
|
104
104
|
* `stack-md` (12) on a phone, one rung down (`stack-sm`, 8) from `sm` up where the inline gutter
|
|
105
105
|
* is 16: the page then sits 16 from the seam and 8 under the header, rather than the 20/12 it
|
|
106
|
-
* used to.
|
|
106
|
+
* used to. The value rides `--basalt-main-pad-block`, which descendants inherit: the data
|
|
107
|
+
* table's page-sticky offset reads it instead of repeating this module's `sm` switch.
|
|
107
108
|
*/
|
|
108
|
-
|
|
109
|
+
--basalt-main-pad-block: var(--vx-space-stack-md);
|
|
110
|
+
padding: var(--basalt-main-pad-block) var(--app-shell-padding);
|
|
109
111
|
/* Without this a wide flex/grid child (a bare table, a nowrap control row) sizes Main to ITSELF. */
|
|
110
112
|
min-width: 0;
|
|
111
113
|
overflow-y: auto;
|
|
@@ -124,7 +126,7 @@
|
|
|
124
126
|
|
|
125
127
|
@media (min-width: 52.5em) {
|
|
126
128
|
.main {
|
|
127
|
-
|
|
129
|
+
--basalt-main-pad-block: var(--vx-space-stack-sm);
|
|
128
130
|
}
|
|
129
131
|
}
|
|
130
132
|
|
|
@@ -327,7 +327,9 @@ function NavSection({ section, collapsed }: { section: SidebarSection; collapsed
|
|
|
327
327
|
<SectionLabel>{section.label}</SectionLabel>
|
|
328
328
|
<IconChevron open={!folded} />
|
|
329
329
|
</UnstyledButton>
|
|
330
|
-
|
|
330
|
+
{/* The rail hides the header (the only way to unfold), so a folded section would strand its
|
|
331
|
+
destinations and their badges: the rail ignores the fold; expanding restores it. */}
|
|
332
|
+
<Collapse expanded={!folded || collapsed}>
|
|
331
333
|
<Stack gap={1}>{items}</Stack>
|
|
332
334
|
</Collapse>
|
|
333
335
|
</div>
|
|
@@ -70,20 +70,14 @@
|
|
|
70
70
|
* one rung down to `stack-sm` from `sm` up). A sticky `<thead>` resolves against the scrollport's
|
|
71
71
|
* CONTENT edge, so at `top: 0` it parked that padding below the band and every body row scrolled
|
|
72
72
|
* through the gap. A negative offset equal to the padding moves the stick line out to the band's
|
|
73
|
-
* edge, flush. The
|
|
74
|
-
* (`data-table.layout.test.ts
|
|
73
|
+
* edge, flush. The padding is read from `--basalt-main-pad-block`, which `.main` sets and owns
|
|
74
|
+
* (including its `sm` switch), so the two cannot drift; `data-table.layout.test.ts` invariant 8
|
|
75
|
+
* pins both steps. A consumer `stickyHeaderOffset` is an
|
|
75
76
|
* inline `--table-sticky-header-offset` on the table and beats this class, so it still wins.
|
|
76
77
|
* Outside a shell no `.mantine-AppShell-main` ancestor matches and the offset stays 0.
|
|
77
78
|
*/
|
|
78
79
|
:global(.mantine-AppShell-main) .pageSticky {
|
|
79
|
-
--table-sticky-header-offset: calc(-1 * var(--
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* theme-allow raw-media-query — mirrors app-main.module.css's own `sm` switch for Main's block padding; invariant 8 in data-table.layout.test.ts fails on drift */
|
|
83
|
-
@media (min-width: 52.5em) {
|
|
84
|
-
:global(.mantine-AppShell-main) .pageSticky {
|
|
85
|
-
--table-sticky-header-offset: calc(-1 * var(--vx-space-stack-sm));
|
|
86
|
-
}
|
|
80
|
+
--table-sticky-header-offset: calc(-1 * var(--basalt-main-pad-block, 0px));
|
|
87
81
|
}
|
|
88
82
|
|
|
89
83
|
/*
|