@dfosco/storyboard 1.0.0-beta.3 → 1.0.0-beta.4

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",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.4",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Storyboard prototyping framework — core engine, React integration, and canvas",
@@ -0,0 +1,25 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": false,
15
+ "noUnusedLocals": false,
16
+ "noUnusedParameters": false,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "allowJs": true,
19
+ "baseUrl": ".",
20
+ "paths": {
21
+ "@/*": ["../*"]
22
+ }
23
+ },
24
+ "include": ["src", "../components", "../prototypes", "../templates"]
25
+ }
@@ -26,18 +26,44 @@ import postcssGlobalData from '@csstools/postcss-global-data'
26
26
  import postcssPresetEnv from 'postcss-preset-env'
27
27
  import browsers from '@github/browserslist-config'
28
28
  import { globSync } from 'glob'
29
- import { readFileSync } from 'fs'
29
+ import { copyFileSync, existsSync, readFileSync } from 'fs'
30
30
 
31
31
  const __dirname = path.dirname(new URL(import.meta.url).pathname)
32
32
 
33
33
  // The content root is the parent of this `.backstage` Vite root.
34
34
  const contentRoot = path.resolve(__dirname, '..')
35
35
 
36
+ // Mirror the canonical `storyboard.config.json` (kept at the CONTENT root) into
37
+ // this system root so the library bootstrap's Vite-root-relative imports
38
+ // (`import storyboardConfig from '../../storyboard.config.json'` in
39
+ // src/library/{mount,index,home,prototypes-entry}.jsx) resolve.
40
+ //
41
+ // This runs at Vite config-eval time — before Rollup resolves any module — so
42
+ // EVERY Vite invocation is self-sufficient: `storyboard dev`, and (crucially) a
43
+ // DIRECT `vite build` / `vite preview` used by CI, the deploy/preview workflows,
44
+ // and build-site.mjs. The mirror copy inside `.backstage/` is gitignored, so on
45
+ // a fresh clone it doesn't exist until something creates it; doing it here is
46
+ // what lets a clean CI build resolve the config import. The content-root copy
47
+ // remains the single source of truth the user edits. Legacy layout never loads
48
+ // this config (it ships a different vite.config.js where system === content).
49
+ function mirrorContentConfig() {
50
+ const src = path.join(contentRoot, 'storyboard.config.json')
51
+ const dest = path.join(__dirname, 'storyboard.config.json')
52
+ if (!existsSync(src)) return
53
+ try {
54
+ copyFileSync(src, dest)
55
+ } catch {
56
+ // Best-effort: a stale/missing mirror surfaces as a clear resolution
57
+ // error downstream; never crash config evaluation over the copy itself.
58
+ }
59
+ }
60
+
61
+ mirrorContentConfig()
62
+
36
63
  // Additional content directories declared by the repo via the `content.dirs`
37
64
  // array in storyboard.config.json. These live alongside the role folders at
38
65
  // the content root and are watched for add/remove (full-reload) and warmed up
39
- // like the built-in role folders. The launcher mirrors the canonical config
40
- // into this system root (see core/paths/resolvePaths.js#mirrorSystemConfig).
66
+ // like the built-in role folders. Reads the mirror created just above.
41
67
  function readExtraContentDirs() {
42
68
  try {
43
69
  const cfg = JSON.parse(readFileSync(path.join(__dirname, 'storyboard.config.json'), 'utf-8'))
@@ -67,8 +93,13 @@ export default defineConfig(() => {
67
93
  base,
68
94
  resolve: {
69
95
  alias: {
70
- // Library + system source lives under `.backstage/src`.
71
- '@': path.resolve(__dirname, './src'),
96
+ // `@` is the author-facing content alias (`@/components/...`,
97
+ // `@/templates/...`). In the legacy layout `@` and `@content` both
98
+ // resolve to `./src` (where content lives); in the backstage layout
99
+ // content lives at the content root, so `@` points there too. System
100
+ // source under `.backstage/src` is loaded via `/src/...` Vite-absolute
101
+ // paths (see index.html) and the `@dfosco/storyboard` package, never `@`.
102
+ '@': contentRoot,
72
103
  // `@content` is the role-folder base for browser content globs
73
104
  // (`@content/prototypes/**`). In the backstage layout it points at
74
105
  // the content root, so the globs reach the sibling content folders
@@ -20,7 +20,7 @@ import { readFileSync, existsSync } from 'node:fs'
20
20
  import { detectWorktreeName, getPort, releasePort } from '../worktree/port.js'
21
21
  import { startFileWatcher } from '../file-watcher/watcher.js'
22
22
  import { compactAll } from '../canvas/compact.js'
23
- import { findSystemRoot, mirrorSystemConfig, ensureContentNodeModulesLink, ensureLinkedDepsOverlay, ensureContentSystemLinks } from '../paths/resolvePaths.js'
23
+ import { findSystemRoot, ensureContentNodeModulesLink, ensureLinkedDepsOverlay, ensureContentSystemLinks } from '../paths/resolvePaths.js'
24
24
  import { parseFlags } from './flags.js'
25
25
  import { setupNeeded, writeUserState, getInstalledStoryboardVersion } from './userState.js'
26
26
  import { dim, magenta, bold } from './intro.js'
@@ -177,12 +177,13 @@ async function main() {
177
177
  // the content root.
178
178
  const viteCwd = systemRoot
179
179
 
180
- // Keep the launcher-owned config mirror inside `.backstage/` fresh so the
181
- // library bootstrap's Vite-root-relative config references resolve, and ensure
182
- // the content-root `node_modules` symlink exists so content above the Vite
183
- // root can resolve its bare imports. Both are no-ops in the legacy layout.
180
+ // Ensure the content-root `node_modules` symlink exists so content above the
181
+ // Vite root can resolve its bare imports. The `storyboard.config.json` mirror
182
+ // into `.backstage/` is handled at Vite config-eval time (see
183
+ // scaffold/backstage/vite.config.js#mirrorContentConfig) so EVERY Vite
184
+ // invocation — `storyboard dev` and direct `vite build`/`vite preview` in
185
+ // CI — stays self-sufficient. All no-ops in the legacy layout.
184
186
  if (mode === 'backstage') {
185
- mirrorSystemConfig(layout)
186
187
  ensureContentNodeModulesLink(layout)
187
188
  // When the library is linked from a sibling monorepo (local development),
188
189
  // its hoisted deps aren't reachable from the Vite root. Overlay them so
@@ -40,7 +40,6 @@
40
40
 
41
41
  import { existsSync, lstatSync, readlinkSync, symlinkSync, unlinkSync, readdirSync, realpathSync, mkdirSync, readFileSync, appendFileSync } from 'node:fs'
42
42
  import { basename, dirname, join, relative, resolve } from 'node:path'
43
- import { copyFileSync } from 'node:fs'
44
43
  import process from 'node:process'
45
44
 
46
45
  export const SYSTEM_DIR = '.backstage'
@@ -230,41 +229,13 @@ export function contentBase(root, ...segments) {
230
229
  return segments.length ? join(base, ...segments) : base
231
230
  }
232
231
 
233
- /**
234
- * Mirror the canonical `storyboard.config.json` (which the user keeps at the
235
- * **content root**) into the system root so that the Vite-root-relative config
236
- * references inside the shipped library bootstrap keep resolving:
237
- *
238
- * - `import storyboardConfig from '../../storyboard.config.json'`
239
- * (mount.jsx / prototypes-entry.jsx — relative to `<systemRoot>/src/library`)
240
- * - `import.meta.glob('/storyboard.config.json')` (mountIsolationApp — rooted
241
- * at the Vite root, i.e. the system root)
242
- *
243
- * In the **legacy** layout the system root and content root are the same
244
- * directory, so this is a no-op (the config is already where those references
245
- * resolve). In the **backstage** layout it copies
246
- * `<contentRoot>/storyboard.config.json` → `<systemRoot>/storyboard.config.json`.
247
- *
248
- * The mirror is a generated artifact owned by the launcher/CLI — it lives
249
- * inside `.backstage/` and is gitignored. The content-root copy remains the
250
- * single source of truth the user edits.
251
- *
252
- * @param {{ mode: 'backstage'|'legacy', systemRoot: string, contentRoot: string }} loc
253
- * @returns {boolean} true if a mirror copy was written.
254
- */
255
- export function mirrorSystemConfig(loc) {
256
- const { mode, systemRoot, contentRoot } = loc
257
- if (mode !== 'backstage') return false
258
- const src = join(contentRoot, 'storyboard.config.json')
259
- const dest = join(systemRoot, 'storyboard.config.json')
260
- if (!existsSync(src)) return false
261
- try {
262
- copyFileSync(src, dest)
263
- return true
264
- } catch {
265
- return false
266
- }
267
- }
232
+ // NOTE: The `storyboard.config.json` mirror (copying the canonical content-root
233
+ // config into the system root so the library bootstrap's Vite-root-relative
234
+ // imports resolve) lives in the backstage Vite config itself
235
+ // (scaffold/backstage/vite.config.js#mirrorContentConfig). Running it at Vite
236
+ // config-eval time makes EVERY Vite invocation self-sufficient — `storyboard
237
+ // dev` and direct `vite build`/`vite preview` in CI alike — instead of only the
238
+ // `storyboard dev` launcher path.
268
239
 
269
240
  /**
270
241
  * Ensure a `node_modules` symlink exists at the CONTENT root pointing into the
@@ -9,7 +9,6 @@ import {
9
9
  contentDir,
10
10
  contentBase,
11
11
  extraContentDirs,
12
- mirrorSystemConfig,
13
12
  ensureContentNodeModulesLink,
14
13
  ensureLinkedDepsOverlay,
15
14
  ensureContentSystemLinks,
@@ -183,28 +182,6 @@ describe('core/paths/resolvePaths', () => {
183
182
  })
184
183
  })
185
184
 
186
- describe('mirrorSystemConfig', () => {
187
- it('is a no-op in the legacy layout', () => {
188
- writeFileSync(join(tmp, 'storyboard.config.json'), '{"port":1234}')
189
- expect(mirrorSystemConfig(findSystemRoot(tmp))).toBe(false)
190
- // No mirror is created (legacy systemRoot === contentRoot, nothing to copy).
191
- expect(readFileSync(join(tmp, 'storyboard.config.json'), 'utf8')).toBe('{"port":1234}')
192
- })
193
-
194
- it('copies content-root config into the system root in the backstage layout', () => {
195
- mkdirSync(join(tmp, SYSTEM_DIR))
196
- writeFileSync(join(tmp, 'storyboard.config.json'), '{"port":4321}')
197
- const loc = findSystemRoot(tmp)
198
- expect(mirrorSystemConfig(loc)).toBe(true)
199
- expect(readFileSync(join(tmp, SYSTEM_DIR, 'storyboard.config.json'), 'utf8')).toBe('{"port":4321}')
200
- })
201
-
202
- it('returns false when there is no content-root config to mirror', () => {
203
- mkdirSync(join(tmp, SYSTEM_DIR))
204
- expect(mirrorSystemConfig(findSystemRoot(tmp))).toBe(false)
205
- })
206
- })
207
-
208
185
  describe('ensureContentNodeModulesLink', () => {
209
186
  it('is a no-op in the legacy layout', () => {
210
187
  expect(ensureContentNodeModulesLink(findSystemRoot(tmp))).toBe(false)
@@ -340,6 +340,22 @@ describe('storyboard-scaffold integration — backstage layout', () => {
340
340
  // vitest.config.js has no scaffold/backstage/ variant → canonical is used.
341
341
  expect(fs.existsSync(path.join(backstage, 'vitest.config.js'))).toBe(true)
342
342
  }, 20000)
343
+
344
+ it('aliases `@` to the content root so author imports (@/components/...) resolve', () => {
345
+ runScaffolder(backstage)
346
+ const vite = fs.readFileSync(path.join(backstage, 'vite.config.js'), 'utf-8')
347
+ // In the backstage layout content lives ABOVE the Vite root, so the author
348
+ // `@` alias must point at the content root — not `.backstage/src` (which
349
+ // would 500/resolve-fail every `@/components/...` import).
350
+ expect(vite).toContain("'@': contentRoot,")
351
+ expect(vite).not.toContain("'@': path.resolve(__dirname, './src')")
352
+ }, 20000)
353
+
354
+ it('writes the backstage tsconfig variant mapping @/* to the content root', () => {
355
+ runScaffolder(backstage)
356
+ const ts = JSON.parse(fs.readFileSync(path.join(backstage, 'tsconfig.json'), 'utf-8'))
357
+ expect(ts.compilerOptions.paths['@/*']).toEqual(['../*'])
358
+ }, 20000)
343
359
  })
344
360
 
345
361
  // ---------------------------------------------------------------------------