diff --git a/AGENTS.md b/AGENTS.md index 7e65a9c0..181203ea 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -46,7 +46,9 @@ console output. ## `src/adapters/` Framework-specific integrations. One file per framework, with a sibling -`*.templates.ts` file for generated file content. +`*.templates.ts` file for generated file content. When an older framework major +needs different generated code, it gets its own adapter that extends the current +one, such as `sveltekit2.ts`. Delete it when support for that major ends. ## `src/subprocesses/` diff --git a/evals/set-up-previews.eval.ts b/evals/set-up-previews.eval.ts index c004f4c0..6fd43151 100644 --- a/evals/set-up-previews.eval.ts +++ b/evals/set-up-previews.eval.ts @@ -37,7 +37,7 @@ it.for(trials)( await mkdir(new URL("src/routes/", project), { recursive: true }); await writeFile( new URL("src/routes/+layout.svelte", project), - "\n\n" + '\n\t\n\n\n' + "

KEEP-ME

\n{@render children()}\n", diff --git a/src/adapters/index.ts b/src/adapters/index.ts index 2a38dae2..31b938ee 100644 --- a/src/adapters/index.ts +++ b/src/adapters/index.ts @@ -18,7 +18,7 @@ import { writeFileRecursive, } from "../lib/file"; import { stringify } from "../lib/json"; -import { readPackageJson } from "../lib/packageJson"; +import { getInstalledMajor, readPackageJson } from "../lib/packageJson"; import { addPreview, getPreviews, @@ -63,6 +63,10 @@ export async function getAdapter(): Promise { return new NuxtAdapter(); } if ("@sveltejs/kit" in allDependencies) { + if ((await getInstalledMajor("@sveltejs/kit")) < 3) { + const { SvelteKit2Adapter } = await import("./sveltekit2"); + return new SvelteKit2Adapter(); + } const { SvelteKitAdapter } = await import("./sveltekit"); return new SvelteKitAdapter(); } diff --git a/src/adapters/sveltekit.templates.ts b/src/adapters/sveltekit.templates.ts index 77cc8493..c0849ad8 100644 --- a/src/adapters/sveltekit.templates.ts +++ b/src/adapters/sveltekit.templates.ts @@ -8,14 +8,14 @@ export function prismicIOFileTemplate({ typescript }: { typescript: boolean }): return dedent` import { createClient as baseCreateClient } from "@prismicio/client"; import { type CreateClientConfig, enableAutoPreviews } from '@prismicio/svelte/kit'; - import { env } from '$env/dynamic/public'; + import { PUBLIC_PRISMIC_ENVIRONMENT } from '$app/env/public'; import prismicConfig from "../../prismic.config.json"; /** * The project's Prismic repository name. */ export const repositoryName = - env.PUBLIC_PRISMIC_ENVIRONMENT || prismicConfig.repositoryName; + PUBLIC_PRISMIC_ENVIRONMENT || prismicConfig.repositoryName; /** * Creates a Prismic client for the project's repository. The client is used to @@ -39,14 +39,14 @@ export function prismicIOFileTemplate({ typescript }: { typescript: boolean }): return dedent` import { createClient as baseCreateClient } from "@prismicio/client"; import { enableAutoPreviews } from '@prismicio/svelte/kit'; - import { env } from '$env/dynamic/public'; + import { PUBLIC_PRISMIC_ENVIRONMENT } from '$app/env/public'; import prismicConfig from "../../prismic.config.json"; /** * The project's Prismic repository name. */ export const repositoryName = - env.PUBLIC_PRISMIC_ENVIRONMENT || prismicConfig.repositoryName; + PUBLIC_PRISMIC_ENVIRONMENT || prismicConfig.repositoryName; /** * Creates a Prismic client for the project's repository. The client is used to @@ -67,15 +67,37 @@ export function prismicIOFileTemplate({ typescript }: { typescript: boolean }): `; } -export function sliceSimulatorPageTemplate(): string { +export function envVarTemplate(): string { + return dedent` + PUBLIC_PRISMIC_ENVIRONMENT: { + public: true, + schema: (value) => value, + description: "The Prismic environment to use instead of the production repository.", + }, + `; +} + +export function envFileTemplate(): string { + return dedent` + import { defineEnvVars } from "@sveltejs/kit/env"; + + export const variables = defineEnvVars({ + ${envVarTemplate()} + }); + `; +} + +export function sliceSimulatorPageTemplate({ extension }: { extension: string }): string { return dedent` - - + + {#snippet children({ slices })} + + {/snippet} `; } @@ -84,7 +106,7 @@ export function previewAPIRouteTemplate({ typescript }: { typescript: boolean }) if (typescript) { return dedent` import { redirectToPreviewURL } from '@prismicio/svelte/kit'; - import { createClient } from '$lib/prismicio'; + import { createClient } from '#lib/prismicio.ts'; import type { RequestHandler } from "./$types"; export const GET: RequestHandler = async ({ fetch, request, cookies }) => { @@ -97,7 +119,7 @@ export function previewAPIRouteTemplate({ typescript }: { typescript: boolean }) return dedent` import { redirectToPreviewURL } from '@prismicio/svelte/kit'; - import { createClient } from '$lib/prismicio'; + import { createClient } from '#lib/prismicio.js'; /* @type {import("./$types").RequestHandler} */ export async function GET({ fetch, request, cookies }) { @@ -108,9 +130,9 @@ export function previewAPIRouteTemplate({ typescript }: { typescript: boolean }) `; } -export function layoutServerTemplate(): string { +export function layoutServerTemplate({ extension }: { extension: string }): string { return dedent` - import { repositoryName } from "$lib/prismicio"; + import { repositoryName } from "#lib/prismicio.${extension}"; export const prerender = "auto"; @@ -151,7 +173,7 @@ export function pageTemplate({ typescript }: { typescript: boolean }): string { return dedent` @@ -184,7 +206,7 @@ export function pageServerTemplate({ if (typescript) { return dedent` import type { PageServerLoad } from "./$types"; - import { createClient } from "$lib/prismicio"; + import { createClient } from "#lib/prismicio.ts"; export const load: PageServerLoad = async ({ params, fetch, cookies }) => { const client = createClient({ fetch, cookies }); @@ -196,7 +218,7 @@ export function pageServerTemplate({ } return dedent` - import { createClient } from "$lib/prismicio"; + import { createClient } from "#lib/prismicio.js"; /** @type {import("./$types").PageServerLoad} */ export async function load({ params, fetch, cookies }) { @@ -211,7 +233,7 @@ export function pageServerTemplate({ if (typescript) { return dedent` import type { PageServerLoad } from "./$types"; - import { createClient } from "$lib/prismicio"; + import { createClient } from "#lib/prismicio.ts"; export const load: PageServerLoad = async ({ fetch, cookies }) => { const client = createClient({ fetch, cookies }); @@ -223,7 +245,7 @@ export function pageServerTemplate({ } return dedent` - import { createClient } from "$lib/prismicio"; + import { createClient } from "#lib/prismicio.js"; /* @type {import("./$types").PageServerLoad} */ export async function load({ fetch, cookies }) { diff --git a/src/adapters/sveltekit.ts b/src/adapters/sveltekit.ts index d05f3dc5..d18ee556 100644 --- a/src/adapters/sveltekit.ts +++ b/src/adapters/sveltekit.ts @@ -17,6 +17,8 @@ import { addDependencies, getNpmPackageVersion } from "../lib/packageJson"; import { dedent, formatObjectKey } from "../lib/string"; import { checkIsTypeScriptProject, findProjectRoot } from "../project"; import { + envFileTemplate, + envVarTemplate, layoutServerTemplate, pageServerTemplate, pageTemplate, @@ -58,9 +60,12 @@ export class SvelteKitAdapter extends Adapter { new URL(`src/lib/prismicio.${extension}`, projectRoot), prismicIOFileTemplate({ typescript }), ); + if (!(await findSourceFile("src/env"))) { + await writeFileRecursive(new URL(`src/env.${extension}`, projectRoot), envFileTemplate()); + } await writeFileIfMissing( new URL("src/routes/slice-simulator/+page.svelte", projectRoot), - sliceSimulatorPageTemplate(), + sliceSimulatorPageTemplate({ extension }), ); await writeFileIfMissing( new URL(`src/params/preview.${extension}`, projectRoot), @@ -87,11 +92,11 @@ export class SvelteKitAdapter extends Adapter { See for more information. `, ); - const serverLayout = await findServerLayout(); + const serverLayout = await findSourceFile("src/routes/+layout.server"); if (!serverLayout) { await writeFileRecursive( new URL(`src/routes/+layout.server.${extension}`, projectRoot), - layoutServerTemplate(), + layoutServerTemplate({ extension }), ); } // The generated layout reads repositoryName from the server layout. When an existing @@ -124,7 +129,29 @@ export class SvelteKitAdapter extends Adapter { from it first. `; - return [await this.getPreviewStep(), homePageStep].filter(Boolean).join("\n\n") || undefined; + return ( + [await this.getEnvStep(), await this.getPreviewStep(), homePageStep] + .filter(Boolean) + .join("\n\n") || undefined + ); + } + + // The generated Prismic client imports PUBLIC_PRISMIC_ENVIRONMENT, which SvelteKit only + // provides when the project's env file declares it. + private async getEnvStep(): Promise { + const envFile = await findSourceFile("src/env"); + if (!envFile) return; + const contents = await readFile(new URL(envFile, await findProjectRoot()), "utf8"); + if (contents.includes("PUBLIC_PRISMIC_ENVIRONMENT")) return; + + return dedent` + Action required: add PUBLIC_PRISMIC_ENVIRONMENT to ${envFile}. + + Your project does not build until you do this. Add these lines to + defineEnvVars({ ... }): + + ${envVarTemplate().replace(/^/gm, " ")} + `; } private async getPreviewStep(): Promise { @@ -132,14 +159,15 @@ export class SvelteKitAdapter extends Adapter { // The layout reads repositoryName from the server layout's data. const serverLayoutPath = - (await findServerLayout()) ?? `src/routes/+layout.server.${await getJsFileExtension()}`; + (await findSourceFile("src/routes/+layout.server")) ?? + `src/routes/+layout.server.${await getJsFileExtension()}`; const serverLayout = await readFile( new URL(serverLayoutPath, await findProjectRoot()), "utf8", ).catch(() => ""); const serverLayoutStep = !serverLayout.includes("repositoryName") && - `Return repositoryName from load in ${serverLayoutPath}, creating the file if needed:\n\n${layoutServerTemplate().replace(/^/gm, " ")}`; + `Return repositoryName from load in ${serverLayoutPath}, creating the file if needed:\n\n${layoutServerTemplate({ extension: serverLayoutPath.split(".").pop()! }).replace(/^/gm, " ")}`; return [ dedent` @@ -219,7 +247,7 @@ export class SvelteKitAdapter extends Adapter { } } -async function deleteStarterHomePage(): Promise { +export async function deleteStarterHomePage(): Promise { const path = new URL("src/routes/+page.svelte", await findProjectRoot()); const contents = await readFile(path, "utf8").catch(() => undefined); if (!STARTER_HOME_PAGES.includes(contents?.replace(/\s/g, "") ?? "")) return; @@ -227,9 +255,9 @@ async function deleteStarterHomePage(): Promise { await rm(path); } -async function findServerLayout(): Promise { +export async function findSourceFile(path: string): Promise { const projectRoot = await findProjectRoot(); - for (const path of ["src/routes/+layout.server.js", "src/routes/+layout.server.ts"]) { - if (await exists(new URL(path, projectRoot))) return path; + for (const extension of ["js", "ts"]) { + if (await exists(new URL(`${path}.${extension}`, projectRoot))) return `${path}.${extension}`; } } diff --git a/src/adapters/sveltekit2.templates.ts b/src/adapters/sveltekit2.templates.ts new file mode 100644 index 00000000..526f1fe4 --- /dev/null +++ b/src/adapters/sveltekit2.templates.ts @@ -0,0 +1,56 @@ +import type { DynamicCustomTypeModel } from "@prismicio/types-internal"; + +import { dedent } from "../lib/string"; +import * as sveltekit from "./sveltekit.templates"; + +// SvelteKit 2 has no #lib, so it imports from $lib without file extensions. +function useLib(code: string): string { + return code.replace(/#lib\/(\w+)(?:\/index)?\.\w+/g, "$lib/$1"); +} + +// SvelteKit 2 has no $app/env. +export function prismicIOFileTemplate({ typescript }: { typescript: boolean }): string { + return sveltekit + .prismicIOFileTemplate({ typescript }) + .replace( + "{ PUBLIC_PRISMIC_ENVIRONMENT } from '$app/env/public'", + "{ env } from '$env/dynamic/public'", + ) + .replace("PUBLIC_PRISMIC_ENVIRONMENT ||", "env.PUBLIC_PRISMIC_ENVIRONMENT ||"); +} + +// @prismicio/svelte v2 passes slices through a slot. +export function sliceSimulatorPageTemplate(): string { + return dedent` + + + + + + `; +} + +export function previewAPIRouteTemplate({ typescript }: { typescript: boolean }): string { + return useLib(sveltekit.previewAPIRouteTemplate({ typescript })); +} + +export function layoutServerTemplate(): string { + return useLib(sveltekit.layoutServerTemplate({ extension: "js" })); +} + +export function pageTemplate({ typescript }: { typescript: boolean }): string { + return useLib(sveltekit.pageTemplate({ typescript })); +} + +export function pageServerTemplate({ + model, + typescript, +}: { + model: DynamicCustomTypeModel; + typescript: boolean; +}): string { + return useLib(sveltekit.pageServerTemplate({ model, typescript })); +} diff --git a/src/adapters/sveltekit2.ts b/src/adapters/sveltekit2.ts new file mode 100644 index 00000000..4f485b93 --- /dev/null +++ b/src/adapters/sveltekit2.ts @@ -0,0 +1,160 @@ +import { readFile } from "node:fs/promises"; + +import type { DynamicCustomTypeModel } from "@prismicio/types-internal"; + +import { checkSourceContains, getJsFileExtension, type PageFile, writeFileIfMissing } from "."; +import { exists, writeFileRecursive } from "../lib/file"; +import { addDependencies, getNpmPackageVersion } from "../lib/packageJson"; +import { dedent } from "../lib/string"; +import { checkIsTypeScriptProject, findProjectRoot } from "../project"; +import { deleteStarterHomePage, findSourceFile, SvelteKitAdapter } from "./sveltekit"; +import { rootLayoutTemplate } from "./sveltekit.templates"; +import { + layoutServerTemplate, + pageServerTemplate, + pageTemplate, + previewAPIRouteTemplate, + prismicIOFileTemplate, + sliceSimulatorPageTemplate, +} from "./sveltekit2.templates"; + +// SvelteKit 2 projects use @prismicio/svelte v2. Delete this file and sveltekit2.templates.ts +// when SvelteKit 2 support ends. +export class SvelteKit2Adapter extends SvelteKitAdapter { + async setupProject(): Promise { + await addDependencies({ + "@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`, + "@prismicio/svelte": "^2", + }); + + const projectRoot = await findProjectRoot(); + const extension = await getJsFileExtension(); + const typescript = await checkIsTypeScriptProject(); + + await writeFileIfMissing( + new URL(`src/lib/prismicio.${extension}`, projectRoot), + prismicIOFileTemplate({ typescript }), + ); + await writeFileIfMissing( + new URL("src/routes/slice-simulator/+page.svelte", projectRoot), + sliceSimulatorPageTemplate(), + ); + await writeFileIfMissing( + new URL(`src/params/preview.${extension}`, projectRoot), + dedent` + export function match(param) { + return param === 'preview'; + } + `, + ); + await writeFileIfMissing( + new URL(`src/routes/api/preview/+server.${extension}`, projectRoot), + previewAPIRouteTemplate({ typescript }), + ); + await writeFileIfMissing( + new URL("src/routes/[[preview=preview]]/README.md", projectRoot), + dedent` + This directory adds support for optional \`/preview\` routes. Do not remove this directory. + + All routes within this directory will be served using the following URLs: + + - \`/example-route\` (prerendered) + - \`/preview/example-route\` (server-rendered) + + See for more information. + `, + ); + const serverLayout = await findSourceFile("src/routes/+layout.server"); + if (!serverLayout) { + await writeFileRecursive( + new URL(`src/routes/+layout.server.${extension}`, projectRoot), + layoutServerTemplate(), + ); + } + // The generated layout reads repositoryName from the server layout. When an existing + // server layout does not return it, the setup instructions cover both files instead. + const returnsRepositoryName = + !serverLayout || + (await readFile(new URL(serverLayout, projectRoot), "utf8")).includes("repositoryName"); + if (returnsRepositoryName) { + await writeFileIfMissing( + new URL("src/routes/+layout.svelte", projectRoot), + rootLayoutTemplate(), + ); + } + await deleteStarterHomePage(); + } + + async getSetupInstructions(): Promise { + const projectRoot = await findProjectRoot(); + + // The layout reads repositoryName from the server layout's data. + const serverLayoutPath = + (await findSourceFile("src/routes/+layout.server")) ?? + `src/routes/+layout.server.${await getJsFileExtension()}`; + const serverLayout = await readFile(new URL(serverLayoutPath, projectRoot), "utf8").catch( + () => "", + ); + const previewStep = + !(await checkSourceContains("PrismicPreview")) && + [ + dedent` + Action required: add to your root layout. + + Previews do not work until you do this, and the CLI cannot edit your + layout for you. Make the change now. + + Change the lines marked - and + in src/routes/+layout.svelte: + + + + {@render children()} + + + `, + !serverLayout.includes("repositoryName") && + `Return repositoryName from load in ${serverLayoutPath}, creating the file if needed:\n\n${layoutServerTemplate().replace(/^/gm, " ")}`, + "Run `prismic docs view sveltekit` for details.", + ] + .filter(Boolean) + .join("\n\n"); + + // The Prismic home page lives in [[preview=preview]], so this page also serves / and wins. + const hidesHomePage = + (await exists(new URL("src/routes/+page.svelte", projectRoot))) && + (await exists(new URL("src/routes/[[preview=preview]]/+page.svelte", projectRoot))); + const homePageStep = + hidesHomePage && + dedent` + Action required: delete src/routes/+page.svelte. + + It also serves / and hides your Prismic home page in + src/routes/[[preview=preview]]/+page.svelte. Move anything you need + from it first. + `; + + return [previewStep, homePageStep].filter(Boolean).join("\n\n") || undefined; + } + + protected async getPageFiles( + model: DynamicCustomTypeModel, + routePath: string, + ): Promise { + const routeDirectory = new URL( + `src/routes/[[preview=preview]]/${routePath}/`, + await findProjectRoot(), + ); + const typescript = await checkIsTypeScriptProject(); + return [ + { path: new URL("+page.svelte", routeDirectory), contents: pageTemplate({ typescript }) }, + { + path: new URL(`+page.server.${await getJsFileExtension()}`, routeDirectory), + contents: pageServerTemplate({ model, typescript }), + }, + ]; + } +} diff --git a/test/env-set.test.ts b/test/env-set.test.ts index e80f0cf5..82307411 100644 --- a/test/env-set.test.ts +++ b/test/env-set.test.ts @@ -1,6 +1,6 @@ -import { readFile, writeFile } from "node:fs/promises"; +import { mkdir, readFile, writeFile } from "node:fs/promises"; -import { it } from "./it"; +import { it, useSvelteKit } from "./it"; it("supports --help", async ({ expect, prismic }) => { const { stdout, exitCode } = await prismic("env", ["set", "--help"]); @@ -30,6 +30,21 @@ it("clears the active environment when setting to production", async ({ expect(after).not.toContain("my-stage-env"); }); +it("asks for the Prismic environment variable in a SvelteKit env file", async ({ + expect, + prismic, + project, + repo, +}) => { + await useSvelteKit(project); + await mkdir(new URL("src/", project), { recursive: true }); + await writeFile(new URL("src/env.ts", project), "export const variables = {};\n"); + + const { stdout, exitCode } = await prismic("env", ["set", repo]); + expect(exitCode).toBe(0); + expect(stdout).toContain("Action required: add PUBLIC_PRISMIC_ENVIRONMENT to src/env.ts."); +}); + it("prints setup instructions", async ({ expect, prismic, repo }) => { const { stdout, exitCode } = await prismic("env", ["set", repo]); expect(exitCode).toBe(0); diff --git a/test/gen-page.test.ts b/test/gen-page.test.ts index 14dbb262..9d0adfc3 100644 --- a/test/gen-page.test.ts +++ b/test/gen-page.test.ts @@ -1,7 +1,7 @@ import { mkdir, writeFile } from "node:fs/promises"; import { sep } from "node:path"; -import { buildCustomType, it, writeLocalCustomType } from "./it"; +import { buildCustomType, it, useSvelteKit, writeLocalCustomType } from "./it"; it("supports --help", async ({ expect, prismic }) => { const { stdout, stderr, exitCode } = await prismic("gen", ["page", "--help"]); @@ -22,6 +22,32 @@ it("writes a missing page", async ({ expect, project, prismic }) => { }); }); +it("writes SvelteKit 3 pages that import from #lib", async ({ expect, project, prismic }) => { + await useSvelteKit(project); + const customType = buildCustomType({ format: "page", repeatable: false }); + await writeLocalCustomType(project, customType); + + const { stderr, exitCode } = await prismic("gen", ["page", customType.id]); + expect(exitCode, stderr).toBe(0); + await expect(project).toHaveFile( + `src/routes/[[preview=preview]]/${customType.id.toLowerCase()}/+page.server.js`, + { contains: 'import { createClient } from "#lib/prismicio.js";' }, + ); +}); + +it("writes SvelteKit 2 pages that import from $lib", async ({ expect, project, prismic }) => { + await useSvelteKit(project, "^2.27.0"); + const customType = buildCustomType({ format: "page", repeatable: false }); + await writeLocalCustomType(project, customType); + + const { stderr, exitCode } = await prismic("gen", ["page", customType.id]); + expect(exitCode, stderr).toBe(0); + await expect(project).toHaveFile( + `src/routes/[[preview=preview]]/${customType.id.toLowerCase()}/+page.server.js`, + { contains: 'import { createClient } from "$lib/prismicio";' }, + ); +}); + it("prints the code for an existing page", async ({ expect, project, prismic }) => { const customType = buildCustomType({ format: "page", repeatable: false }); await writeLocalCustomType(project, customType); diff --git a/test/gen-setup.test.ts b/test/gen-setup.test.ts index 6962757f..3fb50ac6 100644 --- a/test/gen-setup.test.ts +++ b/test/gen-setup.test.ts @@ -36,23 +36,6 @@ it("skips existing files", { timeout: 30_000 }, async ({ expect, project, prismi await expect(project).toHaveFile("prismicio.js", { contains: "// custom client file" }); }); -it( - "generates valid script tags for SvelteKit", - { timeout: 30_000 }, - async ({ expect, project, prismic }) => { - // A SvelteKit project generates a file with a ", not the bundler-escaped "<\/script>". - await expect(project).toHaveFile("src/routes/slice-simulator/+page.svelte", { - contains: "", - }); - }, -); - it("skips installation with --no-install", async ({ expect, project, prismic }) => { const { stderr, exitCode, stdout } = await prismic("gen", ["setup", "--no-install"]); expect(exitCode, stderr).toBe(0); @@ -166,7 +149,104 @@ it( const layout = await readFile(new URL("src/routes/+layout.svelte", project), "utf8"); expect(layout).toContain("const { data, children } = $props();"); expect(layout).toContain(""); - expect(layout).not.toContain("$lib/prismicio"); + expect(layout).not.toContain("#lib/prismicio"); + }, +); + +it( + "generates a slice simulator page with a snippet", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project); + + const { stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + const page = await readFile( + new URL("src/routes/slice-simulator/+page.svelte", project), + "utf8", + ); + // The closing tag must be "", not the bundler-escaped "<\/script>". + expect(page).toContain(""); + expect(page).toContain("import { components } from '#lib/slices/index.js';"); + expect(page).toContain("{#snippet children({ slices })}"); + expect(page).not.toContain("let:slices"); + }, +); + +it( + "declares the Prismic environment variable in a new SvelteKit env file", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project); + + const { stdout, stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + const env = await readFile(new URL("src/env.js", project), "utf8"); + expect(env).toContain("PUBLIC_PRISMIC_ENVIRONMENT: {"); + expect(env).toContain("public: true,"); + const client = await readFile(new URL("src/lib/prismicio.js", project), "utf8"); + expect(client).toContain("import { PUBLIC_PRISMIC_ENVIRONMENT } from '$app/env/public';"); + expect(stdout).not.toContain("PUBLIC_PRISMIC_ENVIRONMENT"); + }, +); + +it( + "asks for the Prismic environment variable in an existing SvelteKit env file", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project); + const env = + 'import { defineEnvVars } from "@sveltejs/kit/env";\n' + + "export const variables = defineEnvVars({ API_KEY: {} });\n"; + await mkdir(new URL("src/", project), { recursive: true }); + await writeFile(new URL("src/env.ts", project), env); + + const { stdout, stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + expect(await readFile(new URL("src/env.ts", project), "utf8")).toBe(env); + await expect(project).not.toHaveFile("src/env.js"); + expect(stdout).toContain("Action required: add PUBLIC_PRISMIC_ENVIRONMENT to src/env.ts."); + expect(stdout).toContain(" PUBLIC_PRISMIC_ENVIRONMENT: {"); + }, +); + +it( + "does not ask for the Prismic environment variable when the env file declares it", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project); + await mkdir(new URL("src/", project), { recursive: true }); + await writeFile( + new URL("src/env.js", project), + "export const variables = { PUBLIC_PRISMIC_ENVIRONMENT: { public: true } };\n", + ); + + const { stdout, stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + expect(stdout).not.toContain("PUBLIC_PRISMIC_ENVIRONMENT"); + }, +); + +it( + "sets up a SvelteKit 2 project with @prismicio/svelte v2", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project, "^2.27.0"); + + const { stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + const packageJson = JSON.parse(await readFile(new URL("package.json", project), "utf8")); + expect(packageJson.dependencies["@prismicio/svelte"]).toBe("^2"); + await expect(project).toHaveFile("src/lib/prismicio.js", { + contains: "import { env } from '$env/dynamic/public';", + }); + await expect(project).toHaveFile("src/routes/slice-simulator/+page.svelte", { + contains: "", + }); + await expect(project).toHaveFile("src/routes/+layout.server.js", { + contains: 'import { repositoryName } from "$lib/prismicio";', + }); + await expect(project).not.toHaveFile("src/env.js"); }, ); @@ -179,7 +259,7 @@ it( const { stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); expect(exitCode, stderr).toBe(0); const serverLayout = await readFile(new URL("src/routes/+layout.server.js", project), "utf8"); - expect(serverLayout).toContain('import { repositoryName } from "$lib/prismicio";'); + expect(serverLayout).toContain('import { repositoryName } from "#lib/prismicio.js";'); expect(serverLayout).toContain('export const prerender = "auto";'); expect(serverLayout).toContain("return { repositoryName };"); }, @@ -269,7 +349,7 @@ it( await writeFile(new URL("src/routes/+layout.svelte", project), "{@render children()}"); await writeFile( new URL("src/routes/+layout.server.js", project), - 'import { repositoryName } from "$lib/prismicio";\nexport const load = () => ({ repositoryName });', + 'import { repositoryName } from "#lib/prismicio.js";\nexport const load = () => ({ repositoryName });', ); const { stdout, stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); @@ -567,3 +647,67 @@ it( expect(stdout).not.toContain("delete src/routes/+page.svelte"); }, ); + +// SvelteKit 2 projects use their own adapter, which copies this setup logic. + +it( + "deletes the SvelteKit starter home page in a SvelteKit 2 project", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project, "^2.27.0"); + await mkdir(new URL("src/routes/", project), { recursive: true }); + await writeFile(new URL("src/routes/+page.svelte", project), SVELTEKIT_STARTER_HOME_PAGE); + + const { stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + await expect(project).not.toHaveFile("src/routes/+page.svelte"); + }, +); + +it( + "asks to delete a changed home page in a SvelteKit 2 project", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project, "^2.27.0"); + await mkdir(new URL("src/routes/[[preview=preview]]/", project), { recursive: true }); + await writeFile(new URL("src/routes/+page.svelte", project), "

My site

\n"); + await writeFile(new URL("src/routes/[[preview=preview]]/+page.svelte", project), ""); + + const { stdout, stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + expect(stdout).toContain("Action required: delete src/routes/+page.svelte."); + }, +); + +it( + "does not generate a SvelteKit 2 layout next to a server layout without the repository name", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project, "^2.27.0"); + await mkdir(new URL("src/routes/", project), { recursive: true }); + await writeFile( + new URL("src/routes/+layout.server.js", project), + 'export const load = () => ({ user: "x" });', + ); + + const { stdout, stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + await expect(project).not.toHaveFile("src/routes/+layout.svelte"); + expect(stdout).toContain("Return repositoryName from load in src/routes/+layout.server.js"); + expect(stdout).toContain('import { repositoryName } from "$lib/prismicio";'); + }, +); + +it( + "does not ask for the Prismic environment variable in a SvelteKit 2 project with its own src/env", + { timeout: 30_000 }, + async ({ expect, project, prismic }) => { + await useSvelteKit(project, "^2.27.0"); + await mkdir(new URL("src/", project), { recursive: true }); + await writeFile(new URL("src/env.ts", project), "export const apiKey = process.env.API_KEY;\n"); + + const { stdout, stderr, exitCode } = await prismic("gen", ["setup", "--no-install"]); + expect(exitCode, stderr).toBe(0); + expect(stdout).not.toContain("PUBLIC_PRISMIC_ENVIRONMENT"); + }, +); diff --git a/test/it.ts b/test/it.ts index 31918bbc..27f58e7e 100644 --- a/test/it.ts +++ b/test/it.ts @@ -196,12 +196,12 @@ export const it = test.extend({ }, }); -export async function useSvelteKit(project: URL): Promise { +export async function useSvelteKit(project: URL, kitVersion = "latest"): Promise { await rm(new URL("node_modules/next/", project), { recursive: true, force: true }); await rm(new URL("app/", project), { recursive: true, force: true }); await writeFile( new URL("package.json", project), - JSON.stringify({ dependencies: { "@sveltejs/kit": "latest", svelte: "latest" } }), + JSON.stringify({ dependencies: { "@sveltejs/kit": kitVersion, svelte: "latest" } }), ); }