@dfosco/storyboard-core 4.0.0-beta.2 → 4.0.0-beta.21
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 +11882 -11126
- package/dist/storyboard-ui.js.map +1 -1
- package/dist/tailwind.css +1 -1
- package/package.json +11 -3
- package/paste.config.json +54 -0
- package/scaffold/deploy.yml +101 -0
- package/scaffold/githooks/pre-push +114 -0
- package/scaffold/manifest.json +11 -0
- package/scaffold/storyboard.config.json +4 -1
- package/src/ActionMenuButton.svelte +12 -2
- package/src/CanvasCreateMenu.svelte +228 -10
- package/src/CanvasSnap.svelte +2 -0
- package/src/CoreUIBar.svelte +152 -3
- package/src/CreateMenuButton.svelte +4 -1
- package/src/InspectorPanel.svelte +2 -0
- package/src/PwaInstallBanner.svelte +124 -0
- package/src/autosync/server.js +99 -111
- package/src/autosync/server.test.js +0 -7
- package/src/canvas/collision.js +206 -0
- package/src/canvas/collision.test.js +271 -0
- package/src/canvas/deriveCanvasId.test.js +40 -0
- package/src/canvas/identity.js +107 -0
- package/src/canvas/identity.test.js +100 -0
- package/src/canvas/server.js +285 -31
- package/src/canvasConfig.js +56 -0
- package/src/canvasConfig.test.js +42 -0
- package/src/cli/canvasAdd.js +185 -0
- package/src/cli/canvasRead.js +208 -0
- package/src/cli/code.js +67 -0
- package/src/cli/create.js +339 -72
- package/src/cli/dev-helpers.js +53 -0
- package/src/cli/dev-helpers.test.js +53 -0
- package/src/cli/dev.js +245 -26
- package/src/cli/flags.js +174 -0
- package/src/cli/flags.test.js +155 -0
- package/src/cli/index.js +84 -13
- package/src/cli/intro.js +37 -0
- package/src/cli/proxy.js +127 -6
- package/src/cli/proxy.test.js +63 -0
- package/src/cli/schemas.js +200 -0
- package/src/cli/serverUrl.js +56 -0
- package/src/cli/setup.js +130 -20
- package/src/cli/snapshots.js +335 -0
- package/src/cli/updateVersion.js +54 -3
- package/src/configSchema.js +125 -0
- package/src/configSchema.test.js +68 -0
- package/src/index.js +5 -0
- package/src/inspector/highlighter.js +10 -2
- package/src/lib/components/ui/trigger-button/trigger-button.svelte +1 -1
- package/src/loader.js +21 -2
- package/src/loader.test.js +63 -1
- package/src/mobileViewport.js +57 -0
- package/src/mobileViewport.test.js +68 -0
- package/src/mountStoryboardCore.js +61 -7
- package/src/rename-watcher/config.json +23 -0
- package/src/rename-watcher/watcher.js +538 -0
- package/src/svelte-plugin-ui/components/Viewfinder.svelte +6 -17
- package/src/tools/handlers/flows.js +6 -7
- package/src/viewfinder.js +21 -9
- package/src/viewfinder.test.js +2 -2
- package/src/vite/server-plugin.js +150 -7
- package/src/workshop/features/createCanvas/CreateCanvasForm.svelte +8 -2
- package/src/workshop/features/createFlow/CreateFlowForm.svelte +1 -1
- package/src/workshop/features/createPage/CreatePageForm.svelte +1 -1
- package/src/workshop/features/createPrototype/CreatePrototypeForm.svelte +2 -2
- package/src/workshop/features/createStory/CreateStoryForm.svelte +160 -0
- package/src/workshop/features/createStory/index.js +14 -0
- package/src/workshop/features/registry.js +2 -0
- package/src/worktree/port.js +57 -1
- package/src/worktree/port.test.js +91 -1
- package/toolbar.config.json +3 -3
- package/widgets.config.json +132 -27
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storyboard canvas add <widget-type> — Add a widget to an existing canvas.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* storyboard canvas add sticky-note --canvas my-canvas
|
|
6
|
+
* storyboard canvas add markdown --canvas my-canvas --x 100 --y 200
|
|
7
|
+
* storyboard canvas add prototype --canvas my-canvas --props '{"src":"my-proto"}'
|
|
8
|
+
*
|
|
9
|
+
* Known widget types: sticky-note, markdown, prototype
|
|
10
|
+
* The server accepts any type string — new widget types work automatically.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import * as p from '@clack/prompts'
|
|
14
|
+
import { parseFlags, hasFlags, formatFlagHelp } from './flags.js'
|
|
15
|
+
import { widgetSchema } from './schemas.js'
|
|
16
|
+
import { ensureDevServer, serverPost, getServerUrl } from './create.js'
|
|
17
|
+
|
|
18
|
+
const KNOWN_TYPES = ['sticky-note', 'markdown', 'prototype', 'story']
|
|
19
|
+
|
|
20
|
+
async function canvasAdd() {
|
|
21
|
+
// argv: storyboard canvas add [type] [--flags]
|
|
22
|
+
// process.argv: [node, script, 'canvas', 'add', ...rest]
|
|
23
|
+
const rest = process.argv.slice(4)
|
|
24
|
+
|
|
25
|
+
if (rest.includes('--help') || rest.includes('-h')) {
|
|
26
|
+
console.log(`\n canvas add flags:\n`)
|
|
27
|
+
console.log(` Positional: <widget-type> Widget type (${KNOWN_TYPES.join(', ')})\n`)
|
|
28
|
+
console.log(formatFlagHelp(widgetSchema))
|
|
29
|
+
console.log('')
|
|
30
|
+
process.exit(0)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Extract positional widget type (first non-flag arg)
|
|
34
|
+
let widgetType = ''
|
|
35
|
+
const flagTokens = []
|
|
36
|
+
for (const token of rest) {
|
|
37
|
+
if (!widgetType && !token.startsWith('-')) {
|
|
38
|
+
widgetType = token
|
|
39
|
+
} else {
|
|
40
|
+
flagTokens.push(token)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const flagMode = hasFlags(rest) || Boolean(widgetType)
|
|
45
|
+
const { flags, errors } = flagMode ? parseFlags(flagTokens, widgetSchema) : { flags: {}, errors: [] }
|
|
46
|
+
|
|
47
|
+
if (errors.length) {
|
|
48
|
+
for (const e of errors) p.log.error(e)
|
|
49
|
+
process.exit(1)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
p.intro('storyboard canvas add')
|
|
53
|
+
await ensureDevServer()
|
|
54
|
+
|
|
55
|
+
// Widget type
|
|
56
|
+
if (!widgetType) {
|
|
57
|
+
widgetType = await p.select({
|
|
58
|
+
message: 'Widget type',
|
|
59
|
+
options: KNOWN_TYPES.map((t) => ({ value: t, label: t })),
|
|
60
|
+
})
|
|
61
|
+
if (p.isCancel(widgetType)) return process.exit(0)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Canvas name
|
|
65
|
+
const canvasName = flags.canvas || await (async () => {
|
|
66
|
+
// Try to fetch available canvases
|
|
67
|
+
let canvases = []
|
|
68
|
+
try {
|
|
69
|
+
const base = getServerUrl()
|
|
70
|
+
const res = await fetch(`${base}/_storyboard/canvas/list`)
|
|
71
|
+
if (res.ok) {
|
|
72
|
+
const data = await res.json()
|
|
73
|
+
canvases = data.canvases || data || []
|
|
74
|
+
}
|
|
75
|
+
} catch {}
|
|
76
|
+
|
|
77
|
+
if (canvases.length > 0) {
|
|
78
|
+
const choice = await p.select({
|
|
79
|
+
message: 'Canvas',
|
|
80
|
+
options: canvases.map((c) => ({ value: c.name || c, label: c.name || c })),
|
|
81
|
+
})
|
|
82
|
+
if (p.isCancel(choice)) process.exit(0)
|
|
83
|
+
return choice
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const v = await p.text({
|
|
87
|
+
message: 'Canvas name',
|
|
88
|
+
placeholder: 'my-canvas',
|
|
89
|
+
validate: (v) => { if (!v) return 'Canvas name is required' },
|
|
90
|
+
})
|
|
91
|
+
if (p.isCancel(v)) process.exit(0)
|
|
92
|
+
return v
|
|
93
|
+
})()
|
|
94
|
+
|
|
95
|
+
const x = flags.x ?? 0
|
|
96
|
+
const y = flags.y ?? 0
|
|
97
|
+
|
|
98
|
+
let props = {}
|
|
99
|
+
if (flags.props) {
|
|
100
|
+
try {
|
|
101
|
+
props = JSON.parse(flags.props)
|
|
102
|
+
} catch {
|
|
103
|
+
p.log.error('--props must be valid JSON')
|
|
104
|
+
process.exit(1)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Story-specific: prompt for storyId and exportName if not in --props
|
|
109
|
+
if (widgetType === 'story' && !props.storyId) {
|
|
110
|
+
// Try to fetch available stories from the dev server
|
|
111
|
+
let stories = []
|
|
112
|
+
try {
|
|
113
|
+
const base = getServerUrl()
|
|
114
|
+
const res = await fetch(`${base}/_storyboard/stories/list`)
|
|
115
|
+
if (res.ok) {
|
|
116
|
+
const data = await res.json()
|
|
117
|
+
stories = data.stories || []
|
|
118
|
+
}
|
|
119
|
+
} catch {}
|
|
120
|
+
|
|
121
|
+
if (stories.length > 0) {
|
|
122
|
+
const storyId = await (async () => {
|
|
123
|
+
const choice = await p.select({
|
|
124
|
+
message: 'Story',
|
|
125
|
+
options: stories.map((s) => ({ value: s.name, label: s.name, hint: s.route || '' })),
|
|
126
|
+
})
|
|
127
|
+
if (p.isCancel(choice)) process.exit(0)
|
|
128
|
+
return choice
|
|
129
|
+
})()
|
|
130
|
+
props.storyId = storyId
|
|
131
|
+
} else {
|
|
132
|
+
const storyId = await (async () => {
|
|
133
|
+
const v = await p.text({
|
|
134
|
+
message: 'Story ID',
|
|
135
|
+
placeholder: 'button-patterns',
|
|
136
|
+
validate: (v) => { if (!v) return 'Story ID is required' },
|
|
137
|
+
})
|
|
138
|
+
if (p.isCancel(v)) process.exit(0)
|
|
139
|
+
return v
|
|
140
|
+
})()
|
|
141
|
+
props.storyId = storyId
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (!props.exportName) {
|
|
145
|
+
const exportName = await (async () => {
|
|
146
|
+
const v = await p.text({
|
|
147
|
+
message: 'Export name (leave empty for all)',
|
|
148
|
+
placeholder: 'Default',
|
|
149
|
+
})
|
|
150
|
+
if (p.isCancel(v)) process.exit(0)
|
|
151
|
+
return v
|
|
152
|
+
})()
|
|
153
|
+
if (exportName) props.exportName = exportName
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!props.width) props.width = 600
|
|
157
|
+
if (!props.height) props.height = 400
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Submit
|
|
161
|
+
const s = p.spinner()
|
|
162
|
+
s.start(`Adding ${widgetType} widget...`)
|
|
163
|
+
|
|
164
|
+
try {
|
|
165
|
+
const result = await serverPost('/_storyboard/canvas/widget', {
|
|
166
|
+
name: canvasName,
|
|
167
|
+
type: widgetType,
|
|
168
|
+
props,
|
|
169
|
+
position: { x, y },
|
|
170
|
+
})
|
|
171
|
+
s.stop(`Widget added!`)
|
|
172
|
+
if (result.id) {
|
|
173
|
+
p.log.success(` ${widgetType} → ${canvasName} (id: ${result.id})`)
|
|
174
|
+
} else {
|
|
175
|
+
p.log.success(` ${widgetType} → ${canvasName}`)
|
|
176
|
+
}
|
|
177
|
+
p.outro('')
|
|
178
|
+
} catch (err) {
|
|
179
|
+
s.stop('Failed to add widget')
|
|
180
|
+
p.log.error(err.message)
|
|
181
|
+
p.outro('')
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
canvasAdd()
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storyboard canvas read — Read canvas state and list widgets with their IDs, URLs, and content.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* storyboard canvas read my-canvas List all widgets
|
|
6
|
+
* storyboard canvas read my-canvas --json Output as JSON
|
|
7
|
+
* storyboard canvas read my-canvas --id abc Get a specific widget by ID
|
|
8
|
+
*
|
|
9
|
+
* Output includes:
|
|
10
|
+
* - Widget ID
|
|
11
|
+
* - Widget type
|
|
12
|
+
* - Position (x, y)
|
|
13
|
+
* - Content (text, url, src depending on type)
|
|
14
|
+
* - File path (for images)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import * as p from '@clack/prompts'
|
|
18
|
+
import { getServerUrl } from './serverUrl.js'
|
|
19
|
+
|
|
20
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`
|
|
21
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`
|
|
22
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`
|
|
23
|
+
|
|
24
|
+
async function checkServer() {
|
|
25
|
+
try {
|
|
26
|
+
await fetch(getServerUrl(), { signal: AbortSignal.timeout(2000) })
|
|
27
|
+
return true
|
|
28
|
+
} catch {
|
|
29
|
+
return false
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Extract the primary content from a widget based on its type.
|
|
35
|
+
*/
|
|
36
|
+
function getWidgetContent(widget) {
|
|
37
|
+
const { type, props = {} } = widget
|
|
38
|
+
switch (type) {
|
|
39
|
+
case 'sticky-note':
|
|
40
|
+
return { content: props.text || '', contentType: 'text' }
|
|
41
|
+
case 'markdown':
|
|
42
|
+
return { content: props.content || '', contentType: 'markdown' }
|
|
43
|
+
case 'prototype':
|
|
44
|
+
return { content: props.src || '', contentType: 'url', url: props.src }
|
|
45
|
+
case 'figma-embed':
|
|
46
|
+
return { content: props.url || '', contentType: 'url', url: props.url }
|
|
47
|
+
case 'link-preview':
|
|
48
|
+
return { content: props.url || '', contentType: 'url', url: props.url }
|
|
49
|
+
case 'image':
|
|
50
|
+
return {
|
|
51
|
+
content: props.src || '',
|
|
52
|
+
contentType: 'image',
|
|
53
|
+
url: props.src ? `/_storyboard/canvas/images/${props.src}` : '',
|
|
54
|
+
filePath: props.src ? `assets/canvas/images/${props.src}` : '',
|
|
55
|
+
}
|
|
56
|
+
default:
|
|
57
|
+
return { content: JSON.stringify(props), contentType: 'props' }
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Format a widget for human-readable output.
|
|
63
|
+
*/
|
|
64
|
+
function formatWidget(widget) {
|
|
65
|
+
const { id, type, position = {} } = widget
|
|
66
|
+
const { content, contentType, url, filePath } = getWidgetContent(widget)
|
|
67
|
+
|
|
68
|
+
const lines = []
|
|
69
|
+
lines.push(`${bold(id)} ${dim(`(${type})`)}`)
|
|
70
|
+
lines.push(` Position: ${position.x ?? 0}, ${position.y ?? 0}`)
|
|
71
|
+
|
|
72
|
+
if (contentType === 'image') {
|
|
73
|
+
lines.push(` File: ${cyan(filePath)}`)
|
|
74
|
+
if (content) lines.push(` Filename: ${content}`)
|
|
75
|
+
} else if (contentType === 'url') {
|
|
76
|
+
lines.push(` URL: ${cyan(url || content)}`)
|
|
77
|
+
} else if (content) {
|
|
78
|
+
const preview = content.length > 80 ? content.slice(0, 77) + '...' : content
|
|
79
|
+
lines.push(` Content: ${preview}`)
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return lines.join('\n')
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function canvasRead() {
|
|
86
|
+
const args = process.argv.slice(4)
|
|
87
|
+
|
|
88
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
89
|
+
console.log(`
|
|
90
|
+
canvas read — Read canvas state and list widgets
|
|
91
|
+
|
|
92
|
+
Usage:
|
|
93
|
+
storyboard canvas read <canvas-name> List all widgets
|
|
94
|
+
storyboard canvas read <canvas-name> --json Output as JSON
|
|
95
|
+
storyboard canvas read <canvas-name> --id <widget-id> Get specific widget
|
|
96
|
+
|
|
97
|
+
Output includes widget ID, type, position, content, URLs, and file paths.
|
|
98
|
+
`)
|
|
99
|
+
process.exit(0)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Parse args
|
|
103
|
+
let canvasName = ''
|
|
104
|
+
let outputJson = false
|
|
105
|
+
let widgetId = ''
|
|
106
|
+
|
|
107
|
+
for (let i = 0; i < args.length; i++) {
|
|
108
|
+
const arg = args[i]
|
|
109
|
+
if (arg === '--json') {
|
|
110
|
+
outputJson = true
|
|
111
|
+
} else if (arg === '--id' && args[i + 1]) {
|
|
112
|
+
widgetId = args[++i]
|
|
113
|
+
} else if (!arg.startsWith('-') && !canvasName) {
|
|
114
|
+
canvasName = arg
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Check server
|
|
119
|
+
const serverUp = await checkServer()
|
|
120
|
+
if (!serverUp) {
|
|
121
|
+
p.log.error('Dev server is not running. Start it with: storyboard dev')
|
|
122
|
+
process.exit(1)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const base = getServerUrl()
|
|
126
|
+
|
|
127
|
+
// If no canvas name, list available canvases
|
|
128
|
+
if (!canvasName) {
|
|
129
|
+
try {
|
|
130
|
+
const res = await fetch(`${base}/_storyboard/canvas/list`)
|
|
131
|
+
if (!res.ok) throw new Error(`${res.status} ${res.statusText}`)
|
|
132
|
+
const data = await res.json()
|
|
133
|
+
const canvases = data.canvases || []
|
|
134
|
+
|
|
135
|
+
if (outputJson) {
|
|
136
|
+
console.log(JSON.stringify(canvases, null, 2))
|
|
137
|
+
} else {
|
|
138
|
+
if (canvases.length === 0) {
|
|
139
|
+
p.log.info('No canvases found')
|
|
140
|
+
} else {
|
|
141
|
+
console.log('\nAvailable canvases:\n')
|
|
142
|
+
for (const c of canvases) {
|
|
143
|
+
console.log(` ${bold(c.name)} ${dim(`(${c.widgetCount} widgets)`)}`)
|
|
144
|
+
}
|
|
145
|
+
console.log('')
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} catch (err) {
|
|
149
|
+
p.log.error(`Failed to list canvases: ${err.message}`)
|
|
150
|
+
process.exit(1)
|
|
151
|
+
}
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Read canvas state
|
|
156
|
+
try {
|
|
157
|
+
let url = `${base}/_storyboard/canvas/read?name=${encodeURIComponent(canvasName)}`
|
|
158
|
+
if (widgetId) url += `&widget=${encodeURIComponent(widgetId)}`
|
|
159
|
+
const res = await fetch(url)
|
|
160
|
+
if (!res.ok) {
|
|
161
|
+
const text = await res.text().catch(() => '')
|
|
162
|
+
throw new Error(`${res.status} ${res.statusText}${text ? ': ' + text : ''}`)
|
|
163
|
+
}
|
|
164
|
+
const data = await res.json()
|
|
165
|
+
const widgets = data.widgets || []
|
|
166
|
+
|
|
167
|
+
// If filtering by widget ID
|
|
168
|
+
if (widgetId) {
|
|
169
|
+
const widget = widgets[0]
|
|
170
|
+
if (!widget) {
|
|
171
|
+
p.log.error(`Widget "${widgetId}" not found in canvas "${canvasName}"`)
|
|
172
|
+
process.exit(1)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (outputJson) {
|
|
176
|
+
const enriched = { ...widget, ...getWidgetContent(widget) }
|
|
177
|
+
console.log(JSON.stringify(enriched, null, 2))
|
|
178
|
+
} else {
|
|
179
|
+
console.log('')
|
|
180
|
+
console.log(formatWidget(widget))
|
|
181
|
+
console.log('')
|
|
182
|
+
}
|
|
183
|
+
return
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// List all widgets
|
|
187
|
+
if (outputJson) {
|
|
188
|
+
const enriched = widgets.map((w) => ({ ...w, ...getWidgetContent(w) }))
|
|
189
|
+
console.log(JSON.stringify({ ...data, widgets: enriched }, null, 2))
|
|
190
|
+
} else {
|
|
191
|
+
console.log(`\n${bold(data.title || canvasName)} ${dim(`(${widgets.length} widgets)`)}\n`)
|
|
192
|
+
|
|
193
|
+
if (widgets.length === 0) {
|
|
194
|
+
console.log(' No widgets on this canvas.\n')
|
|
195
|
+
} else {
|
|
196
|
+
for (const widget of widgets) {
|
|
197
|
+
console.log(formatWidget(widget))
|
|
198
|
+
console.log('')
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} catch (err) {
|
|
203
|
+
p.log.error(`Failed to read canvas: ${err.message}`)
|
|
204
|
+
process.exit(1)
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
canvasRead()
|
package/src/cli/code.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* storyboard code [branch] — Open a worktree in VS Code.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* storyboard code # open current worktree or repo root
|
|
6
|
+
* storyboard code main # open repo root
|
|
7
|
+
* storyboard code <branch> # open .worktrees/<branch>/
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import * as p from '@clack/prompts'
|
|
11
|
+
import { execFileSync } from 'child_process'
|
|
12
|
+
import { existsSync } from 'fs'
|
|
13
|
+
import { resolve } from 'path'
|
|
14
|
+
import { detectWorktreeName, repoRoot, worktreeDir, listWorktrees } from '../worktree/port.js'
|
|
15
|
+
|
|
16
|
+
const branch = process.argv[3] || undefined
|
|
17
|
+
|
|
18
|
+
p.intro('storyboard code')
|
|
19
|
+
|
|
20
|
+
function openInCode(dir) {
|
|
21
|
+
try {
|
|
22
|
+
execFileSync('code', [dir], { stdio: 'inherit' })
|
|
23
|
+
return true
|
|
24
|
+
} catch {
|
|
25
|
+
return false
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const root = repoRoot()
|
|
30
|
+
|
|
31
|
+
if (!branch) {
|
|
32
|
+
// No argument — open current directory
|
|
33
|
+
const name = detectWorktreeName()
|
|
34
|
+
const dir = name === 'main' ? root : worktreeDir(name)
|
|
35
|
+
if (openInCode(dir)) {
|
|
36
|
+
p.outro(`Opened ${name === 'main' ? 'repo root' : `.worktrees/${name}/`}`)
|
|
37
|
+
} else {
|
|
38
|
+
p.log.error('Could not open VS Code. Is the `code` CLI installed?')
|
|
39
|
+
p.log.info('Run `npx storyboard setup` to install it, or open VS Code and run:')
|
|
40
|
+
p.log.info(' Cmd+Shift+P → "Shell Command: Install \'code\' command in PATH"')
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
} else if (branch === 'main') {
|
|
44
|
+
if (openInCode(root)) {
|
|
45
|
+
p.outro('Opened repo root')
|
|
46
|
+
} else {
|
|
47
|
+
p.log.error('Could not open VS Code.')
|
|
48
|
+
process.exit(1)
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
const dir = worktreeDir(branch)
|
|
52
|
+
if (!existsSync(resolve(dir, '.git'))) {
|
|
53
|
+
// List available worktrees as a hint
|
|
54
|
+
const available = listWorktrees()
|
|
55
|
+
p.log.error(`Worktree "${branch}" does not exist.`)
|
|
56
|
+
if (available.length > 0) {
|
|
57
|
+
p.log.info(`Available worktrees: ${available.join(', ')}`)
|
|
58
|
+
}
|
|
59
|
+
process.exit(1)
|
|
60
|
+
}
|
|
61
|
+
if (openInCode(dir)) {
|
|
62
|
+
p.outro(`Opened .worktrees/${branch}/`)
|
|
63
|
+
} else {
|
|
64
|
+
p.log.error('Could not open VS Code.')
|
|
65
|
+
process.exit(1)
|
|
66
|
+
}
|
|
67
|
+
}
|