@dfosco/storyboard-react 4.2.0-beta.1 → 4.2.0-beta.17
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/package.json +5 -4
- package/src/AuthModal/AuthModal.jsx +6 -2
- package/src/BranchBar/BranchBar.jsx +17 -5
- package/src/BranchBar/BranchBar.module.css +11 -2
- package/src/CommandPalette/CommandPalette.jsx +267 -164
- package/src/CommandPalette/command-palette.css +130 -78
- package/src/Icon.jsx +112 -48
- package/src/Viewfinder.jsx +511 -61
- package/src/Viewfinder.module.css +414 -2
- package/src/canvas/CanvasPage.bridge.test.jsx +14 -6
- package/src/canvas/CanvasPage.dragdrop.test.jsx +10 -6
- package/src/canvas/CanvasPage.jsx +157 -174
- package/src/canvas/CanvasPage.module.css +0 -15
- package/src/canvas/CanvasPage.multiselect.test.jsx +10 -6
- package/src/canvas/ConnectorLayer.jsx +5 -5
- package/src/canvas/PageSelector.test.jsx +15 -6
- package/src/canvas/useCanvas.js +1 -1
- package/src/canvas/widgets/ActionWidget.jsx +200 -0
- package/src/canvas/widgets/ActionWidget.module.css +122 -0
- package/src/canvas/widgets/FigmaEmbed.jsx +97 -29
- package/src/canvas/widgets/FigmaEmbed.module.css +61 -0
- package/src/canvas/widgets/ImageWidget.jsx +1 -1
- package/src/canvas/widgets/LinkPreview.jsx +64 -5
- package/src/canvas/widgets/LinkPreview.module.css +127 -0
- package/src/canvas/widgets/MarkdownBlock.jsx +39 -17
- package/src/canvas/widgets/MarkdownBlock.module.css +123 -0
- package/src/canvas/widgets/PrototypeEmbed.jsx +183 -20
- package/src/canvas/widgets/PrototypeEmbed.module.css +117 -0
- package/src/canvas/widgets/PrototypeEmbed.test.jsx +2 -2
- package/src/canvas/widgets/SplitExpandModal.jsx +234 -0
- package/src/canvas/widgets/SplitExpandModal.module.css +335 -0
- package/src/canvas/widgets/SplitScreenTopBar.jsx +30 -0
- package/src/canvas/widgets/SplitScreenTopBar.module.css +58 -0
- package/src/canvas/widgets/StoryWidget.jsx +7 -4
- package/src/canvas/widgets/TerminalReadWidget.jsx +140 -0
- package/src/canvas/widgets/TerminalReadWidget.module.css +92 -0
- package/src/canvas/widgets/TerminalWidget.jsx +299 -49
- package/src/canvas/widgets/TerminalWidget.module.css +155 -1
- package/src/canvas/widgets/WidgetChrome.jsx +19 -14
- package/src/canvas/widgets/WidgetChrome.module.css +10 -0
- package/src/canvas/widgets/embedInteraction.test.jsx +24 -26
- package/src/canvas/widgets/expandUtils.js +188 -0
- package/src/canvas/widgets/index.js +5 -0
- package/src/canvas/widgets/snapshotDisplay.test.jsx +23 -71
- package/src/canvas/widgets/widgetConfig.js +19 -1
- package/src/hooks/useConfig.js +14 -0
- package/src/index.js +4 -0
- package/src/vite/data-plugin.js +264 -14
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useSyncExternalStore, useCallback } from 'react'
|
|
2
|
+
import { getConfig, subscribeToConfig, getConfigSnapshot } from '@dfosco/storyboard-core'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* React hook for reading from the unified config store.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} [domain] - Optional domain key (e.g. 'toolbar', 'canvas')
|
|
8
|
+
* @returns {object} The config object (full or domain slice)
|
|
9
|
+
*/
|
|
10
|
+
export function useConfig(domain) {
|
|
11
|
+
const snapshot = useSyncExternalStore(subscribeToConfig, getConfigSnapshot)
|
|
12
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
13
|
+
return useCallback(() => getConfig(domain), [snapshot, domain])()
|
|
14
|
+
}
|
package/src/index.js
CHANGED
|
@@ -25,6 +25,7 @@ export { useUndoRedo } from './hooks/useUndoRedo.js'
|
|
|
25
25
|
export { useFeatureFlag } from './hooks/useFeatureFlag.js'
|
|
26
26
|
export { useMode } from './hooks/useMode.js'
|
|
27
27
|
export { useThemeState, useThemeSyncTargets } from './hooks/useThemeState.js'
|
|
28
|
+
export { useConfig } from './hooks/useConfig.js'
|
|
28
29
|
|
|
29
30
|
// React Router integration
|
|
30
31
|
export { installHashPreserver } from './hashPreserver.js'
|
|
@@ -50,3 +51,6 @@ export { default as AuthModal } from './AuthModal/AuthModal.jsx'
|
|
|
50
51
|
// Canvas
|
|
51
52
|
export { default as CanvasPage } from './canvas/CanvasPage.jsx'
|
|
52
53
|
export { useCanvas } from './canvas/useCanvas.js'
|
|
54
|
+
|
|
55
|
+
// Icon
|
|
56
|
+
export { default as Icon } from './Icon.jsx'
|
package/src/vite/data-plugin.js
CHANGED
|
@@ -526,6 +526,166 @@ function readModesConfig(root) {
|
|
|
526
526
|
return fallback
|
|
527
527
|
}
|
|
528
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Read a JSON/JSONC file, returning null on failure.
|
|
531
|
+
*/
|
|
532
|
+
function readJsonFile(filePath) {
|
|
533
|
+
try {
|
|
534
|
+
const raw = fs.readFileSync(filePath, 'utf-8')
|
|
535
|
+
const errors = []
|
|
536
|
+
const parsed = parseJsonc(raw, errors)
|
|
537
|
+
return errors.length === 0 ? parsed : null
|
|
538
|
+
} catch {
|
|
539
|
+
return null
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Find a core config file from either the monorepo workspace or node_modules.
|
|
545
|
+
*/
|
|
546
|
+
function readCoreConfigFile(root, filename) {
|
|
547
|
+
const candidates = [
|
|
548
|
+
path.resolve(root, `packages/core/${filename}`),
|
|
549
|
+
path.resolve(root, `node_modules/@dfosco/storyboard-core/${filename}`),
|
|
550
|
+
]
|
|
551
|
+
for (const p of candidates) {
|
|
552
|
+
const parsed = readJsonFile(p)
|
|
553
|
+
if (parsed) return parsed
|
|
554
|
+
}
|
|
555
|
+
return null
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/**
|
|
559
|
+
* Deep-merge helper (same as loader.js deepMerge but available at build time).
|
|
560
|
+
* Arrays are replaced, not concatenated. Objects are recursively merged.
|
|
561
|
+
*/
|
|
562
|
+
function deepMergeBuild(target, source) {
|
|
563
|
+
if (!source || typeof source !== 'object') return target
|
|
564
|
+
if (!target || typeof target !== 'object') return source
|
|
565
|
+
const result = { ...target }
|
|
566
|
+
for (const key of Object.keys(source)) {
|
|
567
|
+
const sv = source[key]
|
|
568
|
+
const tv = target[key]
|
|
569
|
+
if (sv && typeof sv === 'object' && !Array.isArray(sv) && tv && typeof tv === 'object' && !Array.isArray(tv)) {
|
|
570
|
+
result[key] = deepMergeBuild(tv, sv)
|
|
571
|
+
} else {
|
|
572
|
+
result[key] = sv
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return result
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Build the unified config object by reading and merging all config sources.
|
|
580
|
+
*
|
|
581
|
+
* Priority (lowest → highest):
|
|
582
|
+
* core defaults → user widgets → user paste → user toolbar → user commandpalette → storyboard.config.json
|
|
583
|
+
*
|
|
584
|
+
* Returns { unified, warnings } where warnings is an array of overlap messages.
|
|
585
|
+
*/
|
|
586
|
+
function buildUnifiedConfig(root) {
|
|
587
|
+
const warnings = []
|
|
588
|
+
|
|
589
|
+
// 1. Read core defaults
|
|
590
|
+
const coreToolbar = readCoreConfigFile(root, 'toolbar.config.json') || {}
|
|
591
|
+
const coreCommandPalette = readCoreConfigFile(root, 'commandpalette.config.json') || {}
|
|
592
|
+
const corePaste = readCoreConfigFile(root, 'paste.config.json') || {}
|
|
593
|
+
const coreWidgets = readCoreConfigFile(root, 'widgets.config.json') || {}
|
|
594
|
+
|
|
595
|
+
// 2. Read user config files (priority order)
|
|
596
|
+
const userFiles = [
|
|
597
|
+
{ domain: 'widgets', filename: 'widgets.config.json', priority: 1 },
|
|
598
|
+
{ domain: 'paste', filename: 'paste.config.json', priority: 2 },
|
|
599
|
+
{ domain: 'toolbar', filename: 'toolbar.config.json', priority: 3 },
|
|
600
|
+
{ domain: 'commandPalette', filename: 'commandpalette.config.json', priority: 4 },
|
|
601
|
+
]
|
|
602
|
+
|
|
603
|
+
const userConfigs = {}
|
|
604
|
+
for (const { domain, filename } of userFiles) {
|
|
605
|
+
const filePath = path.resolve(root, filename)
|
|
606
|
+
const parsed = readJsonFile(filePath)
|
|
607
|
+
if (parsed) userConfigs[domain] = { data: parsed, filename }
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// 3. Read storyboard.config.json (highest priority)
|
|
611
|
+
// Use the schema-defaulted config for most things, but also read
|
|
612
|
+
// the raw file to know which keys were explicitly set by the user.
|
|
613
|
+
const { config: sbConfig } = readConfig(root)
|
|
614
|
+
const rawSbConfig = readJsonFile(path.resolve(root, 'storyboard.config.json')) || {}
|
|
615
|
+
|
|
616
|
+
// 4. Merge core defaults with user overrides per domain
|
|
617
|
+
const toolbar = userConfigs.toolbar
|
|
618
|
+
? deepMergeBuild(coreToolbar, userConfigs.toolbar.data)
|
|
619
|
+
: coreToolbar
|
|
620
|
+
const commandPalette = userConfigs.commandPalette
|
|
621
|
+
? deepMergeBuild(coreCommandPalette, userConfigs.commandPalette.data)
|
|
622
|
+
: coreCommandPalette
|
|
623
|
+
const paste = userConfigs.paste
|
|
624
|
+
? deepMergeBuild(corePaste, userConfigs.paste.data)
|
|
625
|
+
: corePaste
|
|
626
|
+
const widgets = userConfigs.widgets
|
|
627
|
+
? deepMergeBuild(coreWidgets, userConfigs.widgets.data)
|
|
628
|
+
: coreWidgets
|
|
629
|
+
|
|
630
|
+
// 5. Apply storyboard.config.json overrides (highest priority for all domains)
|
|
631
|
+
// Only merge when the user explicitly defined the key in storyboard.config.json
|
|
632
|
+
// (not from configSchema defaults, which would overwrite core config with empty arrays).
|
|
633
|
+
const finalToolbar = rawSbConfig.toolbar
|
|
634
|
+
? deepMergeBuild(toolbar, sbConfig.toolbar)
|
|
635
|
+
: toolbar
|
|
636
|
+
const finalCommandPalette = rawSbConfig.commandPalette
|
|
637
|
+
? deepMergeBuild(commandPalette, sbConfig.commandPalette)
|
|
638
|
+
: commandPalette
|
|
639
|
+
|
|
640
|
+
// 6. Detect overlaps between user config files and storyboard.config.json
|
|
641
|
+
if (rawSbConfig.toolbar && userConfigs.toolbar) {
|
|
642
|
+
const overlaps = findOverlappingKeys(userConfigs.toolbar.data, rawSbConfig.toolbar)
|
|
643
|
+
for (const key of overlaps) {
|
|
644
|
+
warnings.push(`Config overlap: "${key}" is defined in both toolbar.config.json and storyboard.config.json.toolbar — storyboard.config.json wins.`)
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
if (rawSbConfig.commandPalette && userConfigs.commandPalette) {
|
|
648
|
+
const overlaps = findOverlappingKeys(userConfigs.commandPalette.data, rawSbConfig.commandPalette)
|
|
649
|
+
for (const key of overlaps) {
|
|
650
|
+
warnings.push(`Config overlap: "${key}" is defined in both commandpalette.config.json and storyboard.config.json.commandPalette — storyboard.config.json wins.`)
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// 7. Build the unified config object
|
|
655
|
+
const unified = {
|
|
656
|
+
toolbar: finalToolbar,
|
|
657
|
+
commandPalette: finalCommandPalette,
|
|
658
|
+
paste,
|
|
659
|
+
widgets,
|
|
660
|
+
featureFlags: sbConfig?.featureFlags || {},
|
|
661
|
+
modes: sbConfig?.modes || {},
|
|
662
|
+
ui: sbConfig?.ui || {},
|
|
663
|
+
canvas: sbConfig?.canvas || {},
|
|
664
|
+
comments: sbConfig?.comments || {},
|
|
665
|
+
customerMode: sbConfig?.customerMode || {},
|
|
666
|
+
plugins: sbConfig?.plugins || {},
|
|
667
|
+
repository: sbConfig?.repository || {},
|
|
668
|
+
workshop: sbConfig?.workshop || {},
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
return { unified, warnings }
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* Find top-level keys that exist in both objects (overlap detection).
|
|
676
|
+
*/
|
|
677
|
+
function findOverlappingKeys(a, b, prefix = '') {
|
|
678
|
+
const overlaps = []
|
|
679
|
+
if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return overlaps
|
|
680
|
+
for (const key of Object.keys(a)) {
|
|
681
|
+
if (key in b) {
|
|
682
|
+
const path = prefix ? `${prefix}.${key}` : key
|
|
683
|
+
overlaps.push(path)
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
return overlaps
|
|
687
|
+
}
|
|
688
|
+
|
|
529
689
|
function generateModule({ index, protoFolders, flowRoutes, canvasRoutes, canvasAliases, canvasGroups, storyRoutes }, root) {
|
|
530
690
|
const declarations = []
|
|
531
691
|
const INDEX_KEYS = ['flow', 'object', 'record', 'prototype', 'folder', 'canvas']
|
|
@@ -585,18 +745,28 @@ function generateModule({ index, protoFolders, flowRoutes, canvasRoutes, canvasA
|
|
|
585
745
|
parsed = { ...parsed, folder: protoFolders[name] }
|
|
586
746
|
}
|
|
587
747
|
|
|
588
|
-
// Load
|
|
748
|
+
// Load prototype-level config overrides from the prototype directory.
|
|
749
|
+
// Any config file placed alongside the .prototype.json becomes an override
|
|
750
|
+
// for that domain when the prototype is active.
|
|
589
751
|
if (suffix === 'prototype') {
|
|
590
752
|
const protoDir = path.dirname(absPath)
|
|
591
|
-
const
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
753
|
+
const protoConfigFiles = [
|
|
754
|
+
{ filename: 'toolbar.config.json', key: 'toolbarConfig' },
|
|
755
|
+
{ filename: 'commandpalette.config.json', key: 'commandPaletteConfig' },
|
|
756
|
+
{ filename: 'widgets.config.json', key: 'widgetsConfig' },
|
|
757
|
+
{ filename: 'paste.config.json', key: 'pasteConfig' },
|
|
758
|
+
]
|
|
759
|
+
for (const { filename, key } of protoConfigFiles) {
|
|
760
|
+
const cfgPath = path.join(protoDir, filename)
|
|
761
|
+
if (fs.existsSync(cfgPath)) {
|
|
762
|
+
try {
|
|
763
|
+
const raw = fs.readFileSync(cfgPath, 'utf-8')
|
|
764
|
+
const cfg = parseJsonc(raw)
|
|
765
|
+
if (cfg) {
|
|
766
|
+
parsed = { ...parsed, [key]: cfg }
|
|
767
|
+
}
|
|
768
|
+
} catch { /* skip invalid config */ }
|
|
769
|
+
}
|
|
600
770
|
}
|
|
601
771
|
}
|
|
602
772
|
|
|
@@ -694,6 +864,14 @@ function generateModule({ index, protoFolders, flowRoutes, canvasRoutes, canvasA
|
|
|
694
864
|
const imports = [`import { init } from '@dfosco/storyboard-core'`]
|
|
695
865
|
const initCalls = [`init({ flows, objects, records, prototypes, folders, canvases, stories })`]
|
|
696
866
|
|
|
867
|
+
// Build unified config from all sources
|
|
868
|
+
const { unified: unifiedConfig, warnings: configWarnings } = buildUnifiedConfig(root)
|
|
869
|
+
for (const w of configWarnings) {
|
|
870
|
+
console.warn(`[storyboard] ⚠ ${w}`)
|
|
871
|
+
}
|
|
872
|
+
imports.push(`import { initConfig } from '@dfosco/storyboard-core'`)
|
|
873
|
+
initCalls.push(`initConfig(${JSON.stringify(unifiedConfig)})`)
|
|
874
|
+
|
|
697
875
|
// Feature flags from storyboard.config.json
|
|
698
876
|
const { config } = readConfig(root)
|
|
699
877
|
if (config?.featureFlags && Object.keys(config.featureFlags).length > 0) {
|
|
@@ -737,6 +915,20 @@ function generateModule({ index, protoFolders, flowRoutes, canvasRoutes, canvasA
|
|
|
737
915
|
initCalls.push(`initCustomerModeConfig(${JSON.stringify(config.customerMode)})`)
|
|
738
916
|
}
|
|
739
917
|
|
|
918
|
+
// Client toolbar overrides from root toolbar.config.json
|
|
919
|
+
const clientToolbarPath = path.resolve(root, 'toolbar.config.json')
|
|
920
|
+
try {
|
|
921
|
+
if (fs.existsSync(clientToolbarPath)) {
|
|
922
|
+
const raw = fs.readFileSync(clientToolbarPath, 'utf-8')
|
|
923
|
+
const errors = []
|
|
924
|
+
const parsed = parseJsonc(raw, errors)
|
|
925
|
+
if (parsed && errors.length === 0) {
|
|
926
|
+
imports.push(`import { setClientToolbarOverrides } from '@dfosco/storyboard-core'`)
|
|
927
|
+
initCalls.push(`setClientToolbarOverrides(${JSON.stringify(parsed)})`)
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
} catch { /* skip if unreadable */ }
|
|
931
|
+
|
|
740
932
|
// Log info when multiple flows target the same route
|
|
741
933
|
const routeGroups = {}
|
|
742
934
|
for (const [name, { route, isDefault }] of Object.entries(resolvedFlowRoutes)) {
|
|
@@ -833,7 +1025,7 @@ export default function storyboardDataPlugin() {
|
|
|
833
1025
|
// can't trace into its deps. Include the remark entry points so
|
|
834
1026
|
// Vite pre-bundles the full chain — covers all transitive CJS
|
|
835
1027
|
// packages (debug, extend, etc.) without whack-a-mole.
|
|
836
|
-
include: ['
|
|
1028
|
+
include: ['cmdk', 'remark', 'remark-gfm', 'remark-html', 'use-sync-external-store/shim', 'use-sync-external-store/shim/with-selector'],
|
|
837
1029
|
exclude: ['@dfosco/storyboard-react'],
|
|
838
1030
|
},
|
|
839
1031
|
}
|
|
@@ -974,8 +1166,21 @@ export default function storyboardDataPlugin() {
|
|
|
974
1166
|
return
|
|
975
1167
|
}
|
|
976
1168
|
|
|
977
|
-
// Invalidate when
|
|
978
|
-
|
|
1169
|
+
// Invalidate when any config file inside a prototype changes
|
|
1170
|
+
const protoConfigPattern = /\/(toolbar|commandpalette|widgets|paste)\.config\.json$/
|
|
1171
|
+
if (protoConfigPattern.test(normalized) && normalized.includes('/prototypes/')) {
|
|
1172
|
+
buildResult = null
|
|
1173
|
+
const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
|
|
1174
|
+
if (mod) {
|
|
1175
|
+
server.moduleGraph.invalidateModule(mod)
|
|
1176
|
+
server.ws.send({ type: 'full-reload' })
|
|
1177
|
+
}
|
|
1178
|
+
return
|
|
1179
|
+
}
|
|
1180
|
+
|
|
1181
|
+
// Invalidate when root toolbar.config.json changes
|
|
1182
|
+
if (normalized === path.resolve(root, 'toolbar.config.json').split(path.sep).join('/') ||
|
|
1183
|
+
normalized === path.resolve(root, 'toolbar.config.json')) {
|
|
979
1184
|
buildResult = null
|
|
980
1185
|
const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
|
|
981
1186
|
if (mod) {
|
|
@@ -989,6 +1194,9 @@ export default function storyboardDataPlugin() {
|
|
|
989
1194
|
// Also invalidate when files are added/removed inside .folder/ directories
|
|
990
1195
|
const inFolder = normalized.includes('.folder/')
|
|
991
1196
|
if (!parsed && !inFolder) return
|
|
1197
|
+
// Source files inside .folder/ dirs (jsx, css, etc.) are handled by
|
|
1198
|
+
// Vite's built-in HMR / React Fast Refresh — don't full-reload for them.
|
|
1199
|
+
if (!parsed && inFolder) return
|
|
992
1200
|
// Rebuild index and invalidate virtual module
|
|
993
1201
|
buildResult = null
|
|
994
1202
|
const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
|
|
@@ -1002,6 +1210,9 @@ export default function storyboardDataPlugin() {
|
|
|
1002
1210
|
const parsed = parseDataFile(filePath)
|
|
1003
1211
|
const inFolder = filePath.replace(/\\/g, '/').includes('.folder/')
|
|
1004
1212
|
if (!parsed && !inFolder) return
|
|
1213
|
+
// Source files (jsx, css, etc.) inside .folder/ dirs are handled by
|
|
1214
|
+
// Vite's built-in HMR — don't trigger a full-reload for them.
|
|
1215
|
+
if (!parsed && inFolder) return
|
|
1005
1216
|
|
|
1006
1217
|
// Canvas writers/editors can emit unlink+add for an in-place save.
|
|
1007
1218
|
// Treat canvas add/unlink as runtime data updates and never full-reload
|
|
@@ -1097,8 +1308,14 @@ export default function storyboardDataPlugin() {
|
|
|
1097
1308
|
// Watch storyboard.config.json for changes
|
|
1098
1309
|
const { configPath } = readConfig(root)
|
|
1099
1310
|
watcher.add(configPath)
|
|
1311
|
+
|
|
1312
|
+
// Watch root toolbar.config.json for changes
|
|
1313
|
+
const clientToolbarConfigPath = path.resolve(root, 'toolbar.config.json')
|
|
1314
|
+
watcher.add(clientToolbarConfigPath)
|
|
1315
|
+
|
|
1100
1316
|
const invalidateConfig = (filePath) => {
|
|
1101
|
-
|
|
1317
|
+
const resolved = path.resolve(filePath)
|
|
1318
|
+
if (resolved === configPath || resolved === clientToolbarConfigPath) {
|
|
1102
1319
|
buildResult = null
|
|
1103
1320
|
const mod = server.moduleGraph.getModuleById(RESOLVED_ID)
|
|
1104
1321
|
if (mod) {
|
|
@@ -1158,5 +1375,38 @@ export default function storyboardDataPlugin() {
|
|
|
1158
1375
|
}
|
|
1159
1376
|
}
|
|
1160
1377
|
|
|
1378
|
+
/**
|
|
1379
|
+
* Vite plugin that copies `.storyboard/terminal-snapshots/` into the build
|
|
1380
|
+
* output so TerminalReadWidget can fetch them as static files in production.
|
|
1381
|
+
*/
|
|
1382
|
+
export function terminalSnapshotPlugin() {
|
|
1383
|
+
return {
|
|
1384
|
+
name: 'storyboard-terminal-snapshots',
|
|
1385
|
+
|
|
1386
|
+
generateBundle() {
|
|
1387
|
+
const snapshotsDir = path.resolve('.storyboard/terminal-snapshots')
|
|
1388
|
+
if (!fs.existsSync(snapshotsDir)) return
|
|
1389
|
+
|
|
1390
|
+
const walk = (dir) => {
|
|
1391
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true })
|
|
1392
|
+
for (const entry of entries) {
|
|
1393
|
+
const full = path.join(dir, entry.name)
|
|
1394
|
+
if (entry.isDirectory()) {
|
|
1395
|
+
walk(full)
|
|
1396
|
+
} else if (entry.name.endsWith('.json')) {
|
|
1397
|
+
const rel = path.relative(snapshotsDir, full).replace(/\\/g, '/')
|
|
1398
|
+
this.emitFile({
|
|
1399
|
+
type: 'asset',
|
|
1400
|
+
fileName: `_storyboard/terminal-snapshots/${rel}`,
|
|
1401
|
+
source: fs.readFileSync(full, 'utf-8'),
|
|
1402
|
+
})
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
walk(snapshotsDir)
|
|
1407
|
+
},
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
|
|
1161
1411
|
// Exported for testing
|
|
1162
1412
|
export { resolveTemplateVars, computeTemplateVars, parseDataFile }
|