prismic 0.0.0-canary.169f5fa

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.
@@ -0,0 +1,241 @@
1
+ import type { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
2
+
3
+ import { loadFile } from "magicast";
4
+ import { mkdir, writeFile } from "node:fs/promises";
5
+ import { createRequire } from "node:module";
6
+
7
+ import type { Framework } from ".";
8
+
9
+ import { FrameworkAdapter } from ".";
10
+ import { exists } from "../lib/file";
11
+ import { getNpmPackageVersion } from "../lib/packageJson";
12
+ import { dedent } from "../lib/string";
13
+ import {
14
+ previewAPIRouteTemplate,
15
+ prismicIOFileTemplate,
16
+ rootLayoutTemplate,
17
+ sliceSimulatorPageTemplate,
18
+ sliceTemplate,
19
+ } from "./sveltekit.templates";
20
+
21
+ export class SvelteKitFramework extends FrameworkAdapter {
22
+ readonly id: Framework = "sveltekit";
23
+
24
+ async getDependencies(): Promise<Record<string, string>> {
25
+ return {
26
+ "@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`,
27
+ "@prismicio/svelte": `^${await getNpmPackageVersion("@prismicio/svelte")}`,
28
+ };
29
+ }
30
+
31
+ async initProject(): Promise<void> {
32
+ await super.initProject();
33
+
34
+ await this.#createPrismicIOFile();
35
+ await this.#createSliceSimulatorPage();
36
+ await this.#createPreviewRouteMatcher();
37
+ await this.#createPreviewAPIRoute();
38
+ await this.#createPreviewRouteDirectory();
39
+ await this.#createRootLayoutServerFile();
40
+ await this.#createRootLayoutFile();
41
+ await this.#modifyViteConfig();
42
+ }
43
+
44
+ async createSliceComponent(
45
+ model: SharedSlice,
46
+ sliceDirectory: URL,
47
+ ): Promise<{ componentPath: URL }> {
48
+ const componentPath = new URL("index.svelte", sliceDirectory);
49
+ const contents = sliceTemplate({
50
+ name: model.name,
51
+ typescript: await this.checkIsTypeScriptProject(),
52
+ version: await this.#getSvelteMajor(),
53
+ });
54
+ await writeFile(componentPath, contents);
55
+ return { componentPath };
56
+ }
57
+
58
+ getSliceImportPath(relativeDirectory: string): string {
59
+ return `./${relativeDirectory}/index.svelte`;
60
+ }
61
+
62
+ async getDefaultSliceLibraryPath(projectRoot: URL): Promise<URL> {
63
+ return new URL("src/lib/slices/", projectRoot);
64
+ }
65
+
66
+ async getClientFilePath(): Promise<string | null> {
67
+ return "src/lib/prismicio.ts";
68
+ }
69
+
70
+ async getSlicesDirectoryPath(): Promise<string> {
71
+ return "src/lib/slices/";
72
+ }
73
+
74
+ getSliceComponentExtensions(): string[] {
75
+ return [".svelte"];
76
+ }
77
+
78
+ async getRoutePath(route: string): Promise<{ path: string; extensions: string[] } | null> {
79
+ switch (route) {
80
+ case "/slice-simulator":
81
+ return { path: "src/routes/slice-simulator/+page", extensions: [".svelte"] };
82
+ case "/api/preview":
83
+ return { path: "src/routes/api/preview/+server", extensions: [".ts", ".js"] };
84
+ default:
85
+ return null;
86
+ }
87
+ }
88
+
89
+ async #createPrismicIOFile(): Promise<void> {
90
+ const extension = await this.getJsFileExtension();
91
+ const projectRoot = await this.getProjectRoot();
92
+ const filePath = new URL(`src/lib/prismicio.${extension}`, projectRoot);
93
+
94
+ if (await exists(filePath)) {
95
+ return;
96
+ }
97
+
98
+ const typescript = await this.checkIsTypeScriptProject();
99
+ const contents = prismicIOFileTemplate({ typescript });
100
+ await mkdir(new URL(".", filePath), { recursive: true });
101
+ await writeFile(filePath, contents);
102
+ }
103
+
104
+ async #createSliceSimulatorPage(): Promise<void> {
105
+ const projectRoot = await this.getProjectRoot();
106
+ const filePath = new URL("src/routes/slice-simulator/+page.svelte", projectRoot);
107
+
108
+ if (await exists(filePath)) {
109
+ return;
110
+ }
111
+
112
+ const contents = sliceSimulatorPageTemplate({
113
+ version: await this.#getSvelteMajor(),
114
+ });
115
+ await mkdir(new URL(".", filePath), { recursive: true });
116
+ await writeFile(filePath, contents);
117
+ }
118
+
119
+ async #createPreviewRouteMatcher(): Promise<void> {
120
+ const extension = await this.getJsFileExtension();
121
+ const projectRoot = await this.getProjectRoot();
122
+ const filePath = new URL(`src/params/preview.${extension}`, projectRoot);
123
+
124
+ if (await exists(filePath)) {
125
+ return;
126
+ }
127
+
128
+ const contents = dedent`
129
+ export function match(param) {
130
+ return param === 'preview';
131
+ }
132
+ `;
133
+ await mkdir(new URL(".", filePath), { recursive: true });
134
+ await writeFile(filePath, contents);
135
+ }
136
+
137
+ async #createPreviewAPIRoute(): Promise<void> {
138
+ const extension = await this.getJsFileExtension();
139
+ const projectRoot = await this.getProjectRoot();
140
+ const filePath = new URL(`src/routes/api/preview/+server.${extension}`, projectRoot);
141
+
142
+ if (await exists(filePath)) {
143
+ return;
144
+ }
145
+
146
+ const typescript = await this.checkIsTypeScriptProject();
147
+ const contents = previewAPIRouteTemplate({ typescript });
148
+ await mkdir(new URL(".", filePath), { recursive: true });
149
+ await writeFile(filePath, contents);
150
+ }
151
+
152
+ async #createPreviewRouteDirectory(): Promise<void> {
153
+ const projectRoot = await this.getProjectRoot();
154
+ const filePath = new URL("src/routes/[[preview=preview]]/README.md", projectRoot);
155
+
156
+ if (await exists(filePath)) {
157
+ return;
158
+ }
159
+
160
+ const contents = dedent`
161
+ This directory adds support for optional \`/preview\` routes. Do not remove this directory.
162
+
163
+ All routes within this directory will be served using the following URLs:
164
+
165
+ - \`/example-route\` (prerendered)
166
+ - \`/preview/example-route\` (server-rendered)
167
+
168
+ See <https://prismic.io/docs/svelte-preview> for more information.
169
+ `;
170
+ await mkdir(new URL(".", filePath), { recursive: true });
171
+ await writeFile(filePath, contents);
172
+ }
173
+
174
+ async #createRootLayoutServerFile(): Promise<void> {
175
+ const extension = await this.getJsFileExtension();
176
+ const projectRoot = await this.getProjectRoot();
177
+ const filePath = new URL(`src/routes/+layout.server.${extension}`, projectRoot);
178
+
179
+ if (await exists(filePath)) {
180
+ return;
181
+ }
182
+
183
+ const contents = dedent`
184
+ export const prerender = "auto";
185
+ `;
186
+ await mkdir(new URL(".", filePath), { recursive: true });
187
+ await writeFile(filePath, contents);
188
+ }
189
+
190
+ async #createRootLayoutFile(): Promise<void> {
191
+ const projectRoot = await this.getProjectRoot();
192
+ const filePath = new URL("src/routes/+layout.svelte", projectRoot);
193
+
194
+ if (await exists(filePath)) {
195
+ return;
196
+ }
197
+
198
+ const contents = rootLayoutTemplate({
199
+ version: await this.#getSvelteMajor(),
200
+ });
201
+ await mkdir(new URL(".", filePath), { recursive: true });
202
+ await writeFile(filePath, contents);
203
+ }
204
+
205
+ async #modifyViteConfig(): Promise<void> {
206
+ const projectRoot = await this.getProjectRoot();
207
+ let configUrl = new URL("vite.config.js", projectRoot);
208
+ if (!(await exists(configUrl))) {
209
+ configUrl = new URL("vite.config.ts", projectRoot);
210
+ }
211
+ if (!(await exists(configUrl))) {
212
+ return;
213
+ }
214
+
215
+ const filepath = configUrl.pathname;
216
+ const mod = await loadFile(filepath);
217
+ if (mod.exports.default.$type !== "function-call") {
218
+ return;
219
+ }
220
+
221
+ const config = mod.exports.default.$args[0];
222
+ config.server ??= {};
223
+ config.server.fs ??= {};
224
+ config.server.fs.allow ??= [];
225
+ if (!config.server.fs.allow.includes("./prismic.config.json")) {
226
+ config.server.fs.allow.push("./prismic.config.json");
227
+ }
228
+
229
+ const contents = mod.generate().code.replace(/\n\s*\n(?=\s*server:)/, "\n");
230
+ await writeFile(configUrl, contents);
231
+ }
232
+
233
+ async #getSvelteMajor(): Promise<number> {
234
+ const projectRoot = await this.getProjectRoot();
235
+ const require = createRequire(new URL("package.json", projectRoot));
236
+ const { version } = require("svelte/package.json");
237
+ const major = Number.parseInt(version.split(".")[0]);
238
+ if (Number.isNaN(major)) return Infinity;
239
+ return major;
240
+ }
241
+ }
package/src/index.ts ADDED
@@ -0,0 +1,146 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { parseArgs } from "node:util";
4
+
5
+ import packageJson from "../package.json" with { type: "json" };
6
+ import { getProfile } from "./clients/user";
7
+ import { init } from "./commands/init";
8
+ import { login } from "./commands/login";
9
+ import { logout } from "./commands/logout";
10
+ import { sync } from "./commands/sync";
11
+ import { whoami } from "./commands/whoami";
12
+ import { getFramework } from "./frameworks";
13
+ import { getHost, refreshToken } from "./lib/auth";
14
+ import { safeGetRepositoryFromConfig } from "./lib/config";
15
+ import { ForbiddenRequestError, UnauthorizedRequestError } from "./lib/request";
16
+ import {
17
+ initSegment,
18
+ segmentIdentify,
19
+ segmentSetRepository,
20
+ segmentTrackEnd,
21
+ segmentTrackStart,
22
+ } from "./lib/segment";
23
+ import {
24
+ sentryCaptureError,
25
+ sentrySetContext,
26
+ sentrySetTag,
27
+ sentrySetUser,
28
+ setupSentry,
29
+ } from "./lib/sentry";
30
+
31
+ const HELP = `
32
+ Prismic CLI for managing repositories and configurations.
33
+
34
+ USAGE
35
+ prismic <command> [flags]
36
+
37
+ COMMANDS
38
+ init Initialize a Prismic project
39
+ sync Sync types and slices from Prismic
40
+ login Log in to Prismic
41
+ logout Log out of Prismic
42
+ whoami Show the currently logged in user
43
+
44
+ FLAGS
45
+ -v, --version Show CLI version
46
+ -h, --help Show help for command
47
+
48
+ LEARN MORE
49
+ Use \`prismic <command> --help\` for more information about a command.
50
+ `.trim();
51
+
52
+ const UNTRACKED_COMMANDS = new Set(["login", "logout", "whoami", "sync"]);
53
+ const SKIP_REFRESH_COMMANDS = new Set(["login", "logout"]);
54
+
55
+ const {
56
+ positionals,
57
+ values: { version },
58
+ } = parseArgs({
59
+ options: {
60
+ help: { type: "boolean", short: "h" },
61
+ version: { type: "boolean", short: "v" },
62
+ },
63
+ allowPositionals: true,
64
+ strict: false,
65
+ });
66
+
67
+ setupSentry();
68
+ await initSegment();
69
+
70
+ if (version) {
71
+ console.info(packageJson.version);
72
+ } else {
73
+ const command = positionals[0];
74
+
75
+ const repository = await safeGetRepositoryFromConfig();
76
+ if (repository) {
77
+ segmentSetRepository(repository);
78
+ sentrySetTag("repository", repository);
79
+ sentrySetContext("Repository Data", { name: repository });
80
+ }
81
+
82
+ const framework = await getFramework();
83
+ if (framework) {
84
+ sentrySetTag("framework", framework.id);
85
+ }
86
+
87
+ if (!SKIP_REFRESH_COMMANDS.has(command)) {
88
+ // Refreesh the token and identify the user in the background.
89
+ refreshToken()
90
+ .then(async (token) => {
91
+ if (!token) return;
92
+ const host = await getHost();
93
+ const profile = await getProfile({ token, host });
94
+ segmentIdentify({ shortId: profile.shortId, intercomHash: profile.intercomHash });
95
+ sentrySetUser({ id: profile.shortId });
96
+ })
97
+ .catch(() => {});
98
+ }
99
+
100
+ if (!UNTRACKED_COMMANDS.has(command)) {
101
+ segmentTrackStart(command, { repository });
102
+ }
103
+
104
+ try {
105
+ switch (command) {
106
+ case "init":
107
+ await init();
108
+ break;
109
+ case "sync":
110
+ await sync();
111
+ break;
112
+ case "login":
113
+ await login();
114
+ break;
115
+ case "logout":
116
+ await logout();
117
+ break;
118
+ case "whoami":
119
+ await whoami();
120
+ break;
121
+ default: {
122
+ if (command) {
123
+ console.error(`Unknown command: ${command}`);
124
+ process.exitCode = 1;
125
+ }
126
+ console.info(HELP);
127
+ }
128
+ }
129
+
130
+ if (!UNTRACKED_COMMANDS.has(command)) {
131
+ segmentTrackEnd(command, process.exitCode !== 1);
132
+ }
133
+ } catch (error) {
134
+ if (!UNTRACKED_COMMANDS.has(command)) {
135
+ segmentTrackEnd(command, false, error);
136
+ }
137
+ process.exitCode = 1;
138
+
139
+ if (error instanceof UnauthorizedRequestError || error instanceof ForbiddenRequestError) {
140
+ console.error("Not logged in. Run `prismic login` first.");
141
+ } else {
142
+ await sentryCaptureError(error);
143
+ throw error;
144
+ }
145
+ }
146
+ }
@@ -0,0 +1,176 @@
1
+ import { readFile, rm, writeFile } from "node:fs/promises";
2
+ import { createServer } from "node:http";
3
+ import { homedir } from "node:os";
4
+ import { pathToFileURL } from "node:url";
5
+ import * as v from "valibot";
6
+
7
+ import { refreshToken as baseRefreshToken } from "../clients/auth";
8
+ import { env } from "../env";
9
+ import { exists } from "./file";
10
+ import { stringify } from "./json";
11
+ import { appendTrailingSlash } from "./url";
12
+
13
+ const AUTH_FILE_PATH = new URL(".prismic", appendTrailingSlash(pathToFileURL(homedir())));
14
+ const LOGIN_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes
15
+ const PREFERRED_PORT = 5555;
16
+ const LOGIN_SOURCE = "prismic-cli";
17
+
18
+ const AuthFileSchema = v.object({
19
+ token: v.optional(v.pipe(v.string(), v.nonEmpty())),
20
+ host: v.optional(v.pipe(v.string(), v.nonEmpty())),
21
+ });
22
+ type AuthFile = v.InferOutput<typeof AuthFileSchema>;
23
+
24
+ export async function getToken(): Promise<string | undefined> {
25
+ const auth = await readAuthFile();
26
+ return auth?.token;
27
+ }
28
+
29
+ export async function getHost(): Promise<string | undefined> {
30
+ const auth = await readAuthFile();
31
+ return auth?.host;
32
+ }
33
+
34
+ export async function refreshToken(): Promise<string | undefined> {
35
+ const auth = await readAuthFile();
36
+ const token = auth?.token;
37
+ const host = auth?.host;
38
+ if (!token) return;
39
+ const newToken = await baseRefreshToken(token, { host });
40
+ await saveAuthFile({ token: newToken, host });
41
+ return newToken;
42
+ }
43
+
44
+ export async function logout(): Promise<boolean> {
45
+ const authFileExists = await exists(AUTH_FILE_PATH);
46
+ if (!authFileExists) return true;
47
+
48
+ try {
49
+ await rm(AUTH_FILE_PATH, { force: true });
50
+ return true;
51
+ } catch {
52
+ return false;
53
+ }
54
+ }
55
+
56
+ async function readAuthFile(): Promise<AuthFile | undefined> {
57
+ try {
58
+ const contents = await readFile(AUTH_FILE_PATH, "utf-8");
59
+ const json = JSON.parse(contents);
60
+ return v.parse(AuthFileSchema, json);
61
+ } catch {
62
+ return undefined;
63
+ }
64
+ }
65
+
66
+ async function saveAuthFile(auth: AuthFile): Promise<void> {
67
+ await writeFile(AUTH_FILE_PATH, stringify(auth));
68
+ }
69
+
70
+ export async function createLoginSession(options?: {
71
+ onReady?: (url: URL) => void;
72
+ }): Promise<{ email: string }> {
73
+ const host = env.PRISMIC_HOST;
74
+ const corsOrigin = `https://${host}`;
75
+
76
+ return new Promise((resolve, reject) => {
77
+ const server = createServer((req, res) => {
78
+ if (req.method === "OPTIONS") {
79
+ res.writeHead(204, {
80
+ "Access-Control-Allow-Origin": corsOrigin,
81
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
82
+ "Access-Control-Allow-Headers": "Content-Type",
83
+ });
84
+ res.end();
85
+ return;
86
+ }
87
+
88
+ if (req.method === "POST") {
89
+ let body = "";
90
+
91
+ req.on("data", (chunk) => {
92
+ body += chunk.toString();
93
+ });
94
+
95
+ req.on("end", async () => {
96
+ try {
97
+ const { cookies, email } = JSON.parse(body);
98
+
99
+ const cookie: string | undefined = cookies.find((c: string) =>
100
+ c.startsWith("prismic-auth="),
101
+ );
102
+ const token = cookie?.split(";")[0]?.replace(/^prismic-auth=/, "");
103
+
104
+ if (!token) {
105
+ res.writeHead(400, {
106
+ "Access-Control-Allow-Origin": corsOrigin,
107
+ "Content-Type": "application/json",
108
+ });
109
+ res.end(JSON.stringify({ error: "Invalid request" }));
110
+ return;
111
+ }
112
+
113
+ await saveAuthFile({ token, host });
114
+
115
+ res.writeHead(200, {
116
+ "Access-Control-Allow-Origin": corsOrigin,
117
+ "Content-Type": "application/json",
118
+ });
119
+ res.end(JSON.stringify({ success: true }));
120
+
121
+ clearTimeout(timeoutId);
122
+ server.close();
123
+ resolve({ email });
124
+ } catch {
125
+ res.writeHead(400, {
126
+ "Access-Control-Allow-Origin": corsOrigin,
127
+ "Content-Type": "application/json",
128
+ });
129
+ res.end(JSON.stringify({ error: "Invalid request" }));
130
+ }
131
+ });
132
+
133
+ return;
134
+ }
135
+
136
+ res.writeHead(404);
137
+ res.end();
138
+ });
139
+
140
+ const timeoutId = setTimeout(() => {
141
+ server.close();
142
+ reject(new Error("Login timed out. Please try again."));
143
+ }, LOGIN_TIMEOUT_MS);
144
+
145
+ const onListening = async (): Promise<void> => {
146
+ const address = server.address();
147
+ if (!address || typeof address === "string") {
148
+ clearTimeout(timeoutId);
149
+ server.close();
150
+ reject(new Error("Failed to start login server"));
151
+ return;
152
+ }
153
+
154
+ const url = await buildLoginUrl(host, address.port);
155
+ options?.onReady?.(url);
156
+ };
157
+
158
+ server.on("error", (error: NodeJS.ErrnoException) => {
159
+ if (error.code === "EADDRINUSE" && server.listening === false) {
160
+ server.listen(0, "0.0.0.0", onListening);
161
+ } else {
162
+ clearTimeout(timeoutId);
163
+ reject(error);
164
+ }
165
+ });
166
+
167
+ server.listen(PREFERRED_PORT, "0.0.0.0", onListening);
168
+ });
169
+ }
170
+
171
+ async function buildLoginUrl(host: string, port: number): Promise<URL> {
172
+ const url = new URL("dashboard/cli/login", `https://${host}/`);
173
+ url.searchParams.set("source", LOGIN_SOURCE);
174
+ url.searchParams.set("port", port.toString());
175
+ return url;
176
+ }
@@ -0,0 +1,11 @@
1
+ import { exec } from "node:child_process";
2
+
3
+ export function openBrowser(url: URL): void {
4
+ const cmd =
5
+ process.platform === "darwin"
6
+ ? "open"
7
+ : process.platform === "win32"
8
+ ? "start"
9
+ : "xdg-open";
10
+ exec(`${cmd} "${url.toString()}"`);
11
+ }
@@ -0,0 +1,111 @@
1
+ import { writeFile } from "node:fs/promises";
2
+ import { readFile } from "node:fs/promises";
3
+ import { pathToFileURL } from "node:url";
4
+ import * as v from "valibot";
5
+
6
+ import { findUpward } from "./file";
7
+ import { stringify } from "./json";
8
+
9
+ const CONFIG_FILENAME = "prismic.config.json";
10
+
11
+ const ConfigSchema = v.object({
12
+ repositoryName: v.string(),
13
+ apiEndpoint: v.optional(v.pipe(v.string(), v.url())),
14
+ localSliceMachineSimulatorURL: v.optional(v.pipe(v.string(), v.url())),
15
+ libraries: v.optional(v.array(v.string())),
16
+ adapter: v.optional(v.string()),
17
+ labs: v.optional(
18
+ v.object({
19
+ legacySliceUpgrader: v.optional(v.boolean()),
20
+ }),
21
+ ),
22
+ });
23
+ export type Config = v.InferOutput<typeof ConfigSchema>;
24
+
25
+ export type ConfigResult = SuccessfulConfigResult | FailedConfigResult;
26
+ export type SuccessfulConfigResult = { ok: true; config: Config };
27
+ export type FailedConfigResult = {
28
+ ok: false;
29
+ error: InvalidPrismicConfig | MissingPrismicConfig;
30
+ };
31
+ export type UnknownProjectRootConfigResult = {
32
+ ok: false;
33
+ error: UnknownProjectRoot;
34
+ };
35
+
36
+ export async function createConfig(
37
+ config: Config,
38
+ cwd = pathToFileURL(process.cwd()),
39
+ ): Promise<ConfigResult | UnknownProjectRootConfigResult> {
40
+ const result = await findSuggestedConfigPath(cwd);
41
+ if (!result.ok) return result;
42
+ await writeFile(result.path, stringify(config));
43
+ return { ok: true, config };
44
+ }
45
+
46
+ export async function safeGetRepositoryFromConfig(
47
+ cwd = pathToFileURL(process.cwd()),
48
+ ): Promise<string | undefined> {
49
+ const result = await readConfig(cwd);
50
+ if (result.ok) return result.config.repositoryName;
51
+ }
52
+
53
+ export async function readConfig(cwd = pathToFileURL(process.cwd())): Promise<ConfigResult> {
54
+ const findResult = await findConfig(cwd);
55
+ if (!findResult.ok) return findResult;
56
+
57
+ try {
58
+ const contents = await readFile(findResult.path, "utf8");
59
+ const result = v.safeParse(ConfigSchema, JSON.parse(contents));
60
+ if (!result.success) {
61
+ return { ok: false, error: new InvalidPrismicConfig(result.issues) };
62
+ }
63
+ return { ok: true, config: result.output };
64
+ } catch {
65
+ return { ok: false, error: new InvalidPrismicConfig() };
66
+ }
67
+ }
68
+
69
+ export class InvalidPrismicConfig extends Error {
70
+ issues: v.InferIssue<typeof ConfigSchema>[];
71
+
72
+ constructor(issues: v.InferIssue<typeof ConfigSchema>[] = []) {
73
+ super("prismic.config.json is invalid.");
74
+ this.issues = issues;
75
+ }
76
+ }
77
+
78
+ export async function updateConfig(
79
+ config: Partial<Config>,
80
+ cwd = pathToFileURL(process.cwd()),
81
+ ): Promise<ConfigResult> {
82
+ const findResult = await findConfig(cwd);
83
+ if (!findResult.ok) return findResult;
84
+
85
+ const readResult = await readConfig(cwd);
86
+ if (!readResult.ok) return readResult;
87
+
88
+ const updatedConfig = { ...readResult.config, ...config };
89
+ await writeFile(findResult.path, stringify(updatedConfig));
90
+ return { ok: true, config: updatedConfig };
91
+ }
92
+
93
+ async function findConfig(cwd = pathToFileURL(process.cwd())) {
94
+ const path = await findUpward(CONFIG_FILENAME, { start: cwd, stop: "package.json" });
95
+ if (!path) return { ok: false, error: new MissingPrismicConfig() } as const;
96
+ return { ok: true, path } as const;
97
+ }
98
+
99
+ async function findSuggestedConfigPath(cwd = pathToFileURL(process.cwd())) {
100
+ const path = await findUpward("package.json", { start: cwd });
101
+ if (!path) return { ok: false, error: new UnknownProjectRoot() } as const;
102
+ return { ok: true, path: new URL(CONFIG_FILENAME, path) } as const;
103
+ }
104
+
105
+ export class MissingPrismicConfig extends Error {
106
+ message = "Could not find a prismic.config.json file.";
107
+ }
108
+
109
+ export class UnknownProjectRoot extends Error {
110
+ message = "Could not find a package.json file.";
111
+ }
@@ -0,0 +1,3 @@
1
+ export class NotFoundError extends Error {}
2
+ export class ForbiddenError extends Error {}
3
+ export class UnauthorizedError extends Error {}