@dfosco/storyboard-react 4.0.0-beta.2 → 4.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,10 +1,10 @@
1
1
  {
2
2
  "name": "@dfosco/storyboard-react",
3
- "version": "4.0.0-beta.2",
3
+ "version": "4.0.0-beta.4",
4
4
  "type": "module",
5
5
  "dependencies": {
6
- "@dfosco/storyboard-core": "4.0.0-beta.2",
7
- "@dfosco/tiny-canvas": "4.0.0-beta.2",
6
+ "@dfosco/storyboard-core": "4.0.0-beta.4",
7
+ "@dfosco/tiny-canvas": "4.0.0-beta.4",
8
8
  "@neodrag/react": "^2.3.1",
9
9
  "glob": "^11.0.0",
10
10
  "jsonc-parser": "^3.3.1"
@@ -155,11 +155,99 @@ function getLastModified(root, dirPath) {
155
155
  }
156
156
  }
157
157
 
158
+ /**
159
+ * Batch-fetch git metadata (author + lastModified) for multiple files in a
160
+ * single subprocess, avoiding per-file git overhead during startup.
161
+ *
162
+ * Returns a Map<absPath, { gitAuthor: string|null, lastModified: string|null }>
163
+ */
164
+ function batchGitMetadata(root, filePaths) {
165
+ const result = new Map()
166
+ if (filePaths.length === 0) return result
167
+
168
+ // Initialize all entries
169
+ for (const fp of filePaths) {
170
+ result.set(fp, { gitAuthor: null, lastModified: null })
171
+ }
172
+
173
+ try {
174
+ // Batch lastModified: one git log call with all paths
175
+ // git log -1 gives the most recent commit touching any of these paths,
176
+ // but we need per-path data. Use --name-only to correlate.
177
+ // For efficiency, use a single git log with --format and --name-only
178
+ // that outputs one record per commit touching these files.
179
+ const allDirs = [...new Set(filePaths.map(fp => path.dirname(fp)))]
180
+ const dirsArg = allDirs.map(d => `"${d}"`).join(' ')
181
+
182
+ // Get lastModified per directory in one call using git log --format
183
+ // We output "MARKER<sep>dir<sep>date" per commit, then take the latest per dir.
184
+ const logResult = execSync(
185
+ `git log --format="%aI" --name-only -- ${dirsArg}`,
186
+ { cwd: root, encoding: 'utf-8', timeout: 10000, maxBuffer: 1024 * 1024 },
187
+ ).trim()
188
+
189
+ if (logResult) {
190
+ // Parse: alternating date lines and filename lines separated by blank lines
191
+ const blocks = logResult.split('\n\n')
192
+ const dirDates = new Map() // dir → most recent date
193
+ for (const block of blocks) {
194
+ const lines = block.split('\n').filter(Boolean)
195
+ if (lines.length < 2) continue
196
+ const date = lines[0]
197
+ for (let li = 1; li < lines.length; li++) {
198
+ const fileLine = lines[li].trim()
199
+ if (!fileLine) continue
200
+ const dir = path.dirname(path.resolve(root, fileLine))
201
+ if (!dirDates.has(dir)) {
202
+ dirDates.set(dir, date)
203
+ }
204
+ }
205
+ }
206
+ for (const fp of filePaths) {
207
+ const dir = path.dirname(fp)
208
+ const entry = result.get(fp)
209
+ if (dirDates.has(dir) && entry) {
210
+ entry.lastModified = dirDates.get(dir)
211
+ }
212
+ }
213
+ }
214
+ } catch { /* git not available or failed — leave nulls */ }
215
+
216
+ // Batch gitAuthor: use git log for each file's creation author.
217
+ // Unfortunately --follow --diff-filter=A doesn't combine well with multiple
218
+ // paths, so batch them in a single shell invocation using a for loop.
219
+ try {
220
+ const relPaths = filePaths.map(fp => path.relative(root, fp))
221
+ // Build a shell script that outputs "PATH<tab>AUTHOR" per file
222
+ const cmds = relPaths.map(rp =>
223
+ `echo -n "${rp}\\t"; git log --follow --diff-filter=A --format="%aN" -- "${rp}" | tail -1`
224
+ ).join('; ')
225
+ const authorResult = execSync(cmds, {
226
+ cwd: root, encoding: 'utf-8', timeout: 10000, shell: true, maxBuffer: 1024 * 1024,
227
+ }).trim()
228
+
229
+ if (authorResult) {
230
+ for (const line of authorResult.split('\n')) {
231
+ const tabIdx = line.indexOf('\t')
232
+ if (tabIdx < 0) continue
233
+ const relPath = line.slice(0, tabIdx)
234
+ const author = line.slice(tabIdx + 1).trim()
235
+ if (!author) continue
236
+ const absPath2 = path.resolve(root, relPath)
237
+ const entry = result.get(absPath2)
238
+ if (entry) entry.gitAuthor = author
239
+ }
240
+ }
241
+ } catch { /* git not available */ }
242
+
243
+ return result
244
+ }
245
+
158
246
  /**
159
247
  * Scan the repo for all data files, validate uniqueness, return the index.
160
248
  */
161
249
  function buildIndex(root) {
162
- const ignore = ['node_modules/**', 'dist/**', '.git/**']
250
+ const ignore = ['node_modules/**', 'dist/**', '.git/**', '.worktrees/**', 'public/**']
163
251
  const files = globSync(GLOB_PATTERN, { cwd: root, ignore, absolute: false })
164
252
  const canvasFiles = globSync(CANVAS_GLOB_PATTERN, { cwd: root, ignore, absolute: false })
165
253
 
@@ -339,6 +427,13 @@ function generateModule({ index, protoFolders, flowRoutes, canvasRoutes }, root)
339
427
  const resolvedFlowRoutes = {} // flow name → resolved route (for multi-flow logging)
340
428
  let i = 0
341
429
 
430
+ // Batch-fetch git metadata for all prototype + canvas files in 1-2 subprocesses
431
+ const gitPaths = [
432
+ ...Object.values(index.prototype || {}),
433
+ ...Object.values(index.canvas || {}),
434
+ ]
435
+ const gitMeta = batchGitMetadata(root, gitPaths)
436
+
342
437
  for (const suffix of INDEX_KEYS) {
343
438
  for (const [name, absPath] of Object.entries(index[suffix])) {
344
439
  const varName = `_d${i++}`
@@ -349,18 +444,17 @@ function generateModule({ index, protoFolders, flowRoutes, canvasRoutes }, root)
349
444
 
350
445
  // Auto-fill gitAuthor for prototype metadata from git history
351
446
  if (suffix === 'prototype' && parsed && !parsed.gitAuthor) {
352
- const gitAuthor = getGitAuthor(root, absPath)
353
- if (gitAuthor) {
354
- parsed = { ...parsed, gitAuthor }
447
+ const meta = gitMeta.get(absPath)
448
+ if (meta?.gitAuthor) {
449
+ parsed = { ...parsed, gitAuthor: meta.gitAuthor }
355
450
  }
356
451
  }
357
452
 
358
453
  // Auto-fill lastModified from git history for prototypes
359
454
  if (suffix === 'prototype' && parsed) {
360
- const protoDir = path.dirname(absPath)
361
- const lastModified = getLastModified(root, protoDir)
362
- if (lastModified) {
363
- parsed = { ...parsed, lastModified }
455
+ const meta = gitMeta.get(absPath)
456
+ if (meta?.lastModified) {
457
+ parsed = { ...parsed, lastModified: meta.lastModified }
364
458
  }
365
459
  }
366
460
 
@@ -399,9 +493,9 @@ function generateModule({ index, protoFolders, flowRoutes, canvasRoutes }, root)
399
493
 
400
494
  // Auto-fill gitAuthor for canvas metadata from git history
401
495
  if (suffix === 'canvas' && parsed && !parsed.gitAuthor) {
402
- const gitAuthor = getGitAuthor(root, absPath)
403
- if (gitAuthor) {
404
- parsed = { ...parsed, gitAuthor }
496
+ const meta = gitMeta.get(absPath)
497
+ if (meta?.gitAuthor) {
498
+ parsed = { ...parsed, gitAuthor: meta.gitAuthor }
405
499
  }
406
500
  }
407
501