promptslide 0.3.12 → 0.3.13

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 CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.13](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.12...promptslide-v0.3.13) (2026-03-29)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * pin dependency versions and preserve component names in publish pipeline ([#89](https://github.com/prompticeu/promptslide/issues/89)) ([22b7dcb](https://github.com/prompticeu/promptslide/commit/22b7dcbe33c9852fac2fbfef70943254a0d871cd))
9
+
3
10
  ## [0.3.12](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.11...promptslide-v0.3.12) (2026-03-28)
4
11
 
5
12
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptslide",
3
- "version": "0.3.12",
3
+ "version": "0.3.13",
4
4
  "description": "CLI and slide engine for PromptSlide presentations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -167,7 +167,7 @@ export async function add(args) {
167
167
  const shouldReplace = await confirm(" Replace entire deck-config.ts with this deck?", true)
168
168
  if (shouldReplace) {
169
169
  const slides = item.meta.slides.map(s => ({
170
- componentName: toPascalCase(s.slug),
170
+ componentName: s.componentName || toPascalCase(s.slug),
171
171
  importPath: `@/slides/${s.slug}`,
172
172
  steps: s.steps,
173
173
  section: s.section
@@ -180,7 +180,7 @@ export async function add(args) {
180
180
  } else {
181
181
  // Append individual slides
182
182
  for (const s of item.meta.slides) {
183
- const componentName = toPascalCase(s.slug)
183
+ const componentName = s.componentName || toPascalCase(s.slug)
184
184
  const importPath = `@/slides/${s.slug}`
185
185
  const updated = addSlideToDeckConfig(cwd, { componentName, importPath, steps: s.steps })
186
186
  if (updated) {
@@ -159,7 +159,7 @@ export async function clone(args) {
159
159
  // 6. Generate deck-config.ts
160
160
  if (item.meta?.slides) {
161
161
  const slides = item.meta.slides.map(s => ({
162
- componentName: toPascalCase(s.slug),
162
+ componentName: s.componentName || toPascalCase(s.slug),
163
163
  importPath: `@/slides/${s.slug}`,
164
164
  steps: s.steps,
165
165
  section: s.section
@@ -222,7 +222,7 @@ export async function create(args) {
222
222
  // Reconstruct deck-config.ts from deckConfig metadata
223
223
  if (fromItem.meta?.slides) {
224
224
  const slides = fromItem.meta.slides.map(s => ({
225
- componentName: toPascalCase(s.slug),
225
+ componentName: s.componentName || toPascalCase(s.slug),
226
226
  importPath: `@/slides/${s.slug}`,
227
227
  steps: s.steps,
228
228
  section: s.section
@@ -71,7 +71,7 @@ function detectSteps(content) {
71
71
  return max
72
72
  }
73
73
 
74
- function detectNpmDeps(content) {
74
+ function detectNpmDeps(content, projectDeps = {}) {
75
75
  const deps = {}
76
76
  // Match both unscoped (foo) and scoped (@scope/foo) packages, exclude relative imports
77
77
  const importRegex = /import\s+.*?\s+from\s+["']((?:@[a-zA-Z0-9-]+\/)?[a-zA-Z0-9-][^"']*)["']/g
@@ -81,7 +81,7 @@ function detectNpmDeps(content) {
81
81
  const pkg = full.startsWith("@") ? full.split("/").slice(0, 2).join("/") : full.split("/")[0]
82
82
  // Skip relative imports, react, and internal packages
83
83
  if (pkg.startsWith(".") || pkg === "react" || pkg === "react-dom" || pkg.startsWith("@promptslide/")) continue
84
- deps[pkg] = "latest"
84
+ deps[pkg] = projectDeps[pkg] ?? "latest"
85
85
  }
86
86
  return deps
87
87
  }
@@ -96,6 +96,15 @@ function detectRegistryDeps(content) {
96
96
  return deps
97
97
  }
98
98
 
99
+ function readProjectDeps(cwd) {
100
+ const pkgPath = join(cwd, "package.json")
101
+ if (!existsSync(pkgPath)) return {}
102
+ try {
103
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"))
104
+ return { ...pkg.devDependencies, ...pkg.dependencies }
105
+ } catch { return {} }
106
+ }
107
+
99
108
  /** Detect sibling layout imports — both relative ("./foo") and absolute ("@/layouts/foo"). */
100
109
  function detectLayoutSiblingDeps(content) {
101
110
  const deps = []
@@ -321,7 +330,7 @@ async function publishItem({ filePath, cwd, auth, typeOverride, interactive = tr
321
330
 
322
331
  const type = typeOverride || detectType(filePath) || "slide"
323
332
  const steps = detectSteps(content)
324
- const npmDeps = detectNpmDeps(content)
333
+ const npmDeps = detectNpmDeps(content, readProjectDeps(cwd))
325
334
  const registryDeps = detectRegistryDeps(content)
326
335
  const target = type === "layout" ? "src/layouts/" : "src/slides/"
327
336
 
@@ -396,6 +405,7 @@ async function publishItem({ filePath, cwd, auth, typeOverride, interactive = tr
396
405
 
397
406
  export async function publish(args) {
398
407
  const cwd = process.cwd()
408
+ const projectDeps = readProjectDeps(cwd)
399
409
 
400
410
  console.log()
401
411
  console.log(` ${bold("promptslide")} ${dim("publish")}`)
@@ -692,7 +702,7 @@ export async function publish(args) {
692
702
  skipped++
693
703
  } else {
694
704
  const assetDeps = detectAssetDepsInContent(themeContent, deckSlug, publicFileSet)
695
- const npmDeps = detectNpmDeps(themeContent)
705
+ const npmDeps = detectNpmDeps(themeContent, projectDeps)
696
706
 
697
707
  try {
698
708
  const result = await publishToRegistry({
@@ -730,7 +740,7 @@ export async function publish(args) {
730
740
  }
731
741
 
732
742
  const assetDeps = detectAssetDepsInContent(content, deckSlug, publicFileSet)
733
- const npmDeps = detectNpmDeps(content)
743
+ const npmDeps = detectNpmDeps(content, projectDeps)
734
744
  const siblingDeps = detectLayoutSiblingDeps(content).map(d => `${deckSlug}/${d}`)
735
745
  const regDeps = hasTheme ? [`${deckSlug}/theme`] : []
736
746
  regDeps.push(...siblingDeps, ...assetDeps)
@@ -775,7 +785,7 @@ export async function publish(args) {
775
785
 
776
786
  const assetDeps = detectAssetDepsInContent(content, deckSlug, publicFileSet)
777
787
  const layoutDeps = detectRegistryDeps(content).map(d => `${deckSlug}/${d}`)
778
- const npmDeps = detectNpmDeps(content)
788
+ const npmDeps = detectNpmDeps(content, projectDeps)
779
789
  const steps = detectSteps(content)
780
790
  const slideConfig = deckConfig.slides.find(s => s.slug === slideName)
781
791
  const section = slideConfig?.section || undefined
@@ -836,7 +846,7 @@ export async function publish(args) {
836
846
  // Collect npm deps from shared source files
837
847
  const sharedNpmDeps = {}
838
848
  for (const f of sharedFiles) {
839
- Object.assign(sharedNpmDeps, detectNpmDeps(f.content))
849
+ Object.assign(sharedNpmDeps, detectNpmDeps(f.content, projectDeps))
840
850
  }
841
851
 
842
852
  let deckItemId = null
@@ -905,7 +915,7 @@ export async function publish(args) {
905
915
  // Detect metadata
906
916
  const type = typeOverride || detectType(filePath) || "slide"
907
917
  const steps = detectSteps(content)
908
- const npmDeps = detectNpmDeps(content)
918
+ const npmDeps = detectNpmDeps(content, projectDeps)
909
919
  const registryDeps = detectRegistryDeps(content)
910
920
  const target = type === "layout" ? "src/layouts/" : "src/slides/"
911
921
 
@@ -190,36 +190,53 @@ export async function fetchOrganizations(auth) {
190
190
  * @param {{ registry: string, apiKey: string }} auth - Auth credentials
191
191
  * @returns {Promise<object>} Registry item JSON
192
192
  */
193
- export async function fetchRegistryItem(nameOrUrl, auth) {
193
+ export async function fetchRegistryItem(nameOrUrl, auth, { retries = 2 } = {}) {
194
194
  const url = nameOrUrl.startsWith("http")
195
195
  ? nameOrUrl
196
196
  : `${auth.registry}/api/r/${nameOrUrl}.json`
197
197
 
198
- const res = await fetch(url, {
199
- headers: authHeaders(auth)
200
- })
198
+ let lastError
199
+ for (let attempt = 0; attempt <= retries; attempt++) {
200
+ try {
201
+ const res = await fetch(url, {
202
+ headers: authHeaders(auth)
203
+ })
201
204
 
202
- if (res.status === 401) {
203
- throw new Error("Authentication failed. Run `promptslide login` to re-authenticate.")
204
- }
205
- if (res.status === 403) {
206
- const body = await res.json().catch(() => ({}))
207
- if (body.status === "pending_review") {
208
- throw new Error(`Item "${nameOrUrl}" is pending review. An admin must approve it first.`)
209
- }
210
- if (body.status === "rejected") {
211
- throw new Error(`Item "${nameOrUrl}" was rejected by an admin.`)
205
+ if (res.status === 401) {
206
+ throw new Error("Authentication failed. Run `promptslide login` to re-authenticate.")
207
+ }
208
+ if (res.status === 403) {
209
+ const body = await res.json().catch(() => ({}))
210
+ if (body.status === "pending_review") {
211
+ throw new Error(`Item "${nameOrUrl}" is pending review. An admin must approve it first.`)
212
+ }
213
+ if (body.status === "rejected") {
214
+ throw new Error(`Item "${nameOrUrl}" was rejected by an admin.`)
215
+ }
216
+ throw new Error("Access denied. This item belongs to a different organization.")
217
+ }
218
+ if (res.status === 404) {
219
+ throw new Error(`Item not found: ${nameOrUrl}`)
220
+ }
221
+ if (!res.ok) {
222
+ throw new Error(`Registry error (${res.status}): ${await res.text()}`)
223
+ }
224
+
225
+ return res.json()
226
+ } catch (err) {
227
+ lastError = err
228
+ // Only retry on network errors (fetch failed), not on HTTP-level errors
229
+ if (err.message.includes("Authentication") || err.message.includes("Access denied") ||
230
+ err.message.includes("not found") || err.message.includes("pending review") ||
231
+ err.message.includes("rejected") || err.message.includes("Registry error")) {
232
+ throw err
233
+ }
234
+ if (attempt < retries) {
235
+ await new Promise(r => setTimeout(r, 500 * (attempt + 1)))
236
+ }
212
237
  }
213
- throw new Error("Access denied. This item belongs to a different organization.")
214
- }
215
- if (res.status === 404) {
216
- throw new Error(`Item not found: ${nameOrUrl}`)
217
238
  }
218
- if (!res.ok) {
219
- throw new Error(`Registry error (${res.status}): ${await res.text()}`)
220
- }
221
-
222
- return res.json()
239
+ throw lastError
223
240
  }
224
241
 
225
242
  /**