promptslide 0.3.4 → 0.3.5
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/CHANGELOG.md +8 -0
- package/package.json +1 -1
- package/src/commands/publish.mjs +119 -16
- package/src/utils/deck-config.mjs +47 -17
- package/src/utils/registry.mjs +47 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.5](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.4...promptslide-v0.3.5) (2026-03-08)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* bundle shared sources with deck publish and fix deck-config parsing ([5f7fd1d](https://github.com/prompticeu/promptslide/commit/5f7fd1de6e5fc58a0568bd87830094dd40c9eca7))
|
|
9
|
+
* persist deck and item metadata to lockfile for publish defaults ([#68](https://github.com/prompticeu/promptslide/issues/68)) ([0dc8ca3](https://github.com/prompticeu/promptslide/commit/0dc8ca3f52b86214eb42dacd9e1ead4d05928242))
|
|
10
|
+
|
|
3
11
|
## [0.3.4](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.3...promptslide-v0.3.4) (2026-03-07)
|
|
4
12
|
|
|
5
13
|
|
package/package.json
CHANGED
package/src/commands/publish.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from "node:url"
|
|
|
5
5
|
import { bold, green, cyan, red, dim } from "../utils/ansi.mjs"
|
|
6
6
|
import { requireAuth } from "../utils/auth.mjs"
|
|
7
7
|
import { captureSlideAsDataUri, isPlaywrightAvailable, createCaptureSession } from "../utils/export.mjs"
|
|
8
|
-
import { publishToRegistry, registryItemExists, searchRegistry, updateLockfileItem, updateLockfilePublishConfig, readLockfile, writeLockfile, hashContent, detectPackageManager, requestUploadTokens, uploadBinaryToBlob, assetFileToSlug, detectAssetDepsInContent } from "../utils/registry.mjs"
|
|
8
|
+
import { publishToRegistry, registryItemExists, searchRegistry, updateLockfileItem, updateLockfilePublishConfig, readLockfile, writeLockfile, hashContent, detectPackageManager, requestUploadTokens, uploadBinaryToBlob, assetFileToSlug, detectAssetDepsInContent, readDeckMeta, updateDeckMeta, readItemMeta, updateItemMeta } from "../utils/registry.mjs"
|
|
9
9
|
import { prompt, confirm, select, closePrompts } from "../utils/prompts.mjs"
|
|
10
10
|
import { parseDeckConfig } from "../utils/deck-config.mjs"
|
|
11
11
|
|
|
@@ -91,6 +91,57 @@ function detectRegistryDeps(content) {
|
|
|
91
91
|
return deps
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* Discover shared source files under src/ that aren't slides, layouts, or theme.
|
|
96
|
+
* These are files in directories like src/components/, src/lib/, src/hooks/, etc.
|
|
97
|
+
* Returns an array of { relativePath, fullPath, target } objects.
|
|
98
|
+
*/
|
|
99
|
+
function discoverSharedSources(cwd) {
|
|
100
|
+
const srcDir = join(cwd, "src")
|
|
101
|
+
if (!existsSync(srcDir)) return []
|
|
102
|
+
|
|
103
|
+
// Top-level dirs whose top-level files are published as individual items
|
|
104
|
+
// (slides/*.tsx → slide items, layouts/*.tsx → layout items).
|
|
105
|
+
// Subdirectories within these ARE included as shared sources (e.g. slides/commercial/slide1.tsx).
|
|
106
|
+
const ITEM_DIRS = new Set(["slides", "layouts"])
|
|
107
|
+
// Top-level files already handled (theme.ts, globals.css, deck-config.ts, App.tsx)
|
|
108
|
+
const SKIP_FILES = new Set(["theme.ts", "globals.css", "deck-config.ts", "App.tsx", "vite-env.d.ts"])
|
|
109
|
+
const SOURCE_EXTS = new Set([".tsx", ".ts", ".jsx", ".js"])
|
|
110
|
+
|
|
111
|
+
const results = []
|
|
112
|
+
|
|
113
|
+
function walk(dir, relativeDir, skipTopLevelFiles) {
|
|
114
|
+
if (!existsSync(dir)) return
|
|
115
|
+
for (const entry of readdirSync(dir)) {
|
|
116
|
+
if (entry.startsWith(".")) continue
|
|
117
|
+
const full = join(dir, entry)
|
|
118
|
+
const s = statSync(full)
|
|
119
|
+
if (s.isDirectory()) {
|
|
120
|
+
// Enter slides/ and layouts/ but mark that top-level files are handled elsewhere
|
|
121
|
+
if (!relativeDir && ITEM_DIRS.has(entry)) {
|
|
122
|
+
walk(full, entry, true)
|
|
123
|
+
} else {
|
|
124
|
+
walk(full, relativeDir ? `${relativeDir}/${entry}` : entry, false)
|
|
125
|
+
}
|
|
126
|
+
} else if (s.isFile()) {
|
|
127
|
+
// Skip known top-level files in src/
|
|
128
|
+
if (!relativeDir && SKIP_FILES.has(entry)) continue
|
|
129
|
+
// Skip top-level files in slides/ and layouts/ (published as individual items)
|
|
130
|
+
if (skipTopLevelFiles) continue
|
|
131
|
+
const ext = extname(entry).toLowerCase()
|
|
132
|
+
if (!SOURCE_EXTS.has(ext)) continue
|
|
133
|
+
const relativePath = relativeDir ? `${relativeDir}/${entry}` : entry
|
|
134
|
+
// target mirrors the src/ directory structure for @/ import resolution
|
|
135
|
+
const target = relativeDir ? `src/${relativeDir}/` : "src/"
|
|
136
|
+
results.push({ relativePath, fullPath: full, fileName: entry, target })
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
walk(srcDir, "", false)
|
|
142
|
+
return results
|
|
143
|
+
}
|
|
144
|
+
|
|
94
145
|
const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".webp"])
|
|
95
146
|
const MAX_IMAGE_SIZE = 2 * 1024 * 1024 // 2MB
|
|
96
147
|
|
|
@@ -254,11 +305,13 @@ async function publishItem({ filePath, cwd, auth, typeOverride, interactive = tr
|
|
|
254
305
|
let title, description, tags, section, releaseNotes, previewImage
|
|
255
306
|
|
|
256
307
|
if (interactive) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
308
|
+
const storedMeta = readItemMeta(cwd, slug)
|
|
309
|
+
title = await prompt("Title:", storedMeta.title || titleCase(baseSlug))
|
|
310
|
+
description = await prompt("Description:", storedMeta.description || "")
|
|
311
|
+
const storedTagsDefault = storedMeta.tags?.length ? storedMeta.tags.join(", ") : ""
|
|
312
|
+
const tagsInput = await prompt("Tags (comma-separated):", storedTagsDefault)
|
|
260
313
|
tags = tagsInput ? tagsInput.split(",").map(t => t.trim()).filter(Boolean) : []
|
|
261
|
-
section = await prompt("Section:", "")
|
|
314
|
+
section = await prompt("Section:", storedMeta.section || "")
|
|
262
315
|
releaseNotes = await prompt("Release notes:", "")
|
|
263
316
|
const imagePath = await prompt("Preview image path (leave empty to auto-generate):", "")
|
|
264
317
|
if (imagePath) {
|
|
@@ -307,6 +360,13 @@ async function publishItem({ filePath, cwd, auth, typeOverride, interactive = tr
|
|
|
307
360
|
// Track in lockfile
|
|
308
361
|
const fileHashes = { [target + fileName]: hashContent(content) }
|
|
309
362
|
updateLockfileItem(cwd, slug, result.version ?? 0, fileHashes)
|
|
363
|
+
// Persist item metadata for next publish
|
|
364
|
+
updateItemMeta(cwd, slug, {
|
|
365
|
+
title,
|
|
366
|
+
description: description || undefined,
|
|
367
|
+
tags,
|
|
368
|
+
section: section || undefined
|
|
369
|
+
})
|
|
310
370
|
|
|
311
371
|
return { slug, status: result.status || "published", version: result.version }
|
|
312
372
|
}
|
|
@@ -423,6 +483,9 @@ export async function publish(args) {
|
|
|
423
483
|
? readdirSync(slidesDir).filter(f => f.endsWith(".tsx") || f.endsWith(".ts"))
|
|
424
484
|
: []
|
|
425
485
|
|
|
486
|
+
// Discover shared source files (src/components/, src/lib/, etc.)
|
|
487
|
+
const sharedSources = discoverSharedSources(cwd)
|
|
488
|
+
|
|
426
489
|
const totalItems = publicAssets.length + (hasTheme ? 1 : 0) + layoutEntries.length + slideEntries.length + 1
|
|
427
490
|
|
|
428
491
|
// Display summary
|
|
@@ -431,16 +494,26 @@ export async function publish(args) {
|
|
|
431
494
|
if (hasTheme) console.log(` Theme: 1`)
|
|
432
495
|
if (layoutEntries.length) console.log(` Layouts: ${layoutEntries.length}`)
|
|
433
496
|
console.log(` Slides: ${slideEntries.length}`)
|
|
497
|
+
if (sharedSources.length) console.log(` Shared: ${sharedSources.length} ${dim("(bundled with deck)")}`)
|
|
434
498
|
console.log(` Total: ${totalItems} items`)
|
|
499
|
+
// Info if there are slide files on disk not referenced in deck-config.ts
|
|
500
|
+
const deckConfigSlideCount = deckConfig.slides.length
|
|
501
|
+
if (deckConfigSlideCount !== slideEntries.length) {
|
|
502
|
+
console.log()
|
|
503
|
+
console.log(` ${dim("ℹ")} deck-config.ts has ${deckConfigSlideCount} slides, ${slideEntries.length} slide files on disk`)
|
|
504
|
+
console.log(` ${dim("All slides are published. Only deck-config slides appear in the deck preview.")}`)
|
|
505
|
+
}
|
|
435
506
|
if (deckConfig.transition) {
|
|
436
507
|
console.log(` Transition: ${deckConfig.transition}${deckConfig.directionalTransition ? " (directional)" : ""}`)
|
|
437
508
|
}
|
|
438
509
|
console.log()
|
|
439
510
|
|
|
440
|
-
// Collect deck metadata
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
const
|
|
511
|
+
// Collect deck metadata (use stored values as defaults)
|
|
512
|
+
const storedDeckMeta = readDeckMeta(cwd)
|
|
513
|
+
const title = await prompt("Title:", storedDeckMeta.title || titleCase(deckSlug))
|
|
514
|
+
const description = await prompt("Description:", storedDeckMeta.description || "")
|
|
515
|
+
const storedTagsDefault = storedDeckMeta.tags?.length ? storedDeckMeta.tags.join(", ") : ""
|
|
516
|
+
const tagsInput = await prompt("Tags (comma-separated):", storedTagsDefault)
|
|
444
517
|
const tags = tagsInput ? tagsInput.split(",").map(t => t.trim()).filter(Boolean) : []
|
|
445
518
|
const releaseNotes = await prompt("Release notes:", "")
|
|
446
519
|
|
|
@@ -692,12 +765,28 @@ export async function publish(args) {
|
|
|
692
765
|
|
|
693
766
|
if (captureSession) await captureSession.close()
|
|
694
767
|
|
|
695
|
-
// ── Phase 5: Deck manifest ──
|
|
768
|
+
// ── Phase 5: Deck manifest (includes shared source modules) ──
|
|
696
769
|
itemIndex++
|
|
697
770
|
const slideSlugs = slideEntries.map(f => `${deckSlug}/${f.replace(/\.tsx?$/, "")}`)
|
|
698
771
|
const assetSlugs = publicAssets.map(a => assetFileToSlug(deckSlug, a.relativePath))
|
|
699
772
|
const allDeckDeps = [...slideSlugs, ...assetSlugs]
|
|
700
773
|
|
|
774
|
+
// Bundle shared source files with the deck item so preview/install can resolve @/ imports
|
|
775
|
+
const sharedFiles = sharedSources.map(s => ({
|
|
776
|
+
path: s.fileName,
|
|
777
|
+
target: s.target,
|
|
778
|
+
content: readFileSync(s.fullPath, "utf-8")
|
|
779
|
+
}))
|
|
780
|
+
if (sharedFiles.length) {
|
|
781
|
+
console.log(` ${dim(`Bundling ${sharedFiles.length} shared source file(s) with deck`)}`)
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
// Collect npm deps from shared source files
|
|
785
|
+
const sharedNpmDeps = {}
|
|
786
|
+
for (const f of sharedFiles) {
|
|
787
|
+
Object.assign(sharedNpmDeps, detectNpmDeps(f.content))
|
|
788
|
+
}
|
|
789
|
+
|
|
701
790
|
let deckItemId = null
|
|
702
791
|
try {
|
|
703
792
|
const result = await publishToRegistry({
|
|
@@ -707,7 +796,8 @@ export async function publish(args) {
|
|
|
707
796
|
description: description || undefined,
|
|
708
797
|
tags,
|
|
709
798
|
deckConfig,
|
|
710
|
-
files:
|
|
799
|
+
files: sharedFiles,
|
|
800
|
+
npmDependencies: Object.keys(sharedNpmDeps).length ? sharedNpmDeps : undefined,
|
|
711
801
|
registryDependencies: allDeckDeps.length ? allDeckDeps : undefined,
|
|
712
802
|
releaseNotes: releaseNotes || undefined,
|
|
713
803
|
previewImage: previewImage || undefined,
|
|
@@ -717,6 +807,8 @@ export async function publish(args) {
|
|
|
717
807
|
lockfileUpdates.push({ slug: deckSlug, version: result.version ?? 0, fileHashes: {} })
|
|
718
808
|
deckItemId = result.id
|
|
719
809
|
published++
|
|
810
|
+
// Persist deck metadata for next publish
|
|
811
|
+
updateDeckMeta(cwd, { title, description: description || undefined, tags })
|
|
720
812
|
} catch (err) {
|
|
721
813
|
console.log(` [${itemIndex}/${totalItems}] ${red("✗")} deck ${dim(deckSlug)}: ${err.message}`)
|
|
722
814
|
failed++
|
|
@@ -725,7 +817,9 @@ export async function publish(args) {
|
|
|
725
817
|
// Batch-write all lockfile updates at once
|
|
726
818
|
const finalLock = readLockfile(cwd)
|
|
727
819
|
for (const update of lockfileUpdates) {
|
|
820
|
+
const existing = finalLock.items[update.slug] || {}
|
|
728
821
|
finalLock.items[update.slug] = {
|
|
822
|
+
...existing,
|
|
729
823
|
version: update.version,
|
|
730
824
|
installedAt: new Date().toISOString().split("T")[0],
|
|
731
825
|
files: update.fileHashes
|
|
@@ -864,12 +958,14 @@ export async function publish(args) {
|
|
|
864
958
|
}
|
|
865
959
|
}
|
|
866
960
|
|
|
867
|
-
// Collect metadata for main item
|
|
868
|
-
const
|
|
869
|
-
const
|
|
870
|
-
const
|
|
961
|
+
// Collect metadata for main item (use stored values as defaults)
|
|
962
|
+
const storedMeta = readItemMeta(cwd, slug)
|
|
963
|
+
const title = await prompt("Title:", storedMeta.title || titleCase(baseSlug))
|
|
964
|
+
const description = await prompt("Description:", storedMeta.description || "")
|
|
965
|
+
const storedTagsDefault = storedMeta.tags?.length ? storedMeta.tags.join(", ") : ""
|
|
966
|
+
const tagsInput = await prompt("Tags (comma-separated):", storedTagsDefault)
|
|
871
967
|
const tags = tagsInput ? tagsInput.split(",").map(t => t.trim()).filter(Boolean) : []
|
|
872
|
-
const section = await prompt("Section:", "")
|
|
968
|
+
const section = await prompt("Section:", storedMeta.section || "")
|
|
873
969
|
const releaseNotes = await prompt("Release notes:", "")
|
|
874
970
|
const previewImagePath = await prompt("Preview image path (leave empty to auto-generate):", "")
|
|
875
971
|
let previewImage = null
|
|
@@ -927,6 +1023,13 @@ export async function publish(args) {
|
|
|
927
1023
|
// Track in lockfile
|
|
928
1024
|
const fileHashes = { [target + fileName]: hashContent(content) }
|
|
929
1025
|
updateLockfileItem(cwd, slug, result.version ?? 0, fileHashes)
|
|
1026
|
+
// Persist item metadata for next publish
|
|
1027
|
+
updateItemMeta(cwd, slug, {
|
|
1028
|
+
title,
|
|
1029
|
+
description: description || undefined,
|
|
1030
|
+
tags,
|
|
1031
|
+
section: section || undefined
|
|
1032
|
+
})
|
|
930
1033
|
} catch (err) {
|
|
931
1034
|
console.error(` ${red("Error:")} ${err.message}`)
|
|
932
1035
|
process.exit(1)
|
|
@@ -6,13 +6,16 @@ const DECK_CONFIG_PATH = "src/deck-config.ts"
|
|
|
6
6
|
/**
|
|
7
7
|
* Convert a kebab-case filename to PascalCase component name.
|
|
8
8
|
* e.g. "slide-hero-gradient" → "SlideHeroGradient"
|
|
9
|
+
* e.g. "01-title" → "Slide01Title" (prefixed to ensure valid JS identifier)
|
|
9
10
|
*/
|
|
10
11
|
export function toPascalCase(kebab) {
|
|
11
|
-
|
|
12
|
+
const raw = kebab
|
|
12
13
|
.replace(/\.tsx?$/, "")
|
|
13
14
|
.split("-")
|
|
14
15
|
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
15
16
|
.join("")
|
|
17
|
+
// JS identifiers cannot start with a digit — prefix with "Slide"
|
|
18
|
+
return /^\d/.test(raw) ? `Slide${raw}` : raw
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
/**
|
|
@@ -222,11 +225,20 @@ export function parseDeckConfig(cwd) {
|
|
|
222
225
|
const content = readFileSync(configPath, "utf-8")
|
|
223
226
|
|
|
224
227
|
// 1. Parse imports to build componentName -> slug map
|
|
225
|
-
//
|
|
228
|
+
// Handles: import { SlideTitle } from "@/slides/slide-title"
|
|
229
|
+
// Handles: import { default as SlideTitle } from "@/slides/slide-title"
|
|
230
|
+
// Handles: import { SlideTitle as Alias } from "@/slides/slide-title"
|
|
226
231
|
const componentToSlug = {}
|
|
227
|
-
const importRegex = /import\s*\{
|
|
232
|
+
const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@\/slides\/([^"']+)["']/g
|
|
228
233
|
for (const match of content.matchAll(importRegex)) {
|
|
229
|
-
|
|
234
|
+
const importClause = match[1].trim()
|
|
235
|
+
const slug = match[2].replace(/\.tsx?$/, "")
|
|
236
|
+
// Handle "X", "X as Y" (use Y), "default as Y" (use Y)
|
|
237
|
+
const asMatch = importClause.match(/(?:\w+\s+)?as\s+(\w+)/)
|
|
238
|
+
const componentName = asMatch ? asMatch[1] : importClause.match(/(\w+)/)?.[1]
|
|
239
|
+
if (componentName) {
|
|
240
|
+
componentToSlug[componentName] = slug
|
|
241
|
+
}
|
|
230
242
|
}
|
|
231
243
|
|
|
232
244
|
// 2. Parse transition exports
|
|
@@ -238,20 +250,38 @@ export function parseDeckConfig(cwd) {
|
|
|
238
250
|
const dirMatch = content.match(/export\s+const\s+directionalTransition\s*=\s*(true|false)/)
|
|
239
251
|
if (dirMatch) directionalTransition = dirMatch[1] === "true"
|
|
240
252
|
|
|
241
|
-
// 3.
|
|
253
|
+
// 3. Extract slides array content, then parse entries within it
|
|
242
254
|
const slides = []
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
const slidesArrayMatch = content.match(/export\s+const\s+slides\s*:\s*SlideConfig\[\]\s*=\s*\[/)
|
|
256
|
+
if (slidesArrayMatch) {
|
|
257
|
+
const arrayStartIdx = content.indexOf(slidesArrayMatch[0]) + slidesArrayMatch[0].length
|
|
258
|
+
// Find matching closing bracket
|
|
259
|
+
let bracketDepth = 1
|
|
260
|
+
let arrayEndIdx = arrayStartIdx
|
|
261
|
+
for (let i = arrayStartIdx; i < content.length; i++) {
|
|
262
|
+
if (content[i] === "[") bracketDepth++
|
|
263
|
+
if (content[i] === "]") bracketDepth--
|
|
264
|
+
if (bracketDepth === 0) {
|
|
265
|
+
arrayEndIdx = i
|
|
266
|
+
break
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const arrayContent = content.slice(arrayStartIdx, arrayEndIdx)
|
|
270
|
+
|
|
271
|
+
// Match entries within the slides array only
|
|
272
|
+
const entryRegex = /\{([^}]+)\}/g
|
|
273
|
+
for (const match of arrayContent.matchAll(entryRegex)) {
|
|
274
|
+
const body = match[1]
|
|
275
|
+
const componentMatch = body.match(/component:\s*(\w+)/)
|
|
276
|
+
if (!componentMatch) continue
|
|
277
|
+
const slug = componentToSlug[componentMatch[1]]
|
|
278
|
+
if (!slug) continue // layout or unknown import — skip
|
|
279
|
+
const stepsMatch = body.match(/steps:\s*(\d+)/)
|
|
280
|
+
const entry = { slug, steps: stepsMatch ? parseInt(stepsMatch[1], 10) : 0 }
|
|
281
|
+
const sectionMatch = body.match(/section:\s*["']([^"']+)["']/)
|
|
282
|
+
if (sectionMatch) entry.section = sectionMatch[1]
|
|
283
|
+
slides.push(entry)
|
|
284
|
+
}
|
|
255
285
|
}
|
|
256
286
|
|
|
257
287
|
if (slides.length === 0) return null
|
package/src/utils/registry.mjs
CHANGED
|
@@ -71,7 +71,9 @@ export function isFileDirty(cwd, relativePath, storedHash) {
|
|
|
71
71
|
*/
|
|
72
72
|
export function updateLockfileItem(cwd, slug, version, files) {
|
|
73
73
|
const lock = readLockfile(cwd)
|
|
74
|
+
const existing = lock.items[slug] || {}
|
|
74
75
|
lock.items[slug] = {
|
|
76
|
+
...existing,
|
|
75
77
|
version,
|
|
76
78
|
installedAt: new Date().toISOString().split("T")[0],
|
|
77
79
|
files
|
|
@@ -92,6 +94,51 @@ export function updateLockfilePublishConfig(cwd, config) {
|
|
|
92
94
|
writeLockfile(cwd, lock)
|
|
93
95
|
}
|
|
94
96
|
|
|
97
|
+
/**
|
|
98
|
+
* Read stored deck-level publish metadata (title, description, tags).
|
|
99
|
+
* @param {string} cwd
|
|
100
|
+
* @returns {{ title?: string, description?: string, tags?: string[] }}
|
|
101
|
+
*/
|
|
102
|
+
export function readDeckMeta(cwd) {
|
|
103
|
+
const lock = readLockfile(cwd)
|
|
104
|
+
return lock.deckMeta || {}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Persist deck-level publish metadata so it can be reused as defaults.
|
|
109
|
+
* @param {string} cwd
|
|
110
|
+
* @param {{ title?: string, description?: string, tags?: string[] }} meta
|
|
111
|
+
*/
|
|
112
|
+
export function updateDeckMeta(cwd, meta) {
|
|
113
|
+
const lock = readLockfile(cwd)
|
|
114
|
+
lock.deckMeta = { ...lock.deckMeta, ...meta }
|
|
115
|
+
writeLockfile(cwd, lock)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Read stored per-item publish metadata (title, description, tags, section).
|
|
120
|
+
* @param {string} cwd
|
|
121
|
+
* @param {string} slug
|
|
122
|
+
* @returns {{ title?: string, description?: string, tags?: string[], section?: string }}
|
|
123
|
+
*/
|
|
124
|
+
export function readItemMeta(cwd, slug) {
|
|
125
|
+
const lock = readLockfile(cwd)
|
|
126
|
+
return lock.items[slug]?.meta || {}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Persist per-item publish metadata alongside the version/files entry.
|
|
131
|
+
* @param {string} cwd
|
|
132
|
+
* @param {string} slug
|
|
133
|
+
* @param {{ title?: string, description?: string, tags?: string[], section?: string }} meta
|
|
134
|
+
*/
|
|
135
|
+
export function updateItemMeta(cwd, slug, meta) {
|
|
136
|
+
const lock = readLockfile(cwd)
|
|
137
|
+
if (!lock.items[slug]) lock.items[slug] = {}
|
|
138
|
+
lock.items[slug].meta = meta
|
|
139
|
+
writeLockfile(cwd, lock)
|
|
140
|
+
}
|
|
141
|
+
|
|
95
142
|
/**
|
|
96
143
|
* Remove a single item from the lockfile.
|
|
97
144
|
* @param {string} cwd
|