@dfosco/storyboard 1.0.0-beta.2 → 1.0.0-beta.3
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 +1 -1
- package/src/core/canvas/compact.js +2 -1
- package/src/core/canvas/selectedWidgets.js +7 -2
- package/src/core/canvas/server.js +12 -4
- package/src/core/canvas/server.test.js +64 -0
- package/src/core/file-watcher/watcher.js +1 -1
- package/src/core/vite/docs-handler.js +18 -5
- package/src/core/vite/server-plugin.js +30 -16
- package/src/core/workshop/features/createPrototype/server.js +7 -6
- package/src/core/workshop/features/templateIndex.js +3 -2
package/package.json
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import fs from 'node:fs'
|
|
13
13
|
import path from 'node:path'
|
|
14
14
|
import { materializeFromText, serializeEvent } from './materializer.js'
|
|
15
|
+
import { contentDir } from '../paths/resolvePaths.js'
|
|
15
16
|
|
|
16
17
|
const COMPACT_THRESHOLD_BYTES = 500 * 1024
|
|
17
18
|
|
|
@@ -21,7 +22,7 @@ const COMPACT_THRESHOLD_BYTES = 500 * 1024
|
|
|
21
22
|
* @returns {{ name: string, filePath: string, size: number }[]}
|
|
22
23
|
*/
|
|
23
24
|
export function findCanvasFiles(root) {
|
|
24
|
-
const canvasDir =
|
|
25
|
+
const canvasDir = contentDir(root, 'canvas')
|
|
25
26
|
if (!fs.existsSync(canvasDir)) return []
|
|
26
27
|
|
|
27
28
|
const results = []
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
import fs from 'node:fs'
|
|
20
20
|
import path from 'node:path'
|
|
21
21
|
import { toCanvasId } from './identity.js'
|
|
22
|
+
import { findSystemRoot } from '../paths/resolvePaths.js'
|
|
22
23
|
import { devLog } from '../logger/devLogger.js'
|
|
23
24
|
|
|
24
25
|
const DIR_NAME = '.storyboard'
|
|
@@ -29,12 +30,16 @@ const CLEANUP_INTERVAL_MS = 10_000
|
|
|
29
30
|
* Find a canvas JSONL file by canonical ID (cached).
|
|
30
31
|
*/
|
|
31
32
|
function createPathResolver(root) {
|
|
33
|
+
// Canvas files live at the content root, which differs from the Vite/system
|
|
34
|
+
// `root` in the `.backstage` layout. Walk the content root so canvas IDs
|
|
35
|
+
// resolve to real files (and stored paths are content-root relative).
|
|
36
|
+
const scanRoot = findSystemRoot(root).contentRoot
|
|
32
37
|
const cache = new Map()
|
|
33
38
|
let lastScanFiles = null
|
|
34
39
|
|
|
35
40
|
function scanFiles() {
|
|
36
41
|
const results = []
|
|
37
|
-
const ignore = new Set(['node_modules', 'dist', '.git', '.worktrees', 'worktrees', 'workleafs'])
|
|
42
|
+
const ignore = new Set(['node_modules', 'dist', '.git', '.worktrees', 'worktrees', 'workleafs', '.backstage'])
|
|
38
43
|
function walk(dir, rel) {
|
|
39
44
|
let entries
|
|
40
45
|
try { entries = fs.readdirSync(dir, { withFileTypes: true }) } catch { return }
|
|
@@ -46,7 +51,7 @@ function createPathResolver(root) {
|
|
|
46
51
|
else if (entry.name.endsWith('.canvas.jsonl')) results.push(relPath)
|
|
47
52
|
}
|
|
48
53
|
}
|
|
49
|
-
walk(
|
|
54
|
+
walk(scanRoot, '')
|
|
50
55
|
return results
|
|
51
56
|
}
|
|
52
57
|
|
|
@@ -224,11 +224,17 @@ function parseExportNames(filePath) {
|
|
|
224
224
|
*/
|
|
225
225
|
function findCanvasPath(root, canvasId) {
|
|
226
226
|
const files = findCanvasFiles(root)
|
|
227
|
+
// `findCanvasFiles` walks the content root and returns content-relative
|
|
228
|
+
// paths. In the `.backstage` layout the Vite `root` is the system root
|
|
229
|
+
// (`.backstage`), which is a sibling of the content root — resolving the
|
|
230
|
+
// returned paths against `root` would point at a non-existent
|
|
231
|
+
// `.backstage/canvas/...` file. Resolve against the content root instead.
|
|
232
|
+
const { contentRoot } = findSystemRoot(root)
|
|
227
233
|
|
|
228
234
|
for (const file of files) {
|
|
229
235
|
const id = toCanvasId(file)
|
|
230
236
|
if (id === canvasId) {
|
|
231
|
-
return path.resolve(
|
|
237
|
+
return path.resolve(contentRoot, file)
|
|
232
238
|
}
|
|
233
239
|
}
|
|
234
240
|
|
|
@@ -1025,13 +1031,14 @@ export function createCanvasHandler(ctx) {
|
|
|
1025
1031
|
// GET /list — list all canvases
|
|
1026
1032
|
if (routePath === '/list' && method === 'GET') {
|
|
1027
1033
|
const files = findCanvasFiles(root)
|
|
1034
|
+
const { contentRoot } = findSystemRoot(root)
|
|
1028
1035
|
const canvases = files.map((file) => {
|
|
1029
1036
|
const id = toCanvasId(file)
|
|
1030
1037
|
if (!id) return null
|
|
1031
1038
|
const { segments } = parseCanvasId(id)
|
|
1032
1039
|
const group = segments.length > 1 ? segments.slice(0, -1).join('/') : null
|
|
1033
1040
|
try {
|
|
1034
|
-
const data = readCanvas(path.resolve(
|
|
1041
|
+
const data = readCanvas(path.resolve(contentRoot, file))
|
|
1035
1042
|
return {
|
|
1036
1043
|
name: id,
|
|
1037
1044
|
title: data.title || segments[segments.length - 1],
|
|
@@ -3127,8 +3134,9 @@ export function Default() {
|
|
|
3127
3134
|
|
|
3128
3135
|
// ── Image routes ──────────────────────────────────────────────────
|
|
3129
3136
|
|
|
3130
|
-
const
|
|
3131
|
-
const
|
|
3137
|
+
const assetsDir = contentDir(root, 'assets')
|
|
3138
|
+
const imagesDir = path.join(assetsDir, 'canvas', 'images')
|
|
3139
|
+
const snapshotsDir = path.join(assetsDir, 'canvas', 'snapshots')
|
|
3132
3140
|
|
|
3133
3141
|
const MIME_TO_EXT = { 'image/png': 'png', 'image/jpeg': 'jpg', 'image/webp': 'webp', 'image/gif': 'gif' }
|
|
3134
3142
|
const EXT_TO_MIME = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', webp: 'image/webp', gif: 'image/gif' }
|
|
@@ -847,3 +847,67 @@ describe('GET /folders and POST /create with folderKind', () => {
|
|
|
847
847
|
expect(fs.existsSync(path.join(canvasDir, 'tour.folder'))).toBe(false)
|
|
848
848
|
})
|
|
849
849
|
})
|
|
850
|
+
|
|
851
|
+
describe('.backstage layout — content paths anchored at the content root', () => {
|
|
852
|
+
let root, systemRoot, canvasDir, invoke, lastResponse
|
|
853
|
+
|
|
854
|
+
// In the backstage layout the Vite `root` handed to the canvas handler is the
|
|
855
|
+
// `.backstage` system root, but the user's canvas/asset content lives one
|
|
856
|
+
// level up at the content root. Regression coverage for the paste/add bug
|
|
857
|
+
// where widget writes resolved against `.backstage/...` and 500'd (ENOENT).
|
|
858
|
+
beforeEach(() => {
|
|
859
|
+
const contentRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'sb-backstage-test-'))
|
|
860
|
+
systemRoot = path.join(contentRoot, '.backstage')
|
|
861
|
+
fs.mkdirSync(systemRoot, { recursive: true })
|
|
862
|
+
// Backstage content layout: canvas/ at the content root (no src/ segment).
|
|
863
|
+
canvasDir = path.join(contentRoot, 'canvas')
|
|
864
|
+
fs.mkdirSync(canvasDir, { recursive: true })
|
|
865
|
+
root = contentRoot
|
|
866
|
+
|
|
867
|
+
const lr = { status: null, body: null, bytes: null, headers: null }
|
|
868
|
+
const handler = createCanvasHandler({
|
|
869
|
+
// Hand the handler the system root, exactly like vite (root === .backstage)
|
|
870
|
+
root: systemRoot,
|
|
871
|
+
sendJson: (_res, status, body) => { lr.status = status; lr.body = body },
|
|
872
|
+
})
|
|
873
|
+
const makeRes = () => ({ writeHead() {}, end() {} })
|
|
874
|
+
invoke = (routePath, method, body = {}) =>
|
|
875
|
+
handler(null, makeRes(), { path: routePath, method, body })
|
|
876
|
+
lastResponse = lr
|
|
877
|
+
})
|
|
878
|
+
|
|
879
|
+
afterEach(() => {
|
|
880
|
+
fs.rmSync(root, { recursive: true, force: true })
|
|
881
|
+
})
|
|
882
|
+
|
|
883
|
+
it('POST /widget writes to the content-root canvas file (not under .backstage)', async () => {
|
|
884
|
+
writeCanvas(canvasDir, 'design-system', { title: 'Design System' })
|
|
885
|
+
|
|
886
|
+
await invoke('/widget', 'POST', {
|
|
887
|
+
name: 'design-system',
|
|
888
|
+
type: 'sticky-note',
|
|
889
|
+
props: { text: '# hello' },
|
|
890
|
+
position: { x: 120, y: 80 },
|
|
891
|
+
})
|
|
892
|
+
|
|
893
|
+
expect(lastResponse.status).toBe(201)
|
|
894
|
+
expect(lastResponse.body.success).toBe(true)
|
|
895
|
+
expect(lastResponse.body.widget.type).toBe('sticky-note')
|
|
896
|
+
|
|
897
|
+
// The event must land in the content-root file, and NOT create a stray
|
|
898
|
+
// `.backstage/canvas/...` file.
|
|
899
|
+
const contentFile = path.join(canvasDir, 'design-system.canvas.jsonl')
|
|
900
|
+
expect(fs.readFileSync(contentFile, 'utf-8')).toContain('widget_added')
|
|
901
|
+
expect(fs.existsSync(path.join(systemRoot, 'canvas', 'design-system.canvas.jsonl'))).toBe(false)
|
|
902
|
+
})
|
|
903
|
+
|
|
904
|
+
it('GET /list resolves canvases from the content root', async () => {
|
|
905
|
+
writeCanvas(canvasDir, 'design-system', { title: 'Design System' })
|
|
906
|
+
|
|
907
|
+
await invoke('/list', 'GET')
|
|
908
|
+
|
|
909
|
+
expect(lastResponse.status).toBe(200)
|
|
910
|
+
const names = lastResponse.body.canvases.map((c) => c.name)
|
|
911
|
+
expect(names).toContain('design-system')
|
|
912
|
+
})
|
|
913
|
+
})
|
|
@@ -519,7 +519,7 @@ function processDraftsFlipImages(root, flip, canvasFiles) {
|
|
|
519
519
|
}
|
|
520
520
|
if (!state.widgets || state.widgets.length === 0) return null
|
|
521
521
|
|
|
522
|
-
const imagesDir = path.join(root, 'assets', 'canvas', 'images')
|
|
522
|
+
const imagesDir = path.join(contentDir(root, 'assets'), 'canvas', 'images')
|
|
523
523
|
const draftsDir = path.join(imagesDir, 'drafts')
|
|
524
524
|
const sharedRefs = collectImageRefs(canvasFiles, newAbsPath)
|
|
525
525
|
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
import fs from 'node:fs'
|
|
12
12
|
import path from 'node:path'
|
|
13
13
|
import { execSync } from 'node:child_process'
|
|
14
|
+
import { findSystemRoot, resolvePaths } from '../paths/resolvePaths.js'
|
|
14
15
|
|
|
15
16
|
const README_CANDIDATES = ['README.md', 'readme.md', 'Readme.md']
|
|
16
17
|
const SOURCE_EXTENSIONS = new Set(['.jsx', '.tsx', '.js', '.ts'])
|
|
@@ -61,6 +62,11 @@ export async function collectFiles(dir, rootDir) {
|
|
|
61
62
|
* @returns {(req: import('http').IncomingMessage, res: import('http').ServerResponse, opts: { path: string, method: string }) => Promise<void>}
|
|
62
63
|
*/
|
|
63
64
|
export function docsHandler({ root, sendJson }) {
|
|
65
|
+
// Content (README, prototypes, components, config, git) lives at the content
|
|
66
|
+
// root, which equals `root` in the legacy layout but is the parent of the
|
|
67
|
+
// Vite/system root in the `.backstage` layout.
|
|
68
|
+
const { contentRoot } = findSystemRoot(root)
|
|
69
|
+
root = contentRoot
|
|
64
70
|
return async (req, res, { path: routePath, method }) => {
|
|
65
71
|
// --- GET /readme ---
|
|
66
72
|
if (routePath === '/readme' && method === 'GET') {
|
|
@@ -96,10 +102,17 @@ export function docsHandler({ root, sendJson }) {
|
|
|
96
102
|
return
|
|
97
103
|
}
|
|
98
104
|
|
|
99
|
-
// Security:
|
|
105
|
+
// Security: restrict to content directories (prototypes, components,
|
|
106
|
+
// canvas, assets). In the legacy layout these all live under `src/`; in
|
|
107
|
+
// the `.backstage` layout they sit directly at the content root.
|
|
100
108
|
const relative = path.relative(root, resolved)
|
|
101
|
-
|
|
102
|
-
|
|
109
|
+
const { dirs } = resolvePaths(findSystemRoot(root))
|
|
110
|
+
const allowedRoots = [dirs.prototypes, dirs.components, dirs.canvas]
|
|
111
|
+
const isAllowed = allowedRoots.some(
|
|
112
|
+
(d) => resolved === d || resolved.startsWith(d + path.sep),
|
|
113
|
+
)
|
|
114
|
+
if (!isAllowed) {
|
|
115
|
+
sendJson(res, 403, { error: 'Only files within content directories are accessible' })
|
|
103
116
|
return
|
|
104
117
|
}
|
|
105
118
|
|
|
@@ -114,8 +127,8 @@ export function docsHandler({ root, sendJson }) {
|
|
|
114
127
|
|
|
115
128
|
// --- GET /files ---
|
|
116
129
|
if (routePath === '/files' && method === 'GET') {
|
|
117
|
-
const
|
|
118
|
-
const files = await collectFiles(
|
|
130
|
+
const { dirs } = resolvePaths(findSystemRoot(root))
|
|
131
|
+
const files = await collectFiles(dirs.prototypes, root)
|
|
119
132
|
sendJson(res, 200, { files: files.sort() })
|
|
120
133
|
return
|
|
121
134
|
}
|
|
@@ -38,6 +38,7 @@ import { startMaintenance, stopMaintenance } from '../messaging/hub-maintenance.
|
|
|
38
38
|
import { createArtifactRoutes } from '../artifact/routes.js'
|
|
39
39
|
import { createFileHandler } from '../file/server.js'
|
|
40
40
|
import { buildArtifactManifest, resolveManifestEnv } from '../data/artifactManifest.js'
|
|
41
|
+
import { findSystemRoot, contentDir } from '../paths/resolvePaths.js'
|
|
41
42
|
import { buildDataDiscovery } from '../../internals/vite/data-plugin.js'
|
|
42
43
|
|
|
43
44
|
const API_PREFIX = '/_storyboard/'
|
|
@@ -413,9 +414,10 @@ export default function storyboardServer() {
|
|
|
413
414
|
}
|
|
414
415
|
|
|
415
416
|
// Ignore assets/canvas/ so image/snapshot writes don't trigger reloads
|
|
416
|
-
|
|
417
|
-
server.watcher.unwatch(path.join(
|
|
418
|
-
server.watcher.unwatch(path.join(
|
|
417
|
+
const assetsDir = contentDir(root, 'assets')
|
|
418
|
+
server.watcher.unwatch(path.join(assetsDir, 'canvas', 'images'))
|
|
419
|
+
server.watcher.unwatch(path.join(assetsDir, 'canvas', 'snapshots'))
|
|
420
|
+
server.watcher.unwatch(path.join(assetsDir, '.storyboard-public', 'terminal-snapshots'))
|
|
419
421
|
// The entire `.storyboard/` directory is gitignored runtime state
|
|
420
422
|
// (terminal buffers + snapshots, hub messages, selectedwidgets,
|
|
421
423
|
// logs, server registry). None of it should ever feed Vite's
|
|
@@ -930,14 +932,26 @@ export default function storyboardServer() {
|
|
|
930
932
|
// Build-time: emit a static JSON with source files so the inspector
|
|
931
933
|
// works in deployed environments without the dev middleware.
|
|
932
934
|
async generateBundle() {
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
const
|
|
938
|
-
|
|
939
|
-
|
|
935
|
+
// Content (prototypes/components/canvas, README, config, git) lives at the
|
|
936
|
+
// content root — equal to `root` in legacy, the parent of the Vite/system
|
|
937
|
+
// root in the `.backstage` layout.
|
|
938
|
+
const contentRoot = findSystemRoot(root).contentRoot
|
|
939
|
+
const dirs = {
|
|
940
|
+
prototypes: contentDir(root, 'prototypes'),
|
|
941
|
+
components: contentDir(root, 'components'),
|
|
942
|
+
canvas: contentDir(root, 'canvas'),
|
|
943
|
+
}
|
|
944
|
+
const prototypesDir = dirs.prototypes
|
|
945
|
+
|
|
946
|
+
// Collect file lists (prototypes for the files index; all content role
|
|
947
|
+
// dirs for the inspector source map — mirrors the docs /source allowlist).
|
|
948
|
+
const [prototypeFiles, ...roleFileLists] = await Promise.all([
|
|
949
|
+
collectFiles(prototypesDir, contentRoot),
|
|
950
|
+
collectFiles(dirs.prototypes, contentRoot),
|
|
951
|
+
collectFiles(dirs.components, contentRoot),
|
|
952
|
+
collectFiles(dirs.canvas, contentRoot),
|
|
940
953
|
])
|
|
954
|
+
const allSrcFiles = [...new Set(roleFileLists.flat())]
|
|
941
955
|
|
|
942
956
|
// Read all source file contents
|
|
943
957
|
const sources = {}
|
|
@@ -945,7 +959,7 @@ export default function storyboardServer() {
|
|
|
945
959
|
allSrcFiles.map(async (relPath) => {
|
|
946
960
|
try {
|
|
947
961
|
sources[relPath] = await fs.promises.readFile(
|
|
948
|
-
path.join(
|
|
962
|
+
path.join(contentRoot, relPath),
|
|
949
963
|
'utf-8'
|
|
950
964
|
)
|
|
951
965
|
} catch { /* skip unreadable files */ }
|
|
@@ -957,7 +971,7 @@ export default function storyboardServer() {
|
|
|
957
971
|
try {
|
|
958
972
|
const { execSync } = await import('node:child_process')
|
|
959
973
|
const remote = execSync('git remote get-url origin', {
|
|
960
|
-
cwd:
|
|
974
|
+
cwd: contentRoot,
|
|
961
975
|
encoding: 'utf-8',
|
|
962
976
|
}).trim()
|
|
963
977
|
const match = remote.match(/github\.com[:/]([^/]+)\/([^/.]+)/)
|
|
@@ -965,7 +979,7 @@ export default function storyboardServer() {
|
|
|
965
979
|
} catch { /* no git or no remote */ }
|
|
966
980
|
|
|
967
981
|
if (!repo) {
|
|
968
|
-
const configPath = path.join(
|
|
982
|
+
const configPath = path.join(contentRoot, 'storyboard.config.json')
|
|
969
983
|
try {
|
|
970
984
|
const raw = await fs.promises.readFile(configPath, 'utf-8')
|
|
971
985
|
const cfg = JSON.parse(raw)
|
|
@@ -990,7 +1004,7 @@ export default function storyboardServer() {
|
|
|
990
1004
|
let readmeContent = null
|
|
991
1005
|
for (const candidate of ['README.md', 'readme.md', 'Readme.md']) {
|
|
992
1006
|
try {
|
|
993
|
-
readmeContent = await fs.promises.readFile(path.join(
|
|
1007
|
+
readmeContent = await fs.promises.readFile(path.join(contentRoot, candidate), 'utf-8')
|
|
994
1008
|
break
|
|
995
1009
|
} catch { /* try next */ }
|
|
996
1010
|
}
|
|
@@ -1033,8 +1047,8 @@ export default function storyboardServer() {
|
|
|
1033
1047
|
// Dev server serves these dynamically; production needs the static files.
|
|
1034
1048
|
// Private images (prefixed with ~) are excluded from the build.
|
|
1035
1049
|
for (const dir of [
|
|
1036
|
-
path.join(root, 'assets', 'canvas', 'images'),
|
|
1037
|
-
path.join(root, 'assets', 'canvas', 'snapshots'),
|
|
1050
|
+
path.join(contentDir(root, 'assets'), 'canvas', 'images'),
|
|
1051
|
+
path.join(contentDir(root, 'assets'), 'canvas', 'snapshots'),
|
|
1038
1052
|
]) {
|
|
1039
1053
|
try {
|
|
1040
1054
|
const imageFiles = await fs.promises.readdir(dir)
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
resolveTemplateRecipeEntry,
|
|
23
23
|
} from '../templateIndex.js'
|
|
24
24
|
import { readWorkshopPartials } from '../partialRender.js'
|
|
25
|
+
import { contentDir, contentBase, findSystemRoot } from '../../../paths/resolvePaths.js'
|
|
25
26
|
|
|
26
27
|
const FLOW_SKELETON = JSON.stringify({ $global: [] }, null, 2) + '\n'
|
|
27
28
|
|
|
@@ -74,7 +75,7 @@ function validatePrototypeName(name) {
|
|
|
74
75
|
}
|
|
75
76
|
|
|
76
77
|
function listFolders(root) {
|
|
77
|
-
const prototypesDir =
|
|
78
|
+
const prototypesDir = contentDir(root, 'prototypes')
|
|
78
79
|
if (!fs.existsSync(prototypesDir)) return []
|
|
79
80
|
|
|
80
81
|
return fs.readdirSync(prototypesDir, { withFileTypes: true })
|
|
@@ -237,7 +238,7 @@ export function createPrototypesHandler(ctx) {
|
|
|
237
238
|
const isExternal = Boolean(url)
|
|
238
239
|
|
|
239
240
|
// Determine target directory
|
|
240
|
-
const prototypesDir =
|
|
241
|
+
const prototypesDir = contentDir(root, 'prototypes')
|
|
241
242
|
let targetDir
|
|
242
243
|
|
|
243
244
|
if (folder) {
|
|
@@ -266,7 +267,7 @@ export function createPrototypesHandler(ctx) {
|
|
|
266
267
|
generatePrototypeJson({ title, author, description, partialEntry: null, url: isExternal ? url : undefined }),
|
|
267
268
|
)
|
|
268
269
|
|
|
269
|
-
const relDir = targetDir.replace(root + '/', '')
|
|
270
|
+
const relDir = targetDir.replace(findSystemRoot(root).contentRoot + '/', '')
|
|
270
271
|
const result = {
|
|
271
272
|
success: true,
|
|
272
273
|
path: relDir,
|
|
@@ -306,7 +307,7 @@ export function createPrototypesHandler(ctx) {
|
|
|
306
307
|
if (!partialEntry) {
|
|
307
308
|
content = generateBlankIndexJsx(componentName, title)
|
|
308
309
|
} else {
|
|
309
|
-
const partialDir =
|
|
310
|
+
const partialDir = contentBase(root, partialEntry.baseDir, partialEntry.name)
|
|
310
311
|
const componentFile = findComponentFile(partialDir)
|
|
311
312
|
if (!componentFile) {
|
|
312
313
|
sendJson(res, 400, { error: `No .jsx or .tsx file found in src/${partialEntry.baseDir}/${partialEntry.name}/` })
|
|
@@ -343,7 +344,7 @@ export function createPrototypesHandler(ctx) {
|
|
|
343
344
|
return
|
|
344
345
|
}
|
|
345
346
|
|
|
346
|
-
const prototypesDir =
|
|
347
|
+
const prototypesDir = contentDir(root, 'prototypes')
|
|
347
348
|
let targetDir
|
|
348
349
|
|
|
349
350
|
if (folder) {
|
|
@@ -385,7 +386,7 @@ export function createPrototypesHandler(ctx) {
|
|
|
385
386
|
return
|
|
386
387
|
}
|
|
387
388
|
|
|
388
|
-
const prototypesDir =
|
|
389
|
+
const prototypesDir = contentDir(root, 'prototypes')
|
|
389
390
|
let targetDir
|
|
390
391
|
|
|
391
392
|
if (folder) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs'
|
|
2
2
|
import path from 'node:path'
|
|
3
3
|
import { parse as parseJsonc } from 'jsonc-parser'
|
|
4
|
+
import { contentDir, contentBase } from '../../paths/resolvePaths.js'
|
|
4
5
|
|
|
5
6
|
const TEMPLATE_DIR_NAMES = new Set(['template', 'templates'])
|
|
6
7
|
const RECIPE_DIR_NAMES = new Set(['recipe', 'recipes'])
|
|
@@ -31,7 +32,7 @@ function toPartialKind(directory) {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
function listPrototypeDirs(root) {
|
|
34
|
-
const prototypesDir =
|
|
35
|
+
const prototypesDir = contentDir(root, 'prototypes')
|
|
35
36
|
if (!fs.existsSync(prototypesDir)) return []
|
|
36
37
|
|
|
37
38
|
const results = []
|
|
@@ -91,7 +92,7 @@ function resolveGlobalPartialBaseDir(srcRoot, configDirectory, name) {
|
|
|
91
92
|
}
|
|
92
93
|
|
|
93
94
|
export function buildTemplateRecipeIndex(root, configPartials = []) {
|
|
94
|
-
const srcRoot =
|
|
95
|
+
const srcRoot = contentBase(root)
|
|
95
96
|
const results = []
|
|
96
97
|
const seenIds = new Set()
|
|
97
98
|
|