@dfosco/storyboard-core 3.3.1 → 3.3.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dfosco/storyboard-core",
3
- "version": "3.3.1",
3
+ "version": "3.3.2",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -57,6 +57,8 @@
57
57
  // Roving tabindex: only one button in the toolbar is tabbable at a time
58
58
  let activeToolbarIndex = $state(-1)
59
59
 
60
+ const isLocalDev = typeof window !== 'undefined' && (window as any).__SB_LOCAL_DEV__ === true
61
+
60
62
  const commandMenuConfig = $derived(isMenuHidden('command') ? null : config.menus?.command)
61
63
  const shortcutsConfig = $derived((config as any).shortcuts || {})
62
64
 
@@ -65,6 +67,7 @@
65
67
  const orderedMenus = $derived(Object.entries(allMenus)
66
68
  .filter(([key]) => key !== 'command')
67
69
  .filter(([key]) => !isMenuHidden(key))
70
+ .filter(([, menu]) => !menu.localOnly || isLocalDev)
68
71
  .map(([key, menu]) => ({ key, ...menu })))
69
72
 
70
73
  // Discover menus with sidepanel property
@@ -5,22 +5,27 @@
5
5
  * avoiding the full shiki bundle that registers 200+ lazy-loaded
6
6
  * language chunks (which break in deployed/static environments).
7
7
  *
8
- * Each import() has .catch() so consumer bundlers (esbuild dep optimizer)
9
- * treat them as runtime-fallible and don't fail at build time when shiki
10
- * isn't installed. Returns null when shiki is unavailable.
8
+ * Import specifiers are computed via template literals so consumer
9
+ * bundlers (Rollup, esbuild) can't statically analyze them they
10
+ * skip dynamic imports with variable specifiers instead of erroring.
11
+ * Returns null when shiki is unavailable.
11
12
  */
13
+
14
+ // Variable indirection prevents any bundler from statically resolving
15
+ const SHIKI = 'shiki'
16
+
12
17
  export async function createInspectorHighlighter() {
13
18
  try {
14
19
  const [shikiCore, oniguruma, tsx, jsx, javascript, typescript, githubDark, wasm] =
15
20
  await Promise.all([
16
- import('shiki/core').catch(() => null),
17
- import('shiki/engine/oniguruma').catch(() => null),
18
- import('shiki/dist/langs/tsx.mjs').catch(() => null),
19
- import('shiki/dist/langs/jsx.mjs').catch(() => null),
20
- import('shiki/dist/langs/javascript.mjs').catch(() => null),
21
- import('shiki/dist/langs/typescript.mjs').catch(() => null),
22
- import('shiki/dist/themes/github-dark.mjs').catch(() => null),
23
- import('shiki/wasm').catch(() => null),
21
+ import(/* @vite-ignore */ `${SHIKI}/core`).catch(() => null),
22
+ import(/* @vite-ignore */ `${SHIKI}/engine/oniguruma`).catch(() => null),
23
+ import(/* @vite-ignore */ `${SHIKI}/dist/langs/tsx.mjs`).catch(() => null),
24
+ import(/* @vite-ignore */ `${SHIKI}/dist/langs/jsx.mjs`).catch(() => null),
25
+ import(/* @vite-ignore */ `${SHIKI}/dist/langs/javascript.mjs`).catch(() => null),
26
+ import(/* @vite-ignore */ `${SHIKI}/dist/langs/typescript.mjs`).catch(() => null),
27
+ import(/* @vite-ignore */ `${SHIKI}/dist/themes/github-dark.mjs`).catch(() => null),
28
+ import(/* @vite-ignore */ `${SHIKI}/wasm`).catch(() => null),
24
29
  ])
25
30
 
26
31
  if (!shikiCore || !oniguruma || !tsx || !jsx || !javascript || !typescript || !githubDark || !wasm) {
@@ -176,13 +176,32 @@ export default function storyboardServer() {
176
176
  },
177
177
 
178
178
  transformIndexHtml() {
179
- if (clientScripts.length === 0) return []
179
+ const tags = []
180
180
 
181
- return clientScripts.map((src) => ({
181
+ // Inject local dev flag so the UI can gate dev-only tools
182
+ tags.push({
182
183
  tag: 'script',
183
- attrs: { type: 'module', src: base + src.replace(/^\//, '') },
184
- injectTo: 'body',
185
- }))
184
+ children: 'window.__SB_LOCAL_DEV__=true',
185
+ injectTo: 'head',
186
+ })
187
+
188
+ // Inject base path so the inspector UI can resolve static assets
189
+ // (e.g. inspector.json) when deployed under a subpath
190
+ tags.push({
191
+ tag: 'script',
192
+ children: `window.__STORYBOARD_BASE_PATH__=${JSON.stringify(base)}`,
193
+ injectTo: 'head',
194
+ })
195
+
196
+ for (const src of clientScripts) {
197
+ tags.push({
198
+ tag: 'script',
199
+ attrs: { type: 'module', src: base + src.replace(/^\//, '') },
200
+ injectTo: 'body',
201
+ })
202
+ }
203
+
204
+ return tags
186
205
  },
187
206
 
188
207
  // Build-time: emit a static JSON with source files so the inspector
@@ -62,8 +62,8 @@
62
62
  const canSubmit = $derived(!!kebabName && !nameError && !submitting && (!isExternal || (!!externalUrl.trim() && !urlError)))
63
63
 
64
64
  const templateLabel = $derived(partial ? partials.find(p => p.name === partial)?.name ?? partial : 'No template')
65
- const templates = $derived(partials.filter(p => p.directory === 'template'))
66
- const recipes = $derived(partials.filter(p => p.directory === 'recipe'))
65
+ const templates = $derived(partials.filter(p => p.directory === 'template' || p.directory === 'templates'))
66
+ const recipes = $derived(partials.filter(p => p.directory === 'recipe' || p.directory === 'recipes'))
67
67
  let templateMenuOpen = $state(false)
68
68
 
69
69
  function getApiUrl() {
@@ -20,10 +20,12 @@ import path from 'node:path'
20
20
 
21
21
  const FLOW_SKELETON = JSON.stringify({ $global: [] }, null, 2) + '\n'
22
22
 
23
- /** Map partial directory value → source directory name */
24
- const DIR_MAP = {
25
- recipe: 'recipes',
26
- template: 'templates',
23
+ /**
24
+ * Check whether a partial entry is a template (vs recipe).
25
+ * Accepts both singular and plural forms: "template" or "templates".
26
+ */
27
+ function isTemplate(partialEntry) {
28
+ return partialEntry.directory === 'template' || partialEntry.directory === 'templates'
27
29
  }
28
30
 
29
31
  // ---------------------------------------------------------------------------
@@ -118,10 +120,9 @@ function generateBlankIndexJsx(componentName, title) {
118
120
  * @param {string} title - Human-readable title
119
121
  */
120
122
  function generateIndexJsx({ partialEntry, componentFile, componentName, title }) {
121
- const typeDir = DIR_MAP[partialEntry.directory]
122
- const importPath = `@/${typeDir}/${partialEntry.name}/${componentFile}`
123
+ const importPath = `@/${partialEntry.directory}/${partialEntry.name}/${componentFile}`
123
124
 
124
- if (partialEntry.directory === 'template') {
125
+ if (isTemplate(partialEntry)) {
125
126
  return `import ${componentFile} from '${importPath}'
126
127
 
127
128
  export default function ${componentName}() {
@@ -303,16 +304,10 @@ export function createPrototypesHandler(ctx) {
303
304
  if (!partialEntry) {
304
305
  content = generateBlankIndexJsx(componentName, title)
305
306
  } else {
306
- const typeDir = DIR_MAP[partialEntry.directory]
307
- if (!typeDir) {
308
- sendJson(res, 400, { error: `Invalid directory "${partialEntry.directory}". Must be "recipe" or "template".` })
309
- return
310
- }
311
-
312
- const partialDir = path.join(root, 'src', typeDir, partialEntry.name)
307
+ const partialDir = path.join(root, 'src', partialEntry.directory, partialEntry.name)
313
308
  const componentFile = findComponentFile(partialDir)
314
309
  if (!componentFile) {
315
- sendJson(res, 400, { error: `No .jsx or .tsx file found in src/${typeDir}/${partialEntry.name}/` })
310
+ sendJson(res, 400, { error: `No .jsx or .tsx file found in src/${partialEntry.directory}/${partialEntry.name}/` })
316
311
  return
317
312
  }
318
313
 
@@ -34,6 +34,7 @@
34
34
  "icon": "iconoir/plus-circle",
35
35
  "meta": { "strokeWeight": 2, "scale": 1.1 },
36
36
  "modes": ["*"],
37
+ "localOnly": true,
37
38
  "menuWidth": "260px",
38
39
  "actions": [
39
40
  { "type": "header", "label": "Create" },