@scaffold-ui-kit/preview 0.0.0-stage → 2.3.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/LICENSE +21 -0
- package/README.md +1868 -2
- package/application/host/bootstrap.ts +43 -0
- package/application/host/main.ts +31 -0
- package/application/host/shell-host.ts +47 -0
- package/application/index.html +12 -0
- package/application/lib/base-url.ts +15 -0
- package/application/lib/no-code-block.ts +7 -0
- package/application/lib/no-setup.ts +3 -0
- package/application/lib/no-views.ts +6 -0
- package/application/preview/code/code.html +12 -0
- package/application/preview/code/main.ts +89 -0
- package/application/preview/globals.ts +254 -0
- package/application/preview/html/main.ts +93 -0
- package/application/preview/html/preview.html +12 -0
- package/application/preview/react/main.tsx +146 -0
- package/application/preview/react/preview.html +12 -0
- package/application/preview/svelte/Preview.svelte +104 -0
- package/application/preview/svelte/main.ts +13 -0
- package/application/preview/svelte/preview.html +12 -0
- package/application/preview/svelte/story-props.ts +172 -0
- package/application/preview/view/main.ts +124 -0
- package/application/preview/view/view.html +12 -0
- package/application/preview/vue/main.ts +145 -0
- package/application/preview/vue/preview.html +12 -0
- package/application/shells/charcell/components/Application/index.css +37 -0
- package/application/shells/charcell/components/Application/index.tsx +65 -0
- package/application/shells/charcell/components/Bar/index.css +68 -0
- package/application/shells/charcell/components/Bar/index.tsx +60 -0
- package/application/shells/charcell/components/CodePane/index.css +60 -0
- package/application/shells/charcell/components/CodePane/index.tsx +113 -0
- package/application/shells/charcell/components/ComponentStage/index.css +26 -0
- package/application/shells/charcell/components/ComponentStage/index.tsx +110 -0
- package/application/shells/charcell/components/Control/index.css +20 -0
- package/application/shells/charcell/components/Control/index.tsx +118 -0
- package/application/shells/charcell/components/CopyLink/index.tsx +35 -0
- package/application/shells/charcell/components/Description/index.css +6 -0
- package/application/shells/charcell/components/Description/index.tsx +24 -0
- package/application/shells/charcell/components/EditorAddDialog/index.css +9 -0
- package/application/shells/charcell/components/EditorAddDialog/index.tsx +158 -0
- package/application/shells/charcell/components/EditorPanel/index.css +48 -0
- package/application/shells/charcell/components/EditorPanel/index.tsx +496 -0
- package/application/shells/charcell/components/EventsRow/index.css +42 -0
- package/application/shells/charcell/components/EventsRow/index.tsx +68 -0
- package/application/shells/charcell/components/HeadingRow/index.css +11 -0
- package/application/shells/charcell/components/HeadingRow/index.tsx +16 -0
- package/application/shells/charcell/components/Hint/index.css +16 -0
- package/application/shells/charcell/components/Hint/index.tsx +81 -0
- package/application/shells/charcell/components/HtmlWarnings/index.tsx +45 -0
- package/application/shells/charcell/components/KeyHints/index.css +27 -0
- package/application/shells/charcell/components/KeyHints/index.tsx +106 -0
- package/application/shells/charcell/components/KitCodeBlock/index.css +55 -0
- package/application/shells/charcell/components/KitCodeBlock/index.tsx +18 -0
- package/application/shells/charcell/components/Markdown/index.tsx +16 -0
- package/application/shells/charcell/components/Navigation/index.css +13 -0
- package/application/shells/charcell/components/Navigation/index.tsx +98 -0
- package/application/shells/charcell/components/NavigationSplit/index.css +16 -0
- package/application/shells/charcell/components/NavigationSplit/index.tsx +82 -0
- package/application/shells/charcell/components/OptionControls/index.tsx +93 -0
- package/application/shells/charcell/components/PaneStatus/index.css +7 -0
- package/application/shells/charcell/components/PaneStatus/index.tsx +47 -0
- package/application/shells/charcell/components/PaneWidth/index.tsx +45 -0
- package/application/shells/charcell/components/PreviewGrid/index.css +39 -0
- package/application/shells/charcell/components/PreviewGrid/index.tsx +344 -0
- package/application/shells/charcell/components/PreviewPane/index.css +127 -0
- package/application/shells/charcell/components/PreviewPane/index.tsx +182 -0
- package/application/shells/charcell/components/PromptBar/index.tsx +133 -0
- package/application/shells/charcell/components/ScenarioRow/index.css +36 -0
- package/application/shells/charcell/components/ScenarioRow/index.tsx +71 -0
- package/application/shells/charcell/components/Select/index.tsx +48 -0
- package/application/shells/charcell/components/SlotOutlet/index.css +7 -0
- package/application/shells/charcell/components/SlotOutlet/index.tsx +25 -0
- package/application/shells/charcell/components/StageBar/index.css +7 -0
- package/application/shells/charcell/components/StageBar/index.tsx +109 -0
- package/application/shells/charcell/components/StageSplit/index.css +11 -0
- package/application/shells/charcell/components/StageSplit/index.tsx +99 -0
- package/application/shells/charcell/components/StatusBar/index.tsx +30 -0
- package/application/shells/charcell/components/ViewControls/index.css +15 -0
- package/application/shells/charcell/components/ViewControls/index.tsx +58 -0
- package/application/shells/charcell/components/ViewHeading/index.css +13 -0
- package/application/shells/charcell/components/ViewHeading/index.tsx +8 -0
- package/application/shells/charcell/components/ViewStage/index.css +9 -0
- package/application/shells/charcell/components/ViewStage/index.tsx +25 -0
- package/application/shells/charcell/components/WrapToggle/index.tsx +27 -0
- package/application/shells/charcell/configuration.ts +45 -0
- package/application/shells/charcell/controller-context.ts +27 -0
- package/application/shells/charcell/fit-labels.ts +42 -0
- package/application/shells/charcell/html-warnings.ts +18 -0
- package/application/shells/charcell/index.tsx +82 -0
- package/application/shells/charcell/page-loading.html +57 -0
- package/application/shells/charcell/placement.ts +24 -0
- package/application/shells/charcell/shell-options.ts +23 -0
- package/application/shells/charcell/shell.css +37 -0
- package/application/shells/charcell/shell.tsx +25 -0
- package/application/shells/charcell/split-reset.ts +47 -0
- package/application/shells/charcell/stacked-rows.ts +117 -0
- package/application/shells/charcell/stage-toggles.ts +93 -0
- package/application/types/manifest.d.ts +6 -0
- package/application/types/preview-configuration.d.ts +10 -0
- package/application/types/preview-shell.d.ts +7 -0
- package/application/types/react-registry.d.ts +15 -0
- package/application/types/vue-registry.d.ts +6 -0
- package/dist/build.d.ts +24 -0
- package/dist/build.js +34 -0
- package/dist/check.d.ts +48 -0
- package/dist/check.js +781 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +88 -0
- package/dist/context.d.ts +179 -0
- package/dist/context.js +783 -0
- package/dist/controller/catalog.d.ts +339 -0
- package/dist/controller/catalog.js +367 -0
- package/dist/controller/create-preview-controller.d.ts +532 -0
- package/dist/controller/create-preview-controller.js +1707 -0
- package/dist/controller/editor-tree.d.ts +161 -0
- package/dist/controller/editor-tree.js +373 -0
- package/dist/controller/html-source.d.ts +62 -0
- package/dist/controller/html-source.js +61 -0
- package/dist/controller/index.d.ts +9 -0
- package/dist/controller/index.js +12 -0
- package/dist/controller/persistence.d.ts +191 -0
- package/dist/controller/persistence.js +315 -0
- package/dist/controller/regeneration.d.ts +30 -0
- package/dist/controller/regeneration.js +27 -0
- package/dist/controller/source.d.ts +80 -0
- package/dist/controller/source.js +236 -0
- package/dist/controller/store.d.ts +27 -0
- package/dist/controller/store.js +40 -0
- package/dist/declarations/application/shells/charcell/components/Application/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Bar/index.d.ts +11 -0
- package/dist/declarations/application/shells/charcell/components/CodePane/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/ComponentStage/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Control/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/CopyLink/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/Description/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/EditorAddDialog/index.d.ts +6 -0
- package/dist/declarations/application/shells/charcell/components/EditorPanel/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/EventsRow/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/HeadingRow/index.d.ts +6 -0
- package/dist/declarations/application/shells/charcell/components/Hint/index.d.ts +11 -0
- package/dist/declarations/application/shells/charcell/components/HtmlWarnings/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/KeyHints/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/KitCodeBlock/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/Markdown/index.d.ts +3 -0
- package/dist/declarations/application/shells/charcell/components/Navigation/index.d.ts +8 -0
- package/dist/declarations/application/shells/charcell/components/NavigationSplit/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/OptionControls/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/PaneStatus/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/PaneWidth/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/PreviewGrid/index.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/components/PreviewPane/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/PromptBar/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/ScenarioRow/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/Select/index.d.ts +16 -0
- package/dist/declarations/application/shells/charcell/components/SlotOutlet/index.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/components/StageBar/index.d.ts +2 -0
- package/dist/declarations/application/shells/charcell/components/StageSplit/index.d.ts +17 -0
- package/dist/declarations/application/shells/charcell/components/StatusBar/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/components/ViewControls/index.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/components/ViewHeading/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/ViewStage/index.d.ts +4 -0
- package/dist/declarations/application/shells/charcell/components/WrapToggle/index.d.ts +1 -0
- package/dist/declarations/application/shells/charcell/configuration.d.ts +31 -0
- package/dist/declarations/application/shells/charcell/controller-context.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/fit-labels.d.ts +18 -0
- package/dist/declarations/application/shells/charcell/html-warnings.d.ts +5 -0
- package/dist/declarations/application/shells/charcell/index.d.ts +39 -0
- package/dist/declarations/application/shells/charcell/placement.d.ts +7 -0
- package/dist/declarations/application/shells/charcell/shell-options.d.ts +15 -0
- package/dist/declarations/application/shells/charcell/shell.d.ts +9 -0
- package/dist/declarations/application/shells/charcell/split-reset.d.ts +30 -0
- package/dist/declarations/application/shells/charcell/stacked-rows.d.ts +58 -0
- package/dist/declarations/application/shells/charcell/stage-toggles.d.ts +28 -0
- package/dist/declarations/src/controller/catalog.d.ts +339 -0
- package/dist/declarations/src/controller/create-preview-controller.d.ts +532 -0
- package/dist/declarations/src/controller/editor-tree.d.ts +161 -0
- package/dist/declarations/src/controller/html-source.d.ts +62 -0
- package/dist/declarations/src/controller/index.d.ts +9 -0
- package/dist/declarations/src/controller/persistence.d.ts +191 -0
- package/dist/declarations/src/controller/regeneration.d.ts +30 -0
- package/dist/declarations/src/controller/source.d.ts +80 -0
- package/dist/declarations/src/controller/store.d.ts +27 -0
- package/dist/declarations/src/emit/common.d.ts +103 -0
- package/dist/declarations/src/emit/manifest.d.ts +138 -0
- package/dist/declarations/src/emit/source.d.ts +87 -0
- package/dist/declarations/src/lib/jsx-roots.d.ts +22 -0
- package/dist/declarations/src/lib/module-of-source.d.ts +18 -0
- package/dist/declarations/src/lib/naming.d.ts +29 -0
- package/dist/declarations/src/lib/react-spelling.d.ts +68 -0
- package/dist/declarations/src/lib/scenario-story.d.ts +41 -0
- package/dist/declarations/src/lib/slot-fill.d.ts +15 -0
- package/dist/declarations/src/lib/types.d.ts +741 -0
- package/dist/declarations/src/options.d.ts +224 -0
- package/dist/declarations/src/protocol.d.ts +541 -0
- package/dist/declarations/src/react-view.d.ts +22 -0
- package/dist/declarations/src/react.d.ts +17 -0
- package/dist/declarations/src/shell.d.ts +99 -0
- package/dist/declarations/src/views.d.ts +152 -0
- package/dist/development-server.d.ts +121 -0
- package/dist/development-server.js +331 -0
- package/dist/emit/common.d.ts +103 -0
- package/dist/emit/common.js +170 -0
- package/dist/emit/html-source.d.ts +46 -0
- package/dist/emit/html-source.js +322 -0
- package/dist/emit/manifest.d.ts +138 -0
- package/dist/emit/manifest.js +74 -0
- package/dist/emit/react.d.ts +16 -0
- package/dist/emit/react.js +210 -0
- package/dist/emit/scratch.d.ts +40 -0
- package/dist/emit/scratch.js +116 -0
- package/dist/emit/source.d.ts +87 -0
- package/dist/emit/source.js +789 -0
- package/dist/emit/svelte.d.ts +9 -0
- package/dist/emit/svelte.js +177 -0
- package/dist/emit/vue.d.ts +2 -0
- package/dist/emit/vue.js +152 -0
- package/dist/enumerate.d.ts +2 -0
- package/dist/enumerate.js +162 -0
- package/dist/generate.d.ts +43 -0
- package/dist/generate.js +302 -0
- package/dist/html-stylesheet.d.ts +43 -0
- package/dist/html-stylesheet.js +145 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +21 -0
- package/dist/landing.d.ts +16 -0
- package/dist/landing.js +45 -0
- package/dist/lib/exported-types.d.ts +42 -0
- package/dist/lib/exported-types.js +86 -0
- package/dist/lib/html-attributes.d.ts +2 -0
- package/dist/lib/html-attributes.js +111 -0
- package/dist/lib/html-warnings.d.ts +19 -0
- package/dist/lib/html-warnings.js +79 -0
- package/dist/lib/jsx-roots.d.ts +22 -0
- package/dist/lib/jsx-roots.js +243 -0
- package/dist/lib/module-of-source.d.ts +18 -0
- package/dist/lib/module-of-source.js +24 -0
- package/dist/lib/naming.d.ts +29 -0
- package/dist/lib/naming.js +56 -0
- package/dist/lib/parse-type.d.ts +32 -0
- package/dist/lib/parse-type.js +103 -0
- package/dist/lib/react-spelling.d.ts +68 -0
- package/dist/lib/react-spelling.js +197 -0
- package/dist/lib/scenario-story.d.ts +41 -0
- package/dist/lib/scenario-story.js +84 -0
- package/dist/lib/slot-fill.d.ts +15 -0
- package/dist/lib/slot-fill.js +92 -0
- package/dist/lib/template-files.d.ts +14 -0
- package/dist/lib/template-files.js +25 -0
- package/dist/lib/types.d.ts +741 -0
- package/dist/lib/types.js +13 -0
- package/dist/lib/void-elements.d.ts +7 -0
- package/dist/lib/void-elements.js +13 -0
- package/dist/load.d.ts +37 -0
- package/dist/load.js +82 -0
- package/dist/model.d.ts +44 -0
- package/dist/model.js +464 -0
- package/dist/options.d.ts +224 -0
- package/dist/options.js +223 -0
- package/dist/prop-types.d.ts +26 -0
- package/dist/prop-types.js +102 -0
- package/dist/props-from.d.ts +84 -0
- package/dist/props-from.js +510 -0
- package/dist/protocol.d.ts +541 -0
- package/dist/protocol.js +819 -0
- package/dist/react-view.d.ts +22 -0
- package/dist/react-view.js +36 -0
- package/dist/react.d.ts +17 -0
- package/dist/react.js +23 -0
- package/dist/sass-importers.d.ts +26 -0
- package/dist/sass-importers.js +32 -0
- package/dist/shell-acceptance.d.ts +21 -0
- package/dist/shell-acceptance.js +37 -0
- package/dist/shell-page.d.ts +36 -0
- package/dist/shell-page.js +90 -0
- package/dist/shell-peers.d.ts +38 -0
- package/dist/shell-peers.js +61 -0
- package/dist/shell.d.ts +99 -0
- package/dist/shell.js +52 -0
- package/dist/shells/charcell/page.d.ts +8 -0
- package/dist/shells/charcell/page.js +69 -0
- package/dist/story-source.d.ts +113 -0
- package/dist/story-source.js +271 -0
- package/dist/story.d.ts +5 -0
- package/dist/story.js +28 -0
- package/dist/testing.d.ts +218 -0
- package/dist/testing.js +284 -0
- package/dist/views.d.ts +152 -0
- package/dist/views.js +7 -0
- package/dist/vite-configuration.d.ts +114 -0
- package/dist/vite-configuration.js +578 -0
- package/package.json +123 -3
- package/src/build.ts +51 -0
- package/src/check.ts +1062 -0
- package/src/cli.ts +123 -0
- package/src/context.ts +1168 -0
- package/src/controller/catalog.ts +719 -0
- package/src/controller/create-preview-controller.ts +2683 -0
- package/src/controller/editor-tree.ts +530 -0
- package/src/controller/html-source.ts +114 -0
- package/src/controller/index.ts +105 -0
- package/src/controller/persistence.ts +403 -0
- package/src/controller/regeneration.ts +55 -0
- package/src/controller/source.ts +333 -0
- package/src/controller/store.ts +55 -0
- package/src/development-server.ts +442 -0
- package/src/emit/common.ts +201 -0
- package/src/emit/html-source.ts +402 -0
- package/src/emit/manifest.ts +241 -0
- package/src/emit/react.ts +263 -0
- package/src/emit/scratch.ts +175 -0
- package/src/emit/source.ts +1094 -0
- package/src/emit/svelte.ts +230 -0
- package/src/emit/vue.ts +194 -0
- package/src/enumerate.ts +220 -0
- package/src/generate.ts +443 -0
- package/src/html-stylesheet.ts +173 -0
- package/src/index.ts +60 -0
- package/src/landing.ts +58 -0
- package/src/lib/exported-types.ts +114 -0
- package/src/lib/html-attributes.ts +113 -0
- package/src/lib/html-warnings.ts +81 -0
- package/src/lib/jsx-roots.ts +238 -0
- package/src/lib/module-of-source.ts +26 -0
- package/src/lib/naming.ts +65 -0
- package/src/lib/parse-type.ts +143 -0
- package/src/lib/react-spelling.ts +235 -0
- package/src/lib/scenario-story.ts +97 -0
- package/src/lib/slot-fill.ts +102 -0
- package/src/lib/template-files.ts +31 -0
- package/src/lib/types.ts +778 -0
- package/src/lib/void-elements.ts +15 -0
- package/src/load.ts +144 -0
- package/src/model.ts +568 -0
- package/src/options.ts +435 -0
- package/src/prop-types.ts +133 -0
- package/src/props-from.ts +678 -0
- package/src/protocol.ts +1135 -0
- package/src/react-view.ts +45 -0
- package/src/react.ts +34 -0
- package/src/sass-importers.ts +45 -0
- package/src/shell-acceptance.ts +45 -0
- package/src/shell-page.ts +133 -0
- package/src/shell-peers.ts +70 -0
- package/src/shell.ts +194 -0
- package/src/shells/charcell/page.ts +82 -0
- package/src/story-source.ts +391 -0
- package/src/story.ts +42 -0
- package/src/testing.ts +497 -0
- package/src/views.ts +183 -0
- package/src/vite-configuration.ts +675 -0
package/src/context.ts
ADDED
|
@@ -0,0 +1,1168 @@
|
|
|
1
|
+
// Load and validate the kit configuration's `preview` block. The build leaves
|
|
2
|
+
// the block alone - this package owns it: the kit configuration is found and
|
|
3
|
+
// read by scaffold-ui-kit's own resolver (`scaffold-ui-kit.ts` or
|
|
4
|
+
// `scaffold-ui-kit.json`), and the block is validated by hand (the errors
|
|
5
|
+
// name the offending key) and resolved into absolute paths. Kit-authored TS
|
|
6
|
+
// modules (templates, story files, manual specs, views) are imported through
|
|
7
|
+
// jiti, so the built CLI compiles them on the fly as the build compiles
|
|
8
|
+
// templates.
|
|
9
|
+
|
|
10
|
+
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
import { dirname, isAbsolute, resolve } from 'node:path';
|
|
13
|
+
import { createJiti } from 'jiti';
|
|
14
|
+
import type { BemOptions } from '@js-template-engine/extension-bem';
|
|
15
|
+
import type { TailwindOptions } from '@js-template-engine/extension-tailwind';
|
|
16
|
+
import type { StylesheetPlacement } from '@js-template-engine/types';
|
|
17
|
+
import type {
|
|
18
|
+
ConfigurationLookupOptions,
|
|
19
|
+
ResolvedKitConfiguration,
|
|
20
|
+
} from 'scaffold-ui-kit';
|
|
21
|
+
import {
|
|
22
|
+
ALL_TARGETS,
|
|
23
|
+
PREVIEW_BROWSER_KEYS,
|
|
24
|
+
type ModuleImporter,
|
|
25
|
+
type PreviewBrowserConfiguration,
|
|
26
|
+
type PreviewConfiguration,
|
|
27
|
+
type HandWrittenMapping,
|
|
28
|
+
type PreviewPaneWidth,
|
|
29
|
+
type PreviewShellSetting,
|
|
30
|
+
type PreviewSourceConfiguration,
|
|
31
|
+
type Target,
|
|
32
|
+
} from './lib/types.js';
|
|
33
|
+
import { isPaneWidth, MAXIMUM_PANE_WIDTH } from './controller/persistence.js';
|
|
34
|
+
|
|
35
|
+
export interface ResolvedSource extends PreviewSourceConfiguration {
|
|
36
|
+
/** Absolute (resolved against the kit root). */
|
|
37
|
+
templatesDirectoryAbsolute?: string;
|
|
38
|
+
manualModuleAbsolute?: string;
|
|
39
|
+
storiesDirectoryAbsolute?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A package of a kit built into one package per target (the kit
|
|
44
|
+
* configuration's `packages`), other than the kit root's own.
|
|
45
|
+
*/
|
|
46
|
+
export interface KitLayoutPackage {
|
|
47
|
+
/** The target the package is built for, or `styles` for the package of
|
|
48
|
+
* the kit's stylesheets. */
|
|
49
|
+
role: Target | 'styles';
|
|
50
|
+
/** The package name. */
|
|
51
|
+
name: string;
|
|
52
|
+
/** The package's directory, absolute. */
|
|
53
|
+
directoryAbsolute: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export interface PreviewContext {
|
|
57
|
+
/** The kit's root (where its package.json lives). */
|
|
58
|
+
kitRoot: string;
|
|
59
|
+
/**
|
|
60
|
+
* The kit's other packages, when its configuration builds it into one
|
|
61
|
+
* package per target (`packages`): the styles package first,
|
|
62
|
+
* then each target's. Absent for a kit built as one package.
|
|
63
|
+
*/
|
|
64
|
+
kitPackages?: KitLayoutPackage[];
|
|
65
|
+
/** The kit configuration file, absolute. */
|
|
66
|
+
configurationPath: string;
|
|
67
|
+
/** The kit's own modules a `.ts` configuration loaded beside its file. */
|
|
68
|
+
configurationModules: string[];
|
|
69
|
+
/** What reading the kit configuration warns of, such as its old file
|
|
70
|
+
* name. */
|
|
71
|
+
warnings: string[];
|
|
72
|
+
/** The kit's name (the kit configuration's `name`). */
|
|
73
|
+
kitName: string;
|
|
74
|
+
/** The kit's version (its package.json `version`), when it has one. */
|
|
75
|
+
kitVersion?: string;
|
|
76
|
+
/**
|
|
77
|
+
* The scripted targets the kit builds (the kit configuration's `targets`, its
|
|
78
|
+
* `html` target left out), in the preview's order; all three when the kit
|
|
79
|
+
* names none. A component renders only in these, whatever its own targets.
|
|
80
|
+
*/
|
|
81
|
+
targets: Target[];
|
|
82
|
+
/** Whether the kit builds html (`html` among the kit configuration's
|
|
83
|
+
* `targets`), so the preview shows an html pane beside the other targets'. */
|
|
84
|
+
buildsHtml?: boolean;
|
|
85
|
+
/** The mode the kit builds its Svelte target in (the kit configuration's
|
|
86
|
+
* `svelte.mode`), `legacy` when it names none. */
|
|
87
|
+
svelteMode: 'legacy' | 'runes';
|
|
88
|
+
/** The generated tree (`<kitRoot>/.preview-generated`). */
|
|
89
|
+
generatedRoot: string;
|
|
90
|
+
/** The static-build output (`<kitRoot>/.preview-static`). */
|
|
91
|
+
staticRoot: string;
|
|
92
|
+
/** The preview block, with `options` and `themes` defaulted to none and
|
|
93
|
+
* `controlHints.colorTokens`' unset choice as `null`. */
|
|
94
|
+
configuration: PreviewConfiguration &
|
|
95
|
+
Pick<PreviewBrowserConfiguration, 'options' | 'themes' | 'canvas'>;
|
|
96
|
+
sources: ResolvedSource[];
|
|
97
|
+
/** Absolute path of the kit's custom-views module, when registered. */
|
|
98
|
+
viewsModuleAbsolute?: string;
|
|
99
|
+
/** Absolute path of the kit's code-block module, when it names one. */
|
|
100
|
+
codeBlockModuleAbsolute?: string;
|
|
101
|
+
/** Absolute path of the shell module `preview.shell` names. */
|
|
102
|
+
shellModuleAbsolute: string;
|
|
103
|
+
/** Absolute path of the shell's page module, when it has one: the module
|
|
104
|
+
* `preview.shell.page` names, or the one its package ships. */
|
|
105
|
+
shellPageAbsolute?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Absolute path of the shell shipped with the preview, when the kit's
|
|
108
|
+
* shell is it: named as it is, or through a module of the kit's that
|
|
109
|
+
* imports it to configure it (`charcellShell({ ... })`). Absent under a
|
|
110
|
+
* shell of the kit's own.
|
|
111
|
+
*/
|
|
112
|
+
charcellShellAbsolute?: string;
|
|
113
|
+
/** The kit's `omitPattern`, compiled. */
|
|
114
|
+
omitPattern?: RegExp;
|
|
115
|
+
importModule: ModuleImporter;
|
|
116
|
+
/** The kit's styling settings, which the html source renders with. */
|
|
117
|
+
styling: KitStyling;
|
|
118
|
+
/**
|
|
119
|
+
* The kit's shared modules templates import by specifier (`add.shared`
|
|
120
|
+
* code modules the kit's `package.json` exports), resolved against the
|
|
121
|
+
* kit root. The html
|
|
122
|
+
* source puts their exports in scope, so an expression calling a kit helper
|
|
123
|
+
* evaluates.
|
|
124
|
+
*/
|
|
125
|
+
sharedModulesAbsolute: string[];
|
|
126
|
+
/**
|
|
127
|
+
* The framework-free entries of `@scaffold-ui-kit/helpers` (`/runtime`
|
|
128
|
+
* and `/zag`) as installed for the kit, resolved from the kit root or one
|
|
129
|
+
* of its packages. The html source puts their exports in scope beside the
|
|
130
|
+
* shared modules', so an expression calling a helper evaluates. Empty when
|
|
131
|
+
* the kit does not have the package.
|
|
132
|
+
*/
|
|
133
|
+
helperModulesAbsolute: string[];
|
|
134
|
+
/**
|
|
135
|
+
* The kit's shared modules with the package path its built files import
|
|
136
|
+
* each by (the one the kit's `package.json` exports give it, or an older
|
|
137
|
+
* configuration's `specifier`), the source relative to the kit root as
|
|
138
|
+
* written.
|
|
139
|
+
*/
|
|
140
|
+
sharedSpecifiers: { source: string; specifier: string }[];
|
|
141
|
+
/**
|
|
142
|
+
* The kit's Sass load paths (the kit configuration's top-level
|
|
143
|
+
* `loadPaths`), resolved against the kit root, which the kit's build
|
|
144
|
+
* compiles its SCSS with.
|
|
145
|
+
*/
|
|
146
|
+
sassLoadPathsAbsolute: string[];
|
|
147
|
+
/**
|
|
148
|
+
* The parts of each compound whose root has no template, such as a
|
|
149
|
+
* hand-written component (the kit configuration's `parts`), by root name.
|
|
150
|
+
*/
|
|
151
|
+
parts: Record<string, string[]>;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** The styling keys of the kit configuration the html source reads. */
|
|
155
|
+
export interface KitStyling {
|
|
156
|
+
/** The styling extensions, in configuration order. */
|
|
157
|
+
extensions: Array<'bem' | 'tailwind'>;
|
|
158
|
+
/** Where each component's stylesheet is placed (the kit configuration's
|
|
159
|
+
* `stylesheetPlacement`), `in-file` when it names none. */
|
|
160
|
+
placement: StylesheetPlacement;
|
|
161
|
+
/** The BEM extension's options (the kit configuration's `bem` block). */
|
|
162
|
+
bem?: BemOptions;
|
|
163
|
+
/** The Tailwind extension's options (the kit configuration's `tailwind`
|
|
164
|
+
* block). */
|
|
165
|
+
tailwind?: TailwindOptions;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function fail(message: string): never {
|
|
169
|
+
throw new Error(`[preview] ${message}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The keys the `preview` block takes. */
|
|
173
|
+
const PREVIEW_KEYS = [
|
|
174
|
+
'shell',
|
|
175
|
+
'sources',
|
|
176
|
+
'options',
|
|
177
|
+
'canvas',
|
|
178
|
+
'themes',
|
|
179
|
+
'mount',
|
|
180
|
+
'viewCanvas',
|
|
181
|
+
'paneWidths',
|
|
182
|
+
'controlHints',
|
|
183
|
+
'labels',
|
|
184
|
+
'viewsModule',
|
|
185
|
+
'codeBlock',
|
|
186
|
+
'setupModules',
|
|
187
|
+
'htmlStylesheets',
|
|
188
|
+
'aliases',
|
|
189
|
+
'scssLoadPaths',
|
|
190
|
+
'optimizeDepsExclude',
|
|
191
|
+
'dedupe',
|
|
192
|
+
'omitPattern',
|
|
193
|
+
'handWrittenComponents',
|
|
194
|
+
'port',
|
|
195
|
+
'host',
|
|
196
|
+
'persistence',
|
|
197
|
+
'landing',
|
|
198
|
+
] as const satisfies readonly (keyof PreviewConfiguration)[];
|
|
199
|
+
|
|
200
|
+
/** The keys a `preview.sources` entry takes. */
|
|
201
|
+
const SOURCE_KEYS = [
|
|
202
|
+
'id',
|
|
203
|
+
'label',
|
|
204
|
+
'order',
|
|
205
|
+
'moduleBase',
|
|
206
|
+
'templatesDirectory',
|
|
207
|
+
'manualModule',
|
|
208
|
+
'storiesDirectory',
|
|
209
|
+
] as const satisfies readonly (keyof PreviewSourceConfiguration)[];
|
|
210
|
+
|
|
211
|
+
/** The keys `preview.codeBlock` takes. */
|
|
212
|
+
const CODE_BLOCK_KEYS = [
|
|
213
|
+
'module',
|
|
214
|
+
'framework',
|
|
215
|
+
] as const satisfies readonly (keyof NonNullable<
|
|
216
|
+
PreviewConfiguration['codeBlock']
|
|
217
|
+
>)[];
|
|
218
|
+
|
|
219
|
+
/** The keys `preview.landing` takes. */
|
|
220
|
+
const LANDING_KEYS = ['view', 'component', 'scenario'] as const;
|
|
221
|
+
|
|
222
|
+
/** The keys the object form of a `handWrittenComponents` entry takes. */
|
|
223
|
+
const HAND_WRITTEN_KEYS = [
|
|
224
|
+
'template',
|
|
225
|
+
'slots',
|
|
226
|
+
] as const satisfies readonly (keyof Exclude<HandWrittenMapping, string>)[];
|
|
227
|
+
|
|
228
|
+
/** What to write for a key the `preview` block does not take, where one thing is meant. */
|
|
229
|
+
const PREVIEW_KEY_REPLACEMENTS: ReadonlyMap<string, string> = new Map([
|
|
230
|
+
['scopes', "'sources'"],
|
|
231
|
+
[
|
|
232
|
+
'configurationModule',
|
|
233
|
+
"the keys of the 'preview' block, such as options, canvas, themes and mount",
|
|
234
|
+
],
|
|
235
|
+
]);
|
|
236
|
+
|
|
237
|
+
/** What to write for a key a `preview.sources` entry does not take, where one thing is meant. */
|
|
238
|
+
const SOURCE_KEY_REPLACEMENTS: ReadonlyMap<string, string> = new Map([
|
|
239
|
+
['sidecarsDirectory', "'storiesDirectory'"],
|
|
240
|
+
]);
|
|
241
|
+
|
|
242
|
+
/** Whether a value is a plain object, whose keys can be checked. */
|
|
243
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
244
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Each key of an object that is not among the keys it takes, with its path
|
|
249
|
+
* and the keys the object takes, as the scaffolder reports the
|
|
250
|
+
* configuration's own keys, or what to use instead where `replacements`
|
|
251
|
+
* names it.
|
|
252
|
+
*/
|
|
253
|
+
function unknownKeyProblems(
|
|
254
|
+
value: object,
|
|
255
|
+
allowed: readonly string[],
|
|
256
|
+
path: string,
|
|
257
|
+
description: string,
|
|
258
|
+
replacements: ReadonlyMap<string, string> = new Map()
|
|
259
|
+
): string[] {
|
|
260
|
+
return Object.keys(value)
|
|
261
|
+
.filter((key) => !allowed.includes(key))
|
|
262
|
+
.map((key) => {
|
|
263
|
+
const replacement = replacements.get(key);
|
|
264
|
+
return `unknown key '${path}.${key}'; ${
|
|
265
|
+
replacement === undefined
|
|
266
|
+
? `${description} takes ${allowed.join(', ')}`
|
|
267
|
+
: `use ${replacement}`
|
|
268
|
+
}`;
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* What is wrong with a list of pane widths, or nothing: it must be a list of
|
|
274
|
+
* `{ label, width }`, each label a non-empty string and each width a whole
|
|
275
|
+
* number of CSS pixels from 1 to the most a pane is laid out at, no label
|
|
276
|
+
* and no width named twice.
|
|
277
|
+
*
|
|
278
|
+
* @param paneWidths - The list, as the kit gives it.
|
|
279
|
+
* @param name - Where the kit gives it, for the message.
|
|
280
|
+
* @returns The problem, or `undefined` when the list is sound.
|
|
281
|
+
* @example
|
|
282
|
+
* ```ts
|
|
283
|
+
* paneWidthsProblem([{ label: 'small', width: 375 }], 'paneWidths'); // undefined
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export function paneWidthsProblem(
|
|
287
|
+
paneWidths: unknown,
|
|
288
|
+
name: string
|
|
289
|
+
): string | undefined {
|
|
290
|
+
if (
|
|
291
|
+
!Array.isArray(paneWidths) ||
|
|
292
|
+
paneWidths.some(
|
|
293
|
+
(paneWidth: unknown) =>
|
|
294
|
+
!isPlainObject(paneWidth) ||
|
|
295
|
+
typeof paneWidth.label !== 'string' ||
|
|
296
|
+
paneWidth.label.trim() === '' ||
|
|
297
|
+
!isPaneWidth(paneWidth.width)
|
|
298
|
+
)
|
|
299
|
+
)
|
|
300
|
+
return `${name} must be a list of { label, width }: a label to pick it by, and a width in CSS pixels, a whole number from 1 to ${MAXIMUM_PANE_WIDTH}`;
|
|
301
|
+
const widths = paneWidths as PreviewPaneWidth[];
|
|
302
|
+
if (
|
|
303
|
+
new Set(widths.map((paneWidth) => paneWidth.label)).size !== widths.length
|
|
304
|
+
)
|
|
305
|
+
return `${name} must not name a label twice`;
|
|
306
|
+
if (
|
|
307
|
+
new Set(widths.map((paneWidth) => paneWidth.width)).size !== widths.length
|
|
308
|
+
)
|
|
309
|
+
return `${name} must not name a width twice`;
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** What is wrong with the shape of `preview.landing`, or nothing. */
|
|
314
|
+
function landingShapeProblem(landing: unknown): string | undefined {
|
|
315
|
+
const shape =
|
|
316
|
+
"preview.landing must be { view } or { component, scenario? }: a view's id, or a story's id and, if it is not to open on its baseline, a scenario's, as the preview's address names them";
|
|
317
|
+
if (!isPlainObject(landing)) return shape;
|
|
318
|
+
const candidate: Record<string, unknown> = landing;
|
|
319
|
+
const named = (key: (typeof LANDING_KEYS)[number]) =>
|
|
320
|
+
typeof candidate[key] === 'string' && candidate[key] !== '';
|
|
321
|
+
if ('view' in candidate) {
|
|
322
|
+
if (!named('view') || 'component' in candidate || 'scenario' in candidate)
|
|
323
|
+
return shape;
|
|
324
|
+
return undefined;
|
|
325
|
+
}
|
|
326
|
+
if (!named('component')) return shape;
|
|
327
|
+
if ('scenario' in candidate && !named('scenario')) return shape;
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function validateSource(
|
|
332
|
+
source: unknown,
|
|
333
|
+
index: number
|
|
334
|
+
): PreviewSourceConfiguration {
|
|
335
|
+
const candidate = source as Partial<PreviewSourceConfiguration>;
|
|
336
|
+
if (!candidate || typeof candidate !== 'object')
|
|
337
|
+
fail(`preview.sources[${index}] must be an object`);
|
|
338
|
+
for (const key of ['id', 'label', 'moduleBase'] as const) {
|
|
339
|
+
if (typeof candidate[key] !== 'string' || !candidate[key]) {
|
|
340
|
+
fail(`preview.sources[${index}].${key} must be a non-empty string`);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
if (typeof candidate.order !== 'number')
|
|
344
|
+
fail(`preview.sources[${index}].order must be a number`);
|
|
345
|
+
return candidate as PreviewSourceConfiguration;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* What is wrong with a `preview.shell` that is set, or `undefined` when it
|
|
350
|
+
* is a specifier or a `{ module, page }` of specifiers.
|
|
351
|
+
*/
|
|
352
|
+
function shellSettingProblem(setting: unknown): string | undefined {
|
|
353
|
+
if (typeof setting === 'string') return undefined;
|
|
354
|
+
const shape =
|
|
355
|
+
"preview.shell must be a package specifier or a module path, or an object with 'module' and optional 'page'";
|
|
356
|
+
if (typeof setting !== 'object' || setting === null || Array.isArray(setting))
|
|
357
|
+
return shape;
|
|
358
|
+
const { module, page, ...rest } = setting as Record<string, unknown>;
|
|
359
|
+
const unknown = Object.keys(rest);
|
|
360
|
+
if (unknown.length > 0)
|
|
361
|
+
return `unknown key 'preview.shell.${unknown[0]}'; preview.shell takes module and page`;
|
|
362
|
+
if (typeof module !== 'string' || !module)
|
|
363
|
+
return "preview.shell.module is required: the shell's module, as a package specifier or a module path relative to the kit root";
|
|
364
|
+
if (page !== undefined && (typeof page !== 'string' || !page))
|
|
365
|
+
return "preview.shell.page must be a package specifier or a module path relative to the kit root: the shell's page module";
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/** The shell shipped with the preview, named when a kit configures none. */
|
|
370
|
+
export const CHARCELL_SHELL = '@scaffold-ui-kit/preview/shells/charcell';
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Where `preview.shell` points: a module in the kit when it is
|
|
374
|
+
* written as a path (relative, absolute, or one that exists under the kit
|
|
375
|
+
* root, like the other module settings), else a package resolved from the
|
|
376
|
+
* kit's root the way the kit's own imports are.
|
|
377
|
+
*/
|
|
378
|
+
function resolveShellModule(kitRoot: string, shell: string): string {
|
|
379
|
+
if (isModulePath(kitRoot, shell)) {
|
|
380
|
+
const inKit = resolve(kitRoot, shell);
|
|
381
|
+
if (!existsSync(inKit)) {
|
|
382
|
+
fail(`preview.shell names a module that does not exist: ${inKit}`);
|
|
383
|
+
}
|
|
384
|
+
return inKit;
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
return createRequire(resolve(kitRoot, 'package.json')).resolve(shell);
|
|
388
|
+
} catch {
|
|
389
|
+
return fail(
|
|
390
|
+
`preview.shell '${shell}' is neither a module in the kit nor a package installed in it (resolved from ${kitRoot}). ` +
|
|
391
|
+
`Install the package the shell comes from, or give a path relative to the kit root. ` +
|
|
392
|
+
`The shell shipped with the preview is '${CHARCELL_SHELL}'; it needs charcell and @zag-js/react installed.`
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/** Whether a module setting is written as a path: relative, absolute, or
|
|
398
|
+
* one that exists under the kit root. Anything else is a package specifier. */
|
|
399
|
+
function isModulePath(kitRoot: string, setting: string): boolean {
|
|
400
|
+
return (
|
|
401
|
+
setting.startsWith('.') ||
|
|
402
|
+
isAbsolute(setting) ||
|
|
403
|
+
existsSync(resolve(kitRoot, setting))
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Whether the package a specifier names lists `<specifier>/<subpath>` among
|
|
409
|
+
* its exports, read from its `package.json`; `false` when the package or its
|
|
410
|
+
* exports cannot be read.
|
|
411
|
+
*/
|
|
412
|
+
function packageExports(
|
|
413
|
+
fromKit: NodeJS.Require,
|
|
414
|
+
specifier: string,
|
|
415
|
+
subpath: string
|
|
416
|
+
): boolean {
|
|
417
|
+
const [scopeOrName, name] = specifier.split('/');
|
|
418
|
+
const packageName = specifier.startsWith('@')
|
|
419
|
+
? `${scopeOrName}/${name}`
|
|
420
|
+
: scopeOrName!;
|
|
421
|
+
try {
|
|
422
|
+
const manifest = fromKit(`${packageName}/package.json`) as {
|
|
423
|
+
exports?: unknown;
|
|
424
|
+
};
|
|
425
|
+
const key = `.${specifier.slice(packageName.length)}/${subpath}`;
|
|
426
|
+
return (
|
|
427
|
+
typeof manifest.exports === 'object' &&
|
|
428
|
+
manifest.exports !== null &&
|
|
429
|
+
key in manifest.exports
|
|
430
|
+
);
|
|
431
|
+
} catch {
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Where the shell's page module is: the one `preview.shell.page` names, a
|
|
438
|
+
* path in the kit or a package resolved from the kit's root, as the shell
|
|
439
|
+
* itself is; or, with none named, the module at the shell's specifier
|
|
440
|
+
* followed by `/page`, when the shell is named as a package specifier and
|
|
441
|
+
* its package has one. A shell with no page has none, and the host page
|
|
442
|
+
* starts empty.
|
|
443
|
+
*
|
|
444
|
+
* @param kitRoot - The kit's root directory.
|
|
445
|
+
* @param shell - The shell module, as `preview.shell` names it.
|
|
446
|
+
* @param page - The page module, as `preview.shell.page` names it.
|
|
447
|
+
* @returns The page module's absolute path, or `undefined` with no page.
|
|
448
|
+
*/
|
|
449
|
+
function resolveShellPage(
|
|
450
|
+
kitRoot: string,
|
|
451
|
+
shell: string,
|
|
452
|
+
page: string | undefined
|
|
453
|
+
): string | undefined {
|
|
454
|
+
const fromKit = createRequire(resolve(kitRoot, 'package.json'));
|
|
455
|
+
if (page === undefined) {
|
|
456
|
+
if (isModulePath(kitRoot, shell)) return undefined;
|
|
457
|
+
try {
|
|
458
|
+
return fromKit.resolve(`${shell}/page`);
|
|
459
|
+
} catch (error) {
|
|
460
|
+
// A package that exports no page has none; one whose export cannot
|
|
461
|
+
// be loaded is a mistake worth naming.
|
|
462
|
+
const code = (error as NodeJS.ErrnoException).code;
|
|
463
|
+
if (
|
|
464
|
+
code === 'ERR_PACKAGE_PATH_NOT_EXPORTED' ||
|
|
465
|
+
(code === 'MODULE_NOT_FOUND' && !packageExports(fromKit, shell, 'page'))
|
|
466
|
+
) {
|
|
467
|
+
return undefined;
|
|
468
|
+
}
|
|
469
|
+
return fail(
|
|
470
|
+
`the shell '${shell}' exports a page at '${shell}/page' that does not resolve: ${(error as Error).message}`
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (isModulePath(kitRoot, page)) {
|
|
475
|
+
const inKit = resolve(kitRoot, page);
|
|
476
|
+
if (!existsSync(inKit)) {
|
|
477
|
+
fail(`preview.shell.page names a module that does not exist: ${inKit}`);
|
|
478
|
+
}
|
|
479
|
+
return inKit;
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
return fromKit.resolve(page);
|
|
483
|
+
} catch {
|
|
484
|
+
return fail(
|
|
485
|
+
`preview.shell.page '${page}' is neither a module in the kit nor a package installed in it (resolved from ${kitRoot}). ` +
|
|
486
|
+
`Give a path relative to the kit root, or the page module of an installed shell, such as '${CHARCELL_SHELL}/page'.`
|
|
487
|
+
);
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/** Whether a module's source imports the shell shipped with the preview. */
|
|
492
|
+
const IMPORTS_CHARCELL_SHELL = new RegExp(
|
|
493
|
+
`(?:from\\s*|import\\s*\\(\\s*|require\\s*\\(\\s*)['"]${CHARCELL_SHELL.replace(/\./g, '\\.')}['"]`
|
|
494
|
+
);
|
|
495
|
+
|
|
496
|
+
/**
|
|
497
|
+
* Where the shell shipped with the preview is, when the kit's shell is it:
|
|
498
|
+
* the shell module itself when `preview.shell` names it, or the module it
|
|
499
|
+
* resolves to from a module of the kit's that imports it to configure it.
|
|
500
|
+
* `undefined` under a shell of the kit's own, or when the kit's module
|
|
501
|
+
* imports it from a package the kit has not installed.
|
|
502
|
+
*/
|
|
503
|
+
function resolveCharcellShell(
|
|
504
|
+
shell: string,
|
|
505
|
+
shellModuleAbsolute: string
|
|
506
|
+
): string | undefined {
|
|
507
|
+
if (shell === CHARCELL_SHELL) return shellModuleAbsolute;
|
|
508
|
+
let source: string;
|
|
509
|
+
try {
|
|
510
|
+
source = readFileSync(shellModuleAbsolute, 'utf8');
|
|
511
|
+
} catch {
|
|
512
|
+
return undefined;
|
|
513
|
+
}
|
|
514
|
+
if (!IMPORTS_CHARCELL_SHELL.test(source)) return undefined;
|
|
515
|
+
try {
|
|
516
|
+
return createRequire(shellModuleAbsolute).resolve(CHARCELL_SHELL);
|
|
517
|
+
} catch {
|
|
518
|
+
return undefined;
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function readPackageVersion(kitRoot: string): string | undefined {
|
|
523
|
+
const packagePath = resolve(kitRoot, 'package.json');
|
|
524
|
+
if (!existsSync(packagePath)) return undefined;
|
|
525
|
+
// A package.json that is not JSON has no version to read; `check` reports it.
|
|
526
|
+
let manifest: { version?: unknown } | null;
|
|
527
|
+
try {
|
|
528
|
+
manifest = JSON.parse(readFileSync(packagePath, 'utf8')) as {
|
|
529
|
+
version?: unknown;
|
|
530
|
+
} | null;
|
|
531
|
+
} catch {
|
|
532
|
+
return undefined;
|
|
533
|
+
}
|
|
534
|
+
const version = manifest?.version;
|
|
535
|
+
return typeof version === 'string' ? version : undefined;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** The kit configuration's keys the preview reads beside its block. */
|
|
539
|
+
type KitConfigurationRead = Pick<KitStyling, 'bem' | 'tailwind'> & {
|
|
540
|
+
name?: string;
|
|
541
|
+
stylesheetPlacement?: StylesheetPlacement;
|
|
542
|
+
targets?: unknown;
|
|
543
|
+
styling?: Array<'bem' | 'tailwind'>;
|
|
544
|
+
preview?: unknown;
|
|
545
|
+
add?: { shared?: Array<{ source?: unknown; specifier?: unknown }> };
|
|
546
|
+
packages?: unknown;
|
|
547
|
+
loadPaths?: unknown;
|
|
548
|
+
svelte?: { mode?: unknown };
|
|
549
|
+
parts?: unknown;
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* The kit configuration's `parts`, each root's list of part names; what is
|
|
554
|
+
* not a list of names is left out, as the kit's build reports it.
|
|
555
|
+
*/
|
|
556
|
+
function partsOf(value: unknown): Record<string, string[]> {
|
|
557
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
558
|
+
return {};
|
|
559
|
+
const parts: Record<string, string[]> = {};
|
|
560
|
+
for (const [root, rootParts] of Object.entries(value)) {
|
|
561
|
+
if (Array.isArray(rootParts))
|
|
562
|
+
parts[root] = rootParts.filter(
|
|
563
|
+
(part): part is string => typeof part === 'string' && part !== ''
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
return parts;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** scaffold-ui-kit, loaded as the CommonJS package it is. */
|
|
570
|
+
function scaffoldUiKit(): typeof import('scaffold-ui-kit') {
|
|
571
|
+
return createRequire(import.meta.url)(
|
|
572
|
+
'scaffold-ui-kit'
|
|
573
|
+
) as typeof import('scaffold-ui-kit');
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
/** The resolver and module loader of scaffold-ui-kit the preview reads a
|
|
577
|
+
* kit's configuration with. */
|
|
578
|
+
type KitResolver = Pick<
|
|
579
|
+
typeof import('scaffold-ui-kit'),
|
|
580
|
+
'resolveKitConfiguration'
|
|
581
|
+
>;
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* The scaffold-ui-kit the kit has installed, when it has the resolver, so
|
|
585
|
+
* the preview reads the configuration as the kit's own build does; else the
|
|
586
|
+
* preview's own copy.
|
|
587
|
+
*/
|
|
588
|
+
function kitResolver(kitRoot: string): KitResolver {
|
|
589
|
+
const own = scaffoldUiKit();
|
|
590
|
+
let installed: Partial<KitResolver> | undefined;
|
|
591
|
+
try {
|
|
592
|
+
installed = createRequire(resolve(kitRoot, 'package.json'))(
|
|
593
|
+
'scaffold-ui-kit'
|
|
594
|
+
) as Partial<KitResolver>;
|
|
595
|
+
} catch {
|
|
596
|
+
return own;
|
|
597
|
+
}
|
|
598
|
+
return typeof installed.resolveKitConfiguration === 'function'
|
|
599
|
+
? (installed as KitResolver)
|
|
600
|
+
: own;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* The problems that keep a value from reaching the browser as JSON, each
|
|
605
|
+
* naming where in the value it sits: a function, `undefined` in a list, a
|
|
606
|
+
* number JSON has no form for, and an object other than a plain one or a
|
|
607
|
+
* list (a regular expression, a date, a map).
|
|
608
|
+
*
|
|
609
|
+
* @param value - The value.
|
|
610
|
+
* @param path - Where the value sits, as `preview.options`.
|
|
611
|
+
* @returns One message per problem.
|
|
612
|
+
* @example
|
|
613
|
+
* ```ts
|
|
614
|
+
* jsonProblems({ apply: () => {} }, 'preview.options[0]');
|
|
615
|
+
* // ["preview.options[0].apply is a function, which can't be sent to the browser as JSON"]
|
|
616
|
+
* ```
|
|
617
|
+
*/
|
|
618
|
+
export function jsonProblems(
|
|
619
|
+
value: unknown,
|
|
620
|
+
path: string,
|
|
621
|
+
ancestors: WeakMap<object, string> = new WeakMap()
|
|
622
|
+
): string[] {
|
|
623
|
+
const unsendable = (what: string) => [
|
|
624
|
+
`${path} is ${what}, which can't be sent to the browser as JSON`,
|
|
625
|
+
];
|
|
626
|
+
if (value === null) return [];
|
|
627
|
+
switch (typeof value) {
|
|
628
|
+
case 'string':
|
|
629
|
+
case 'boolean':
|
|
630
|
+
return [];
|
|
631
|
+
case 'number':
|
|
632
|
+
return Number.isFinite(value) ? [] : unsendable(String(value));
|
|
633
|
+
case 'undefined':
|
|
634
|
+
return unsendable('undefined');
|
|
635
|
+
case 'function':
|
|
636
|
+
return unsendable('a function');
|
|
637
|
+
case 'bigint':
|
|
638
|
+
case 'symbol':
|
|
639
|
+
return unsendable(`a ${typeof value}`);
|
|
640
|
+
}
|
|
641
|
+
const object = value as object;
|
|
642
|
+
// An object inside itself has no end to write out.
|
|
643
|
+
const outer = ancestors.get(object);
|
|
644
|
+
if (outer !== undefined)
|
|
645
|
+
return [
|
|
646
|
+
`${path} is ${outer} again, a cycle that can't be sent to the browser as JSON`,
|
|
647
|
+
];
|
|
648
|
+
const prototype = Object.getPrototypeOf(object);
|
|
649
|
+
if (
|
|
650
|
+
!Array.isArray(object) &&
|
|
651
|
+
prototype !== Object.prototype &&
|
|
652
|
+
prototype !== null
|
|
653
|
+
)
|
|
654
|
+
return unsendable(`${object.constructor?.name ?? 'an object'} object`);
|
|
655
|
+
ancestors.set(object, path);
|
|
656
|
+
const problems = Array.isArray(object)
|
|
657
|
+
? object.flatMap((entry, index) =>
|
|
658
|
+
jsonProblems(entry, `${path}[${index}]`, ancestors)
|
|
659
|
+
)
|
|
660
|
+
: Object.entries(object as Record<string, unknown>)
|
|
661
|
+
.filter(([, entry]) => entry !== undefined)
|
|
662
|
+
.flatMap(([key, entry]) =>
|
|
663
|
+
jsonProblems(entry, `${path}.${key}`, ancestors)
|
|
664
|
+
);
|
|
665
|
+
// Only an ancestor makes a cycle; the same object twice side by side is fine.
|
|
666
|
+
ancestors.delete(object);
|
|
667
|
+
return problems;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
/**
|
|
671
|
+
* Finds the kit configuration (`--configuration`, the kit's `package.json`
|
|
672
|
+
* pointer, or `scaffold-ui-kit.ts` or `scaffold-ui-kit.json` at the kit
|
|
673
|
+
* root) and resolves the preview context from its `preview` block.
|
|
674
|
+
*
|
|
675
|
+
* @param kitRoot - The kit root.
|
|
676
|
+
* @param options - The configuration file a command was given, if any.
|
|
677
|
+
* @returns The context.
|
|
678
|
+
*/
|
|
679
|
+
export function loadPreviewContext(
|
|
680
|
+
kitRoot: string,
|
|
681
|
+
options: ConfigurationLookupOptions = {}
|
|
682
|
+
): PreviewContext {
|
|
683
|
+
const resolver = kitResolver(kitRoot);
|
|
684
|
+
let resolved: ResolvedKitConfiguration;
|
|
685
|
+
try {
|
|
686
|
+
resolved = resolver.resolveKitConfiguration(kitRoot, options);
|
|
687
|
+
} catch (error) {
|
|
688
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
689
|
+
}
|
|
690
|
+
const configurationPath = resolved.file.path;
|
|
691
|
+
const configurationName = resolved.file.name;
|
|
692
|
+
const warnings = [...resolved.warnings];
|
|
693
|
+
const configurationModules = new Set(resolved.modules ?? []);
|
|
694
|
+
const kitConfiguration = resolved.configuration as KitConfigurationRead;
|
|
695
|
+
|
|
696
|
+
const authoredBlock: unknown = kitConfiguration.preview;
|
|
697
|
+
if (!isPlainObject(authoredBlock)) {
|
|
698
|
+
fail(
|
|
699
|
+
`${configurationName} has no preview block${
|
|
700
|
+
'extensions' in (kitConfiguration as object)
|
|
701
|
+
? "; the preview's settings go in a top-level 'preview' block, not 'extensions'"
|
|
702
|
+
: ''
|
|
703
|
+
}`
|
|
704
|
+
);
|
|
705
|
+
}
|
|
706
|
+
const authored = authoredBlock as unknown as PreviewConfiguration;
|
|
707
|
+
// A misspelled key would otherwise go unread without a word: every unknown
|
|
708
|
+
// key fails, the block's and each object's inside it, all named at once.
|
|
709
|
+
const unknownKeys = [
|
|
710
|
+
...unknownKeyProblems(
|
|
711
|
+
authored,
|
|
712
|
+
PREVIEW_KEYS,
|
|
713
|
+
'preview',
|
|
714
|
+
"the 'preview' block",
|
|
715
|
+
PREVIEW_KEY_REPLACEMENTS
|
|
716
|
+
),
|
|
717
|
+
...(Array.isArray(authored.sources) ? authored.sources : []).flatMap(
|
|
718
|
+
(source: unknown, index) =>
|
|
719
|
+
source && typeof source === 'object' && !Array.isArray(source)
|
|
720
|
+
? unknownKeyProblems(
|
|
721
|
+
source,
|
|
722
|
+
SOURCE_KEYS,
|
|
723
|
+
`preview.sources[${index}]`,
|
|
724
|
+
'a source',
|
|
725
|
+
SOURCE_KEY_REPLACEMENTS
|
|
726
|
+
)
|
|
727
|
+
: []
|
|
728
|
+
),
|
|
729
|
+
...(isPlainObject(authored.codeBlock)
|
|
730
|
+
? unknownKeyProblems(
|
|
731
|
+
authored.codeBlock,
|
|
732
|
+
CODE_BLOCK_KEYS,
|
|
733
|
+
'preview.codeBlock',
|
|
734
|
+
'a code block'
|
|
735
|
+
)
|
|
736
|
+
: []),
|
|
737
|
+
...(isPlainObject(authored.setupModules)
|
|
738
|
+
? unknownKeyProblems(
|
|
739
|
+
authored.setupModules,
|
|
740
|
+
ALL_TARGETS,
|
|
741
|
+
'preview.setupModules',
|
|
742
|
+
"the 'setupModules' map"
|
|
743
|
+
)
|
|
744
|
+
: []),
|
|
745
|
+
...(isPlainObject(authored.landing)
|
|
746
|
+
? unknownKeyProblems(
|
|
747
|
+
authored.landing,
|
|
748
|
+
LANDING_KEYS,
|
|
749
|
+
'preview.landing',
|
|
750
|
+
'the landing entry'
|
|
751
|
+
)
|
|
752
|
+
: []),
|
|
753
|
+
...(isPlainObject(authored.handWrittenComponents)
|
|
754
|
+
? Object.entries(authored.handWrittenComponents).flatMap(
|
|
755
|
+
([name, mapping]: [string, unknown]) =>
|
|
756
|
+
isPlainObject(mapping)
|
|
757
|
+
? unknownKeyProblems(
|
|
758
|
+
mapping,
|
|
759
|
+
HAND_WRITTEN_KEYS,
|
|
760
|
+
`preview.handWrittenComponents.${name}`,
|
|
761
|
+
'a hand-written component'
|
|
762
|
+
)
|
|
763
|
+
: []
|
|
764
|
+
)
|
|
765
|
+
: []),
|
|
766
|
+
];
|
|
767
|
+
if (unknownKeys.length > 0) fail(unknownKeys.join('\n[preview] '));
|
|
768
|
+
if (!Array.isArray(authored.sources) || authored.sources.length === 0) {
|
|
769
|
+
fail('preview.sources must be a non-empty array');
|
|
770
|
+
}
|
|
771
|
+
authored.sources.forEach(validateSource);
|
|
772
|
+
const shellSetting = authored.shell as unknown;
|
|
773
|
+
if (shellSetting === undefined || shellSetting === '') {
|
|
774
|
+
fail(
|
|
775
|
+
`preview.shell is required: the shell that renders the preview's interface, as a package specifier or a module path relative to the kit root. ` +
|
|
776
|
+
`To use the shell shipped with the preview, install charcell and @zag-js/react and set shell: '${CHARCELL_SHELL}'.`
|
|
777
|
+
);
|
|
778
|
+
}
|
|
779
|
+
const shellProblem = shellSettingProblem(shellSetting);
|
|
780
|
+
if (shellProblem !== undefined) fail(shellProblem);
|
|
781
|
+
const shell =
|
|
782
|
+
typeof shellSetting === 'string'
|
|
783
|
+
? shellSetting
|
|
784
|
+
: (shellSetting as PreviewShellSetting).module;
|
|
785
|
+
const shellPage =
|
|
786
|
+
typeof shellSetting === 'string'
|
|
787
|
+
? undefined
|
|
788
|
+
: (shellSetting as PreviewShellSetting).page;
|
|
789
|
+
const canvas = authored.canvas as unknown;
|
|
790
|
+
if (
|
|
791
|
+
canvas !== undefined &&
|
|
792
|
+
(!isPlainObject(canvas) ||
|
|
793
|
+
typeof canvas.namePrefix !== 'string' ||
|
|
794
|
+
canvas.namePrefix === '')
|
|
795
|
+
) {
|
|
796
|
+
fail(
|
|
797
|
+
"preview.canvas must be { namePrefix, pageBackground?, extraCss? }: namePrefix the prefix for the names the preview sets in each frame, such as 'my-ui-kit'"
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
for (const key of ['options', 'themes'] as const) {
|
|
801
|
+
if (authored[key] !== undefined && !Array.isArray(authored[key]))
|
|
802
|
+
fail(`preview.${key} must be a list`);
|
|
803
|
+
}
|
|
804
|
+
for (const key of [
|
|
805
|
+
'mount',
|
|
806
|
+
'viewCanvas',
|
|
807
|
+
'controlHints',
|
|
808
|
+
'labels',
|
|
809
|
+
] as const) {
|
|
810
|
+
if (authored[key] !== undefined && !isPlainObject(authored[key]))
|
|
811
|
+
fail(`preview.${key} must be an object`);
|
|
812
|
+
}
|
|
813
|
+
// What the preview's pages read reaches them as JSON, so it must be JSON.
|
|
814
|
+
const unsendable = PREVIEW_BROWSER_KEYS.filter(
|
|
815
|
+
(key) => authored[key] !== undefined
|
|
816
|
+
).flatMap((key) => jsonProblems(authored[key], `preview.${key}`));
|
|
817
|
+
if (unsendable.length > 0) fail(unsendable.join('\n[preview] '));
|
|
818
|
+
const colorTokens = authored.controlHints?.colorTokens;
|
|
819
|
+
if (colorTokens !== undefined && !Array.isArray(colorTokens))
|
|
820
|
+
fail('preview.controlHints.colorTokens must be a list of tokens');
|
|
821
|
+
const configuration: PreviewContext['configuration'] = {
|
|
822
|
+
...authored,
|
|
823
|
+
options: authored.options ?? [],
|
|
824
|
+
themes: authored.themes ?? [],
|
|
825
|
+
// The kit's name, made safe for the names it prefixes, by default.
|
|
826
|
+
canvas: authored.canvas ?? {
|
|
827
|
+
namePrefix: (kitConfiguration.name ?? 'kit')
|
|
828
|
+
.replace(/[^A-Za-z0-9_-]/g, '-')
|
|
829
|
+
.replace(/^-+|-+$/g, ''),
|
|
830
|
+
},
|
|
831
|
+
shell,
|
|
832
|
+
...(authored.controlHints
|
|
833
|
+
? {
|
|
834
|
+
controlHints: {
|
|
835
|
+
...authored.controlHints,
|
|
836
|
+
...(colorTokens
|
|
837
|
+
? { colorTokens: colorTokens.map((token) => token ?? null) }
|
|
838
|
+
: {}),
|
|
839
|
+
},
|
|
840
|
+
}
|
|
841
|
+
: {}),
|
|
842
|
+
};
|
|
843
|
+
let omitPattern: RegExp | undefined;
|
|
844
|
+
if (configuration.omitPattern !== undefined) {
|
|
845
|
+
try {
|
|
846
|
+
omitPattern = new RegExp(configuration.omitPattern);
|
|
847
|
+
} catch {
|
|
848
|
+
fail(
|
|
849
|
+
`preview.omitPattern must be a regular expression: ${String(configuration.omitPattern)}`
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
const handWritten = configuration.handWrittenComponents;
|
|
855
|
+
if (
|
|
856
|
+
handWritten !== undefined &&
|
|
857
|
+
(typeof handWritten !== 'object' ||
|
|
858
|
+
handWritten === null ||
|
|
859
|
+
Array.isArray(handWritten) ||
|
|
860
|
+
Object.values(handWritten).some(
|
|
861
|
+
(value) =>
|
|
862
|
+
typeof value !== 'string' &&
|
|
863
|
+
(typeof value !== 'object' ||
|
|
864
|
+
value === null ||
|
|
865
|
+
typeof value.template !== 'string' ||
|
|
866
|
+
(value.slots !== undefined &&
|
|
867
|
+
(typeof value.slots !== 'object' ||
|
|
868
|
+
Object.values(value.slots).some(
|
|
869
|
+
(slot) => typeof slot !== 'string'
|
|
870
|
+
))))
|
|
871
|
+
))
|
|
872
|
+
) {
|
|
873
|
+
fail(
|
|
874
|
+
"preview.handWrittenComponents must map each component name to 'content', a template name, or { template, slots }"
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
const dedupe = configuration.dedupe;
|
|
879
|
+
if (
|
|
880
|
+
dedupe !== undefined &&
|
|
881
|
+
(!Array.isArray(dedupe) ||
|
|
882
|
+
dedupe.some((name) => typeof name !== 'string' || name === ''))
|
|
883
|
+
) {
|
|
884
|
+
fail('preview.dedupe must be a list of package names');
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
const htmlStylesheets = configuration.htmlStylesheets;
|
|
888
|
+
if (
|
|
889
|
+
htmlStylesheets !== undefined &&
|
|
890
|
+
(!Array.isArray(htmlStylesheets) ||
|
|
891
|
+
htmlStylesheets.some((path) => typeof path !== 'string' || path === ''))
|
|
892
|
+
) {
|
|
893
|
+
fail(
|
|
894
|
+
'preview.htmlStylesheets must be a list of stylesheet paths, relative to the kit root'
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
const codeBlock = configuration.codeBlock;
|
|
899
|
+
if (
|
|
900
|
+
codeBlock !== undefined &&
|
|
901
|
+
(typeof codeBlock !== 'object' ||
|
|
902
|
+
codeBlock === null ||
|
|
903
|
+
typeof codeBlock.module !== 'string' ||
|
|
904
|
+
codeBlock.module === '' ||
|
|
905
|
+
(codeBlock.framework !== undefined &&
|
|
906
|
+
!['react', 'vue', 'svelte', 'dom'].includes(codeBlock.framework)))
|
|
907
|
+
) {
|
|
908
|
+
fail(
|
|
909
|
+
"preview.codeBlock must be { module, framework? }: a module path relative to the kit root, and 'react', 'vue', 'svelte' or 'dom'"
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
const persistence = configuration.persistence;
|
|
914
|
+
if (
|
|
915
|
+
persistence !== undefined &&
|
|
916
|
+
persistence !== 'url' &&
|
|
917
|
+
persistence !== 'url-and-local-storage'
|
|
918
|
+
) {
|
|
919
|
+
fail(
|
|
920
|
+
`preview.persistence must be 'url' or 'url-and-local-storage', not ${JSON.stringify(persistence)}`
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
if (configuration.landing !== undefined) {
|
|
925
|
+
const problem = landingShapeProblem(configuration.landing);
|
|
926
|
+
if (problem) fail(problem);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const sources: ResolvedSource[] = [...configuration.sources]
|
|
930
|
+
.sort((a, b) => a.order - b.order)
|
|
931
|
+
.map((source) => {
|
|
932
|
+
const templatesDirectoryAbsolute = source.templatesDirectory
|
|
933
|
+
? resolve(kitRoot, source.templatesDirectory)
|
|
934
|
+
: undefined;
|
|
935
|
+
const storiesDirectoryAbsolute = source.storiesDirectory
|
|
936
|
+
? resolve(kitRoot, source.storiesDirectory)
|
|
937
|
+
: undefined;
|
|
938
|
+
return {
|
|
939
|
+
...source,
|
|
940
|
+
templatesDirectoryAbsolute,
|
|
941
|
+
manualModuleAbsolute: source.manualModule
|
|
942
|
+
? resolve(kitRoot, source.manualModule)
|
|
943
|
+
: undefined,
|
|
944
|
+
storiesDirectoryAbsolute,
|
|
945
|
+
};
|
|
946
|
+
});
|
|
947
|
+
|
|
948
|
+
const kitTargets = kitConfiguration.targets;
|
|
949
|
+
if (
|
|
950
|
+
kitTargets !== undefined &&
|
|
951
|
+
(!Array.isArray(kitTargets) ||
|
|
952
|
+
kitTargets.some((target) => typeof target !== 'string'))
|
|
953
|
+
) {
|
|
954
|
+
fail(`${configurationName} targets must be a list of target names`);
|
|
955
|
+
}
|
|
956
|
+
const targets = kitTargets
|
|
957
|
+
? ALL_TARGETS.filter((target) => (kitTargets as string[]).includes(target))
|
|
958
|
+
: [...ALL_TARGETS];
|
|
959
|
+
|
|
960
|
+
const jiti = createJiti(import.meta.url);
|
|
961
|
+
const importModule: ModuleImporter = (absolutePath) =>
|
|
962
|
+
jiti.import(absolutePath);
|
|
963
|
+
const shellModuleAbsolute = resolveShellModule(kitRoot, shell);
|
|
964
|
+
const shellPageAbsolute = resolveShellPage(kitRoot, shell, shellPage);
|
|
965
|
+
const charcellShellAbsolute = resolveCharcellShell(
|
|
966
|
+
shell,
|
|
967
|
+
shellModuleAbsolute
|
|
968
|
+
);
|
|
969
|
+
if (charcellShellAbsolute !== undefined && shellPageAbsolute === undefined) {
|
|
970
|
+
warnings.push(
|
|
971
|
+
`preview.shell names a module that configures the charcell shell and no page, so the host page starts empty and light until the shell loads; ` +
|
|
972
|
+
`name the shell's page beside it: shell: { module: '${shell}', page: '${CHARCELL_SHELL}/page' }`
|
|
973
|
+
);
|
|
974
|
+
}
|
|
975
|
+
const kitPackages = kitPackagesOf(kitRoot, kitConfiguration.packages);
|
|
976
|
+
const sharedSpecifiers = sharedSpecifiersOf(
|
|
977
|
+
kitRoot,
|
|
978
|
+
kitConfiguration.add?.shared,
|
|
979
|
+
(kitPackages ?? []).map(({ directoryAbsolute }) => directoryAbsolute)
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
return {
|
|
983
|
+
kitRoot,
|
|
984
|
+
...(kitPackages === undefined ? {} : { kitPackages }),
|
|
985
|
+
configurationPath,
|
|
986
|
+
configurationModules: [...configurationModules].filter(
|
|
987
|
+
(module) => module !== configurationPath
|
|
988
|
+
),
|
|
989
|
+
warnings,
|
|
990
|
+
kitName: kitConfiguration.name ?? 'kit',
|
|
991
|
+
kitVersion: readPackageVersion(kitRoot),
|
|
992
|
+
targets,
|
|
993
|
+
buildsHtml: (kitTargets as string[] | undefined)?.includes('html') ?? false,
|
|
994
|
+
svelteMode: kitConfiguration.svelte?.mode === 'runes' ? 'runes' : 'legacy',
|
|
995
|
+
generatedRoot: resolve(kitRoot, '.preview-generated'),
|
|
996
|
+
staticRoot: resolve(kitRoot, '.preview-static'),
|
|
997
|
+
configuration,
|
|
998
|
+
sources,
|
|
999
|
+
viewsModuleAbsolute: configuration.viewsModule
|
|
1000
|
+
? resolve(kitRoot, configuration.viewsModule)
|
|
1001
|
+
: undefined,
|
|
1002
|
+
codeBlockModuleAbsolute: configuration.codeBlock
|
|
1003
|
+
? resolve(kitRoot, configuration.codeBlock.module)
|
|
1004
|
+
: undefined,
|
|
1005
|
+
shellModuleAbsolute: shellModuleAbsolute,
|
|
1006
|
+
...(shellPageAbsolute === undefined ? {} : { shellPageAbsolute }),
|
|
1007
|
+
...(charcellShellAbsolute === undefined ? {} : { charcellShellAbsolute }),
|
|
1008
|
+
omitPattern,
|
|
1009
|
+
importModule,
|
|
1010
|
+
styling: {
|
|
1011
|
+
extensions: kitConfiguration.styling ?? [],
|
|
1012
|
+
placement: kitConfiguration.stylesheetPlacement ?? 'in-file',
|
|
1013
|
+
...(kitConfiguration.bem === undefined
|
|
1014
|
+
? {}
|
|
1015
|
+
: { bem: kitConfiguration.bem }),
|
|
1016
|
+
...(kitConfiguration.tailwind === undefined
|
|
1017
|
+
? {}
|
|
1018
|
+
: { tailwind: kitConfiguration.tailwind }),
|
|
1019
|
+
},
|
|
1020
|
+
parts: partsOf(kitConfiguration.parts),
|
|
1021
|
+
sassLoadPathsAbsolute: (Array.isArray(kitConfiguration.loadPaths)
|
|
1022
|
+
? kitConfiguration.loadPaths
|
|
1023
|
+
: []
|
|
1024
|
+
)
|
|
1025
|
+
.filter((path): path is string => typeof path === 'string')
|
|
1026
|
+
.map((path) => resolve(kitRoot, path)),
|
|
1027
|
+
sharedModulesAbsolute: sharedSpecifiers
|
|
1028
|
+
.filter(({ source }) => /(^|\/)[^./]*$|\.[cm]?[jt]sx?$/.test(source))
|
|
1029
|
+
.map(({ source }) => resolve(kitRoot, source)),
|
|
1030
|
+
sharedSpecifiers,
|
|
1031
|
+
helperModulesAbsolute: helperModulesOf([
|
|
1032
|
+
kitRoot,
|
|
1033
|
+
...(kitPackages ?? []).map(({ directoryAbsolute }) => directoryAbsolute),
|
|
1034
|
+
]),
|
|
1035
|
+
};
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
/** The package whose framework-free entries a story's html has in scope. */
|
|
1039
|
+
const HELPERS_PACKAGE = '@scaffold-ui-kit/helpers';
|
|
1040
|
+
|
|
1041
|
+
/** The entries of `@scaffold-ui-kit/helpers` that import no framework. */
|
|
1042
|
+
const FRAMEWORK_FREE_HELPER_ENTRIES = ['./runtime', './zag'];
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Each framework-free helper entry as the first of the directories that has
|
|
1046
|
+
* the package installed gives it: the package found in `node_modules` from
|
|
1047
|
+
* that directory up, and the entry's file from its `exports` (`import`, else
|
|
1048
|
+
* `default`). Nothing else is searched, so a kit without the package gets
|
|
1049
|
+
* none. An entry the package does not export is left out.
|
|
1050
|
+
*/
|
|
1051
|
+
function helperModulesOf(directories: readonly string[]): string[] {
|
|
1052
|
+
for (const directory of directories) {
|
|
1053
|
+
const packageDirectory = installedPackageDirectory(
|
|
1054
|
+
directory,
|
|
1055
|
+
HELPERS_PACKAGE
|
|
1056
|
+
);
|
|
1057
|
+
if (packageDirectory === undefined) continue;
|
|
1058
|
+
let exportsField: unknown;
|
|
1059
|
+
try {
|
|
1060
|
+
exportsField = (
|
|
1061
|
+
JSON.parse(
|
|
1062
|
+
readFileSync(resolve(packageDirectory, 'package.json'), 'utf8')
|
|
1063
|
+
) as { exports?: unknown }
|
|
1064
|
+
).exports;
|
|
1065
|
+
} catch {
|
|
1066
|
+
continue;
|
|
1067
|
+
}
|
|
1068
|
+
if (!isPlainObject(exportsField)) continue;
|
|
1069
|
+
return FRAMEWORK_FREE_HELPER_ENTRIES.flatMap((entry) => {
|
|
1070
|
+
const target = exportsField[entry];
|
|
1071
|
+
const file =
|
|
1072
|
+
typeof target === 'string'
|
|
1073
|
+
? target
|
|
1074
|
+
: isPlainObject(target)
|
|
1075
|
+
? (target.import ?? target.default)
|
|
1076
|
+
: undefined;
|
|
1077
|
+
return typeof file === 'string'
|
|
1078
|
+
? [realpathSync(resolve(packageDirectory, file))]
|
|
1079
|
+
: [];
|
|
1080
|
+
});
|
|
1081
|
+
}
|
|
1082
|
+
return [];
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* The directory of a package installed for `start`, as Node finds it: in
|
|
1087
|
+
* `node_modules` of `start` or of a directory above it, its links resolved.
|
|
1088
|
+
*/
|
|
1089
|
+
function installedPackageDirectory(
|
|
1090
|
+
start: string,
|
|
1091
|
+
name: string
|
|
1092
|
+
): string | undefined {
|
|
1093
|
+
let directory = resolve(start);
|
|
1094
|
+
for (;;) {
|
|
1095
|
+
const candidate = resolve(directory, 'node_modules', name);
|
|
1096
|
+
if (existsSync(resolve(candidate, 'package.json'))) {
|
|
1097
|
+
return realpathSync(candidate);
|
|
1098
|
+
}
|
|
1099
|
+
const parent = dirname(directory);
|
|
1100
|
+
if (parent === directory) return undefined;
|
|
1101
|
+
directory = parent;
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* The kit's shared modules with the specifier its built files import each
|
|
1107
|
+
* by: the one the kit's `package.json` exports give the module, else the
|
|
1108
|
+
* `specifier` a configuration still names.
|
|
1109
|
+
*/
|
|
1110
|
+
function sharedSpecifiersOf(
|
|
1111
|
+
kitRoot: string,
|
|
1112
|
+
sharedModules: unknown,
|
|
1113
|
+
packageDirectories: readonly string[]
|
|
1114
|
+
): { source: string; specifier: string }[] {
|
|
1115
|
+
const authored = (Array.isArray(sharedModules) ? sharedModules : []).filter(
|
|
1116
|
+
(sharedModule): sharedModule is { source: string; specifier?: unknown } =>
|
|
1117
|
+
isPlainObject(sharedModule) && typeof sharedModule.source === 'string'
|
|
1118
|
+
);
|
|
1119
|
+
const { sharedModulesOf } = scaffoldUiKit();
|
|
1120
|
+
const published = sharedModulesOf(
|
|
1121
|
+
kitRoot,
|
|
1122
|
+
authored.map(({ source }) => ({ source })),
|
|
1123
|
+
packageDirectories
|
|
1124
|
+
);
|
|
1125
|
+
return authored.flatMap((sharedModule, index) => {
|
|
1126
|
+
const specifier =
|
|
1127
|
+
published[index].import ??
|
|
1128
|
+
(typeof sharedModule.specifier === 'string'
|
|
1129
|
+
? sharedModule.specifier
|
|
1130
|
+
: undefined);
|
|
1131
|
+
return specifier === undefined
|
|
1132
|
+
? []
|
|
1133
|
+
: [{ source: sharedModule.source, specifier }];
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
/**
|
|
1138
|
+
* The kit's packages besides the kit root's, from its configuration's
|
|
1139
|
+
* `packages`: the styles package, then each target's, their
|
|
1140
|
+
* directories resolved against the kit root. `undefined` for a kit without
|
|
1141
|
+
* `packages`; an entry without a name and directory is left out, as the
|
|
1142
|
+
* kit's build reports it.
|
|
1143
|
+
*/
|
|
1144
|
+
function kitPackagesOf(
|
|
1145
|
+
kitRoot: string,
|
|
1146
|
+
packages: unknown
|
|
1147
|
+
): KitLayoutPackage[] | undefined {
|
|
1148
|
+
if (!isPlainObject(packages)) return undefined;
|
|
1149
|
+
const roles: Array<KitLayoutPackage['role']> = ['styles', ...ALL_TARGETS];
|
|
1150
|
+
return roles.flatMap((role) => {
|
|
1151
|
+
const entry = packages[role];
|
|
1152
|
+
if (
|
|
1153
|
+
!isPlainObject(entry) ||
|
|
1154
|
+
typeof entry.name !== 'string' ||
|
|
1155
|
+
entry.name === '' ||
|
|
1156
|
+
typeof entry.directory !== 'string' ||
|
|
1157
|
+
entry.directory === ''
|
|
1158
|
+
)
|
|
1159
|
+
return [];
|
|
1160
|
+
return [
|
|
1161
|
+
{
|
|
1162
|
+
role,
|
|
1163
|
+
name: entry.name,
|
|
1164
|
+
directoryAbsolute: resolve(kitRoot, entry.directory),
|
|
1165
|
+
},
|
|
1166
|
+
];
|
|
1167
|
+
});
|
|
1168
|
+
}
|