From 18330d7a3efcced4efdbbf9a2f5e318d397316de Mon Sep 17 00:00:00 2001 From: Angelo Ashmore Date: Fri, 14 Aug 2026 02:18:54 +0000 Subject: [PATCH] fix: quote slice IDs in generated slice index files when necessary The generated components map wrote slice IDs as bare object keys. An ID that is not a valid JavaScript identifier (for example, an ID with a dash) made the file invalid. The API accepts such IDs, so a pull can deliver them. Now the generator quotes an ID when it is not a valid identifier. Valid IDs stay bare, so formatters do not change the output. Co-Authored-By: Claude Fable 5 --- src/adapters/nextjs.ts | 4 ++-- src/adapters/nuxt.ts | 4 ++-- src/adapters/sveltekit.ts | 4 ++-- src/lib/string.ts | 4 ++++ test/setup.ts | 2 +- test/slice-create.test.ts | 9 +++++++++ 6 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/adapters/nextjs.ts b/src/adapters/nextjs.ts index c456fbc..4d67ed4 100644 --- a/src/adapters/nextjs.ts +++ b/src/adapters/nextjs.ts @@ -15,7 +15,7 @@ import { getSimulatorUrl, setSimulatorUrl, } from "../lib/prismic/clients/core"; -import { dedent } from "../lib/string"; +import { dedent, formatObjectKey } from "../lib/string"; import { appendTrailingSlash } from "../lib/url"; import { buildRoutePath, getRepositoryName } from "../project"; import { checkIsTypeScriptProject, findProjectRoot } from "../project"; @@ -104,7 +104,7 @@ export class NextJsAdapter extends Adapter { }); const componentLines = slices.map((slice) => { const componentName = pascalCase(slice.model.name); - return `${slice.model.id}: ${componentName}`; + return `${formatObjectKey(slice.model.id)}: ${componentName}`; }); const contents = dedent` // Code generated by Prismic. DO NOT EDIT. diff --git a/src/adapters/nuxt.ts b/src/adapters/nuxt.ts index 84fa4f5..1d9ac66 100644 --- a/src/adapters/nuxt.ts +++ b/src/adapters/nuxt.ts @@ -16,7 +16,7 @@ import { getSimulatorUrl, setSimulatorUrl, } from "../lib/prismic/clients/core"; -import { dedent } from "../lib/string"; +import { dedent, formatObjectKey } from "../lib/string"; import { appendTrailingSlash } from "../lib/url"; import { buildRoutePath, getRepositoryName, readConfig, updateConfig } from "../project"; import { checkIsTypeScriptProject, findProjectRoot } from "../project"; @@ -90,7 +90,7 @@ export class NuxtAdapter extends Adapter { const slices = allSlices.filter((slice) => slice.library.href === library.href); const componentLines = slices.map((slice) => { const relativeDirectory = relative(fileURLToPath(library), fileURLToPath(slice.directory)); - return `${slice.model.id}: defineAsyncComponent(() => import("./${relativeDirectory}/index.vue"))`; + return `${formatObjectKey(slice.model.id)}: defineAsyncComponent(() => import("./${relativeDirectory}/index.vue"))`; }); const contents = dedent` // Code generated by Prismic. DO NOT EDIT. diff --git a/src/adapters/sveltekit.ts b/src/adapters/sveltekit.ts index 7d62d06..2594e53 100644 --- a/src/adapters/sveltekit.ts +++ b/src/adapters/sveltekit.ts @@ -17,7 +17,7 @@ import { getSimulatorUrl, setSimulatorUrl, } from "../lib/prismic/clients/core"; -import { dedent } from "../lib/string"; +import { dedent, formatObjectKey } from "../lib/string"; import { appendTrailingSlash } from "../lib/url"; import { buildRoutePath, getRepositoryName } from "../project"; import { checkIsTypeScriptProject, findProjectRoot } from "../project"; @@ -107,7 +107,7 @@ export class SvelteKitAdapter extends Adapter { }); const componentLines = slices.map((slice) => { const componentName = pascalCase(slice.model.name); - return `${slice.model.id}: ${componentName}`; + return `${formatObjectKey(slice.model.id)}: ${componentName}`; }); const contents = dedent` // Code generated by Prismic. DO NOT EDIT. diff --git a/src/lib/string.ts b/src/lib/string.ts index b4bb75b..71ae245 100644 --- a/src/lib/string.ts +++ b/src/lib/string.ts @@ -15,6 +15,10 @@ export function dedent(strings: TemplateStringsArray | string, ...values: unknow return baseDedentWithOptions(Object.assign(resolved, { raw: resolved }), ...values); } +export function formatObjectKey(key: string): string { + return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key); +} + export function formatTable( rows: string[][], config?: { headers?: string[]; separator?: string }, diff --git a/test/setup.ts b/test/setup.ts index c453fd0..ba6c3dd 100644 --- a/test/setup.ts +++ b/test/setup.ts @@ -64,7 +64,7 @@ expect.extend({ const sliceIndexPath = new URL("../index.js", sliceDirectory); try { const sliceIndex = await readFile(sliceIndexPath, "utf8"); - if (!new RegExp(`\\b${slice.id}: `).test(sliceIndex)) { + if (!new RegExp(`(?:"|\\b)${slice.id}"?: `).test(sliceIndex)) { problems.push( `slice "${slice.id}" not found in slice library index (${sliceIndexPath.href})`, ); diff --git a/test/slice-create.test.ts b/test/slice-create.test.ts index 9475b66..15c9afe 100644 --- a/test/slice-create.test.ts +++ b/test/slice-create.test.ts @@ -34,6 +34,15 @@ it("creates a slice with a custom id", async ({ expect, prismic, project }) => { expect(created).toBeDefined(); }); +it("quotes the slice ID in the slice index file", async ({ expect, prismic, project }) => { + const { id, name } = buildSlice(); + + const { stderr, exitCode } = await prismic("slice", ["create", name, "--id", id]); + expect(exitCode, stderr).toBe(0); + + await expect(project).toHaveFile("slices/index.js", { contains: `"${id}": ${name}` }); +}); + it("creates a slice in the first configured library", async ({ expect, prismic,