@dfosco/storyboard 0.7.1 → 0.9.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/dist/storyboard-ui.css +1 -1
- package/dist/storyboard-ui.js +14128 -10953
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/dist/tiny-canvas.css +1 -1
- package/package.json +138 -128
- package/scaffold/AGENTS.md +9 -7
- package/scaffold/README.md +18 -0
- package/scaffold/deploy.yml +21 -0
- package/scaffold/gitignore +8 -14
- package/scaffold/preview.yml +40 -0
- package/scaffold/{manifest.json → scaffold.config.json} +73 -3
- package/scaffold/scripts/aggregate-artifacts.mjs +257 -0
- package/scaffold/skills/canvas/SKILL.md +1 -1
- package/scaffold/skills/migrate-0.5.0/SKILL.md +1 -1
- package/scaffold/skills/ship/SKILL.md +2 -2
- package/scaffold/skills/worktree/SKILL.md +1 -1
- package/scaffold/src/_prototype.jsx +17 -0
- package/scaffold/src/_story.jsx +13 -0
- package/scaffold/src/library/README.md +34 -0
- package/scaffold/src/library/_app.jsx +41 -0
- package/scaffold/src/library/_app.module.css +2 -0
- package/scaffold/src/library/create.jsx +10 -0
- package/scaffold/src/library/index.jsx +14 -0
- package/scaffold/src/library/mount.jsx +51 -0
- package/scaffold/src/library/prototypes-entry.jsx +57 -0
- package/scaffold/src/library/routes.jsx +205 -0
- package/scaffold/src/library/viewfinder.jsx +13 -0
- package/scaffold/src/library/workspace.jsx +17 -0
- package/scripts/aggregate-artifacts.mjs +257 -0
- package/src/canvas/style.css +16 -16
- package/src/core/artifact/operations.js +54 -15
- package/src/core/artifact/schemas/canvas.schema.json +7 -1
- package/src/core/autosync/server.test.js +1 -1
- package/src/core/canvas/hot-pool.js +21 -33
- package/src/core/canvas/materializer.js +39 -13
- package/src/core/canvas/selectedWidgets.js +1 -1
- package/src/core/canvas/server.js +376 -100
- package/src/core/canvas/server.test.js +472 -2
- package/src/core/canvas/terminal-server.js +144 -57
- package/src/core/canvas/undoRedo.js +174 -0
- package/src/core/canvas/undoRedo.test.js +250 -0
- package/src/core/cli/artifact.js +2 -0
- package/src/core/cli/buildManifest.js +34 -0
- package/src/core/cli/canvasRedo.js +44 -0
- package/src/core/cli/canvasUndo.js +40 -0
- package/src/core/cli/create.js +151 -23
- package/src/core/cli/dev.js +23 -35
- package/src/core/cli/index.js +21 -0
- package/src/core/cli/schemas.js +8 -4
- package/src/core/cli/serverUrl.js +24 -0
- package/src/core/cli/setup.js +14 -25
- package/src/core/cli/terminal-welcome.js +12 -6
- package/src/core/cli/updateVersion.js +42 -2
- package/src/core/cli/workleaf.js +140 -0
- package/src/core/comments/api.js +1 -1
- package/src/core/comments/queries.js +3 -0
- package/src/core/comments/ui/CommentWindow.jsx +3 -2
- package/src/core/comments/ui/comment-layout.css +14 -14
- package/src/core/data/artifactIndex.js +235 -0
- package/src/core/data/artifactIndex.test.js +203 -0
- package/src/core/data/artifactManifest.js +290 -0
- package/src/core/data/artifactManifest.test.js +207 -0
- package/src/core/data/viewfinder.js +47 -7
- package/src/core/index.js +24 -1
- package/src/core/inspector/highlighter.js +1 -1
- package/src/core/inspector/mouseMode.js +2 -2
- package/src/core/lib/components/ui/avatar/avatar-fallback.jsx +2 -2
- package/src/core/lib/components/ui/avatar/avatar-image.jsx +2 -2
- package/src/core/lib/components/ui/avatar/avatar.jsx +2 -2
- package/src/core/lib/components/ui/checkbox/checkbox.jsx +12 -9
- package/src/core/lib/components/ui/dialog/dialog-content.jsx +45 -31
- package/src/core/lib/components/ui/dialog/dialog-trigger.jsx +9 -5
- package/src/core/lib/components/ui/dialog/dialog.jsx +12 -3
- package/src/core/lib/components/ui/dialog/dialog.module.css +118 -0
- package/src/core/lib/components/ui/dialog/index.js +4 -34
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-group.jsx +3 -3
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-checkbox-item.jsx +19 -9
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-content.jsx +8 -8
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group-heading.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-group.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-item.jsx +15 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-portal.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-group.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-radio-item.jsx +14 -7
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-separator.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-content.jsx +8 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub-trigger.jsx +4 -4
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-sub.jsx +2 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu-trigger.jsx +8 -2
- package/src/core/lib/components/ui/dropdown-menu/dropdown-menu.jsx +2 -2
- package/src/core/lib/components/ui/label/label.jsx +1 -2
- package/src/core/lib/components/ui/panel/panel-close.jsx +7 -4
- package/src/core/lib/components/ui/panel/panel-content.jsx +7 -9
- package/src/core/lib/components/ui/panel/panel-title.jsx +3 -3
- package/src/core/lib/components/ui/panel/panel.jsx +11 -2
- package/src/core/lib/components/ui/separator/separator.jsx +6 -4
- package/src/core/lib/components/ui/tooltip/tooltip-content.jsx +7 -6
- package/src/core/lib/components/ui/tooltip/tooltip-trigger.jsx +13 -6
- package/src/core/lib/components/ui/tooltip/tooltip.jsx +5 -5
- package/src/core/modes/modes.css +4 -4
- package/src/core/mountStoryboardCore.js +287 -83
- package/src/core/mountStoryboardCore.test.js +198 -0
- package/src/core/rename-watcher/config.json +1 -1
- package/src/core/rename-watcher/watcher.js +330 -37
- package/src/core/rename-watcher/watcher.test.js +253 -0
- package/src/core/scaffold/fragments.js +287 -0
- package/src/core/scaffold/fragments.test.js +401 -0
- package/src/core/scaffold/index.js +291 -0
- package/src/core/scaffold/migrateLegacyGitignore.js +157 -0
- package/src/core/scaffold/scaffold-integration.test.js +247 -0
- package/src/core/scaffold.js +4 -97
- package/src/core/stores/canvasInteractionStore.js +120 -0
- package/src/core/stores/canvasInteractionStore.test.js +126 -0
- package/src/core/stores/configSchema.js +64 -8
- package/src/core/stores/configSchema.test.js +3 -0
- package/src/core/stores/customerModeConfig.js +24 -3
- package/src/core/stores/customerModeConfig.test.js +46 -0
- package/src/core/stores/presentationStore.js +118 -0
- package/src/core/stores/presentationStore.test.js +114 -0
- package/src/core/stores/recentArtifacts.js +49 -2
- package/src/core/stores/recentArtifacts.test.js +59 -6
- package/src/core/stores/themeBootstrap.js +264 -0
- package/src/core/stores/themeBootstrap.test.js +211 -0
- package/src/core/stores/themeStore.test.js +154 -0
- package/src/core/stores/themeStore.ts +235 -220
- package/src/core/stores/uiConfig.js +10 -2
- package/src/core/styles/generateThemesCss.js +63 -0
- package/src/core/styles/tailwind.css +150 -94
- package/src/core/styles/workspace-chrome.css +13 -0
- package/src/core/tools/handlers/paletteTheme.js +11 -8
- package/src/core/ui/ActionMenuButton.jsx +2 -1
- package/src/core/ui/AutosyncMenuButton.css +7 -7
- package/src/core/ui/BranchSelect.module.css +5 -5
- package/src/core/ui/CanvasCreateMenu.jsx +1 -19
- package/src/core/ui/ChromeSlot.jsx +92 -0
- package/src/core/ui/ChromeSlot.test.jsx +140 -0
- package/src/core/ui/CoreUIBar.css +5 -5
- package/src/core/ui/CoreUIBar.jsx +170 -142
- package/src/core/ui/InspectorPanel.css +5 -5
- package/src/core/ui/InspectorPanel.jsx +9 -9
- package/src/core/ui/PwaInstallBanner.css +5 -5
- package/src/core/ui/PwaInstallBanner.jsx +5 -5
- package/src/core/ui/ThemeMenuButton.jsx +45 -30
- package/src/core/ui/core-ui-colors.css +28 -66
- package/src/core/ui/sidepanel.css +12 -12
- package/src/core/vite/server-plugin.js +13 -0
- package/src/core/workleaf/cleanup.js +102 -0
- package/src/core/workleaf/create.js +199 -0
- package/src/core/workleaf/delete.js +101 -0
- package/src/core/workleaf/list.js +57 -0
- package/src/core/workleaf/merge.js +238 -0
- package/src/core/workleaf/paths.js +146 -0
- package/src/core/workleaf/paths.test.js +230 -0
- package/src/core/workshop/features/createCanvas/CreateCanvasForm.jsx +130 -34
- package/src/core/workshop/features/createFlow/server.js +5 -1
- package/src/core/workshop/features/createPrototype/server.js +5 -1
- package/src/core/workshop/features/templateIndex.js +28 -1
- package/src/core/worktree/port.js +48 -5
- package/src/core/worktree/port.test.js +42 -0
- package/src/internals/ArtifactForm/ArtifactForm.jsx +173 -112
- package/src/internals/ArtifactForm/ArtifactForm.module.css +51 -69
- package/src/internals/ArtifactForm/artifactSchemas.js +40 -5
- package/src/internals/AuthModal/AuthModal.module.css +34 -34
- package/src/internals/BaseUiForm/BaseUiForm.jsx +293 -0
- package/src/internals/BaseUiForm/BaseUiForm.module.css +542 -0
- package/src/internals/BranchBar/BranchBar.module.css +4 -4
- package/src/internals/BranchesDropdown/BranchesDropdown.jsx +69 -0
- package/src/internals/BranchesDropdown/BranchesDropdown.module.css +84 -0
- package/src/internals/CommandPalette/CommandPalette.jsx +41 -29
- package/src/internals/CommandPalette/CreateArtifactForm.jsx +150 -0
- package/src/internals/CommandPalette/CreateDialog.jsx +20 -110
- package/src/internals/CommandPalette/WidgetArtifactDialog.css +16 -16
- package/src/internals/CommandPalette/WidgetArtifactDialog.jsx +1 -1
- package/src/internals/CommandPalette/command-palette.css +14 -14
- package/src/internals/CommandPalette/createHelpers.js +30 -0
- package/src/internals/CreatePage/CreatePage.jsx +57 -0
- package/src/internals/CreatePage/CreatePage.module.css +34 -0
- package/src/internals/Dialog.jsx +51 -0
- package/src/internals/FlowError.module.css +5 -5
- package/src/internals/PrototypeErrorBoundary.module.css +15 -15
- package/src/internals/SimpleWorkspace/SimpleWorkspace.jsx +534 -0
- package/src/internals/SimpleWorkspace/SimpleWorkspace.module.css +323 -0
- package/src/internals/Tooltip.jsx +39 -0
- package/src/internals/Viewfinder.jsx +229 -64
- package/src/internals/Viewfinder.module.css +253 -182
- package/src/internals/canvas/CanvasControls.module.css +11 -11
- package/src/internals/canvas/CanvasPage.bridge.test.jsx +11 -37
- package/src/internals/canvas/CanvasPage.jsx +358 -217
- package/src/internals/canvas/CanvasPage.module.css +20 -19
- package/src/internals/canvas/CanvasPage.multiselect.test.jsx +39 -26
- package/src/internals/canvas/CanvasToolbar.module.css +11 -11
- package/src/internals/canvas/ConnectorLayer.jsx +39 -2
- package/src/internals/canvas/ConnectorLayer.module.css +10 -10
- package/src/internals/canvas/PageSelector.jsx +15 -1
- package/src/internals/canvas/PageSelector.module.css +41 -27
- package/src/internals/canvas/canvasApi.js +40 -5
- package/src/internals/canvas/canvasTheme.js +22 -111
- package/src/internals/canvas/isolation/createIsolationMiddleware.js +68 -0
- package/src/internals/canvas/isolation/mountIsolationApp.jsx +88 -0
- package/src/internals/canvas/isolation/prototypeRoutes.jsx +140 -0
- package/src/internals/canvas/isolation/prototypesEntry.jsx +28 -0
- package/src/internals/canvas/isolation/storiesEntry.jsx +20 -0
- package/src/internals/canvas/isolation/storyRoutes.jsx +55 -0
- package/src/internals/canvas/useUndoRedo.js +75 -60
- package/src/internals/canvas/useUndoRedo.test.js +46 -178
- package/src/internals/canvas/widgets/CodePenEmbed.jsx +16 -36
- package/src/internals/canvas/widgets/CodePenEmbed.module.css +12 -12
- package/src/internals/canvas/widgets/CropOverlay.module.css +23 -23
- package/src/internals/canvas/widgets/ExpandedPane.module.css +8 -8
- package/src/internals/canvas/widgets/ExpandedPaneTopBar.jsx +1 -1
- package/src/internals/canvas/widgets/FigmaEmbed.jsx +15 -36
- package/src/internals/canvas/widgets/FigmaEmbed.module.css +15 -15
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.jsx +30 -21
- package/src/internals/canvas/widgets/FrozenTerminalOverlay.module.css +5 -5
- package/src/internals/canvas/widgets/ImageWidget.jsx +7 -2
- package/src/internals/canvas/widgets/ImageWidget.module.css +3 -3
- package/src/internals/canvas/widgets/LinkPreview.module.css +79 -80
- package/src/internals/canvas/widgets/MarkdownBlock.module.css +31 -31
- package/src/internals/canvas/widgets/PromptWidget.jsx +2 -16
- package/src/internals/canvas/widgets/PromptWidget.module.css +33 -33
- package/src/internals/canvas/widgets/PrototypeEmbed.jsx +92 -17
- package/src/internals/canvas/widgets/PrototypeEmbed.module.css +58 -56
- package/src/internals/canvas/widgets/PrototypeEmbed.test.jsx +118 -4
- package/src/internals/canvas/widgets/ResizeHandle.module.css +4 -4
- package/src/internals/canvas/widgets/StorySetWidget.jsx +53 -25
- package/src/internals/canvas/widgets/StoryWidget.jsx +71 -56
- package/src/internals/canvas/widgets/StoryWidget.module.css +22 -20
- package/src/internals/canvas/widgets/TerminalReadWidget.jsx +13 -15
- package/src/internals/canvas/widgets/TerminalReadWidget.module.css +4 -4
- package/src/internals/canvas/widgets/TerminalWidget.jsx +56 -45
- package/src/internals/canvas/widgets/TerminalWidget.module.css +10 -10
- package/src/internals/canvas/widgets/TilesWidget.module.css +17 -17
- package/src/internals/canvas/widgets/WidgetChrome.jsx +1 -1
- package/src/internals/canvas/widgets/WidgetChrome.module.css +76 -61
- package/src/internals/canvas/widgets/WidgetWrapper.module.css +2 -2
- package/src/internals/canvas/widgets/embedOverlay.module.css +14 -4
- package/src/internals/canvas/widgets/embedTheme.js +12 -104
- package/src/internals/canvas/widgets/expandUtils.js +19 -19
- package/src/internals/canvas/widgets/normalizeLegacyEmbedSrc.js +74 -0
- package/src/internals/canvas/widgets/widgetConfig.js +5 -5
- package/src/internals/context.jsx +22 -1
- package/src/internals/context.test.jsx +47 -0
- package/src/internals/hooks/useArtifactIndex.js +31 -0
- package/src/internals/hooks/useArtifactIndex.test.js +45 -0
- package/src/internals/hooks/usePrototypeReloadGuard.js +14 -8
- package/src/internals/index.js +6 -0
- package/src/internals/loadConsumerWrapper.js +60 -0
- package/src/internals/story/ComponentSetPage.jsx +75 -34
- package/src/internals/story/ComponentSetPage.module.css +14 -14
- package/src/internals/story/StoryPage.jsx +16 -25
- package/src/internals/story/StoryPage.module.css +2 -2
- package/src/internals/vite/data-plugin.js +317 -149
- package/src/internals/vite/data-plugin.test.js +11 -11
- package/{scaffold/storyboard.config.json → storyboard.config.json} +25 -0
- package/terminal.config.json +1 -1
- package/src/core/lib/components/ui/card/card-action.jsx +0 -11
- package/src/core/lib/components/ui/card/card-content.jsx +0 -11
- package/src/core/lib/components/ui/card/card-description.jsx +0 -11
- package/src/core/lib/components/ui/card/card-footer.jsx +0 -11
- package/src/core/lib/components/ui/card/card-header.jsx +0 -19
- package/src/core/lib/components/ui/card/card-title.jsx +0 -11
- package/src/core/lib/components/ui/card/card.jsx +0 -17
- package/src/core/lib/components/ui/card/index.js +0 -25
- package/src/core/lib/components/ui/collapsible/collapsible-content.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/collapsible-trigger.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/collapsible.jsx +0 -7
- package/src/core/lib/components/ui/collapsible/index.js +0 -13
- package/src/core/lib/components/ui/dialog/dialog-close.jsx +0 -7
- package/src/core/lib/components/ui/dialog/dialog-description.jsx +0 -15
- package/src/core/lib/components/ui/dialog/dialog-footer.jsx +0 -23
- package/src/core/lib/components/ui/dialog/dialog-header.jsx +0 -11
- package/src/core/lib/components/ui/dialog/dialog-overlay.jsx +0 -15
- package/src/core/lib/components/ui/dialog/dialog-portal.jsx +0 -4
- package/src/core/lib/components/ui/dialog/dialog-title.jsx +0 -15
- package/src/core/lib/components/ui/popover/index.js +0 -28
- package/src/core/lib/components/ui/popover/popover-close.jsx +0 -7
- package/src/core/lib/components/ui/popover/popover-content.jsx +0 -22
- package/src/core/lib/components/ui/popover/popover-description.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-header.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-portal.jsx +0 -4
- package/src/core/lib/components/ui/popover/popover-title.jsx +0 -11
- package/src/core/lib/components/ui/popover/popover-trigger.jsx +0 -8
- package/src/core/lib/components/ui/popover/popover.jsx +0 -4
- package/src/core/lib/components/ui/select/index.js +0 -37
- package/src/core/lib/components/ui/select/select-content.jsx +0 -30
- package/src/core/lib/components/ui/select/select-group-heading.jsx +0 -17
- package/src/core/lib/components/ui/select/select-group.jsx +0 -15
- package/src/core/lib/components/ui/select/select-item.jsx +0 -26
- package/src/core/lib/components/ui/select/select-label.jsx +0 -11
- package/src/core/lib/components/ui/select/select-portal.jsx +0 -4
- package/src/core/lib/components/ui/select/select-scroll-down-button.jsx +0 -18
- package/src/core/lib/components/ui/select/select-scroll-up-button.jsx +0 -18
- package/src/core/lib/components/ui/select/select-separator.jsx +0 -15
- package/src/core/lib/components/ui/select/select-trigger.jsx +0 -25
- package/src/core/lib/components/ui/select/select.jsx +0 -4
- package/src/core/lib/components/ui/sheet/index.js +0 -34
- package/src/core/lib/components/ui/sheet/sheet-close.jsx +0 -7
- package/src/core/lib/components/ui/sheet/sheet-content.jsx +0 -35
- package/src/core/lib/components/ui/sheet/sheet-description.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-footer.jsx +0 -11
- package/src/core/lib/components/ui/sheet/sheet-header.jsx +0 -11
- package/src/core/lib/components/ui/sheet/sheet-overlay.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-portal.jsx +0 -4
- package/src/core/lib/components/ui/sheet/sheet-title.jsx +0 -15
- package/src/core/lib/components/ui/sheet/sheet-trigger.jsx +0 -7
- package/src/core/lib/components/ui/sheet/sheet.jsx +0 -4
- package/src/core/lib/components/ui/toggle/index.js +0 -8
- package/src/core/lib/components/ui/toggle/toggle.jsx +0 -36
- package/src/core/lib/components/ui/toggle-group/index.js +0 -10
- package/src/core/lib/components/ui/toggle-group/toggle-group-item.jsx +0 -29
- package/src/core/lib/components/ui/toggle-group/toggle-group.jsx +0 -43
- package/src/internals/canvas/StoryErrorBoundary.jsx +0 -47
- package/src/internals/canvas/WebGLContextPool.jsx +0 -291
- package/src/internals/canvas/WebGLContextPool.test.jsx +0 -165
- package/src/internals/canvas/componentIsolate.jsx +0 -144
- package/src/internals/canvas/componentSetIsolate.jsx +0 -300
- package/src/internals/canvas/widgets/ComponentSetWidget.module.css +0 -89
- package/src/internals/canvas/widgets/InlineStoryRenderer.jsx +0 -129
- package/src/internals/canvas/widgets/InlineStoryRenderer.test.jsx +0 -68
- package/src/internals/canvas/widgets/StorySetWidget.module.css +0 -89
- package/src/primer/ThemeSync.jsx +0 -73
- package/src/primer/index.js +0 -1
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Suspense } from 'react'
|
|
2
|
+
import { Outlet } from 'react-router-dom'
|
|
3
|
+
// Direct imports bypass the barrel re-export in @dfosco/storyboard/index.js.
|
|
4
|
+
// The barrel eagerly re-exports CanvasPage (3K lines), CommandPalette (1.3K lines),
|
|
5
|
+
// and Viewfinder (1.5K lines) — all resolved by Vite dev even though _app only
|
|
6
|
+
// needs StoryboardProvider and AppErrorBoundary.
|
|
7
|
+
import StoryboardProvider from '@dfosco/storyboard/context'
|
|
8
|
+
import { AppErrorBoundary } from '@dfosco/storyboard/error-boundary'
|
|
9
|
+
import './_app.module.css'
|
|
10
|
+
import '@dfosco/storyboard/canvas/style.css'
|
|
11
|
+
|
|
12
|
+
function PageLoading() {
|
|
13
|
+
return (
|
|
14
|
+
<div style={{
|
|
15
|
+
display: 'flex',
|
|
16
|
+
alignItems: 'center',
|
|
17
|
+
justifyContent: 'center',
|
|
18
|
+
minHeight: '100vh',
|
|
19
|
+
backgroundColor: 'var(--bgColor-default, #0d1117)',
|
|
20
|
+
}}>
|
|
21
|
+
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" style={{ animation: 'spin 0.8s linear infinite' }}>
|
|
22
|
+
<circle cx="12" cy="12" r="10" stroke="var(--fgColor-muted, #484f58)" strokeWidth="2.5" opacity="0.25" />
|
|
23
|
+
<path d="M12 2a10 10 0 0 1 10 10" stroke="var(--fgColor-default, #e6edf3)" strokeWidth="2.5" strokeLinecap="round" />
|
|
24
|
+
</svg>
|
|
25
|
+
<style>{`@keyframes spin { to { transform: rotate(360deg) } }`}</style>
|
|
26
|
+
</div>
|
|
27
|
+
)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default function App() {
|
|
31
|
+
return (
|
|
32
|
+
<StoryboardProvider>
|
|
33
|
+
<Suspense fallback={<PageLoading />}>
|
|
34
|
+
<Outlet />
|
|
35
|
+
</Suspense>
|
|
36
|
+
</StoryboardProvider>
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Last-resort error boundary — generouted wires this via _app?.Catch
|
|
41
|
+
export const Catch = AppErrorBoundary
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { CreatePage } from '@dfosco/storyboard'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `/create` route — standalone schema-driven artifact creator.
|
|
5
|
+
* Opened by `storyboard create --ui` to give a focused creation surface
|
|
6
|
+
* without the workspace chrome.
|
|
7
|
+
*/
|
|
8
|
+
export default function CreateRoute() {
|
|
9
|
+
return <CreatePage basePath={import.meta.env.BASE_URL} />
|
|
10
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { SimpleWorkspace } from '@dfosco/storyboard'
|
|
2
|
+
|
|
3
|
+
const pageModules = import.meta.glob('/src/prototypes/*/*.jsx')
|
|
4
|
+
|
|
5
|
+
export default function IndexPage() {
|
|
6
|
+
return (
|
|
7
|
+
<SimpleWorkspace
|
|
8
|
+
title="Storyboard"
|
|
9
|
+
subtitle="Collaborative workspace for design & code"
|
|
10
|
+
pageModules={pageModules}
|
|
11
|
+
basePath={import.meta.env.BASE_URL}
|
|
12
|
+
/>
|
|
13
|
+
)
|
|
14
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DO NOT EDIT — storyboard-owned library file.
|
|
3
|
+
*
|
|
4
|
+
* Synced from @dfosco/storyboard. Customize via storyboard.config.json,
|
|
5
|
+
* src/_prototype.jsx, src/_story.jsx, or src/library/_app.jsx.
|
|
6
|
+
* See src/library/README.md for the full customization surface.
|
|
7
|
+
*
|
|
8
|
+
* Workspace SPA entry. Mounts the React Router with the file-based prototype
|
|
9
|
+
* routes (see ./routes.jsx) and bootstraps storyboard core (devtools, comments,
|
|
10
|
+
* theming, etc.) via mountStoryboardCore.
|
|
11
|
+
*/
|
|
12
|
+
import { StrictMode } from 'react'
|
|
13
|
+
import { createRoot } from 'react-dom/client'
|
|
14
|
+
import { RouterProvider, createBrowserRouter } from 'react-router-dom'
|
|
15
|
+
import { routes } from './routes'
|
|
16
|
+
|
|
17
|
+
import { installHashPreserver } from '@dfosco/storyboard/hash-preserver'
|
|
18
|
+
import { mountStoryboardCore } from '@dfosco/storyboard/core'
|
|
19
|
+
import '@dfosco/storyboard/comments/ui/comment-layout.css'
|
|
20
|
+
import storyboardConfig from '../../storyboard.config.json'
|
|
21
|
+
import { installPrototypeOverlayIsolation } from '../prototypeOverlayIsolation.js'
|
|
22
|
+
|
|
23
|
+
// Suppress Vite error overlays that originate in /src/prototypes/ so a
|
|
24
|
+
// broken prototype can't take down the canvas surface — see
|
|
25
|
+
// .agents/plans/vite-isolation.md and src/prototypeOverlayIsolation.js.
|
|
26
|
+
installPrototypeOverlayIsolation()
|
|
27
|
+
|
|
28
|
+
// Redirect after canvas creation — Vite full-reloads when a new
|
|
29
|
+
// .canvas.jsonl is created. The form sets ?redirect=/canvas/name
|
|
30
|
+
// which survives the reload. We navigate once the route is registered.
|
|
31
|
+
const redirectParam = new URLSearchParams(window.location.search).get('redirect')
|
|
32
|
+
if (redirectParam) {
|
|
33
|
+
const base = (import.meta.env.BASE_URL || '/').replace(/\/$/, '')
|
|
34
|
+
window.location.replace(base + redirectParam)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const router = createBrowserRouter(routes, {
|
|
38
|
+
basename: import.meta.env.BASE_URL,
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
installHashPreserver(router, import.meta.env.BASE_URL)
|
|
42
|
+
mountStoryboardCore(storyboardConfig, { basePath: import.meta.env.BASE_URL })
|
|
43
|
+
|
|
44
|
+
const rootElement = document.getElementById('root')
|
|
45
|
+
const root = createRoot(rootElement)
|
|
46
|
+
|
|
47
|
+
root.render(
|
|
48
|
+
<StrictMode>
|
|
49
|
+
<RouterProvider router={router} />
|
|
50
|
+
</StrictMode>
|
|
51
|
+
)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DO NOT EDIT — storyboard-owned library file.
|
|
3
|
+
*
|
|
4
|
+
* Synced from @dfosco/storyboard. Customize via src/_prototype.jsx.
|
|
5
|
+
* See src/library/README.md for the full customization surface.
|
|
6
|
+
*
|
|
7
|
+
* Isolated prototypes entry point (see .agents/plans/vite-isolation.md).
|
|
8
|
+
*
|
|
9
|
+
* This is the second Vite HTML entry — prototypes.html loads this file.
|
|
10
|
+
* Goal: physically separate the prototypes module graph + Rollup chunk graph
|
|
11
|
+
* from the canvas app so a broken prototype cannot poison canvas surfaces.
|
|
12
|
+
*
|
|
13
|
+
* Uses createHashRouter so prototype routes live in the URL hash of
|
|
14
|
+
* prototypes.html (e.g. prototypes.html#/MyProto/SignupForm). This avoids
|
|
15
|
+
* needing any server rewrite — every iframe load resolves to the same
|
|
16
|
+
* prototypes.html document, then the router renders the matched prototype.
|
|
17
|
+
*
|
|
18
|
+
* Does NOT install the workspace-level hash preserver — that hook is the
|
|
19
|
+
* canvas/workshop SPA concern, not relevant inside an isolated prototype.
|
|
20
|
+
*
|
|
21
|
+
* The prototype provider chain (Primer / Tailwind / fonts / design tokens)
|
|
22
|
+
* comes from the consumer's `src/_prototype.jsx` — wired by the route layout
|
|
23
|
+
* inside ./routes.jsx. When absent, prototypes render with no shared providers.
|
|
24
|
+
*/
|
|
25
|
+
import { StrictMode } from 'react'
|
|
26
|
+
import { createRoot } from 'react-dom/client'
|
|
27
|
+
import { RouterProvider, createHashRouter } from 'react-router-dom'
|
|
28
|
+
import { routes, getRoutesForProto } from './routes'
|
|
29
|
+
import { mountStoryboardCore } from '@dfosco/storyboard/core'
|
|
30
|
+
import '@dfosco/storyboard/comments/ui/comment-layout.css'
|
|
31
|
+
import storyboardConfig from '../../storyboard.config.json'
|
|
32
|
+
|
|
33
|
+
// Mount storyboard core. In embed mode (_sb_embed in the URL — always true
|
|
34
|
+
// for canvas iframes) this short-circuits past UI mounting and only:
|
|
35
|
+
// - honors the canvas-aware theme (_sb_theme_target, _sb_canvas_theme)
|
|
36
|
+
// - sets up the iframe→parent postMessage navigation bridge
|
|
37
|
+
mountStoryboardCore(storyboardConfig, { basePath: import.meta.env.BASE_URL })
|
|
38
|
+
|
|
39
|
+
// Per-prototype filtering: when PrototypeEmbed loads us with ?proto=Name in
|
|
40
|
+
// dev, narrow the router to that prototype's subtree. Other prototypes'
|
|
41
|
+
// lazy() entries never appear in the route table, so a broken sibling
|
|
42
|
+
// prototype cannot leak into this iframe's module graph or HMR scope. Falls
|
|
43
|
+
// back to the full route tree when no proto param is set (production builds
|
|
44
|
+
// + direct-load flows).
|
|
45
|
+
const protoParam = new URLSearchParams(window.location.search).get('proto')
|
|
46
|
+
const activeRoutes = protoParam ? getRoutesForProto(protoParam) : routes
|
|
47
|
+
|
|
48
|
+
const router = createHashRouter(activeRoutes)
|
|
49
|
+
|
|
50
|
+
const rootElement = document.getElementById('root')
|
|
51
|
+
const root = createRoot(rootElement)
|
|
52
|
+
|
|
53
|
+
root.render(
|
|
54
|
+
<StrictMode>
|
|
55
|
+
<RouterProvider router={router} />
|
|
56
|
+
</StrictMode>
|
|
57
|
+
)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DO NOT EDIT — storyboard-owned library file.
|
|
3
|
+
*
|
|
4
|
+
* Synced from @dfosco/storyboard. Customize via src/_prototype.jsx (wraps
|
|
5
|
+
* every prototype route) or src/library/_app.jsx (workspace SPA shell).
|
|
6
|
+
* See src/library/README.md for the full customization surface.
|
|
7
|
+
*
|
|
8
|
+
* Custom generouted routes entry for storyboard.
|
|
9
|
+
*
|
|
10
|
+
* generouted hardcodes /src/pages/ in both its import.meta.glob patterns and
|
|
11
|
+
* its route-path regex. This module mirrors the generouted runtime but scans
|
|
12
|
+
* two directories:
|
|
13
|
+
* - /src/library/ — storyboard's workspace pages (workspace, viewfinder,
|
|
14
|
+
* create, index) plus _app.jsx (SPA shell wrapper).
|
|
15
|
+
* - /src/prototypes/ — the consumer's actual prototypes.
|
|
16
|
+
*
|
|
17
|
+
* Both directories have their prefix segment stripped from the URL — so
|
|
18
|
+
* src/library/workspace.jsx mounts at /workspace, and
|
|
19
|
+
* src/prototypes/MyProto/index.jsx mounts at /MyProto.
|
|
20
|
+
*
|
|
21
|
+
* Every PROTOTYPE route (anything originating from /src/prototypes/) is
|
|
22
|
+
* wrapped in the consumer's src/_prototype.jsx (resolved via
|
|
23
|
+
* `getPrototypeWrapper`). LIBRARY routes (workspace, viewfinder, create,
|
|
24
|
+
* index) are NOT wrapped — they own their own provider chain inherited
|
|
25
|
+
* from _app.jsx.
|
|
26
|
+
*/
|
|
27
|
+
import { Fragment, Suspense } from 'react'
|
|
28
|
+
import { Outlet, useLocation } from 'react-router-dom'
|
|
29
|
+
import PrototypeErrorBoundary, { ImportErrorFallback } from '@dfosco/storyboard/error-boundary'
|
|
30
|
+
import { getPrototypeWrapper } from '@dfosco/storyboard/wrappers'
|
|
31
|
+
import {
|
|
32
|
+
generatePreservedRoutes,
|
|
33
|
+
generateRegularRoutes,
|
|
34
|
+
generateModalRoutes,
|
|
35
|
+
patterns,
|
|
36
|
+
} from '@generouted/react-router/core'
|
|
37
|
+
|
|
38
|
+
// Patch the route regex to strip src/library/, src/prototypes/, .folder/,
|
|
39
|
+
// and `drafts/` segments. A `drafts/` dir is a gitignored scratch root —
|
|
40
|
+
// prototypes inside route at the same URL as a non-prefixed sibling (so
|
|
41
|
+
// `prototypes/drafts/Foo/` and `prototypes/Foo/` both resolve at `/Foo`).
|
|
42
|
+
patterns.route = [/^.*\/src\/(library|prototypes)\/|^\/(library|prototypes)\/|[^/]*\.folder\/|(?<=^|\/)drafts\/|\.(jsx|tsx|mdx)$/g, '']
|
|
43
|
+
|
|
44
|
+
// `_app.jsx` lives in src/library/ — it's storyboard's SPA shell
|
|
45
|
+
// (StoryboardProvider + suspense + error boundary). Consumers can copy
|
|
46
|
+
// it to src/library/_app.jsx and customize freely. `404` is currently a
|
|
47
|
+
// no-op convention, kept for parity with generouted preserved routes.
|
|
48
|
+
const PRESERVED = import.meta.glob('/src/library/(_app|404).{jsx,tsx}', { eager: true })
|
|
49
|
+
const MODALS = import.meta.glob('/src/prototypes/**/[+]*.{jsx,tsx}', { eager: true })
|
|
50
|
+
|
|
51
|
+
const PrototypeWrapper = getPrototypeWrapper()
|
|
52
|
+
|
|
53
|
+
// Library workspace pages — non-prototype routes (Workspace, Viewfinder,
|
|
54
|
+
// Create, landing index). Bypasses the PrototypeWrapper.
|
|
55
|
+
const LIBRARY_ROUTES = import.meta.glob(
|
|
56
|
+
['/src/library/*.{jsx,tsx}', '!/src/library/(_app|404|mount|prototypes-entry|routes).{jsx,tsx}'],
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
// Lazy-load prototype routes — only the matched route's module is fetched.
|
|
60
|
+
// This prevents story/canvas URLs from paying for every prototype page's
|
|
61
|
+
// imports (Primer, Reshaped, user components, etc.) on first load.
|
|
62
|
+
const PROTOTYPE_ROUTES = import.meta.glob(
|
|
63
|
+
['/src/prototypes/**/[\\w[-]*.{jsx,tsx,mdx}', '!/src/prototypes/**/(_!(layout)*(/*)?|_app|404)*'],
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
const preservedRoutes = generatePreservedRoutes(PRESERVED)
|
|
67
|
+
const modalRoutes = generateModalRoutes(MODALS)
|
|
68
|
+
|
|
69
|
+
function makeLazyRoute(importFn, key) {
|
|
70
|
+
const index = /index\.(jsx|tsx|mdx)$/.test(key) && !key.includes('prototypes/index') && !key.includes('library/index')
|
|
71
|
+
? { index: true }
|
|
72
|
+
: {}
|
|
73
|
+
return {
|
|
74
|
+
...index,
|
|
75
|
+
lazy: async () => {
|
|
76
|
+
try {
|
|
77
|
+
const module = await importFn()
|
|
78
|
+
const Default = module?.default || Fragment
|
|
79
|
+
const Page = () =>
|
|
80
|
+
module?.Pending ? (
|
|
81
|
+
<Suspense fallback={<module.Pending />}>
|
|
82
|
+
<Default />
|
|
83
|
+
</Suspense>
|
|
84
|
+
) : (
|
|
85
|
+
<Default />
|
|
86
|
+
)
|
|
87
|
+
return {
|
|
88
|
+
Component: Page,
|
|
89
|
+
ErrorBoundary: module?.Catch || PrototypeErrorBoundary,
|
|
90
|
+
loader: module?.Loader,
|
|
91
|
+
action: module?.Action,
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
// Import itself failed (syntax error, broken dependency, etc.)
|
|
95
|
+
return {
|
|
96
|
+
Component: () => <ImportErrorFallback error={err} route={key} />,
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const libraryRoutes = generateRegularRoutes(LIBRARY_ROUTES, makeLazyRoute)
|
|
104
|
+
const prototypeRoutes = generateRegularRoutes(PROTOTYPE_ROUTES, makeLazyRoute)
|
|
105
|
+
|
|
106
|
+
const _app = preservedRoutes?.['_app']
|
|
107
|
+
const _404 = preservedRoutes?.['404']
|
|
108
|
+
const Default = _app?.default || Outlet
|
|
109
|
+
|
|
110
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
111
|
+
const Modals_ = () => {
|
|
112
|
+
const Modal = modalRoutes[useLocation().state?.modal] || Fragment
|
|
113
|
+
return <Modal />
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
117
|
+
const Layout = () => (
|
|
118
|
+
<>
|
|
119
|
+
<Default /> <Modals_ />
|
|
120
|
+
</>
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
124
|
+
const App = () =>
|
|
125
|
+
_app?.Pending ? (
|
|
126
|
+
<Suspense fallback={<_app.Pending />}>
|
|
127
|
+
<Layout />
|
|
128
|
+
</Suspense>
|
|
129
|
+
) : (
|
|
130
|
+
<Layout />
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
const app = { Component: _app?.default ? App : Layout, ErrorBoundary: _app?.Catch, loader: _app?.Loader }
|
|
134
|
+
const fallback = { path: '*', Component: _404?.default || Fragment }
|
|
135
|
+
|
|
136
|
+
// Wrap every PROTOTYPE route in <PrototypeWrapper> via a layout route.
|
|
137
|
+
// The wrapper resolves to consumer's src/_prototype.jsx — or a Fragment
|
|
138
|
+
// pass-through when absent (truly blank slate).
|
|
139
|
+
//
|
|
140
|
+
// The wrapping <div data-storyboard-prototype> uses `display: contents` so
|
|
141
|
+
// it never affects layout — it only exposes a CSS-selector hook
|
|
142
|
+
// (`[data-storyboard-prototype] selector`) that consumers can use to scope
|
|
143
|
+
// prototype-side styles.
|
|
144
|
+
//
|
|
145
|
+
// eslint-disable-next-line react-refresh/only-export-components
|
|
146
|
+
function PrototypeLayout() {
|
|
147
|
+
return (
|
|
148
|
+
<Suspense fallback={null}>
|
|
149
|
+
<PrototypeWrapper>
|
|
150
|
+
<div data-storyboard-prototype style={{ display: 'contents' }}>
|
|
151
|
+
<Outlet />
|
|
152
|
+
</div>
|
|
153
|
+
</PrototypeWrapper>
|
|
154
|
+
</Suspense>
|
|
155
|
+
)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const prototypeLayoutRoute = {
|
|
159
|
+
Component: PrototypeLayout,
|
|
160
|
+
children: prototypeRoutes,
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export const routes = [{
|
|
164
|
+
...app,
|
|
165
|
+
children: [...libraryRoutes, prototypeLayoutRoute, fallback],
|
|
166
|
+
}]
|
|
167
|
+
|
|
168
|
+
// Return a routes tree filtered to a single prototype subtree. Used by the
|
|
169
|
+
// per-prototype iframe entry (see prototypes-entry.jsx + PrototypeEmbed
|
|
170
|
+
// dev URL builder). Filtering at this layer keeps a broken sibling
|
|
171
|
+
// prototype from ever appearing in the matched route's lazy() chain — only
|
|
172
|
+
// the requested prototype's modules are reachable from the router.
|
|
173
|
+
//
|
|
174
|
+
// Library routes (workspace, viewfinder, create) are NEVER in the iframe
|
|
175
|
+
// path because the iframe entry uses a different routes module
|
|
176
|
+
// (prototypeRoutes.jsx inside the library). So filtering here only needs
|
|
177
|
+
// to walk the prototype subtree.
|
|
178
|
+
export function getRoutesForProto(proto) {
|
|
179
|
+
if (!proto) return routes
|
|
180
|
+
const matchesProto = (route) => {
|
|
181
|
+
const seg = (route.path || '').split('/').filter(Boolean)[0]
|
|
182
|
+
return seg === proto
|
|
183
|
+
}
|
|
184
|
+
const filter = (children) => children
|
|
185
|
+
.map((r) => {
|
|
186
|
+
if (r.path === '*') return r
|
|
187
|
+
if (!r.path) {
|
|
188
|
+
if (r.children) return { ...r, children: filter(r.children) }
|
|
189
|
+
return r
|
|
190
|
+
}
|
|
191
|
+
if (matchesProto(r)) {
|
|
192
|
+
return r.children ? { ...r, children: filter(r.children) } : r
|
|
193
|
+
}
|
|
194
|
+
return null
|
|
195
|
+
})
|
|
196
|
+
.filter(Boolean)
|
|
197
|
+
return [{
|
|
198
|
+
...routes[0],
|
|
199
|
+
children: routes[0].children.map((child) => {
|
|
200
|
+
// Only the prototype-layout child has nested children we want to filter.
|
|
201
|
+
if (child === prototypeLayoutRoute) return { ...child, children: filter(child.children || []) }
|
|
202
|
+
return child
|
|
203
|
+
}),
|
|
204
|
+
}]
|
|
205
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { useEffect } from 'react'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Backward-compat redirect: /viewfinder → /workspace
|
|
5
|
+
* Kept for one release cycle so existing bookmarks and links still work.
|
|
6
|
+
*/
|
|
7
|
+
export default function ViewfinderRedirect() {
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const base = (import.meta.env.BASE_URL || '/').replace(/\/+$/, '')
|
|
10
|
+
window.location.replace(`${base}/workspace${window.location.hash}`)
|
|
11
|
+
}, [])
|
|
12
|
+
return null
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { flows } from 'virtual:storyboard-data-index'
|
|
2
|
+
import { Workspace } from '@dfosco/storyboard'
|
|
3
|
+
|
|
4
|
+
const pageModules = import.meta.glob('/src/prototypes/*.jsx')
|
|
5
|
+
|
|
6
|
+
export default function WorkspacePage() {
|
|
7
|
+
return (
|
|
8
|
+
<Workspace
|
|
9
|
+
title="Storyboard"
|
|
10
|
+
subtitle="Where design work goes"
|
|
11
|
+
flows={flows}
|
|
12
|
+
hideDefaultFlow={true}
|
|
13
|
+
pageModules={pageModules}
|
|
14
|
+
basePath={import.meta.env.BASE_URL}
|
|
15
|
+
/>
|
|
16
|
+
)
|
|
17
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs'
|
|
4
|
+
import path from 'path'
|
|
5
|
+
import process from 'process'
|
|
6
|
+
|
|
7
|
+
const INDEX_FILENAME = 'artifacts.index.json'
|
|
8
|
+
const TMP_FILENAME = '.artifacts.index.json.tmp'
|
|
9
|
+
const SCHEMA_VERSION = 1
|
|
10
|
+
|
|
11
|
+
function compareBranchNames(a, b) {
|
|
12
|
+
if (a.branch === 'main' && b.branch !== 'main') return -1
|
|
13
|
+
if (b.branch === 'main' && a.branch !== 'main') return 1
|
|
14
|
+
return String(a.branch || '').localeCompare(String(b.branch || ''))
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function withTrailingSlash(folder) {
|
|
18
|
+
if (!folder) return ''
|
|
19
|
+
return folder.endsWith('/') ? folder : `${folder}/`
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function generatedAtMs(manifest) {
|
|
23
|
+
const value = Date.parse(manifest?.generatedAt || '')
|
|
24
|
+
return Number.isNaN(value) ? 0 : value
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function deriveFolder(manifest, dir, ghPagesDir) {
|
|
28
|
+
if (dir === ghPagesDir) return ''
|
|
29
|
+
if (typeof manifest.folder === 'string') return manifest.folder
|
|
30
|
+
return path.basename(dir)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function deriveBranch(manifest, folder) {
|
|
34
|
+
if (!folder) return 'main'
|
|
35
|
+
if (typeof manifest.branch === 'string' && manifest.branch) return manifest.branch
|
|
36
|
+
return manifest.deployBranch || folder.replace(/^branch--/, '')
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function deriveDeployBranch(manifest, branch, folder) {
|
|
40
|
+
if (!folder || branch === 'main') return 'main'
|
|
41
|
+
if (typeof manifest.deployBranch === 'string' && manifest.deployBranch) return manifest.deployBranch
|
|
42
|
+
return folder.replace(/^branch--/, '')
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function readManifest(dir) {
|
|
46
|
+
const manifestPath = path.join(dir, 'artifacts.json')
|
|
47
|
+
const label = path.basename(dir) || dir
|
|
48
|
+
|
|
49
|
+
let raw
|
|
50
|
+
try {
|
|
51
|
+
raw = fs.readFileSync(manifestPath, 'utf8')
|
|
52
|
+
} catch (err) {
|
|
53
|
+
if (err?.code === 'ENOENT') {
|
|
54
|
+
console.info(`[aggregate-artifacts] Skipping ${label}: artifacts.json not found`)
|
|
55
|
+
return { manifest: null, readError: false }
|
|
56
|
+
}
|
|
57
|
+
console.warn(`[aggregate-artifacts] Skipping ${label}: could not read artifacts.json (${err.message})`)
|
|
58
|
+
return { manifest: null, readError: true }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
let manifest
|
|
62
|
+
try {
|
|
63
|
+
manifest = JSON.parse(raw)
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.warn(`[aggregate-artifacts] Skipping ${label}: malformed artifacts.json (${err.message})`)
|
|
66
|
+
return { manifest: null, readError: false }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (manifest?.schemaVersion !== SCHEMA_VERSION) {
|
|
70
|
+
console.warn(`[aggregate-artifacts] Skipping ${label}: unsupported schemaVersion ${manifest?.schemaVersion}`)
|
|
71
|
+
return { manifest: null, readError: false }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return { manifest, readError: false }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function listManifestDirs(ghPagesDir) {
|
|
78
|
+
const entries = fs.readdirSync(ghPagesDir, { withFileTypes: true })
|
|
79
|
+
const branchDirs = entries
|
|
80
|
+
.filter(entry => entry.isDirectory() && entry.name.startsWith('branch--'))
|
|
81
|
+
.map(entry => path.join(ghPagesDir, entry.name))
|
|
82
|
+
.sort((a, b) => path.basename(a).localeCompare(path.basename(b)))
|
|
83
|
+
|
|
84
|
+
return [ghPagesDir, ...branchDirs]
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function collectManifests(ghPagesDir) {
|
|
88
|
+
const manifestsByFolder = new Map()
|
|
89
|
+
let readErrorCount = 0
|
|
90
|
+
|
|
91
|
+
for (const dir of listManifestDirs(ghPagesDir)) {
|
|
92
|
+
const { manifest, readError } = readManifest(dir)
|
|
93
|
+
if (readError) readErrorCount += 1
|
|
94
|
+
if (!manifest) continue
|
|
95
|
+
|
|
96
|
+
const folder = deriveFolder(manifest, dir, ghPagesDir)
|
|
97
|
+
const current = { manifest, dir, folder }
|
|
98
|
+
const previous = manifestsByFolder.get(folder)
|
|
99
|
+
|
|
100
|
+
if (!previous) {
|
|
101
|
+
manifestsByFolder.set(folder, current)
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const previousBranch = deriveBranch(previous.manifest, previous.folder)
|
|
106
|
+
const currentBranch = deriveBranch(manifest, folder)
|
|
107
|
+
const keepCurrent = generatedAtMs(manifest) > generatedAtMs(previous.manifest)
|
|
108
|
+
const keptBranch = keepCurrent ? currentBranch : previousBranch
|
|
109
|
+
|
|
110
|
+
console.warn(
|
|
111
|
+
`[aggregate-artifacts] Branch folder collision for "${folder || '(root)'}" between "${previousBranch}" and "${currentBranch}"; keeping "${keptBranch}"`,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if (keepCurrent) manifestsByFolder.set(folder, current)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (readErrorCount > 1) {
|
|
118
|
+
throw new Error(`Failed to read ${readErrorCount} artifacts manifests; preserving existing ${INDEX_FILENAME}`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const manifests = [...manifestsByFolder.values()]
|
|
122
|
+
if (manifests.length === 0) {
|
|
123
|
+
throw new Error(`No readable artifacts.json manifests found; preserving existing ${INDEX_FILENAME}`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return manifests
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function addPrototype(index, manifestInfo, item) {
|
|
130
|
+
const id = item?.artifactId || item?.id
|
|
131
|
+
if (!id) return
|
|
132
|
+
|
|
133
|
+
const { manifest, folder } = manifestInfo
|
|
134
|
+
const branch = deriveBranch(manifest, folder)
|
|
135
|
+
const key = String(id)
|
|
136
|
+
|
|
137
|
+
if (!index.prototypes[key]) {
|
|
138
|
+
index.prototypes[key] = { id: key, branches: [] }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
index.prototypes[key].branches.push({
|
|
142
|
+
branch,
|
|
143
|
+
folder: withTrailingSlash(folder),
|
|
144
|
+
title: item.title ?? item.id ?? key,
|
|
145
|
+
route: item.route ?? null,
|
|
146
|
+
isExternal: Boolean(item.isExternal),
|
|
147
|
+
externalUrl: item.externalUrl ?? null,
|
|
148
|
+
folderName: item.folder ?? null,
|
|
149
|
+
sha: manifest.sha ?? null,
|
|
150
|
+
lastModified: item.lastModified ?? null,
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function addCanvas(index, manifestInfo, item) {
|
|
155
|
+
const id = item?.artifactId || item?.id
|
|
156
|
+
if (!id) return
|
|
157
|
+
|
|
158
|
+
const { manifest, folder } = manifestInfo
|
|
159
|
+
const branch = deriveBranch(manifest, folder)
|
|
160
|
+
const key = String(id)
|
|
161
|
+
|
|
162
|
+
if (!index.canvases[key]) {
|
|
163
|
+
index.canvases[key] = { id: key, branches: [] }
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
index.canvases[key].branches.push({
|
|
167
|
+
branch,
|
|
168
|
+
folder: withTrailingSlash(folder),
|
|
169
|
+
name: item.name ?? item.id ?? key,
|
|
170
|
+
route: item.route ?? null,
|
|
171
|
+
folderName: item.folder ?? null,
|
|
172
|
+
sha: manifest.sha ?? null,
|
|
173
|
+
lastModified: item.lastModified ?? null,
|
|
174
|
+
})
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function sortArtifactMap(map) {
|
|
178
|
+
const sorted = {}
|
|
179
|
+
|
|
180
|
+
for (const key of Object.keys(map).sort((a, b) => a.localeCompare(b))) {
|
|
181
|
+
sorted[key] = {
|
|
182
|
+
...map[key],
|
|
183
|
+
branches: [...map[key].branches].sort(compareBranchNames),
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return sorted
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function buildIndex(manifests) {
|
|
191
|
+
const index = {
|
|
192
|
+
schemaVersion: SCHEMA_VERSION,
|
|
193
|
+
generatedAt: new Date().toISOString(),
|
|
194
|
+
branches: [],
|
|
195
|
+
prototypes: {},
|
|
196
|
+
canvases: {},
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
for (const manifestInfo of manifests) {
|
|
200
|
+
const { manifest, folder } = manifestInfo
|
|
201
|
+
const branch = deriveBranch(manifest, folder)
|
|
202
|
+
const deployBranch = deriveDeployBranch(manifest, branch, folder)
|
|
203
|
+
|
|
204
|
+
index.branches.push({ branch, deployBranch, folder })
|
|
205
|
+
|
|
206
|
+
for (const item of Array.isArray(manifest.prototypes) ? manifest.prototypes : []) {
|
|
207
|
+
addPrototype(index, manifestInfo, item)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
for (const item of Array.isArray(manifest.canvases) ? manifest.canvases : []) {
|
|
211
|
+
addCanvas(index, manifestInfo, item)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
index.branches = [...index.branches].sort(compareBranchNames)
|
|
216
|
+
index.prototypes = sortArtifactMap(index.prototypes)
|
|
217
|
+
index.canvases = sortArtifactMap(index.canvases)
|
|
218
|
+
|
|
219
|
+
return index
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function writeIndex(ghPagesDir, index) {
|
|
223
|
+
const targetPath = path.join(ghPagesDir, INDEX_FILENAME)
|
|
224
|
+
const tmpPath = path.join(ghPagesDir, TMP_FILENAME)
|
|
225
|
+
const contents = `${JSON.stringify(index, null, 2)}\n`
|
|
226
|
+
|
|
227
|
+
fs.writeFileSync(tmpPath, contents, 'utf8')
|
|
228
|
+
fs.renameSync(tmpPath, targetPath)
|
|
229
|
+
|
|
230
|
+
return targetPath
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function aggregateArtifacts(ghPagesDir) {
|
|
234
|
+
const resolvedDir = path.resolve(ghPagesDir)
|
|
235
|
+
const manifests = collectManifests(resolvedDir)
|
|
236
|
+
const index = buildIndex(manifests)
|
|
237
|
+
return writeIndex(resolvedDir, index)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function main() {
|
|
241
|
+
const ghPagesDir = process.argv[2]
|
|
242
|
+
|
|
243
|
+
if (!ghPagesDir) {
|
|
244
|
+
console.error('Usage: node aggregate-artifacts.mjs <gh-pages-dir>')
|
|
245
|
+
process.exit(1)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
try {
|
|
249
|
+
const targetPath = aggregateArtifacts(ghPagesDir)
|
|
250
|
+
console.info(`[aggregate-artifacts] Wrote ${targetPath}`)
|
|
251
|
+
} catch (err) {
|
|
252
|
+
console.error(`[aggregate-artifacts] ${err.message}`)
|
|
253
|
+
process.exit(1)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
main()
|