promptslide 0.3.15 → 0.3.17
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 +14 -0
- package/README.md +16 -12
- package/dist/index.d.ts +17 -7
- package/dist/index.js +987 -211
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/commands/add.mjs +9 -2
- package/src/commands/clone.mjs +62 -22
- package/src/commands/create.mjs +8 -1
- package/src/commands/publish.mjs +34 -8
- package/src/commands/pull.mjs +66 -29
- package/src/commands/studio.mjs +3 -0
- package/src/commands/to-image.mjs +18 -5
- package/src/commands/to-pdf.mjs +27 -0
- package/src/core/animated.tsx +3 -3
- package/src/core/annotations/adapters/http.ts +8 -0
- package/src/core/annotations/annotation-overlay.tsx +77 -32
- package/src/core/annotations/annotation-panel.tsx +184 -51
- package/src/core/annotations/types.ts +5 -0
- package/src/core/annotations/use-annotations.ts +57 -7
- package/src/core/slide-deck.tsx +657 -115
- package/src/core/transitions.ts +5 -3
- package/src/core/types.ts +2 -0
- package/src/core/use-slide-navigation.ts +93 -3
- package/src/index.mjs +6 -0
- package/src/utils/deck-config.mjs +30 -11
- package/src/utils/deck-sync.mjs +78 -0
- package/src/utils/export.mjs +151 -52
- package/src/utils/registry.mjs +23 -0
- package/src/utils/studio-discovery.mjs +37 -0
- package/src/vite/plugin.mjs +146 -16
- package/templates/default/AGENTS.md +3 -2
- package/templates/default/src/deck-config.ts +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "promptslide",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.17",
|
|
4
4
|
"description": "CLI and slide engine for PromptSlide presentations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
],
|
|
25
25
|
"scripts": {
|
|
26
26
|
"build": "tsup",
|
|
27
|
+
"test": "node --test test/*.test.mjs",
|
|
27
28
|
"prepublishOnly": "npm run build"
|
|
28
29
|
},
|
|
29
30
|
"license": "MIT",
|
package/src/commands/add.mjs
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
addSlideToDeckConfig,
|
|
22
22
|
replaceDeckConfig
|
|
23
23
|
} from "../utils/deck-config.mjs"
|
|
24
|
+
import { collectRegistrySlideSlugs, partitionDeckSlides } from "../utils/deck-sync.mjs"
|
|
24
25
|
import { confirm, closePrompts } from "../utils/prompts.mjs"
|
|
25
26
|
|
|
26
27
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
@@ -164,9 +165,15 @@ export async function add(args) {
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
} else if (item.type === "deck" && item.meta?.slides) {
|
|
168
|
+
const registrySlideSlugs = collectRegistrySlideSlugs(resolved.items)
|
|
169
|
+
const { available, missing } = partitionDeckSlides(item.meta.slides, registrySlideSlugs)
|
|
170
|
+
if (missing.length > 0) {
|
|
171
|
+
console.log(` ${yellow("⚠")} Skipped ${missing.length} slide(s) listed in the deck but not included in this version:`)
|
|
172
|
+
console.log(` ${dim(missing.map(s => s.slug).join(", "))}`)
|
|
173
|
+
}
|
|
167
174
|
const shouldReplace = await confirm(" Replace entire deck-config.ts with this deck?", true)
|
|
168
175
|
if (shouldReplace) {
|
|
169
|
-
const slides =
|
|
176
|
+
const slides = available.map(s => ({
|
|
170
177
|
componentName: s.componentName || toPascalCase(s.slug),
|
|
171
178
|
importPath: `@/slides/${s.slug}`,
|
|
172
179
|
steps: s.steps,
|
|
@@ -179,7 +186,7 @@ export async function add(args) {
|
|
|
179
186
|
console.log(` ${green("✓")} Replaced ${cyan("deck-config.ts")} ${dim(`(${slides.length} slides)`)}`)
|
|
180
187
|
} else {
|
|
181
188
|
// Append individual slides
|
|
182
|
-
for (const s of
|
|
189
|
+
for (const s of available) {
|
|
183
190
|
const componentName = s.componentName || toPascalCase(s.slug)
|
|
184
191
|
const importPath = `@/slides/${s.slug}`
|
|
185
192
|
const updated = addSlideToDeckConfig(cwd, { componentName, importPath, steps: s.steps })
|
package/src/commands/clone.mjs
CHANGED
|
@@ -5,9 +5,14 @@ import { fileURLToPath } from "node:url"
|
|
|
5
5
|
import { bold, green, cyan, red, dim, yellow } from "../utils/ansi.mjs"
|
|
6
6
|
import { requireAuth } from "../utils/auth.mjs"
|
|
7
7
|
import { hexToOklch } from "../utils/colors.mjs"
|
|
8
|
-
import { closePrompts } from "../utils/prompts.mjs"
|
|
9
|
-
import { fetchRegistryItem, resolveRegistryDependencies, writeLockfile } from "../utils/registry.mjs"
|
|
10
8
|
import { toPascalCase, replaceDeckConfig } from "../utils/deck-config.mjs"
|
|
9
|
+
import { collectRegistrySlideSlugs, partitionDeckSlides } from "../utils/deck-sync.mjs"
|
|
10
|
+
import { closePrompts } from "../utils/prompts.mjs"
|
|
11
|
+
import {
|
|
12
|
+
fetchRegistryItem,
|
|
13
|
+
resolveRegistryDependencies,
|
|
14
|
+
writeLockfile
|
|
15
|
+
} from "../utils/registry.mjs"
|
|
11
16
|
import { ensureTsConfig } from "../utils/tsconfig.mjs"
|
|
12
17
|
|
|
13
18
|
const __filename = fileURLToPath(import.meta.url)
|
|
@@ -62,7 +67,9 @@ export async function clone(args) {
|
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
if (item.type !== "deck") {
|
|
65
|
-
console.error(
|
|
70
|
+
console.error(
|
|
71
|
+
` ${red("Error:")} "${slug}" is a ${item.type}, not a deck. Only decks can be cloned.`
|
|
72
|
+
)
|
|
66
73
|
process.exit(1)
|
|
67
74
|
}
|
|
68
75
|
|
|
@@ -74,8 +81,12 @@ export async function clone(args) {
|
|
|
74
81
|
const localParts = CLI_VERSION.match(/^(\d+)\.(\d+)/)
|
|
75
82
|
if (pubParts && localParts && pubParts[2] !== localParts[2]) {
|
|
76
83
|
console.log()
|
|
77
|
-
console.log(
|
|
78
|
-
|
|
84
|
+
console.log(
|
|
85
|
+
` ${yellow("⚠")} This deck was published with promptslide ${bold(`v${item.promptslideVersion}`)}`
|
|
86
|
+
)
|
|
87
|
+
console.log(
|
|
88
|
+
` You have ${bold(`v${CLI_VERSION}`)} installed — some slides may need updating.`
|
|
89
|
+
)
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
92
|
|
|
@@ -99,7 +110,11 @@ export async function clone(args) {
|
|
|
99
110
|
const replacements = [
|
|
100
111
|
{
|
|
101
112
|
path: join(targetDir, "package.json"),
|
|
102
|
-
values: {
|
|
113
|
+
values: {
|
|
114
|
+
"{{PROJECT_SLUG}}": dirName,
|
|
115
|
+
"{{PROJECT_NAME}}": projectName,
|
|
116
|
+
"{{PROMPTSLIDE_VERSION}}": `^${CLI_VERSION}`
|
|
117
|
+
}
|
|
103
118
|
},
|
|
104
119
|
{
|
|
105
120
|
path: join(targetDir, "src", "theme.ts"),
|
|
@@ -148,7 +163,10 @@ export async function clone(args) {
|
|
|
148
163
|
|
|
149
164
|
const dataUriPrefix = file.content.match(/^data:[^;]+;base64,/)
|
|
150
165
|
if (dataUriPrefix) {
|
|
151
|
-
writeFileSync(
|
|
166
|
+
writeFileSync(
|
|
167
|
+
targetPath,
|
|
168
|
+
Buffer.from(file.content.slice(dataUriPrefix[0].length), "base64")
|
|
169
|
+
)
|
|
152
170
|
} else {
|
|
153
171
|
writeFileSync(targetPath, file.content, "utf-8")
|
|
154
172
|
}
|
|
@@ -158,9 +176,16 @@ export async function clone(args) {
|
|
|
158
176
|
|
|
159
177
|
// 6. Generate deck-config.ts
|
|
160
178
|
if (item.meta?.slides) {
|
|
161
|
-
const
|
|
179
|
+
const registrySlideSlugs = collectRegistrySlideSlugs(resolved.items)
|
|
180
|
+
const { available, missing } = partitionDeckSlides(item.meta.slides, registrySlideSlugs)
|
|
181
|
+
if (missing.length > 0) {
|
|
182
|
+
console.log(` ${yellow("!")} Skipped ${missing.length} slide(s) listed in the deck but not included in this version:`)
|
|
183
|
+
console.log(` ${dim(missing.map(s => s.slug).join(", "))}`)
|
|
184
|
+
}
|
|
185
|
+
const slides = available.map(s => ({
|
|
162
186
|
componentName: s.componentName || toPascalCase(s.slug),
|
|
163
187
|
importPath: `@/slides/${s.slug}`,
|
|
188
|
+
id: s.id,
|
|
164
189
|
steps: s.steps,
|
|
165
190
|
section: s.section
|
|
166
191
|
}))
|
|
@@ -168,7 +193,9 @@ export async function clone(args) {
|
|
|
168
193
|
transition: item.meta.transition,
|
|
169
194
|
directionalTransition: item.meta.directionalTransition
|
|
170
195
|
})
|
|
171
|
-
console.log(
|
|
196
|
+
console.log(
|
|
197
|
+
` ${green("✓")} Generated ${cyan("deck-config.ts")} ${dim(`(${slides.length} slides)`)}`
|
|
198
|
+
)
|
|
172
199
|
}
|
|
173
200
|
|
|
174
201
|
// 7. Add npm dependencies
|
|
@@ -188,24 +215,37 @@ export async function clone(args) {
|
|
|
188
215
|
if (item.id) {
|
|
189
216
|
try {
|
|
190
217
|
const annotationsRes = await fetch(`${auth.registry}/api/items/${item.id}/annotations`, {
|
|
191
|
-
headers: {
|
|
218
|
+
headers: {
|
|
219
|
+
Authorization: `Bearer ${auth.token}`,
|
|
220
|
+
...(auth.organizationId ? { "X-Organization-Id": auth.organizationId } : {})
|
|
221
|
+
}
|
|
192
222
|
})
|
|
193
223
|
if (annotationsRes.ok) {
|
|
194
224
|
const data = await annotationsRes.json()
|
|
195
225
|
const annotations = data.annotations ?? []
|
|
196
226
|
if (annotations.length > 0) {
|
|
197
|
-
const annotationsFile = {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
227
|
+
const annotationsFile = {
|
|
228
|
+
version: 1,
|
|
229
|
+
annotations: annotations.map(a => ({
|
|
230
|
+
id: a.id,
|
|
231
|
+
slideIndex: a.slideIndex,
|
|
232
|
+
...(a.slideId ? { slideId: a.slideId } : {}),
|
|
233
|
+
slideTitle: a.slideTitle,
|
|
234
|
+
target: a.target,
|
|
235
|
+
body: a.body,
|
|
236
|
+
createdAt: a.createdAt,
|
|
237
|
+
status: a.status,
|
|
238
|
+
...(a.resolution ? { resolution: a.resolution } : {})
|
|
239
|
+
}))
|
|
240
|
+
}
|
|
241
|
+
writeFileSync(
|
|
242
|
+
join(targetDir, "annotations.json"),
|
|
243
|
+
JSON.stringify(annotationsFile, null, 2) + "\n",
|
|
244
|
+
"utf-8"
|
|
245
|
+
)
|
|
246
|
+
console.log(
|
|
247
|
+
` ${green("✓")} Pulled ${cyan("annotations.json")} ${dim(`(${annotations.length} annotation${annotations.length === 1 ? "" : "s"})`)}`
|
|
248
|
+
)
|
|
209
249
|
}
|
|
210
250
|
}
|
|
211
251
|
} catch {
|
package/src/commands/create.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { hexToOklch, isValidHex } from "../utils/colors.mjs"
|
|
|
9
9
|
import { prompt, confirm, closePrompts } from "../utils/prompts.mjs"
|
|
10
10
|
import { fetchRegistryItem, resolveRegistryDependencies, writeLockfile } from "../utils/registry.mjs"
|
|
11
11
|
import { toPascalCase, replaceDeckConfig } from "../utils/deck-config.mjs"
|
|
12
|
+
import { collectRegistrySlideSlugs, partitionDeckSlides } from "../utils/deck-sync.mjs"
|
|
12
13
|
import { ensureTsConfig } from "../utils/tsconfig.mjs"
|
|
13
14
|
|
|
14
15
|
const __filename = fileURLToPath(import.meta.url)
|
|
@@ -221,7 +222,13 @@ export async function create(args) {
|
|
|
221
222
|
|
|
222
223
|
// Reconstruct deck-config.ts from deckConfig metadata
|
|
223
224
|
if (fromItem.meta?.slides) {
|
|
224
|
-
const
|
|
225
|
+
const registrySlideSlugs = collectRegistrySlideSlugs(resolved.items)
|
|
226
|
+
const { available, missing } = partitionDeckSlides(fromItem.meta.slides, registrySlideSlugs)
|
|
227
|
+
if (missing.length > 0) {
|
|
228
|
+
console.log(` ${yellow("⚠")} Skipped ${missing.length} slide(s) listed in the deck but not included in this version:`)
|
|
229
|
+
console.log(` ${dim(missing.map(s => s.slug).join(", "))}`)
|
|
230
|
+
}
|
|
231
|
+
const slides = available.map(s => ({
|
|
225
232
|
componentName: s.componentName || toPascalCase(s.slug),
|
|
226
233
|
importPath: `@/slides/${s.slug}`,
|
|
227
234
|
steps: s.steps,
|
package/src/commands/publish.mjs
CHANGED
|
@@ -5,9 +5,10 @@ import { fileURLToPath } from "node:url"
|
|
|
5
5
|
import { bold, green, cyan, red, yellow, 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, fetchRegistryItem, searchRegistry, updateLockfileItem, updateLockfilePublishConfig, readLockfile, writeLockfile, hashContent, detectPackageManager, requestUploadTokens, uploadBinaryToBlob, assetFileToSlug, detectAssetDepsInContent, readDeckMeta, updateDeckMeta, readItemMeta, updateItemMeta } from "../utils/registry.mjs"
|
|
8
|
+
import { publishToRegistry, registryItemExists, fetchRegistryItem, searchRegistry, updateLockfileItem, updateLockfilePublishConfig, readLockfile, writeLockfile, hashContent, detectPackageManager, requestUploadTokens, uploadBinaryToBlob, assetFileToSlug, detectAssetDepsInContent, readDeckMeta, updateDeckMeta, readItemMeta, updateItemMeta, pruneMissingLockfileItems } 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
|
+
import { selectSlidesForPublish } from "../utils/deck-sync.mjs"
|
|
11
12
|
|
|
12
13
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
13
14
|
const CLI_VERSION = JSON.parse(readFileSync(join(__dirname, "..", "..", "package.json"), "utf-8")).version
|
|
@@ -412,7 +413,7 @@ export async function publish(args) {
|
|
|
412
413
|
console.log()
|
|
413
414
|
|
|
414
415
|
if (args[0] === "--help" || args[0] === "-h") {
|
|
415
|
-
console.log(` ${bold("Usage:")} promptslide publish ${dim("[file] [--type slide|layout|deck|theme]")}`)
|
|
416
|
+
console.log(` ${bold("Usage:")} promptslide publish ${dim("[file] [--type slide|layout|deck|theme] [--all]")}`)
|
|
416
417
|
console.log()
|
|
417
418
|
console.log(` Publish a slide, layout, or entire deck to the registry.`)
|
|
418
419
|
console.log()
|
|
@@ -421,6 +422,9 @@ export async function publish(args) {
|
|
|
421
422
|
console.log(` promptslide publish --type layout`)
|
|
422
423
|
console.log(` promptslide publish --type deck`)
|
|
423
424
|
console.log()
|
|
425
|
+
console.log(` ${bold("Options:")}`)
|
|
426
|
+
console.log(` --all Include slide files not referenced by deck-config.ts in a deck publish`)
|
|
427
|
+
console.log()
|
|
424
428
|
process.exit(0)
|
|
425
429
|
}
|
|
426
430
|
|
|
@@ -433,6 +437,7 @@ export async function publish(args) {
|
|
|
433
437
|
|
|
434
438
|
// Determine file to publish
|
|
435
439
|
let typeOverride = null
|
|
440
|
+
const includeUnreferencedSlides = args.includes("--all")
|
|
436
441
|
const typeIdx = args.indexOf("--type")
|
|
437
442
|
if (typeIdx !== -1 && args[typeIdx + 1]) {
|
|
438
443
|
typeOverride = args[typeIdx + 1]
|
|
@@ -538,9 +543,23 @@ export async function publish(args) {
|
|
|
538
543
|
: []
|
|
539
544
|
|
|
540
545
|
const slidesDir = join(cwd, "src", "slides")
|
|
541
|
-
const
|
|
546
|
+
const slideFilesOnDisk = existsSync(slidesDir)
|
|
542
547
|
? readdirSync(slidesDir).filter(f => f.endsWith(".tsx") || f.endsWith(".ts"))
|
|
543
548
|
: []
|
|
549
|
+
const slideSelection = selectSlidesForPublish(
|
|
550
|
+
slideFilesOnDisk,
|
|
551
|
+
deckConfig.slides,
|
|
552
|
+
includeUnreferencedSlides
|
|
553
|
+
)
|
|
554
|
+
if (slideSelection.missing.length > 0) {
|
|
555
|
+
console.error(` ${red("Error:")} deck-config.ts references slide files that do not exist:`)
|
|
556
|
+
console.error(` ${slideSelection.missing.join(", ")}`)
|
|
557
|
+
console.error(` ${dim("Restore the files or remove their entries from deck-config.ts before publishing.")}`)
|
|
558
|
+
console.log()
|
|
559
|
+
closePrompts()
|
|
560
|
+
process.exit(1)
|
|
561
|
+
}
|
|
562
|
+
const slideEntries = slideSelection.selected
|
|
544
563
|
|
|
545
564
|
// Discover shared source files (src/components/, src/lib/, etc.)
|
|
546
565
|
const sharedSources = discoverSharedSources(cwd)
|
|
@@ -555,12 +574,14 @@ export async function publish(args) {
|
|
|
555
574
|
console.log(` Slides: ${slideEntries.length}`)
|
|
556
575
|
if (sharedSources.length) console.log(` Shared: ${sharedSources.length} ${dim("(bundled with deck)")}`)
|
|
557
576
|
console.log(` Total: ${totalItems} items`)
|
|
558
|
-
|
|
559
|
-
const deckConfigSlideCount = deckConfig.slides.length
|
|
560
|
-
if (deckConfigSlideCount !== slideEntries.length) {
|
|
577
|
+
if (slideSelection.unreferenced.length > 0) {
|
|
561
578
|
console.log()
|
|
562
|
-
|
|
563
|
-
|
|
579
|
+
if (includeUnreferencedSlides) {
|
|
580
|
+
console.log(` ${yellow("!")} Including ${slideSelection.unreferenced.length} slide file(s) not referenced by deck-config.ts because --all was set:`)
|
|
581
|
+
} else {
|
|
582
|
+
console.log(` ${dim("ℹ")} Skipping ${slideSelection.unreferenced.length} slide file(s) not referenced by deck-config.ts:`)
|
|
583
|
+
}
|
|
584
|
+
console.log(` ${dim(slideSelection.unreferenced.join(", "))}`)
|
|
564
585
|
}
|
|
565
586
|
if (deckConfig.transition) {
|
|
566
587
|
console.log(` Transition: ${deckConfig.transition}${deckConfig.directionalTransition ? " (directional)" : ""}`)
|
|
@@ -612,6 +633,11 @@ export async function publish(args) {
|
|
|
612
633
|
|
|
613
634
|
// Read lockfile for skip-if-unchanged
|
|
614
635
|
const lock = readLockfile(cwd)
|
|
636
|
+
const prunedLockItems = pruneMissingLockfileItems(cwd, lock)
|
|
637
|
+
if (prunedLockItems.length > 0) {
|
|
638
|
+
writeLockfile(cwd, lock)
|
|
639
|
+
console.log(` ${dim("Pruned missing files from lockfile:")} ${prunedLockItems.join(", ")}`)
|
|
640
|
+
}
|
|
615
641
|
let published = 0
|
|
616
642
|
let skipped = 0
|
|
617
643
|
let failed = 0
|
package/src/commands/pull.mjs
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"
|
|
2
1
|
import { execFileSync } from "node:child_process"
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs"
|
|
3
3
|
import { join, dirname, resolve, sep } from "node:path"
|
|
4
4
|
|
|
5
5
|
import { bold, green, cyan, red, yellow, dim } from "../utils/ansi.mjs"
|
|
6
6
|
import { requireAuth } from "../utils/auth.mjs"
|
|
7
|
+
import { toPascalCase, addSlideToDeckConfig, replaceDeckConfig } from "../utils/deck-config.mjs"
|
|
8
|
+
import { confirm, closePrompts } from "../utils/prompts.mjs"
|
|
7
9
|
import {
|
|
8
10
|
fetchRegistryItem,
|
|
9
11
|
resolveRegistryDependencies,
|
|
@@ -15,12 +17,7 @@ import {
|
|
|
15
17
|
hashFile,
|
|
16
18
|
isFileDirty
|
|
17
19
|
} from "../utils/registry.mjs"
|
|
18
|
-
import {
|
|
19
|
-
toPascalCase,
|
|
20
|
-
addSlideToDeckConfig,
|
|
21
|
-
replaceDeckConfig
|
|
22
|
-
} from "../utils/deck-config.mjs"
|
|
23
|
-
import { confirm, closePrompts } from "../utils/prompts.mjs"
|
|
20
|
+
import { collectRegistrySlideSlugs, partitionDeckSlides } from "../utils/deck-sync.mjs"
|
|
24
21
|
|
|
25
22
|
export async function pull(args) {
|
|
26
23
|
const cwd = process.cwd()
|
|
@@ -50,7 +47,9 @@ export async function pull(args) {
|
|
|
50
47
|
|
|
51
48
|
if (!deckSlug) {
|
|
52
49
|
console.error(` ${red("Error:")} No deck slug found in lockfile.`)
|
|
53
|
-
console.error(
|
|
50
|
+
console.error(
|
|
51
|
+
` ${dim("Run")} ${cyan("promptslide publish --type deck")} ${dim("first to set it.")}`
|
|
52
|
+
)
|
|
54
53
|
console.log()
|
|
55
54
|
process.exit(1)
|
|
56
55
|
}
|
|
@@ -68,7 +67,9 @@ export async function pull(args) {
|
|
|
68
67
|
}
|
|
69
68
|
|
|
70
69
|
const versionTag = deckItem.version ? ` ${dim(`v${deckItem.version}`)}` : ""
|
|
71
|
-
console.log(
|
|
70
|
+
console.log(
|
|
71
|
+
` Found ${bold(deckItem.title || deckItem.name)} ${dim(`(${deckItem.type})`)}${versionTag}`
|
|
72
|
+
)
|
|
72
73
|
|
|
73
74
|
// Resolve all dependencies (slides, layouts, themes, assets)
|
|
74
75
|
let resolved
|
|
@@ -137,7 +138,10 @@ export async function pull(args) {
|
|
|
137
138
|
}
|
|
138
139
|
} else if (!storedHash) {
|
|
139
140
|
// File exists but not in lockfile (first pull or untracked)
|
|
140
|
-
const overwrite = await confirm(
|
|
141
|
+
const overwrite = await confirm(
|
|
142
|
+
` Overwrite ${relativePath}? ${dim("(local changes will be lost)")}`,
|
|
143
|
+
false
|
|
144
|
+
)
|
|
141
145
|
if (!overwrite) {
|
|
142
146
|
console.log(` ${dim("Skipped")} ${relativePath}`)
|
|
143
147
|
continue
|
|
@@ -150,7 +154,10 @@ export async function pull(args) {
|
|
|
150
154
|
mkdirSync(targetDir, { recursive: true })
|
|
151
155
|
const dataUriPrefix = file.content.match(/^data:[^;]+;base64,/)
|
|
152
156
|
if (dataUriPrefix) {
|
|
153
|
-
writeFileSync(
|
|
157
|
+
writeFileSync(
|
|
158
|
+
targetPath,
|
|
159
|
+
Buffer.from(file.content.slice(dataUriPrefix[0].length), "base64")
|
|
160
|
+
)
|
|
154
161
|
} else {
|
|
155
162
|
writeFileSync(targetPath, file.content, "utf-8")
|
|
156
163
|
}
|
|
@@ -174,11 +181,18 @@ export async function pull(args) {
|
|
|
174
181
|
|
|
175
182
|
// Update deck-config.ts
|
|
176
183
|
if (deckItem.type === "deck" && deckItem.meta?.slides) {
|
|
184
|
+
const registrySlideSlugs = collectRegistrySlideSlugs(resolved.items)
|
|
185
|
+
const { available, missing } = partitionDeckSlides(deckItem.meta.slides, registrySlideSlugs)
|
|
186
|
+
if (missing.length > 0) {
|
|
187
|
+
console.log(` ${yellow("!")} Skipped ${missing.length} slide(s) listed in the deck but not included in this version:`)
|
|
188
|
+
console.log(` ${dim(missing.map(s => s.slug).join(", "))}`)
|
|
189
|
+
}
|
|
177
190
|
const shouldReplace = await confirm(" Replace deck-config.ts with pulled deck config?", true)
|
|
178
191
|
if (shouldReplace) {
|
|
179
|
-
const slides =
|
|
192
|
+
const slides = available.map(s => ({
|
|
180
193
|
componentName: s.componentName || toPascalCase(s.slug),
|
|
181
194
|
importPath: `@/slides/${s.slug}`,
|
|
195
|
+
id: s.id,
|
|
182
196
|
steps: s.steps,
|
|
183
197
|
section: s.section
|
|
184
198
|
}))
|
|
@@ -186,13 +200,21 @@ export async function pull(args) {
|
|
|
186
200
|
transition: deckItem.meta.transition,
|
|
187
201
|
directionalTransition: deckItem.meta.directionalTransition
|
|
188
202
|
})
|
|
189
|
-
console.log(
|
|
203
|
+
console.log(
|
|
204
|
+
` ${green("+")} Replaced ${cyan("deck-config.ts")} ${dim(`(${slides.length} slides)`)}`
|
|
205
|
+
)
|
|
190
206
|
} else {
|
|
191
207
|
// Append individual slides
|
|
192
|
-
for (const s of
|
|
208
|
+
for (const s of available) {
|
|
193
209
|
const componentName = s.componentName || toPascalCase(s.slug)
|
|
194
210
|
const importPath = `@/slides/${s.slug}`
|
|
195
|
-
const updated = addSlideToDeckConfig(cwd, {
|
|
211
|
+
const updated = addSlideToDeckConfig(cwd, {
|
|
212
|
+
componentName,
|
|
213
|
+
importPath,
|
|
214
|
+
steps: s.steps,
|
|
215
|
+
id: s.id,
|
|
216
|
+
section: s.section
|
|
217
|
+
})
|
|
196
218
|
if (updated) {
|
|
197
219
|
console.log(` ${green("+")} Added ${componentName} to ${cyan("deck-config.ts")}`)
|
|
198
220
|
}
|
|
@@ -220,24 +242,37 @@ export async function pull(args) {
|
|
|
220
242
|
if (deckItem.id) {
|
|
221
243
|
try {
|
|
222
244
|
const annotationsRes = await fetch(`${auth.registry}/api/items/${deckItem.id}/annotations`, {
|
|
223
|
-
headers: {
|
|
245
|
+
headers: {
|
|
246
|
+
Authorization: `Bearer ${auth.token}`,
|
|
247
|
+
...(auth.organizationId ? { "X-Organization-Id": auth.organizationId } : {})
|
|
248
|
+
}
|
|
224
249
|
})
|
|
225
250
|
if (annotationsRes.ok) {
|
|
226
251
|
const data = await annotationsRes.json()
|
|
227
252
|
const annotations = data.annotations ?? []
|
|
228
253
|
if (annotations.length > 0) {
|
|
229
|
-
const annotationsFile = {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
254
|
+
const annotationsFile = {
|
|
255
|
+
version: 1,
|
|
256
|
+
annotations: annotations.map(a => ({
|
|
257
|
+
id: a.id,
|
|
258
|
+
slideIndex: a.slideIndex,
|
|
259
|
+
...(a.slideId ? { slideId: a.slideId } : {}),
|
|
260
|
+
slideTitle: a.slideTitle,
|
|
261
|
+
target: a.target,
|
|
262
|
+
body: a.body,
|
|
263
|
+
createdAt: a.createdAt,
|
|
264
|
+
status: a.status,
|
|
265
|
+
...(a.resolution ? { resolution: a.resolution } : {})
|
|
266
|
+
}))
|
|
267
|
+
}
|
|
268
|
+
writeFileSync(
|
|
269
|
+
join(cwd, "annotations.json"),
|
|
270
|
+
JSON.stringify(annotationsFile, null, 2) + "\n",
|
|
271
|
+
"utf-8"
|
|
272
|
+
)
|
|
273
|
+
console.log(
|
|
274
|
+
` ${green("+")} ${cyan("annotations.json")} ${dim(`(${annotations.length} annotation${annotations.length === 1 ? "" : "s"})`)}`
|
|
275
|
+
)
|
|
241
276
|
}
|
|
242
277
|
}
|
|
243
278
|
} catch {
|
|
@@ -250,7 +285,9 @@ export async function pull(args) {
|
|
|
250
285
|
if (written.length === 0) {
|
|
251
286
|
console.log(` ${green("+")} Everything is up to date.`)
|
|
252
287
|
} else {
|
|
253
|
-
console.log(
|
|
288
|
+
console.log(
|
|
289
|
+
` ${green("+")} Pulled ${bold(String(written.length))} file(s) from ${bold(deckSlug)}`
|
|
290
|
+
)
|
|
254
291
|
}
|
|
255
292
|
console.log()
|
|
256
293
|
closePrompts()
|
package/src/commands/studio.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createServer } from "vite"
|
|
2
2
|
|
|
3
3
|
import { bold, dim } from "../utils/ansi.mjs"
|
|
4
|
+
import { registerStudioServer } from "../utils/studio-discovery.mjs"
|
|
4
5
|
import { ensureTsConfig } from "../utils/tsconfig.mjs"
|
|
5
6
|
import { createViteConfig } from "../vite/config.mjs"
|
|
6
7
|
|
|
@@ -29,6 +30,8 @@ export async function studio(args) {
|
|
|
29
30
|
})
|
|
30
31
|
|
|
31
32
|
await server.listen()
|
|
33
|
+
const baseUrl = server.resolvedUrls?.local[0]
|
|
34
|
+
if (baseUrl) registerStudioServer(cwd, baseUrl)
|
|
32
35
|
server.printUrls()
|
|
33
36
|
server.bindCLIShortcuts({ print: true })
|
|
34
37
|
}
|
|
@@ -8,6 +8,8 @@ export async function toImage(args) {
|
|
|
8
8
|
const slidePath = args[0]
|
|
9
9
|
const outputIndex = args.indexOf("-o")
|
|
10
10
|
const output = outputIndex !== -1 ? args[outputIndex + 1] : null
|
|
11
|
+
const portArg = args.find(arg => arg.startsWith("--port="))
|
|
12
|
+
const studioPort = portArg ? Number(portArg.slice(7)) : undefined
|
|
11
13
|
|
|
12
14
|
if (!slidePath || slidePath === "--help" || slidePath === "-h") {
|
|
13
15
|
console.log()
|
|
@@ -15,6 +17,7 @@ export async function toImage(args) {
|
|
|
15
17
|
console.log()
|
|
16
18
|
console.log(` ${bold("Options:")}`)
|
|
17
19
|
console.log(` -o <file> Output file path (default: <slide-name>.png)`)
|
|
20
|
+
console.log(` --port=N Studio port to reuse (default: auto-discover)`)
|
|
18
21
|
console.log()
|
|
19
22
|
console.log(` ${bold("Examples:")}`)
|
|
20
23
|
console.log(` ${dim("promptslide to-image src/slides/slide-title.tsx")}`)
|
|
@@ -26,7 +29,9 @@ export async function toImage(args) {
|
|
|
26
29
|
if (!(await isPlaywrightAvailable())) {
|
|
27
30
|
console.error()
|
|
28
31
|
console.error(` ${red("Error:")} Playwright is required for image export.`)
|
|
29
|
-
console.error(
|
|
32
|
+
console.error(
|
|
33
|
+
` Install it with: ${bold("npm install -D playwright && npx playwright install chromium")}`
|
|
34
|
+
)
|
|
30
35
|
console.error()
|
|
31
36
|
process.exit(1)
|
|
32
37
|
}
|
|
@@ -36,10 +41,18 @@ export async function toImage(args) {
|
|
|
36
41
|
console.log()
|
|
37
42
|
console.log(` ${dim("Capturing screenshot...")}`)
|
|
38
43
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
let buffer
|
|
45
|
+
try {
|
|
46
|
+
buffer = await captureSlideScreenshot({
|
|
47
|
+
cwd: process.cwd(),
|
|
48
|
+
slidePath,
|
|
49
|
+
studioPort
|
|
50
|
+
})
|
|
51
|
+
} catch (error) {
|
|
52
|
+
console.error(` ${red("Error:")} ${error.message}`)
|
|
53
|
+
process.exitCode = 1
|
|
54
|
+
return
|
|
55
|
+
}
|
|
43
56
|
|
|
44
57
|
if (!buffer) {
|
|
45
58
|
console.error(` ${red("Error:")} Failed to capture screenshot.`)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { writeFileSync } from "node:fs"
|
|
2
|
+
import { resolve } from "node:path"
|
|
3
|
+
|
|
4
|
+
import { bold, green, dim, red } from "../utils/ansi.mjs"
|
|
5
|
+
import { captureDeckPdf } from "../utils/export.mjs"
|
|
6
|
+
|
|
7
|
+
export async function toPdf(args) {
|
|
8
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
9
|
+
console.log(` ${bold("Usage:")} promptslide to-pdf [-o slides.pdf] [--port=N]`)
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
const outputIndex = args.indexOf("-o")
|
|
13
|
+
const output = resolve(outputIndex >= 0 ? args[outputIndex + 1] : "slides.pdf")
|
|
14
|
+
const portArg = args.find(arg => arg.startsWith("--port="))
|
|
15
|
+
const studioPort = portArg ? Number(portArg.slice(7)) : undefined
|
|
16
|
+
console.log(` ${dim("Rendering deck PDF...")}`)
|
|
17
|
+
let pdf
|
|
18
|
+
try {
|
|
19
|
+
pdf = await captureDeckPdf({ cwd: process.cwd(), studioPort })
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error(` ${red("Error:")} ${error.message}`)
|
|
22
|
+
process.exitCode = 1
|
|
23
|
+
return
|
|
24
|
+
}
|
|
25
|
+
writeFileSync(output, pdf)
|
|
26
|
+
console.log(` ${green("✓")} Saved to ${bold(output)}`)
|
|
27
|
+
}
|
package/src/core/animated.tsx
CHANGED
|
@@ -48,7 +48,7 @@ const animationVariants: Record<AnimationType, Variants> = {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
interface AnimatedProps {
|
|
51
|
-
/** Which step reveals this content (
|
|
51
|
+
/** Which step reveals this content (0 = visible from slide entry) */
|
|
52
52
|
step: number
|
|
53
53
|
/** Animation type */
|
|
54
54
|
animation?: AnimationType
|
|
@@ -83,7 +83,7 @@ export function Animated({
|
|
|
83
83
|
|
|
84
84
|
return (
|
|
85
85
|
<motion.div
|
|
86
|
-
initial="hidden"
|
|
86
|
+
initial={isVisible ? false : "hidden"}
|
|
87
87
|
animate={isVisible ? "visible" : "hidden"}
|
|
88
88
|
variants={animationVariants[animation]}
|
|
89
89
|
transition={{
|
|
@@ -138,7 +138,7 @@ export function AnimatedGroup({
|
|
|
138
138
|
|
|
139
139
|
return (
|
|
140
140
|
<motion.div
|
|
141
|
-
initial="hidden"
|
|
141
|
+
initial={isVisible ? false : "hidden"}
|
|
142
142
|
animate={isVisible ? "visible" : "hidden"}
|
|
143
143
|
variants={containerVariants}
|
|
144
144
|
className={className}
|
|
@@ -38,6 +38,14 @@ export function createHttpAdapter(): AnnotationStorageAdapter {
|
|
|
38
38
|
async remove(id) {
|
|
39
39
|
cache = cache.filter(a => a.id !== id)
|
|
40
40
|
await saveAll(cache)
|
|
41
|
+
},
|
|
42
|
+
async update(id, patch) {
|
|
43
|
+
cache = cache.map(a => (a.id === id ? { ...a, ...patch } : a))
|
|
44
|
+
await saveAll(cache)
|
|
45
|
+
},
|
|
46
|
+
async replaceAll(annotations) {
|
|
47
|
+
cache = annotations
|
|
48
|
+
await saveAll(cache)
|
|
41
49
|
}
|
|
42
50
|
}
|
|
43
51
|
}
|