diff --git a/src/adapters/nextjs.ts b/src/adapters/nextjs.ts index 08cce10..c21d83b 100644 --- a/src/adapters/nextjs.ts +++ b/src/adapters/nextjs.ts @@ -6,11 +6,13 @@ import { relative } from "node:path"; import { fileURLToPath } from "node:url"; import { Adapter } from "."; +import { getHost, getToken } from "../auth"; +import { addPreview, getPreviews, getSimulatorUrl, setSimulatorUrl } from "../clients/core"; import { exists, writeFileRecursive } from "../lib/file"; import { addDependencies, findPackageJson, getNpmPackageVersion } from "../lib/packageJson"; import { dedent } from "../lib/string"; import { appendTrailingSlash } from "../lib/url"; -import { buildRoutePath } from "../project"; +import { buildRoutePath, getRepositoryName } from "../project"; import { checkIsTypeScriptProject, findProjectRoot } from "../project"; import { pageTemplate, @@ -38,7 +40,24 @@ export class NextJsAdapter extends Adapter { await createRevalidateRoute(); } - onProjectInitialized(): void {} + async onProjectInitialized(): Promise { + const repo = await getRepositoryName(); + const token = await getToken(); + const host = await getHost(); + + const simulatorUrl = await getSimulatorUrl({ repo, token, host }); + if (!simulatorUrl) { + await setSimulatorUrl("http://localhost:3000/slice-simulator", { repo, token, host }); + } + + const previews = await getPreviews({ repo, token, host }); + if (previews.length === 0) { + await addPreview( + { name: "Development", websiteURL: "http://localhost:3000", resolverPath: "/api/preview" }, + { repo, token, host }, + ); + } + } async onSliceCreated(model: SharedSlice, library: URL): Promise { const sliceDirectoryName = pascalCase(model.name); @@ -102,7 +121,7 @@ export class NextJsAdapter extends Adapter { async getDefaultCustomTypeLibrary(): Promise { const projectRoot = await findProjectRoot(); - const defaultCustomTypeLibrary = new URL("customtypes/", projectRoot) + const defaultCustomTypeLibrary = new URL("customtypes/", projectRoot); return defaultCustomTypeLibrary; } } diff --git a/src/adapters/nuxt.ts b/src/adapters/nuxt.ts index ed0f1b9..7d358f4 100644 --- a/src/adapters/nuxt.ts +++ b/src/adapters/nuxt.ts @@ -7,11 +7,13 @@ import { relative } from "node:path"; import { fileURLToPath } from "node:url"; import { Adapter } from "."; +import { getHost, getToken } from "../auth"; +import { addPreview, getPreviews, getSimulatorUrl, setSimulatorUrl } from "../clients/core"; import { exists, writeFileRecursive } from "../lib/file"; import { addDependencies, getNpmPackageVersion } from "../lib/packageJson"; import { dedent } from "../lib/string"; import { appendTrailingSlash } from "../lib/url"; -import { buildRoutePath, readConfig, updateConfig } from "../project"; +import { buildRoutePath, getRepositoryName, readConfig, updateConfig } from "../project"; import { checkIsTypeScriptProject, findProjectRoot } from "../project"; import { pageTemplate, sliceSimulatorPageTemplate, sliceTemplate } from "./nuxt.templates"; @@ -31,7 +33,24 @@ export class NuxtAdapter extends Adapter { await modifySliceLibraryPath(this); } - onProjectInitialized(): void {} + async onProjectInitialized(): Promise { + const repo = await getRepositoryName(); + const token = await getToken(); + const host = await getHost(); + + const simulatorUrl = await getSimulatorUrl({ repo, token, host }); + if (!simulatorUrl) { + await setSimulatorUrl("http://localhost:3000/slice-simulator", { repo, token, host }); + } + + const previews = await getPreviews({ repo, token, host }); + if (previews.length === 0) { + await addPreview( + { name: "Development", websiteURL: "http://localhost:3000", resolverPath: "/preview" }, + { repo, token, host }, + ); + } + } async onSliceCreated(model: SharedSlice, library: URL): Promise { const sliceDirectoryName = pascalCase(model.name); @@ -87,7 +106,7 @@ export class NuxtAdapter extends Adapter { async getDefaultCustomTypeLibrary(): Promise { const projectRoot = await findProjectRoot(); - const defaultCustomTypeLibrary = new URL("customtypes/", projectRoot) + const defaultCustomTypeLibrary = new URL("customtypes/", projectRoot); return defaultCustomTypeLibrary; } } diff --git a/src/adapters/sveltekit.ts b/src/adapters/sveltekit.ts index 76a6c62..d200626 100644 --- a/src/adapters/sveltekit.ts +++ b/src/adapters/sveltekit.ts @@ -8,11 +8,13 @@ import { relative } from "node:path"; import { fileURLToPath } from "node:url"; import { Adapter } from "."; +import { getHost, getToken } from "../auth"; +import { addPreview, getPreviews, getSimulatorUrl, setSimulatorUrl } from "../clients/core"; import { exists, writeFileRecursive } from "../lib/file"; import { addDependencies, findPackageJson, getNpmPackageVersion } from "../lib/packageJson"; import { dedent } from "../lib/string"; import { appendTrailingSlash } from "../lib/url"; -import { buildRoutePath } from "../project"; +import { buildRoutePath, getRepositoryName } from "../project"; import { checkIsTypeScriptProject, findProjectRoot } from "../project"; import { pageServerTemplate, @@ -42,7 +44,24 @@ export class SvelteKitAdapter extends Adapter { await modifyViteConfig(); } - onProjectInitialized(): void {} + async onProjectInitialized(): Promise { + const repo = await getRepositoryName(); + const token = await getToken(); + const host = await getHost(); + + const simulatorUrl = await getSimulatorUrl({ repo, token, host }); + if (!simulatorUrl) { + await setSimulatorUrl("http://localhost:5173/slice-simulator", { repo, token, host }); + } + + const previews = await getPreviews({ repo, token, host }); + if (previews.length === 0) { + await addPreview( + { name: "Development", websiteURL: "http://localhost:5173", resolverPath: "/api/preview" }, + { repo, token, host }, + ); + } + } async onSliceCreated(model: SharedSlice, library: URL): Promise { const sliceDirectoryName = pascalCase(model.name); @@ -103,7 +122,7 @@ export class SvelteKitAdapter extends Adapter { async getDefaultCustomTypeLibrary(): Promise { const projectRoot = await findProjectRoot(); - const defaultCustomTypeLibrary = new URL("customtypes/", projectRoot) + const defaultCustomTypeLibrary = new URL("customtypes/", projectRoot); return defaultCustomTypeLibrary; } } diff --git a/src/commands/init.ts b/src/commands/init.ts index 315a8bb..3c0d05c 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -96,7 +96,7 @@ export default createCommand(config, async ({ values }) => { } } - let repo = explicitRepo ?? legacySliceMachineConfig?.repositoryName; + let repo = (explicitRepo ?? legacySliceMachineConfig?.repositoryName)?.toLowerCase(); if (repo) { const hasRepoAccess = profile.repositories.some((repository) => repository.domain === repo); if (!hasRepoAccess) { diff --git a/test/init.test.ts b/test/init.test.ts index 7b137e6..78a4efb 100644 --- a/test/init.test.ts +++ b/test/init.test.ts @@ -1,6 +1,15 @@ import { access, readFile, rm, writeFile } from "node:fs/promises"; +import { onTestFinished } from "vitest"; import { captureOutput, it } from "./it"; +import { + addPreview, + createRepository, + deleteRepository, + getPreviews, + getRepository, + setSimulatorUrl, +} from "./prismic"; it("supports --help", async ({ expect, prismic }) => { const { stdout, exitCode } = await prismic("init", ["--help"]); @@ -18,16 +27,63 @@ it("creates a repo if --repo is not provided and no legacy config exists", async expect, project, prismic, + token, + host, + password, }) => { await rm(new URL("prismic.config.json", project)); const { exitCode, stdout } = await prismic("init"); + const createdRepositoryMatch = stdout.match(/^Created repository: ([a-z0-9-]+)$/m); + const name = createdRepositoryMatch?.[1]; + if (!name) throw new Error(`Could not find created repository name in output:\n${stdout}`); + onTestFinished(() => deleteRepository(name, { token, password, host })); + expect(exitCode).toBe(0); expect(stdout).toContain("Created repository:"); expect(stdout).toContain("Initialized Prismic for repository"); const configRaw = await readFile(new URL("prismic.config.json", project), "utf-8"); const config = JSON.parse(configRaw); - expect(config.repositoryName).toMatch(/^[a-f0-9]{8}$/); + expect(config.repositoryName).toBe(name); + + const repository = await getRepository({ repo: name, token, host }); + expect(repository.simulatorUrl).toBe("http://localhost:3000/slice-simulator"); + + const previews = await getPreviews({ repo: name, token, host }); + const dev = previews.find((p) => p.url === "http://localhost:3000/api/preview"); + expect(dev?.label).toBe("Development"); +}, 60_000); + +it("preserves existing preview config", async ({ + expect, + project, + prismic, + token, + password, + host, +}) => { + const rawName = `CLI-Test-${crypto.randomUUID().slice(0, 8)}`; + const name = rawName.toLowerCase(); + onTestFinished(() => deleteRepository(name, { token, password, host })); + await createRepository(name, { token, host }); + + const presetSimulator = "https://staging.example.com/slice-simulator"; + await setSimulatorUrl(presetSimulator, { repo: name, token, host }); + await addPreview("https://staging.example.com/api/preview", "Staging", { + repo: name, + token, + host, + }); + + await rm(new URL("prismic.config.json", project)); + const { exitCode } = await prismic("init", ["--repo", rawName]); + expect(exitCode).toBe(0); + + const repository = await getRepository({ repo: name, token, host }); + expect(repository.simulatorUrl).toBe(presetSimulator); + + const previews = await getPreviews({ repo: name, token, host }); + expect(previews.map((p) => p.label)).toEqual(["Staging"]); }, 60_000); it("initializes a project with --repo when logged in", async ({ diff --git a/test/prismic.ts b/test/prismic.ts index 941baac..290a2d1 100644 --- a/test/prismic.ts +++ b/test/prismic.ts @@ -353,6 +353,20 @@ export async function deleteWriteToken(tokenValue: string, config: RepoConfig): throw new Error(`Failed to delete write token: ${res.status} ${await res.text()}`); } +export async function setSimulatorUrl(simulatorUrl: string, config: RepoConfig): Promise { + const host = config.host ?? DEFAULT_HOST; + const url = new URL("core/repository", `https://${config.repo}.${host}/`); + const res = await fetch(url, { + method: "PATCH", + headers: { + "Content-Type": "application/json", + Cookie: `prismic-auth=${config.token}`, + }, + body: JSON.stringify({ simulator_url: simulatorUrl }), + }); + if (!res.ok) throw new Error(`Failed to set simulator URL: ${res.status} ${await res.text()}`); +} + export async function getRepository( config: RepoConfig, ): Promise<{ name: string; framework: string; simulatorUrl?: string }> {