promptslide 0.3.7 → 0.3.9

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,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.3.9](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.8...promptslide-v0.3.9) (2026-03-24)
4
+
5
+
6
+ ### Bug Fixes
7
+
8
+ * preserve component name casing through publish/pull roundtrip ([a34e83a](https://github.com/prompticeu/promptslide/commit/a34e83a7b3869ef7487b361bba23fd4cce72a0b6))
9
+ * preserve component name casing through publish/pull roundtrip ([e04d96c](https://github.com/prompticeu/promptslide/commit/e04d96c7367b69f8aff2f97c4a29a56a610a3c78))
10
+
11
+ ## [0.3.8](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.7...promptslide-v0.3.8) (2026-03-23)
12
+
13
+
14
+ ### Bug Fixes
15
+
16
+ * include layouts and theme in deck registryDependencies ([#81](https://github.com/prompticeu/promptslide/issues/81)) ([4dbd699](https://github.com/prompticeu/promptslide/commit/4dbd699480d692625cf65dd32c556cfe62fc529a))
17
+ * sanitize deck slug from lockfile to match validation rules ([#79](https://github.com/prompticeu/promptslide/issues/79)) ([8692313](https://github.com/prompticeu/promptslide/commit/869231362e9485d3f177ca5337d3a5ff92743f47))
18
+
3
19
  ## [0.3.7](https://github.com/prompticeu/promptslide/compare/promptslide-v0.3.6...promptslide-v0.3.7) (2026-03-19)
4
20
 
5
21
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptslide",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "CLI and slide engine for PromptSlide presentations",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -15,12 +15,17 @@ const CLI_VERSION = JSON.parse(readFileSync(join(__dirname, "..", "..", "package
15
15
  function readDeckSlug(cwd) {
16
16
  const lock = readLockfile(cwd)
17
17
  // Prefer stored deck slug — migrate old two-part format (e.g. "my-deck/name" → "my-deck")
18
- if (lock.deckSlug) return lock.deckSlug.split("/")[0]
18
+ // Sanitize to match validation rules (lowercase alphanumeric + hyphens only)
19
+ if (lock.deckSlug) return sanitizeSlug(lock.deckSlug.split("/")[0])
19
20
  // Migrate legacy deckPrefix (old lockfiles stored prefix separately)
20
- if (lock.deckPrefix) return lock.deckPrefix
21
+ if (lock.deckPrefix) return sanitizeSlug(lock.deckPrefix)
21
22
  return ""
22
23
  }
23
24
 
25
+ function sanitizeSlug(raw) {
26
+ return raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
27
+ }
28
+
24
29
  function defaultDeckSlug(cwd) {
25
30
  const dirName = basename(cwd)
26
31
  return dirName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "")
@@ -91,6 +96,24 @@ function detectRegistryDeps(content) {
91
96
  return deps
92
97
  }
93
98
 
99
+ /** Detect sibling layout imports — both relative ("./foo") and absolute ("@/layouts/foo"). */
100
+ function detectLayoutSiblingDeps(content) {
101
+ const deps = []
102
+ // Relative imports: import ... from "./shared-footer"
103
+ const relativeRegex = /import\s+.*?\s+from\s+["']\.\/([^"']+)["']/g
104
+ for (const match of content.matchAll(relativeRegex)) {
105
+ const name = match[1].replace(/\.tsx?$/, "")
106
+ deps.push(name)
107
+ }
108
+ // Absolute imports: import ... from "@/layouts/shared-footer"
109
+ const absoluteRegex = /import\s+.*?\s+from\s+["']@\/layouts\/([^"']+)["']/g
110
+ for (const match of content.matchAll(absoluteRegex)) {
111
+ const name = match[1].replace(/\.tsx?$/, "")
112
+ if (!deps.includes(name)) deps.push(name)
113
+ }
114
+ return deps
115
+ }
116
+
94
117
  /**
95
118
  * Discover shared source files under src/ that aren't slides, layouts, or theme.
96
119
  * These are files in directories like src/components/, src/lib/, src/hooks/, etc.
@@ -708,8 +731,9 @@ export async function publish(args) {
708
731
 
709
732
  const assetDeps = detectAssetDepsInContent(content, deckSlug, publicFileSet)
710
733
  const npmDeps = detectNpmDeps(content)
734
+ const siblingDeps = detectLayoutSiblingDeps(content).map(d => `${deckSlug}/${d}`)
711
735
  const regDeps = hasTheme ? [`${deckSlug}/theme`] : []
712
- regDeps.push(...assetDeps)
736
+ regDeps.push(...siblingDeps, ...assetDeps)
713
737
 
714
738
  try {
715
739
  const result = await publishToRegistry({
@@ -793,9 +817,11 @@ export async function publish(args) {
793
817
 
794
818
  // ── Phase 5: Deck manifest (includes shared source modules) ──
795
819
  itemIndex++
820
+ const themeSlugs = hasTheme ? [`${deckSlug}/theme`] : []
821
+ const layoutSlugs = layoutEntries.map(f => `${deckSlug}/${f.replace(/\.tsx?$/, "")}`)
796
822
  const slideSlugs = slideEntries.map(f => `${deckSlug}/${f.replace(/\.tsx?$/, "")}`)
797
823
  const assetSlugs = publicAssets.map(a => assetFileToSlug(deckSlug, a.relativePath))
798
- const allDeckDeps = [...slideSlugs, ...assetSlugs]
824
+ const allDeckDeps = [...themeSlugs, ...layoutSlugs, ...slideSlugs, ...assetSlugs]
799
825
 
800
826
  // Bundle shared source files with the deck item so preview/install can resolve @/ imports
801
827
  const sharedFiles = sharedSources.map(s => ({
@@ -177,7 +177,7 @@ export async function pull(args) {
177
177
  const shouldReplace = await confirm(" Replace deck-config.ts with pulled deck config?", true)
178
178
  if (shouldReplace) {
179
179
  const slides = deckItem.meta.slides.map(s => ({
180
- componentName: toPascalCase(s.slug),
180
+ componentName: s.componentName || toPascalCase(s.slug),
181
181
  importPath: `@/slides/${s.slug}`,
182
182
  steps: s.steps,
183
183
  section: s.section
@@ -190,7 +190,7 @@ export async function pull(args) {
190
190
  } else {
191
191
  // Append individual slides
192
192
  for (const s of deckItem.meta.slides) {
193
- const componentName = toPascalCase(s.slug)
193
+ const componentName = s.componentName || toPascalCase(s.slug)
194
194
  const importPath = `@/slides/${s.slug}`
195
195
  const updated = addSlideToDeckConfig(cwd, { componentName, importPath, steps: s.steps, section: s.section })
196
196
  if (updated) {
@@ -224,11 +224,12 @@ export function parseDeckConfig(cwd) {
224
224
 
225
225
  const content = readFileSync(configPath, "utf-8")
226
226
 
227
- // 1. Parse imports to build componentName -> slug map
227
+ // 1. Parse imports to build componentName -> slug map (and reverse)
228
228
  // Handles: import { SlideTitle } from "@/slides/slide-title"
229
229
  // Handles: import { default as SlideTitle } from "@/slides/slide-title"
230
230
  // Handles: import { SlideTitle as Alias } from "@/slides/slide-title"
231
231
  const componentToSlug = {}
232
+ const slugToComponent = {}
232
233
  const importRegex = /import\s*\{([^}]+)\}\s*from\s*["']@\/slides\/([^"']+)["']/g
233
234
  for (const match of content.matchAll(importRegex)) {
234
235
  const importClause = match[1].trim()
@@ -238,6 +239,7 @@ export function parseDeckConfig(cwd) {
238
239
  const componentName = asMatch ? asMatch[1] : importClause.match(/(\w+)/)?.[1]
239
240
  if (componentName) {
240
241
  componentToSlug[componentName] = slug
242
+ slugToComponent[slug] = componentName
241
243
  }
242
244
  }
243
245
 
@@ -278,6 +280,8 @@ export function parseDeckConfig(cwd) {
278
280
  if (!slug) continue // layout or unknown import — skip
279
281
  const stepsMatch = body.match(/steps:\s*(\d+)/)
280
282
  const entry = { slug, steps: stepsMatch ? parseInt(stepsMatch[1], 10) : 0 }
283
+ // Preserve original component name so pull can reconstruct the correct export
284
+ if (slugToComponent[slug]) entry.componentName = slugToComponent[slug]
281
285
  const sectionMatch = body.match(/section:\s*["']([^"']+)["']/)
282
286
  if (sectionMatch) entry.section = sectionMatch[1]
283
287
  slides.push(entry)