basalt-ui 2.0.1 → 2.0.3
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 +18 -0
- package/MIGRATING.md +37 -19
- package/configs/check.yml +5 -5
- package/dist/cli/config.d.ts +10 -0
- package/dist/cli/config.d.ts.map +1 -1
- package/dist/cli/config.js +1 -1
- package/dist/cli/doctor-upgrade.d.ts +1 -1
- package/dist/cli/doctor-upgrade.d.ts.map +1 -1
- package/dist/cli/doctor-upgrade.js +36 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +28 -3
- package/dist/cli/index.d.ts +28 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +60 -7
- package/dist/cli/init.d.ts.map +1 -1
- package/dist/cli/init.js +7 -1
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +5 -0
- package/dist/provider/index.d.ts +8 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +3 -2
- package/dist/provider/spotlight-actions.d.ts +3 -2
- package/dist/provider/spotlight-actions.d.ts.map +1 -1
- package/dist/provider/spotlight-actions.js +5 -3
- package/dist/shell/app-sidebar.d.ts.map +1 -1
- package/dist/shell/app-sidebar.js +9 -2
- package/dist/shell/app-sidebar.module.css +6 -0
- package/dist/shell/app-sidebar.module.css.d.ts +1 -0
- package/dist/tokens/core.d.ts +12 -11
- package/dist/tokens/core.d.ts.map +1 -1
- package/dist/tokens/core.js +9 -1
- package/llms.txt +2 -2
- package/package.json +1 -1
- package/src/cli/config.ts +11 -1
- package/src/cli/doctor-upgrade.ts +49 -2
- package/src/cli/doctor.ts +37 -3
- package/src/cli/index.ts +93 -7
- package/src/cli/init.ts +10 -1
- package/src/cli/sync.ts +6 -0
- package/src/provider/index.tsx +10 -2
- package/src/provider/spotlight-actions.tsx +8 -3
- package/src/shell/app-sidebar.module.css +6 -0
- package/src/shell/app-sidebar.module.css.d.ts +1 -0
- package/src/shell/app-sidebar.tsx +16 -2
- package/src/tokens/core.ts +25 -9
package/dist/provider/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { BuildPaletteOpts } from '../tokens/core.js';
|
|
|
5
5
|
import type { SizeClass } from '../shell/use-size-class.js';
|
|
6
6
|
import type { Telemetry } from '../telemetry.js';
|
|
7
7
|
import type { BasaltRouter } from './create-router.js';
|
|
8
|
+
import type { FlatNavItem } from '../router-tanstack/nav.js';
|
|
8
9
|
export type BasaltProviderProps = {
|
|
9
10
|
/** From `createBasaltRouter`; the provider mounts it and reads its `queryClient`. */
|
|
10
11
|
router: BasaltRouter;
|
|
@@ -41,6 +42,12 @@ export type BasaltProviderProps = {
|
|
|
41
42
|
* can answer. Default `'compact'`. SSR only; an SSR desktop app SHOULD pass it.
|
|
42
43
|
*/
|
|
43
44
|
sizeClassHint?: SizeClass;
|
|
45
|
+
/**
|
|
46
|
+
* Label for the `nav.<id>` Spotlight entries generated from `defineNav`, e.g.
|
|
47
|
+
* `(item) => `Go to ${item.label}``. Default: the destination's own label. A `defineCommands`
|
|
48
|
+
* entry with id `nav.<id>` replaces the generated one outright.
|
|
49
|
+
*/
|
|
50
|
+
navCommandLabel?: (item: FlatNavItem) => string;
|
|
44
51
|
} & Omit<MantineProviderProps, 'children' | 'theme' | 'defaultColorScheme' | 'cssVariablesResolver'>;
|
|
45
52
|
type RootBoundaryProps = {
|
|
46
53
|
children: ReactNode;
|
|
@@ -76,6 +83,6 @@ export declare function composeInjectedCss(base: string, fontsCss: string, radiu
|
|
|
76
83
|
* closes the gap between hydration and the next client paint.
|
|
77
84
|
*/
|
|
78
85
|
export declare function useHostAttribute(): void;
|
|
79
|
-
export declare function BasaltProvider({ router, theme, telemetry, fallback, injectPalette, paletteOptions, defaultColorScheme, nonce, sizeClassHint, ...rest }: BasaltProviderProps): import("react").JSX.Element;
|
|
86
|
+
export declare function BasaltProvider({ router, theme, telemetry, fallback, injectPalette, paletteOptions, defaultColorScheme, nonce, sizeClassHint, navCommandLabel, ...rest }: BasaltProviderProps): import("react").JSX.Element;
|
|
80
87
|
export {};
|
|
81
88
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AASzD,OAAO,EAAE,SAAS,EAAqE,MAAM,OAAO,CAAA;AACpG,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAStD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,iBAAiB,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/provider/index.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAA;AASzD,OAAO,EAAE,SAAS,EAAqE,MAAM,OAAO,CAAA;AACpG,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAKjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAStD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAExD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAE7C,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,iBAAiB,CAAA;AAExE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAGzD,MAAM,MAAM,mBAAmB,GAAG;IAChC,qFAAqF;IACrF,MAAM,EAAE,YAAY,CAAA;IACpB,qFAAqF;IACrF,KAAK,CAAC,EAAE,oBAAoB,CAAC,OAAO,CAAC,CAAA;IACrC;;;;OAIG;IACH,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,yGAAyG;IACzG,QAAQ,CAAC,EAAE,SAAS,GAAG,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,SAAS,CAAC,CAAA;IACtD;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB;;;OAGG;IACH,cAAc,CAAC,EAAE,gBAAgB,CAAA;IACjC,8CAA8C;IAC9C,kBAAkB,CAAC,EAAE,oBAAoB,CAAC,oBAAoB,CAAC,CAAA;IAC/D;;;OAGG;IACH,KAAK,CAAC,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,aAAa,CAAC,EAAE,SAAS,CAAA;IACzB;;;;OAIG;IACH,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,CAAA;CAChD,GAAG,IAAI,CAAC,oBAAoB,EAAE,UAAU,GAAG,OAAO,GAAG,oBAAoB,GAAG,sBAAsB,CAAC,CAAA;AAIpG,KAAK,iBAAiB,GAAG;IACvB,QAAQ,EAAE,SAAS,CAAA;IACnB,IAAI,EAAE,SAAS,CAAA;IACf,QAAQ,EAAE,mBAAmB,CAAC,UAAU,CAAC,CAAA;CAC1C,CAAA;AAED,KAAK,aAAa,GAAG;IAAE,QAAQ,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAA;AAwB1D,mGAAmG;AACnG,qBAAa,YAAa,SAAQ,SAAS,CAAC,iBAAiB,EAAE,aAAa,CAAC;gBAC/D,KAAK,EAAE,iBAAiB;IAKpC,MAAM,CAAC,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa;IAIrD,iBAAiB,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,GAAG,IAAI;IAczD,MAAM,IAAI,SAAS;CAO7B;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,MAAM,EACjB,UAAU,EAAE,MAAM,GACjB,MAAM,CAER;AAYD;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,IAAI,IAAI,CAmBvC;AAqMD,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,KAAK,EACL,SAAS,EACT,QAAQ,EACR,aAAoB,EACpB,cAAc,EACd,kBAA2B,EAC3B,KAAK,EACL,aAAyB,EACzB,eAAe,EACf,GAAG,IAAI,EACR,EAAE,mBAAmB,+BA6ErB"}
|
package/dist/provider/index.js
CHANGED
|
@@ -242,6 +242,7 @@ function BasaltProvider({
|
|
|
242
242
|
defaultColorScheme = "dark",
|
|
243
243
|
nonce,
|
|
244
244
|
sizeClassHint = "compact",
|
|
245
|
+
navCommandLabel,
|
|
245
246
|
...rest
|
|
246
247
|
}) {
|
|
247
248
|
useDuplicateProviderGuard();
|
|
@@ -259,8 +260,8 @@ function BasaltProvider({
|
|
|
259
260
|
const [devTool, setDevTool] = useState(null);
|
|
260
261
|
const [actions, setActions] = useState([]);
|
|
261
262
|
const refreshActions = useCallback(
|
|
262
|
-
() => setActions(buildSpotlightActions(router, setDevTool)),
|
|
263
|
-
[router]
|
|
263
|
+
() => setActions(buildSpotlightActions(router, setDevTool, navCommandLabel)),
|
|
264
|
+
[router, navCommandLabel]
|
|
264
265
|
);
|
|
265
266
|
useEffect(refreshActions, [refreshActions]);
|
|
266
267
|
const resolvedTheme = useLabTheme(theme);
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The provider's Spotlight list: the `defineCommands` registry, then the `defineNav` destinations
|
|
3
|
-
* (via `flattenNav`), then the three dev-dock commands in dev. A snapshot rebuilt each time Spotlight
|
|
3
|
+
* (via `flattenNav`, skipping any id a registered `nav.<id>` command already owns), then the three dev-dock commands in dev. A snapshot rebuilt each time Spotlight
|
|
4
4
|
* opens, so a command's `when()` is current. Not for use during render (`detectMac` reads `navigator`).
|
|
5
5
|
*/
|
|
6
6
|
import type { SpotlightActionData } from '@mantine/spotlight';
|
|
7
7
|
import type { AnyRouter } from '@tanstack/react-router';
|
|
8
|
+
import type { FlatNavItem } from '../router-tanstack/nav.js';
|
|
8
9
|
import type { BasaltDevDockTool } from '../dev-dock.js';
|
|
9
|
-
export declare function buildSpotlightActions(router: AnyRouter, openDevTool: (tool: BasaltDevDockTool) => void): SpotlightActionData[];
|
|
10
|
+
export declare function buildSpotlightActions(router: AnyRouter, openDevTool: (tool: BasaltDevDockTool) => void, navCommandLabel?: (item: FlatNavItem) => string): SpotlightActionData[];
|
|
10
11
|
//# sourceMappingURL=spotlight-actions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spotlight-actions.d.ts","sourceRoot":"","sources":["../../src/provider/spotlight-actions.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAIvD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AASpD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,
|
|
1
|
+
{"version":3,"file":"spotlight-actions.d.ts","sourceRoot":"","sources":["../../src/provider/spotlight-actions.tsx"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAIvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AASpD,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,SAAS,EACjB,WAAW,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,EAC9C,eAAe,CAAC,EAAE,CAAC,IAAI,EAAE,WAAW,KAAK,MAAM,GAC9C,mBAAmB,EAAE,CAiDvB"}
|
|
@@ -7,9 +7,10 @@ const DEV_TOOLS = [
|
|
|
7
7
|
{ tool: "query", label: "Query devtools" },
|
|
8
8
|
{ tool: "theme", label: "Theme lab" }
|
|
9
9
|
];
|
|
10
|
-
function buildSpotlightActions(router, openDevTool) {
|
|
10
|
+
function buildSpotlightActions(router, openDevTool, navCommandLabel) {
|
|
11
11
|
const actions = [];
|
|
12
|
-
|
|
12
|
+
const commands = getActiveCommands();
|
|
13
|
+
for (const [id, cmd] of Object.entries(commands)) {
|
|
13
14
|
if (cmd.when !== void 0 && !cmd.when()) continue;
|
|
14
15
|
actions.push({
|
|
15
16
|
id,
|
|
@@ -27,9 +28,10 @@ function buildSpotlightActions(router, openDevTool) {
|
|
|
27
28
|
if (nav !== void 0) {
|
|
28
29
|
for (const item of flattenNav(nav)) {
|
|
29
30
|
if (item.disabled === true) continue;
|
|
31
|
+
if (`nav.${item.id}` in commands) continue;
|
|
30
32
|
actions.push({
|
|
31
33
|
id: `nav.${item.id}`,
|
|
32
|
-
label: item.label,
|
|
34
|
+
label: navCommandLabel?.(item) ?? item.label,
|
|
33
35
|
group: item.groupLabel,
|
|
34
36
|
leftSection: item.icon,
|
|
35
37
|
// `link` is the destination's own `linkOptions`, so a store-backed `search` thunk restores
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"app-sidebar.d.ts","sourceRoot":"","sources":["../../src/shell/app-sidebar.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAe,cAAc,EAAE,MAAM,aAAa,CAAA;AAI5E,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAE1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAgBjF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C;;;;OAIG;IACH,KAAK,EAAE,WAAW,GAAG;QAAE,IAAI,CAAC,EAAE,eAAe,EAAE,CAAA;KAAE,CAAA;IACjD,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACtC;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IACvC;;;;OAIG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,mBAAmB,GAAG;QAAE,OAAO,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAA;IACjE;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CACxB,CAAA;
|
|
1
|
+
{"version":3,"file":"app-sidebar.d.ts","sourceRoot":"","sources":["../../src/shell/app-sidebar.tsx"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAA;AAElD,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAe,cAAc,EAAE,MAAM,aAAa,CAAA;AAI5E,OAAO,KAAK,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAE1E,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAA;AAgBjF,MAAM,MAAM,eAAe,GAAG,WAAW,GAAG;IAC1C;;;;OAIG;IACH,KAAK,EAAE,WAAW,GAAG;QAAE,IAAI,CAAC,EAAE,eAAe,EAAE,CAAA;KAAE,CAAA;IACjD,QAAQ,EAAE,cAAc,EAAE,CAAA;IAC1B,SAAS,EAAE,OAAO,CAAA;IAClB;;;;OAIG;IACH,gBAAgB,EAAE,MAAM,IAAI,CAAA;IAC5B;;;;;;;;;;;OAWG;IACH,iBAAiB,CAAC,EAAE,gBAAgB,EAAE,CAAA;IACtC;;;;;;;;;;;;;;;;OAgBG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAAA;IACvC;;;;OAIG;IACH,OAAO,CAAC,EAAE,kBAAkB,CAAA;IAC5B;;;;OAIG;IACH,MAAM,CAAC,EAAE,mBAAmB,GAAG;QAAE,OAAO,CAAC,EAAE,oBAAoB,CAAA;KAAE,CAAA;IACjE;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,YAAY,EAAE,CAAA;CACxB,CAAA;AAiUD,wBAAgB,UAAU,CAAC,KAAK,EAAE,eAAe,+BA2KhD"}
|
|
@@ -88,14 +88,21 @@ function NavLinkBody({
|
|
|
88
88
|
collapsed = false
|
|
89
89
|
}) {
|
|
90
90
|
const Anchor = item.Anchor;
|
|
91
|
+
const icon = iconOrInitial(item.icon, item.label);
|
|
92
|
+
const railDot = collapsed && (item.badge !== void 0 && item.badge !== null || Boolean(item.count));
|
|
91
93
|
const shared = {
|
|
92
94
|
classNames: { root: classes.link },
|
|
93
95
|
label: item.label,
|
|
94
|
-
leftSection:
|
|
96
|
+
leftSection: railDot ? /* @__PURE__ */ jsxs("span", { className: classes.navIconDotHost, children: [
|
|
97
|
+
icon,
|
|
98
|
+
/* @__PURE__ */ jsx("span", { className: classes.blockDot, "data-basalt-rail-dot": true, "aria-hidden": true })
|
|
99
|
+
] }) : icon,
|
|
95
100
|
// Only the collapsed rail hides the label (CSS), leaving the icon alone — name the link there.
|
|
96
101
|
// Expanded, the visible text IS the name and carries the count badge; an `aria-label` would
|
|
97
102
|
// override both and drop the count ("Inbox 5" → "Inbox").
|
|
98
|
-
...collapsed && {
|
|
103
|
+
...collapsed && {
|
|
104
|
+
"aria-label": item.count ? `${item.label}, ${item.count}` : item.label
|
|
105
|
+
},
|
|
99
106
|
rightSection: item.badge ?? (item.count ? /* @__PURE__ */ jsx(NavCountBadge, { count: item.count }) : void 0),
|
|
100
107
|
active,
|
|
101
108
|
// Basalt is the one that stamps `aria-current`, not the theme and not a router `<Link>`'s own
|
|
@@ -472,6 +472,12 @@
|
|
|
472
472
|
box-shadow: 0 0 0 2px var(--mantine-color-body);
|
|
473
473
|
}
|
|
474
474
|
|
|
475
|
+
/* Host for a nav row's rail dot: the dot anchors on the icon itself, not the 48px row. */
|
|
476
|
+
.navIconDotHost {
|
|
477
|
+
position: relative;
|
|
478
|
+
display: inline-flex;
|
|
479
|
+
}
|
|
480
|
+
|
|
475
481
|
.blockItems {
|
|
476
482
|
display: flex;
|
|
477
483
|
flex-direction: column;
|
package/dist/tokens/core.d.ts
CHANGED
|
@@ -190,18 +190,19 @@ export declare function seriesTokens<const T extends SeriesMap>(map: T, prefix?:
|
|
|
190
190
|
* not the widened SeriesMap, enabling downstream exact-key checks in groupTokens / seriesTokens).
|
|
191
191
|
*/
|
|
192
192
|
export declare function defineSeries<const T extends SeriesMap>(map: T): T;
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
declare const GROUP_REF: unique symbol;
|
|
194
|
+
/** A `var(--vx-<name>-<key>)` ref minted by `groupTokens`. The brand lives on the VALUES, so it never shows up in `keyof` the group. */
|
|
195
|
+
export type GroupRef = string & {
|
|
196
|
+
readonly [GROUP_REF]: true;
|
|
197
|
+
};
|
|
195
198
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
+
* What `groupTokens` returns. `BuildPaletteOpts.groups` takes these, so the `${name}-` key that
|
|
200
|
+
* emits the variables and the one that references them come from one call and cannot be spelled
|
|
201
|
+
* two ways. A bare series map (object values) and a plain string record (unbranded values) are
|
|
202
|
+
* both rejected; the runtime payload sits under the non-enumerable {@link SERIES_GROUP} symbol.
|
|
199
203
|
*/
|
|
200
204
|
export type SeriesGroup = {
|
|
201
|
-
readonly [
|
|
202
|
-
readonly prefix: string;
|
|
203
|
-
readonly map: SeriesMap;
|
|
204
|
-
};
|
|
205
|
+
readonly [key: string]: GroupRef;
|
|
205
206
|
};
|
|
206
207
|
/**
|
|
207
208
|
* Build a single namespaced group's token refs (`var(--vx-<name>-<key>)`).
|
|
@@ -210,8 +211,8 @@ export type SeriesGroup = {
|
|
|
210
211
|
* error at the call site rather than a silent `Record<string, string>` widening.
|
|
211
212
|
*/
|
|
212
213
|
export declare function groupTokens<const T extends SeriesMap>(name: string, map: T): {
|
|
213
|
-
[K in keyof T]:
|
|
214
|
-
}
|
|
214
|
+
readonly [K in keyof T]: GroupRef;
|
|
215
|
+
};
|
|
215
216
|
/**
|
|
216
217
|
* The attribute + values the per-scheme blocks key off. Defaults reproduce Mantine's own toggle
|
|
217
218
|
* (`data-mantine-color-scheme` = `dark` / `light`), so a consumer only names the axis it differs
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/tokens/core.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAYH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAUvE,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,MAAM,WAAW,CAAA;AAMjC,OAAO,EAAE,qBAAqB,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,UAAU,CAAA;AACnF,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAA;AAM5D,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AACxC,YAAY,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AAO7C,OAAO,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AACzC,YAAY,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AAI5C,OAAO,EACL,iBAAiB,EACjB,0BAA0B,EAC1B,cAAc,EACd,qBAAqB,EACrB,YAAY,GACb,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAEpD,wFAAwF;AACxF,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAEvD,8FAA8F;AAC9F,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAA;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,GAAI,OAAO,MAAM,EAAE,GAAG,MAAM,KAAG,MACoB,CAAA;AA6DrE,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA4Jb;;;;;OAKG;;IAEH;;wDAEoD;;CAE5C,CAAA;AAEV;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,WAAW;IACtB;;;;;OAKG;;CAEK,CAAA;AAOV;;;;;;;;GAQG;AACH,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAExC;AAwbD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,EACpD,GAAG,EAAE,CAAC,EACN,MAAM,SAAK,GACV;KAAG,CAAC,IAAI,MAAM,CAAC,GAAG,MAAM;CAAE,CAQ5B;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,CAAC,CAAC,SAAS,SAAS,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,CAEjE;
|
|
1
|
+
{"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../../src/tokens/core.ts"],"names":[],"mappings":"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"}
|
package/dist/tokens/core.js
CHANGED
|
@@ -596,7 +596,15 @@ function buildPaletteCss(opts = {}, data = buildPaletteData()) {
|
|
|
596
596
|
const derived = extraDerived ? `${derivedBlock}
|
|
597
597
|
${extraDerived}` : derivedBlock;
|
|
598
598
|
const side = (s) => {
|
|
599
|
-
const extra = groups.map((group) =>
|
|
599
|
+
const extra = groups.map((group) => {
|
|
600
|
+
const payload = group[SERIES_GROUP];
|
|
601
|
+
if (!payload) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
"BuildPaletteOpts.groups: every group must come from groupTokens(name, map)"
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
return groupSide(payload.prefix, payload.map, s);
|
|
607
|
+
}).join("\n");
|
|
600
608
|
const framework = frameworkPrimitives(s, data, legacyAliases);
|
|
601
609
|
return extra ? `${framework}
|
|
602
610
|
${extra}` : framework;
|
package/llms.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# basalt-ui v2.0.
|
|
1
|
+
# basalt-ui v2.0.3
|
|
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.
|
|
16
|
+
Version: 2.0.3
|
|
17
17
|
|
|
18
18
|
## basalt-ui
|
|
19
19
|
Import: import { ... } from 'basalt-ui'
|
package/package.json
CHANGED
package/src/cli/config.ts
CHANGED
|
@@ -4,6 +4,16 @@ import type { GuardKind, GuardSeverity } from '../guard'
|
|
|
4
4
|
|
|
5
5
|
/** Shape of the optional `"basalt"` key in a consumer's package.json. */
|
|
6
6
|
export type BasaltConfig = {
|
|
7
|
+
/**
|
|
8
|
+
* The Vite app's directory relative to the install root, for a workspace monorepo whose manifest,
|
|
9
|
+
* `.claude/` and AGENTS block live at the root. App-scoped checks (`tsconfig-base`, `app-wiring`)
|
|
10
|
+
* and the router seeds target it. Default: the single workspace package that depends on basalt-ui
|
|
11
|
+
* and has a Vite entry, else the root itself.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* { app: 'apps/dashboard' }
|
|
15
|
+
*/
|
|
16
|
+
app?: string
|
|
7
17
|
/** Source roots to scan. Default: `['src']`. Set explicitly for a monorepo layout. */
|
|
8
18
|
roots?: string[]
|
|
9
19
|
/** Files exempt from the scan (they ARE the palette source). Default: argo's exempt set. */
|
|
@@ -181,7 +191,7 @@ export function resolveRoots(cfg: BasaltConfig): string[] {
|
|
|
181
191
|
|
|
182
192
|
/** Every `BasaltConfig` key, as data for `doctor`'s `config-keys` (a test pins it to the type). */
|
|
183
193
|
export const BASALT_CONFIG_KEYS = (
|
|
184
|
-
'roots exempt profile include spacingSteps severity forbiddenAccents accentHue rawRadius rawSurface ' +
|
|
194
|
+
'app roots exempt profile include spacingSteps severity forbiddenAccents accentHue rawRadius rawSurface ' +
|
|
185
195
|
'cardWithBorder offSystemSurfaceVar mantineShadeIndex rawHtmlLayout inlineSpacing inlineDisplay ' +
|
|
186
196
|
'rawMotionValue rawFormControl sub16InputFont seriesModulePath exemptRules aiMajorSkewReason'
|
|
187
197
|
).split(' ') as (keyof BasaltConfig)[]
|
|
@@ -352,9 +352,53 @@ function registersRouterPlugin(src: string): boolean {
|
|
|
352
352
|
})
|
|
353
353
|
}
|
|
354
354
|
|
|
355
|
+
const RELATIVE_IMPORT =
|
|
356
|
+
/(?:\b(?:import|export)\b[^'";]*?\bfrom|\bimport)\s*['"](\.{1,2}\/[^'"]+)['"]/g
|
|
357
|
+
|
|
358
|
+
/** The source file a relative specifier names: as written, `.ts`/`.tsx` appended, or a directory index. */
|
|
359
|
+
function resolveRelative(from: string, spec: string): string | null {
|
|
360
|
+
const base = resolve(dirname(from), spec.replace(/\.[jt]sx?$/, ''))
|
|
361
|
+
return (
|
|
362
|
+
[
|
|
363
|
+
resolve(dirname(from), spec),
|
|
364
|
+
`${base}.ts`,
|
|
365
|
+
`${base}.tsx`,
|
|
366
|
+
`${base}/index.ts`,
|
|
367
|
+
`${base}/index.tsx`,
|
|
368
|
+
].find((f) => /\.[jt]sx?$/.test(f) && existsSync(f)) ?? null
|
|
369
|
+
)
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Whether a module the entry imports (relative imports, three deep) calls `createBasaltRouter` —
|
|
374
|
+
* the router often lives in a singleton module other code also imports.
|
|
375
|
+
*/
|
|
376
|
+
function routerCreatedInImports(cwd: string, entry: string): boolean {
|
|
377
|
+
const seen = new Set([resolve(cwd, entry)])
|
|
378
|
+
let frontier = [...seen]
|
|
379
|
+
for (let depth = 0; depth < 3 && frontier.length > 0; depth++) {
|
|
380
|
+
const next: string[] = []
|
|
381
|
+
for (const file of frontier) {
|
|
382
|
+
for (const m of (readIfExists(file) ?? '').matchAll(RELATIVE_IMPORT)) {
|
|
383
|
+
const dep = resolveRelative(file, m[1] ?? '')
|
|
384
|
+
if (dep === null || seen.has(dep)) continue
|
|
385
|
+
seen.add(dep)
|
|
386
|
+
const { code, inLiteral } = tokenize(readIfExists(dep) ?? '')
|
|
387
|
+
const called = [...code.matchAll(/\bcreateBasaltRouter\b/g)].some(
|
|
388
|
+
(c) => !inLiteral(c.index),
|
|
389
|
+
)
|
|
390
|
+
if (called) return true
|
|
391
|
+
next.push(dep)
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
frontier = next
|
|
395
|
+
}
|
|
396
|
+
return false
|
|
397
|
+
}
|
|
398
|
+
|
|
355
399
|
/**
|
|
356
400
|
* `app-wiring`: the entry module `index.html` loads (default src/main.tsx) mounts `BasaltProvider`
|
|
357
|
-
* on a `createBasaltRouter` router, and the router tree is generated: the vite config calls a
|
|
401
|
+
* on a `createBasaltRouter` router (called in the entry or a module it imports), and the router tree is generated: the vite config calls a
|
|
358
402
|
* binding imported from `@tanstack/router-plugin` in `plugins`, or a `routeTree.gen.*` already sits
|
|
359
403
|
* beside the entry (or in src/). `basaltViteConfig` does not register that plugin, so naming it
|
|
360
404
|
* proves nothing.
|
|
@@ -371,7 +415,10 @@ export function appWiringCheck(cwd: string): Check {
|
|
|
371
415
|
const { code, inLiteral } = tokenize(text)
|
|
372
416
|
const used = (name: string): boolean =>
|
|
373
417
|
[...code.matchAll(new RegExp(`\\b${name}\\b`, 'g'))].some((m) => !inLiteral(m.index))
|
|
374
|
-
const missing = ['BasaltProvider', 'createBasaltRouter'].filter(
|
|
418
|
+
const missing = ['BasaltProvider', 'createBasaltRouter'].filter(
|
|
419
|
+
(name) =>
|
|
420
|
+
!used(name) && !(name === 'createBasaltRouter' && routerCreatedInImports(cwd, entry)),
|
|
421
|
+
)
|
|
375
422
|
if (missing.length > 0) {
|
|
376
423
|
return {
|
|
377
424
|
fail:
|
package/src/cli/doctor.ts
CHANGED
|
@@ -26,7 +26,9 @@ import {
|
|
|
26
26
|
readBasaltConfig,
|
|
27
27
|
readIfExists,
|
|
28
28
|
readManifest,
|
|
29
|
+
resolveAppDir,
|
|
29
30
|
resolveProjectDir,
|
|
31
|
+
ROUTER_SEED_DESTS,
|
|
30
32
|
sameFile,
|
|
31
33
|
scannableFiles,
|
|
32
34
|
scanBlock,
|
|
@@ -135,7 +137,18 @@ function resolveAiMajorSkewReason(cfg: BasaltConfig): {
|
|
|
135
137
|
export function doctor(invocationCwd: string = process.cwd(), flags: string[] = []): number {
|
|
136
138
|
if (conflictingProfileFlags('doctor', flags)) return 1
|
|
137
139
|
const project = resolveProjectDir(invocationCwd)
|
|
138
|
-
|
|
140
|
+
// Inside the app package of a monorepo the manifest is above: read the install root instead.
|
|
141
|
+
const aboveRoot =
|
|
142
|
+
project.relocatedFrom === null && !existsSync(resolve(project.dir, MANIFEST_PATH))
|
|
143
|
+
? findManifestAbove(project.dir)
|
|
144
|
+
: null
|
|
145
|
+
const insideApp =
|
|
146
|
+
aboveRoot !== null &&
|
|
147
|
+
(() => {
|
|
148
|
+
const app = resolveAppDir(aboveRoot)
|
|
149
|
+
return !('error' in app) && app.dir === project.dir
|
|
150
|
+
})()
|
|
151
|
+
const cwd = insideApp && aboveRoot !== null ? aboveRoot : project.dir
|
|
139
152
|
const cfg = readBasaltConfig(cwd)
|
|
140
153
|
const result: DoctorResult = { hardFailures: 0, warnings: 0, skipped: 0 }
|
|
141
154
|
const lines: string[] = [`\nbasalt-ui doctor — ${cwd}\n`]
|
|
@@ -160,6 +173,11 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
|
|
|
160
173
|
lines.push(` → BASALT_CWD relocated from ${project.relocatedFrom} to ${cwd}.\n`)
|
|
161
174
|
}
|
|
162
175
|
|
|
176
|
+
if (insideApp)
|
|
177
|
+
lines.push(` → ${project.dir} is the app package; checking the install root ${cwd}.\n`)
|
|
178
|
+
|
|
179
|
+
const app = resolveAppDir(cwd)
|
|
180
|
+
const appDir = 'error' in app ? cwd : app.dir
|
|
163
181
|
const profile = declaredProfile(cfg, flags)
|
|
164
182
|
if (profile === 'tokens-only') {
|
|
165
183
|
lines.push(
|
|
@@ -192,6 +210,22 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
|
|
|
192
210
|
|
|
193
211
|
const install = findBasaltInstall(cwd)
|
|
194
212
|
|
|
213
|
+
// ── App placement: the agent layer is at the root, the app is where `resolveAppDir` says ────
|
|
214
|
+
if ('error' in app) {
|
|
215
|
+
fail(`app-dir: ${app.error}`)
|
|
216
|
+
} else if (app.rel !== '') {
|
|
217
|
+
pass(`app-dir: the app is ${app.rel} (agent layer and manifest stay at the install root)`)
|
|
218
|
+
const ledger = readManifest(cwd).files
|
|
219
|
+
for (const seed of Object.values(ROUTER_SEED_DESTS)) {
|
|
220
|
+
if (ledger[seed] !== undefined && existsSync(resolve(cwd, seed))) {
|
|
221
|
+
warn(
|
|
222
|
+
`app-dir: ${seed} was seeded at the install root by an earlier \`sync\`, but the app is ` +
|
|
223
|
+
`${app.rel} — it is dead code there. Delete it (\`sync\` seeds ${app.rel}/${seed} instead).`,
|
|
224
|
+
)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
195
229
|
// ── Hard check 2: install matches the manifest, and doctor says WHERE (the AGENTS-block's claim)
|
|
196
230
|
const manifestVersion = profile === 'tokens-only' ? undefined : readManifest(cwd).basaltVersion
|
|
197
231
|
const at = install.dir === null ? '' : `${relative(cwd, install.dir)} (${install.version})`
|
|
@@ -380,7 +414,7 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
|
|
|
380
414
|
)
|
|
381
415
|
} else {
|
|
382
416
|
const upgrade: [string, Check][] = [
|
|
383
|
-
['tsconfig-base: tsconfig.json extends the shipped preset', tsconfigBaseCheck(
|
|
417
|
+
['tsconfig-base: tsconfig.json extends the shipped preset', tsconfigBaseCheck(appDir)],
|
|
384
418
|
[
|
|
385
419
|
'retired-lint-rules: .oxlintrc.json sets no retired basalt/* rule',
|
|
386
420
|
retiredLintRulesCheck(cwd, install),
|
|
@@ -391,7 +425,7 @@ export function doctor(invocationCwd: string = process.cwd(), flags: string[] =
|
|
|
391
425
|
],
|
|
392
426
|
[
|
|
393
427
|
'app-wiring: the entry mounts BasaltProvider and the router tree is generated',
|
|
394
|
-
appWiringCheck(
|
|
428
|
+
appWiringCheck(appDir),
|
|
395
429
|
],
|
|
396
430
|
['exact-peers: installed exact-pinned peers match basalt-ui', exactPeersCheck(cwd, install)],
|
|
397
431
|
[
|
package/src/cli/index.ts
CHANGED
|
@@ -474,6 +474,8 @@ type TemplateVars = {
|
|
|
474
474
|
* `bunx basalt-ui` only as the pre-install fallback.
|
|
475
475
|
*/
|
|
476
476
|
BASALT_BIN: string
|
|
477
|
+
/** The CI gate steps after install: `make check` when the repo owns one, else the built-in set. */
|
|
478
|
+
GATE_STEPS: string
|
|
477
479
|
}
|
|
478
480
|
|
|
479
481
|
// The block markers the AGENTS.md template emits. The begin marker carries the framework version
|
|
@@ -536,6 +538,26 @@ export function basaltBinCommand(install: BasaltInstall, cwd: string): string {
|
|
|
536
538
|
return `${relativePosix(cwd, binDir).replace(/^\.\//, '')}/basalt-ui`
|
|
537
539
|
}
|
|
538
540
|
|
|
541
|
+
/**
|
|
542
|
+
* The consumer owns the CI gate when its Makefile has a `check:` target: `doctor` and `sync --check`
|
|
543
|
+
* stay as steps of their own (drift is never skippable), then `make check` replaces the built-in
|
|
544
|
+
* lint/theme/format steps. Without one, the built-in steps run. Rendered at `{{GATE_STEPS}}`.
|
|
545
|
+
*/
|
|
546
|
+
export function ciGateSteps(cwd: string): string {
|
|
547
|
+
const owned = /^check\s*:/m.test(readIfExists(resolve(cwd, 'Makefile')) ?? '')
|
|
548
|
+
const drift = ["- run: '{{BASALT_BIN}} doctor'", "- run: '{{BASALT_BIN}} sync --check'"]
|
|
549
|
+
const steps = owned
|
|
550
|
+
? [...drift, '- run: make check']
|
|
551
|
+
: [
|
|
552
|
+
'- run: bunx oxlint .',
|
|
553
|
+
drift[0],
|
|
554
|
+
"- run: '{{BASALT_BIN}} check-theme'",
|
|
555
|
+
drift[1],
|
|
556
|
+
"- run: bunx oxfmt {{ROOTS_GLOBS}} '!**/dist/**' --check",
|
|
557
|
+
]
|
|
558
|
+
return steps.join('\n ')
|
|
559
|
+
}
|
|
560
|
+
|
|
539
561
|
/** Build the template-variable map from the framework root + consumer cwd/config. */
|
|
540
562
|
function buildTemplateVars(pkgRoot: string, cwd: string, cfg: BasaltConfig): TemplateVars {
|
|
541
563
|
const install = findBasaltInstall(cwd)
|
|
@@ -548,15 +570,20 @@ function buildTemplateVars(pkgRoot: string, cwd: string, cfg: BasaltConfig): Tem
|
|
|
548
570
|
OXLINT_PRESET_PATH: shippedAssetPath(install, cwd, OXLINT_FILES.preset),
|
|
549
571
|
LEFTHOOK_PRESET_PATH: shippedAssetPath(install, cwd, 'configs/lefthook.yml'),
|
|
550
572
|
BASALT_BIN: basaltBinCommand(install, cwd),
|
|
573
|
+
GATE_STEPS: ciGateSteps(cwd),
|
|
551
574
|
}
|
|
552
575
|
}
|
|
553
576
|
|
|
554
577
|
/** Substitute every `{{KEY}}` placeholder from the variable map; unknown keys are left verbatim. */
|
|
555
578
|
function fillTemplate(tpl: string, vars: TemplateVars): string {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
579
|
+
const fill = (text: string): string =>
|
|
580
|
+
text.replace(/\{\{(\w+)\}\}/g, (m, key: string) => {
|
|
581
|
+
const v = (vars as Record<string, string>)[key]
|
|
582
|
+
return v ?? m
|
|
583
|
+
})
|
|
584
|
+
// The marker is a YAML comment so the template stays valid YAML; GATE_STEPS itself carries
|
|
585
|
+
// placeholders, so it expands before the generic pass.
|
|
586
|
+
return fill(tpl.replace('# {{GATE_STEPS}}', vars.GATE_STEPS))
|
|
560
587
|
}
|
|
561
588
|
|
|
562
589
|
/**
|
|
@@ -654,6 +681,56 @@ export const ROUTER_SEED_DESTS = {
|
|
|
654
681
|
main: 'src/main.tsx',
|
|
655
682
|
} as const
|
|
656
683
|
|
|
684
|
+
/** A router seed's dest, relative to the install root: under the app dir when it is not the root. */
|
|
685
|
+
export function routerSeedDest(appRel: string, seed: keyof typeof ROUTER_SEED_DESTS): string {
|
|
686
|
+
return appRel === '' ? ROUTER_SEED_DESTS[seed] : `${appRel}/${ROUTER_SEED_DESTS[seed]}`
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
/** The app directory a command's app-scoped half targets: `rel` is '' when it is the install root. */
|
|
690
|
+
export type AppResolution = { dir: string; rel: string } | { error: string }
|
|
691
|
+
|
|
692
|
+
/** `dir` relative to `root` without relativePosix's leading `./` ('' for the root itself). */
|
|
693
|
+
const appRelative = (root: string, dir: string): string =>
|
|
694
|
+
relativePosix(root, dir).replace(/^\.\/?/, '')
|
|
695
|
+
|
|
696
|
+
const hasViteEntry = (dir: string): boolean =>
|
|
697
|
+
existsSync(resolve(dir, 'src/main.tsx')) || existsSync(resolve(dir, 'index.html'))
|
|
698
|
+
|
|
699
|
+
/**
|
|
700
|
+
* Where the Vite app lives relative to the install root `root` (manifest, `.claude/`, AGENTS block).
|
|
701
|
+
* ONE resolution for `doctor`, `sync` and `init`, in order: `basalt.app` in the root package.json;
|
|
702
|
+
* else the single workspace package that depends on basalt-ui AND has a Vite entry; else `root`
|
|
703
|
+
* itself (a single-package app). Two such packages is an error — the choice is not inferable.
|
|
704
|
+
*/
|
|
705
|
+
export function resolveAppDir(root: string): AppResolution {
|
|
706
|
+
const self = { dir: root, rel: '' }
|
|
707
|
+
const explicit = readBasaltConfig(root).app
|
|
708
|
+
if (explicit !== undefined) {
|
|
709
|
+
const dir = resolve(root, explicit)
|
|
710
|
+
const rel = appRelative(root, dir)
|
|
711
|
+
if (rel.startsWith('..') || !existsSync(resolve(dir, 'package.json'))) {
|
|
712
|
+
return {
|
|
713
|
+
error: `basalt.app "${explicit}" must be a directory with a package.json inside ${root}.`,
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return dir === root ? self : { dir, rel }
|
|
717
|
+
}
|
|
718
|
+
const candidates = collectWorkspacePackages(root).packages.filter(
|
|
719
|
+
(pkg) => pkg.dir !== root && hasDependency(pkg.dir, 'basalt-ui') && hasViteEntry(pkg.dir),
|
|
720
|
+
)
|
|
721
|
+
if (candidates.length === 0) return self
|
|
722
|
+
const [only] = candidates
|
|
723
|
+
if (candidates.length === 1 && only !== undefined) {
|
|
724
|
+
return { dir: only.dir, rel: appRelative(root, only.dir) }
|
|
725
|
+
}
|
|
726
|
+
const names = candidates.map((pkg) => appRelative(root, pkg.dir)).join(', ')
|
|
727
|
+
return {
|
|
728
|
+
error:
|
|
729
|
+
`${candidates.length} workspace packages depend on basalt-ui and have a Vite entry (${names}) — ` +
|
|
730
|
+
'cannot tell which is the app. Set `basalt.app` (e.g. `"basalt": { "app": "apps/web" }`) in the root package.json.',
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
|
|
657
734
|
/** True when `pkgName` appears in the consumer's dependencies, devDependencies, or peerDependencies. */
|
|
658
735
|
function hasDependency(cwd: string, pkgName: string): boolean {
|
|
659
736
|
try {
|
|
@@ -687,12 +764,18 @@ export type PlacementFlags = {
|
|
|
687
764
|
/** Absolute path of the detected repo root. Meaningful only when `isPackageRepoRoot` is false —
|
|
688
765
|
* otherwise it equals the package dir and is never read. */
|
|
689
766
|
repoRoot: string
|
|
767
|
+
/** The app directory relative to the install root ('' = the root itself); see {@link resolveAppDir}. */
|
|
768
|
+
appRel: string
|
|
769
|
+
/** Non-null when the app directory is ambiguous or invalid: commands refuse rather than guess. */
|
|
770
|
+
appError: string | null
|
|
690
771
|
}
|
|
691
772
|
|
|
692
773
|
/** The default placement — today's single-package-at-repo-root behaviour, unconditionally. */
|
|
693
774
|
export const ROOT_PLACEMENT: PlacementFlags = {
|
|
694
775
|
isPackageRepoRoot: true,
|
|
695
776
|
repoRoot: '',
|
|
777
|
+
appRel: '',
|
|
778
|
+
appError: null,
|
|
696
779
|
}
|
|
697
780
|
|
|
698
781
|
/**
|
|
@@ -725,9 +808,12 @@ export function oxlintBinResolves(dir: string): boolean {
|
|
|
725
808
|
/** Resolve a run's placement flags once, from the consumer cwd. */
|
|
726
809
|
export function resolvePlacement(cwd: string): PlacementFlags {
|
|
727
810
|
const repoRoot = findRepoRoot(cwd)
|
|
811
|
+
const app = resolveAppDir(cwd)
|
|
728
812
|
return {
|
|
729
813
|
isPackageRepoRoot: repoRoot === cwd,
|
|
730
814
|
repoRoot,
|
|
815
|
+
appRel: 'error' in app ? '' : app.rel,
|
|
816
|
+
appError: 'error' in app ? app.error : null,
|
|
731
817
|
}
|
|
732
818
|
}
|
|
733
819
|
|
|
@@ -854,7 +940,7 @@ export function managedFiles(placement: PlacementFlags = ROOT_PLACEMENT): Manage
|
|
|
854
940
|
|
|
855
941
|
/** TanStack Router root route with QueryClient context wiring (seed — consumer-owned after init). */
|
|
856
942
|
const rootRoute: ManagedFile = {
|
|
857
|
-
dest:
|
|
943
|
+
dest: routerSeedDest(placement.appRel, 'root'),
|
|
858
944
|
mode: 'seed',
|
|
859
945
|
source: 'agent/templates/__root.tpl',
|
|
860
946
|
render: (ctx) => {
|
|
@@ -866,7 +952,7 @@ export function managedFiles(placement: PlacementFlags = ROOT_PLACEMENT): Manage
|
|
|
866
952
|
|
|
867
953
|
/** The leaf `NAV` module `__root.tsx` imports (seed — consumer-owned after init). */
|
|
868
954
|
const navModule: ManagedFile = {
|
|
869
|
-
dest:
|
|
955
|
+
dest: routerSeedDest(placement.appRel, 'nav'),
|
|
870
956
|
mode: 'seed',
|
|
871
957
|
source: 'agent/templates/nav.tpl',
|
|
872
958
|
render: (ctx) => readSource(ctx.pkgRoot, 'agent/templates/nav.tpl'),
|
|
@@ -874,7 +960,7 @@ export function managedFiles(placement: PlacementFlags = ROOT_PLACEMENT): Manage
|
|
|
874
960
|
|
|
875
961
|
/** The 2.0 entry: mounts the router and declares the `Register` augmentation `defineNav` needs. */
|
|
876
962
|
const mainEntry: ManagedFile = {
|
|
877
|
-
dest:
|
|
963
|
+
dest: routerSeedDest(placement.appRel, 'main'),
|
|
878
964
|
mode: 'seed',
|
|
879
965
|
source: 'agent/templates/main.tpl',
|
|
880
966
|
render: (ctx) => readSource(ctx.pkgRoot, 'agent/templates/main.tpl'),
|
package/src/cli/init.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
declaredProfile,
|
|
14
14
|
readBasaltConfig,
|
|
15
15
|
ROUTER_SEED_DESTS,
|
|
16
|
+
routerSeedDest,
|
|
16
17
|
findBasaltInstall,
|
|
17
18
|
managedFiles,
|
|
18
19
|
mergeOxlintExtends,
|
|
@@ -82,6 +83,12 @@ export function init(cwd: string = process.cwd(), scaffoldFlags: ScaffoldFlags =
|
|
|
82
83
|
)
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
const appError = resolvePlacement(cwd).appError
|
|
87
|
+
if (appError !== null) {
|
|
88
|
+
console.error(`✖ basalt-ui init: ${appError}`)
|
|
89
|
+
return 1
|
|
90
|
+
}
|
|
91
|
+
|
|
85
92
|
const rootsState = reconcileRoots(cwd, { write: true })
|
|
86
93
|
|
|
87
94
|
// Built AFTER the roots patch — every roots-derived template variable reads the key just written.
|
|
@@ -120,7 +127,9 @@ export function init(cwd: string = process.cwd(), scaffoldFlags: ScaffoldFlags =
|
|
|
120
127
|
const placement = resolvePlacement(cwd)
|
|
121
128
|
// A tokens-only consumer has no use for the Mantine/React scaffolds (doctor skips them there too).
|
|
122
129
|
const tokensOnly = declaredProfile(readBasaltConfig(cwd), []) === 'tokens-only'
|
|
123
|
-
const routerSeeds: string[] =
|
|
130
|
+
const routerSeeds: string[] = (
|
|
131
|
+
Object.keys(ROUTER_SEED_DESTS) as (keyof typeof ROUTER_SEED_DESTS)[]
|
|
132
|
+
).map((seed) => routerSeedDest(placement.appRel, seed))
|
|
124
133
|
const files = managedFiles(placement).filter((f) => !(tokensOnly && routerSeeds.includes(f.dest)))
|
|
125
134
|
|
|
126
135
|
const writtenFiles: string[] = []
|