promptslide 0.3.4 → 0.3.6
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 +15 -0
- package/package.json +1 -1
- package/src/commands/publish.mjs +124 -16
- package/src/utils/deck-config.mjs +47 -17
- package/src/utils/registry.mjs +47 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.6](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.5...promptslide-v0.3.6) (2026-03-11)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* show organization hint in publish command ([#73](https://github.com/prompticeu/promptslide/issues/73)) ([3002f25](https://github.com/prompticeu/promptslide/commit/3002f255f386a5bf646381808324ebda96c57707))
|
|
9
|
+
|
|
10
|
+
## [0.3.5](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.4...promptslide-v0.3.5) (2026-03-08)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Features
|
|
14
|
+
|
|
15
|
+
* bundle shared sources with deck publish and fix deck-config parsing ([5f7fd1d](https://github.com/prompticeu/promptslide/commit/5f7fd1de6e5fc58a0568bd87830094dd40c9eca7))
|
|
16
|
+
* 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))
|
|
17
|
+
|
|
3
18
|
## [0.3.4](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.3...promptslide-v0.3.4) (2026-03-07)
|
|
4
19
|
|
|
5
20
|
|
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
|
}
|
|
@@ -333,6 +393,11 @@ export async function publish(args) {
|
|
|
333
393
|
|
|
334
394
|
const auth = requireAuth()
|
|
335
395
|
|
|
396
|
+
if (auth.organizationName) {
|
|
397
|
+
console.log(` ${dim("Organization:")} ${bold(auth.organizationName)} ${dim(`· switch with promptslide org`)}`)
|
|
398
|
+
console.log()
|
|
399
|
+
}
|
|
400
|
+
|
|
336
401
|
// Determine file to publish
|
|
337
402
|
let typeOverride = null
|
|
338
403
|
const typeIdx = args.indexOf("--type")
|
|
@@ -423,6 +488,9 @@ export async function publish(args) {
|
|
|
423
488
|
? readdirSync(slidesDir).filter(f => f.endsWith(".tsx") || f.endsWith(".ts"))
|
|
424
489
|
: []
|
|
425
490
|
|
|
491
|
+
// Discover shared source files (src/components/, src/lib/, etc.)
|
|
492
|
+
const sharedSources = discoverSharedSources(cwd)
|
|
493
|
+
|
|
426
494
|
const totalItems = publicAssets.length + (hasTheme ? 1 : 0) + layoutEntries.length + slideEntries.length + 1
|
|
427
495
|
|
|
428
496
|
// Display summary
|
|
@@ -431,16 +499,26 @@ export async function publish(args) {
|
|
|
431
499
|
if (hasTheme) console.log(` Theme: 1`)
|
|
432
500
|
if (layoutEntries.length) console.log(` Layouts: ${layoutEntries.length}`)
|
|
433
501
|
console.log(` Slides: ${slideEntries.length}`)
|
|
502
|
+
if (sharedSources.length) console.log(` Shared: ${sharedSources.length} ${dim("(bundled with deck)")}`)
|
|
434
503
|
console.log(` Total: ${totalItems} items`)
|
|
504
|
+
// Info if there are slide files on disk not referenced in deck-config.ts
|
|
505
|
+
const deckConfigSlideCount = deckConfig.slides.length
|
|
506
|
+
if (deckConfigSlideCount !== slideEntries.length) {
|
|
507
|
+
console.log()
|
|
508
|
+
console.log(` ${dim("ℹ")} deck-config.ts has ${deckConfigSlideCount} slides, ${slideEntries.length} slide files on disk`)
|
|
509
|
+
console.log(` ${dim("All slides are published. Only deck-config slides appear in the deck preview.")}`)
|
|
510
|
+
}
|
|
435
511
|
if (deckConfig.transition) {
|
|
436
512
|
console.log(` Transition: ${deckConfig.transition}${deckConfig.directionalTransition ? " (directional)" : ""}`)
|
|
437
513
|
}
|
|
438
514
|
console.log()
|
|
439
515
|
|
|
440
|
-
// Collect deck metadata
|
|
441
|
-
const
|
|
442
|
-
const
|
|
443
|
-
const
|
|
516
|
+
// Collect deck metadata (use stored values as defaults)
|
|
517
|
+
const storedDeckMeta = readDeckMeta(cwd)
|
|
518
|
+
const title = await prompt("Title:", storedDeckMeta.title || titleCase(deckSlug))
|
|
519
|
+
const description = await prompt("Description:", storedDeckMeta.description || "")
|
|
520
|
+
const storedTagsDefault = storedDeckMeta.tags?.length ? storedDeckMeta.tags.join(", ") : ""
|
|
521
|
+
const tagsInput = await prompt("Tags (comma-separated):", storedTagsDefault)
|
|
444
522
|
const tags = tagsInput ? tagsInput.split(",").map(t => t.trim()).filter(Boolean) : []
|
|
445
523
|
const releaseNotes = await prompt("Release notes:", "")
|
|
446
524
|
|
|
@@ -692,12 +770,28 @@ export async function publish(args) {
|
|
|
692
770
|
|
|
693
771
|
if (captureSession) await captureSession.close()
|
|
694
772
|
|
|
695
|
-
// ── Phase 5: Deck manifest ──
|
|
773
|
+
// ── Phase 5: Deck manifest (includes shared source modules) ──
|
|
696
774
|
itemIndex++
|
|
697
775
|
const slideSlugs = slideEntries.map(f => `${deckSlug}/${f.replace(/\.tsx?$/, "")}`)
|
|
698
776
|
const assetSlugs = publicAssets.map(a => assetFileToSlug(deckSlug, a.relativePath))
|
|
699
777
|
const allDeckDeps = [...slideSlugs, ...assetSlugs]
|
|
700
778
|
|
|
779
|
+
// Bundle shared source files with the deck item so preview/install can resolve @/ imports
|
|
780
|
+
const sharedFiles = sharedSources.map(s => ({
|
|
781
|
+
path: s.fileName,
|
|
782
|
+
target: s.target,
|
|
783
|
+
content: readFileSync(s.fullPath, "utf-8")
|
|
784
|
+
}))
|
|
785
|
+
if (sharedFiles.length) {
|
|
786
|
+
console.log(` ${dim(`Bundling ${sharedFiles.length} shared source file(s) with deck`)}`)
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Collect npm deps from shared source files
|
|
790
|
+
const sharedNpmDeps = {}
|
|
791
|
+
for (const f of sharedFiles) {
|
|
792
|
+
Object.assign(sharedNpmDeps, detectNpmDeps(f.content))
|
|
793
|
+
}
|
|
794
|
+
|
|
701
795
|
let deckItemId = null
|
|
702
796
|
try {
|
|
703
797
|
const result = await publishToRegistry({
|
|
@@ -707,7 +801,8 @@ export async function publish(args) {
|
|
|
707
801
|
description: description || undefined,
|
|
708
802
|
tags,
|
|
709
803
|
deckConfig,
|
|
710
|
-
files:
|
|
804
|
+
files: sharedFiles,
|
|
805
|
+
npmDependencies: Object.keys(sharedNpmDeps).length ? sharedNpmDeps : undefined,
|
|
711
806
|
registryDependencies: allDeckDeps.length ? allDeckDeps : undefined,
|
|
712
807
|
releaseNotes: releaseNotes || undefined,
|
|
713
808
|
previewImage: previewImage || undefined,
|
|
@@ -717,6 +812,8 @@ export async function publish(args) {
|
|
|
717
812
|
lockfileUpdates.push({ slug: deckSlug, version: result.version ?? 0, fileHashes: {} })
|
|
718
813
|
deckItemId = result.id
|
|
719
814
|
published++
|
|
815
|
+
// Persist deck metadata for next publish
|
|
816
|
+
updateDeckMeta(cwd, { title, description: description || undefined, tags })
|
|
720
817
|
} catch (err) {
|
|
721
818
|
console.log(` [${itemIndex}/${totalItems}] ${red("✗")} deck ${dim(deckSlug)}: ${err.message}`)
|
|
722
819
|
failed++
|
|
@@ -725,7 +822,9 @@ export async function publish(args) {
|
|
|
725
822
|
// Batch-write all lockfile updates at once
|
|
726
823
|
const finalLock = readLockfile(cwd)
|
|
727
824
|
for (const update of lockfileUpdates) {
|
|
825
|
+
const existing = finalLock.items[update.slug] || {}
|
|
728
826
|
finalLock.items[update.slug] = {
|
|
827
|
+
...existing,
|
|
729
828
|
version: update.version,
|
|
730
829
|
installedAt: new Date().toISOString().split("T")[0],
|
|
731
830
|
files: update.fileHashes
|
|
@@ -864,12 +963,14 @@ export async function publish(args) {
|
|
|
864
963
|
}
|
|
865
964
|
}
|
|
866
965
|
|
|
867
|
-
// Collect metadata for main item
|
|
868
|
-
const
|
|
869
|
-
const
|
|
870
|
-
const
|
|
966
|
+
// Collect metadata for main item (use stored values as defaults)
|
|
967
|
+
const storedMeta = readItemMeta(cwd, slug)
|
|
968
|
+
const title = await prompt("Title:", storedMeta.title || titleCase(baseSlug))
|
|
969
|
+
const description = await prompt("Description:", storedMeta.description || "")
|
|
970
|
+
const storedTagsDefault = storedMeta.tags?.length ? storedMeta.tags.join(", ") : ""
|
|
971
|
+
const tagsInput = await prompt("Tags (comma-separated):", storedTagsDefault)
|
|
871
972
|
const tags = tagsInput ? tagsInput.split(",").map(t => t.trim()).filter(Boolean) : []
|
|
872
|
-
const section = await prompt("Section:", "")
|
|
973
|
+
const section = await prompt("Section:", storedMeta.section || "")
|
|
873
974
|
const releaseNotes = await prompt("Release notes:", "")
|
|
874
975
|
const previewImagePath = await prompt("Preview image path (leave empty to auto-generate):", "")
|
|
875
976
|
let previewImage = null
|
|
@@ -927,6 +1028,13 @@ export async function publish(args) {
|
|
|
927
1028
|
// Track in lockfile
|
|
928
1029
|
const fileHashes = { [target + fileName]: hashContent(content) }
|
|
929
1030
|
updateLockfileItem(cwd, slug, result.version ?? 0, fileHashes)
|
|
1031
|
+
// Persist item metadata for next publish
|
|
1032
|
+
updateItemMeta(cwd, slug, {
|
|
1033
|
+
title,
|
|
1034
|
+
description: description || undefined,
|
|
1035
|
+
tags,
|
|
1036
|
+
section: section || undefined
|
|
1037
|
+
})
|
|
930
1038
|
} catch (err) {
|
|
931
1039
|
console.error(` ${red("Error:")} ${err.message}`)
|
|
932
1040
|
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
|