prismic 0.0.0-pr.28.59bf330
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/LICENSE +202 -0
- package/README.md +69 -0
- package/dist/builders-hKD4IrLX-DsO7BUQw.mjs +97 -0
- package/dist/dist-B11B2hHn.mjs +1 -0
- package/dist/dist-DT8CtumB.mjs +1 -0
- package/dist/framework-CfjEoVk0.mjs +17 -0
- package/dist/index.mjs +2537 -0
- package/dist/nextjs-9z7YrSnS.mjs +312 -0
- package/dist/nuxt-KoJ61G2q.mjs +59 -0
- package/dist/sveltekit-DjXKCG78.mjs +226 -0
- package/package.json +58 -0
- package/src/codegen-types.ts +82 -0
- package/src/codegen.ts +45 -0
- package/src/custom-type-add-field-boolean.ts +185 -0
- package/src/custom-type-add-field-color.ts +168 -0
- package/src/custom-type-add-field-date.ts +171 -0
- package/src/custom-type-add-field-embed.ts +168 -0
- package/src/custom-type-add-field-geo-point.ts +165 -0
- package/src/custom-type-add-field-group.ts +142 -0
- package/src/custom-type-add-field-image.ts +168 -0
- package/src/custom-type-add-field-key-text.ts +168 -0
- package/src/custom-type-add-field-link.ts +191 -0
- package/src/custom-type-add-field-number.ts +200 -0
- package/src/custom-type-add-field-rich-text.ts +192 -0
- package/src/custom-type-add-field-select.ts +174 -0
- package/src/custom-type-add-field-timestamp.ts +171 -0
- package/src/custom-type-add-field-uid.ts +151 -0
- package/src/custom-type-add-field.ts +116 -0
- package/src/custom-type-connect-slice.ts +178 -0
- package/src/custom-type-create.ts +98 -0
- package/src/custom-type-disconnect-slice.ts +134 -0
- package/src/custom-type-list.ts +110 -0
- package/src/custom-type-remove-field.ts +135 -0
- package/src/custom-type-remove.ts +103 -0
- package/src/custom-type-set-name.ts +102 -0
- package/src/custom-type-view.ts +118 -0
- package/src/custom-type.ts +85 -0
- package/src/docs-fetch.ts +146 -0
- package/src/docs-list.ts +131 -0
- package/src/docs.ts +54 -0
- package/src/env.d.ts +12 -0
- package/src/framework/index.ts +399 -0
- package/src/framework/nextjs.templates.ts +426 -0
- package/src/framework/nextjs.ts +216 -0
- package/src/framework/nuxt.templates.ts +74 -0
- package/src/framework/nuxt.ts +250 -0
- package/src/framework/sveltekit.templates.ts +278 -0
- package/src/framework/sveltekit.ts +241 -0
- package/src/index.ts +155 -0
- package/src/init.ts +173 -0
- package/src/lib/auth.ts +200 -0
- package/src/lib/browser.ts +11 -0
- package/src/lib/config.ts +111 -0
- package/src/lib/custom-types-api.ts +385 -0
- package/src/lib/field-path.ts +81 -0
- package/src/lib/file.ts +49 -0
- package/src/lib/json.ts +3 -0
- package/src/lib/packageJson.ts +35 -0
- package/src/lib/profile.ts +39 -0
- package/src/lib/request.ts +116 -0
- package/src/lib/segment.ts +145 -0
- package/src/lib/sentry.ts +63 -0
- package/src/lib/string.ts +10 -0
- package/src/lib/url.ts +31 -0
- package/src/locale-add.ts +116 -0
- package/src/locale-list.ts +107 -0
- package/src/locale-remove.ts +88 -0
- package/src/locale-set-default.ts +131 -0
- package/src/locale.ts +60 -0
- package/src/login.ts +45 -0
- package/src/logout.ts +36 -0
- package/src/page-type-add-field-boolean.ts +179 -0
- package/src/page-type-add-field-color.ts +165 -0
- package/src/page-type-add-field-date.ts +168 -0
- package/src/page-type-add-field-embed.ts +165 -0
- package/src/page-type-add-field-geo-point.ts +162 -0
- package/src/page-type-add-field-group.ts +139 -0
- package/src/page-type-add-field-image.ts +165 -0
- package/src/page-type-add-field-key-text.ts +165 -0
- package/src/page-type-add-field-link.ts +188 -0
- package/src/page-type-add-field-number.ts +197 -0
- package/src/page-type-add-field-rich-text.ts +189 -0
- package/src/page-type-add-field-select.ts +171 -0
- package/src/page-type-add-field-timestamp.ts +168 -0
- package/src/page-type-add-field-uid.ts +148 -0
- package/src/page-type-add-field.ts +116 -0
- package/src/page-type-connect-slice.ts +178 -0
- package/src/page-type-create.ts +128 -0
- package/src/page-type-disconnect-slice.ts +134 -0
- package/src/page-type-list.ts +109 -0
- package/src/page-type-remove-field.ts +135 -0
- package/src/page-type-remove.ts +103 -0
- package/src/page-type-set-name.ts +102 -0
- package/src/page-type-set-repeatable.ts +111 -0
- package/src/page-type-view.ts +118 -0
- package/src/page-type.ts +90 -0
- package/src/preview-add.ts +126 -0
- package/src/preview-get-simulator.ts +104 -0
- package/src/preview-list.ts +106 -0
- package/src/preview-remove-simulator.ts +80 -0
- package/src/preview-remove.ts +109 -0
- package/src/preview-set-name.ts +137 -0
- package/src/preview-set-simulator.ts +116 -0
- package/src/preview.ts +75 -0
- package/src/pull.ts +236 -0
- package/src/push.ts +409 -0
- package/src/repo-create.ts +175 -0
- package/src/repo-get-access.ts +86 -0
- package/src/repo-list.ts +100 -0
- package/src/repo-set-access.ts +100 -0
- package/src/repo-set-name.ts +102 -0
- package/src/repo-view.ts +113 -0
- package/src/repo.ts +70 -0
- package/src/slice-add-field-boolean.ts +219 -0
- package/src/slice-add-field-color.ts +205 -0
- package/src/slice-add-field-date.ts +205 -0
- package/src/slice-add-field-embed.ts +205 -0
- package/src/slice-add-field-geo-point.ts +202 -0
- package/src/slice-add-field-group.ts +170 -0
- package/src/slice-add-field-image.ts +202 -0
- package/src/slice-add-field-key-text.ts +205 -0
- package/src/slice-add-field-link.ts +224 -0
- package/src/slice-add-field-number.ts +205 -0
- package/src/slice-add-field-rich-text.ts +229 -0
- package/src/slice-add-field-select.ts +211 -0
- package/src/slice-add-field-timestamp.ts +205 -0
- package/src/slice-add-field.ts +111 -0
- package/src/slice-add-variation.ts +142 -0
- package/src/slice-create.ts +164 -0
- package/src/slice-list-variations.ts +71 -0
- package/src/slice-list.ts +60 -0
- package/src/slice-remove-field.ts +125 -0
- package/src/slice-remove-variation.ts +113 -0
- package/src/slice-remove.ts +92 -0
- package/src/slice-rename.ts +104 -0
- package/src/slice-set-screenshot.ts +239 -0
- package/src/slice-view.ts +83 -0
- package/src/slice.ts +95 -0
- package/src/status.ts +834 -0
- package/src/sync.ts +259 -0
- package/src/token-create.ts +203 -0
- package/src/token-delete.ts +182 -0
- package/src/token-list.ts +223 -0
- package/src/token-set-name.ts +193 -0
- package/src/token.ts +60 -0
- package/src/webhook-add-header.ts +118 -0
- package/src/webhook-create.ts +152 -0
- package/src/webhook-disable.ts +109 -0
- package/src/webhook-enable.ts +132 -0
- package/src/webhook-list.ts +93 -0
- package/src/webhook-remove-header.ts +117 -0
- package/src/webhook-remove.ts +106 -0
- package/src/webhook-set-triggers.ts +148 -0
- package/src/webhook-status.ts +90 -0
- package/src/webhook-test.ts +106 -0
- package/src/webhook-view.ts +147 -0
- package/src/webhook.ts +95 -0
- package/src/whoami.ts +62 -0
package/src/lib/auth.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { access, 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
|
+
|
|
6
|
+
import { appendTrailingSlash } from "./url";
|
|
7
|
+
|
|
8
|
+
const AUTH_FILE_PATH = new URL(
|
|
9
|
+
".prismic",
|
|
10
|
+
appendTrailingSlash(pathToFileURL(homedir())),
|
|
11
|
+
);
|
|
12
|
+
const DEFAULT_HOST = "https://prismic.io";
|
|
13
|
+
const LOGIN_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes
|
|
14
|
+
const PREFERRED_PORT = 5555;
|
|
15
|
+
|
|
16
|
+
type AuthContents = {
|
|
17
|
+
token?: string;
|
|
18
|
+
base?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function saveToken(
|
|
22
|
+
token: string,
|
|
23
|
+
options?: { base?: string },
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
const contents: AuthContents = { token, base: options?.base };
|
|
26
|
+
await writeFile(AUTH_FILE_PATH, JSON.stringify(contents, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function isAuthenticated(): Promise<boolean> {
|
|
30
|
+
const token = await readToken();
|
|
31
|
+
if (!token) return false;
|
|
32
|
+
|
|
33
|
+
// Verify token is still valid by calling the profile endpoint
|
|
34
|
+
try {
|
|
35
|
+
const host = await readHost();
|
|
36
|
+
host.hostname = `user-service.${host.hostname}`;
|
|
37
|
+
const url = new URL("profile", host);
|
|
38
|
+
|
|
39
|
+
const response = await fetch(url, {
|
|
40
|
+
headers: {
|
|
41
|
+
Accept: "application/json",
|
|
42
|
+
Cookie: `SESSION=fake_session; prismic-auth=${token}`,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
await removeToken();
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function readToken(): Promise<string | undefined> {
|
|
58
|
+
const auth = await readAuthFile();
|
|
59
|
+
return auth?.token;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function readHost(): Promise<URL> {
|
|
63
|
+
try {
|
|
64
|
+
const auth = await readAuthFile();
|
|
65
|
+
if (!auth?.base) return new URL(DEFAULT_HOST);
|
|
66
|
+
return new URL(auth.base);
|
|
67
|
+
} catch {
|
|
68
|
+
return new URL(DEFAULT_HOST);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function createLoginSession(options?: {
|
|
73
|
+
onReady?: (url: URL) => void;
|
|
74
|
+
}): Promise<{ email: string }> {
|
|
75
|
+
const host = await readHost();
|
|
76
|
+
const corsOrigin = host.origin;
|
|
77
|
+
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const server = createServer((req, res) => {
|
|
80
|
+
if (req.method === "OPTIONS") {
|
|
81
|
+
res.writeHead(204, {
|
|
82
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
83
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
84
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
85
|
+
});
|
|
86
|
+
res.end();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (req.method === "POST") {
|
|
91
|
+
let body = "";
|
|
92
|
+
|
|
93
|
+
req.on("data", (chunk) => {
|
|
94
|
+
body += chunk.toString();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
req.on("end", async () => {
|
|
98
|
+
try {
|
|
99
|
+
const { cookies, email } = JSON.parse(body);
|
|
100
|
+
|
|
101
|
+
const cookie: string | undefined = cookies.find((c: string) =>
|
|
102
|
+
c.startsWith("prismic-auth="),
|
|
103
|
+
);
|
|
104
|
+
const token = cookie?.split(";")[0]?.replace(/^prismic-auth=/, "");
|
|
105
|
+
|
|
106
|
+
if (!token) {
|
|
107
|
+
res.writeHead(400, {
|
|
108
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
});
|
|
111
|
+
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await saveToken(token);
|
|
116
|
+
|
|
117
|
+
res.writeHead(200, {
|
|
118
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
});
|
|
121
|
+
res.end(JSON.stringify({ success: true }));
|
|
122
|
+
|
|
123
|
+
clearTimeout(timeoutId);
|
|
124
|
+
server.close();
|
|
125
|
+
resolve({ email });
|
|
126
|
+
} catch {
|
|
127
|
+
res.writeHead(400, {
|
|
128
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
});
|
|
131
|
+
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
res.writeHead(404);
|
|
139
|
+
res.end();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const timeoutId = setTimeout(() => {
|
|
143
|
+
server.close();
|
|
144
|
+
reject(new Error("Login timed out. Please try again."));
|
|
145
|
+
}, LOGIN_TIMEOUT_MS);
|
|
146
|
+
|
|
147
|
+
const onListening = (): void => {
|
|
148
|
+
const address = server.address();
|
|
149
|
+
if (!address || typeof address === "string") {
|
|
150
|
+
clearTimeout(timeoutId);
|
|
151
|
+
server.close();
|
|
152
|
+
reject(new Error("Failed to start login server"));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const url = buildLoginUrl(host, address.port);
|
|
157
|
+
options?.onReady?.(url);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
server.on("error", (error: NodeJS.ErrnoException) => {
|
|
161
|
+
if (error.code === "EADDRINUSE" && server.listening === false) {
|
|
162
|
+
server.listen(0, "0.0.0.0", onListening);
|
|
163
|
+
} else {
|
|
164
|
+
clearTimeout(timeoutId);
|
|
165
|
+
reject(error);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
server.listen(PREFERRED_PORT, "0.0.0.0", onListening);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildLoginUrl(host: URL, port: number): URL {
|
|
174
|
+
const url = new URL("/dashboard/cli/login", host);
|
|
175
|
+
url.searchParams.set("source", "prismic-cli");
|
|
176
|
+
url.searchParams.set("port", port.toString());
|
|
177
|
+
return url;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function readAuthFile(): Promise<AuthContents | undefined> {
|
|
181
|
+
try {
|
|
182
|
+
const contents = await readFile(AUTH_FILE_PATH, "utf-8");
|
|
183
|
+
return JSON.parse(contents);
|
|
184
|
+
} catch {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function removeToken(): Promise<boolean> {
|
|
190
|
+
try {
|
|
191
|
+
await access(AUTH_FILE_PATH);
|
|
192
|
+
} catch {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const auth = await readAuthFile();
|
|
197
|
+
if (!auth) return false;
|
|
198
|
+
await rm(AUTH_FILE_PATH);
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
@@ -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
|
+
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,385 @@
|
|
|
1
|
+
import type { CustomType, SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
2
|
+
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
|
|
5
|
+
import { readHost, readToken } from "./auth";
|
|
6
|
+
|
|
7
|
+
const SharedSliceSchema = v.object({
|
|
8
|
+
id: v.string(),
|
|
9
|
+
type: v.literal("SharedSlice"),
|
|
10
|
+
name: v.string(),
|
|
11
|
+
description: v.optional(v.string()),
|
|
12
|
+
variations: v.array(
|
|
13
|
+
v.object({
|
|
14
|
+
id: v.string(),
|
|
15
|
+
name: v.string(),
|
|
16
|
+
description: v.optional(v.string()),
|
|
17
|
+
docURL: v.optional(v.string()),
|
|
18
|
+
version: v.optional(v.string()),
|
|
19
|
+
imageUrl: v.optional(v.string()),
|
|
20
|
+
primary: v.optional(v.record(v.string(), v.unknown())),
|
|
21
|
+
items: v.optional(v.record(v.string(), v.unknown())),
|
|
22
|
+
}),
|
|
23
|
+
),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
export const CustomTypeSchema = v.object({
|
|
27
|
+
id: v.string(),
|
|
28
|
+
label: v.optional(v.string()),
|
|
29
|
+
repeatable: v.boolean(),
|
|
30
|
+
status: v.boolean(),
|
|
31
|
+
format: v.optional(v.string()),
|
|
32
|
+
json: v.record(v.string(), v.unknown()),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export type FetchResult<T> = { ok: true; value: T } | { ok: false; error: string };
|
|
36
|
+
|
|
37
|
+
export async function getCustomTypesApiUrl(): Promise<URL> {
|
|
38
|
+
const host = await readHost();
|
|
39
|
+
host.hostname = `customtypes.${host.hostname}`;
|
|
40
|
+
return host;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function fetchRemoteCustomTypes(repo: string): Promise<FetchResult<CustomType[]>> {
|
|
44
|
+
const token = await readToken();
|
|
45
|
+
if (!token) {
|
|
46
|
+
return { ok: false, error: "Not authenticated" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
50
|
+
const url = new URL("customtypes", baseUrl);
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
const response = await fetch(url, {
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Bearer ${token}`,
|
|
56
|
+
repository: repo,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (!response.ok) {
|
|
61
|
+
if (response.status === 401) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
error: "Unauthorized. Your session may have expired. Run `prismic login` again.",
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
if (response.status === 403) {
|
|
68
|
+
return {
|
|
69
|
+
ok: false,
|
|
70
|
+
error: `Access denied. You may not have access to repository "${repo}".`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const data = await response.json();
|
|
77
|
+
const result = v.safeParse(v.array(CustomTypeSchema), data);
|
|
78
|
+
if (!result.success) {
|
|
79
|
+
return { ok: false, error: "Invalid response from Custom Types API" };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return { ok: true, value: result.output as CustomType[] };
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export async function fetchRemoteSlices(repo: string): Promise<FetchResult<SharedSlice[]>> {
|
|
89
|
+
const token = await readToken();
|
|
90
|
+
if (!token) {
|
|
91
|
+
return { ok: false, error: "Not authenticated" };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
95
|
+
const url = new URL("slices", baseUrl);
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
const response = await fetch(url, {
|
|
99
|
+
headers: {
|
|
100
|
+
Authorization: `Bearer ${token}`,
|
|
101
|
+
repository: repo,
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (!response.ok) {
|
|
106
|
+
if (response.status === 401) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
error: "Unauthorized. Your session may have expired. Run `prismic login` again.",
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (response.status === 403) {
|
|
113
|
+
return {
|
|
114
|
+
ok: false,
|
|
115
|
+
error: `Access denied. You may not have access to repository "${repo}".`,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const data = await response.json();
|
|
122
|
+
const result = v.safeParse(v.array(SharedSliceSchema), data);
|
|
123
|
+
if (!result.success) {
|
|
124
|
+
return { ok: false, error: "Invalid response from Custom Types API" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { ok: true, value: result.output as SharedSlice[] };
|
|
128
|
+
} catch (error) {
|
|
129
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export async function insertCustomType(
|
|
134
|
+
repo: string,
|
|
135
|
+
model: CustomType,
|
|
136
|
+
): Promise<FetchResult<void>> {
|
|
137
|
+
const token = await readToken();
|
|
138
|
+
if (!token) {
|
|
139
|
+
return { ok: false, error: "Not authenticated" };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
143
|
+
const url = new URL("customtypes/insert", baseUrl);
|
|
144
|
+
|
|
145
|
+
try {
|
|
146
|
+
const response = await fetch(url, {
|
|
147
|
+
method: "POST",
|
|
148
|
+
headers: {
|
|
149
|
+
Authorization: `Bearer ${token}`,
|
|
150
|
+
repository: repo,
|
|
151
|
+
"Content-Type": "application/json",
|
|
152
|
+
},
|
|
153
|
+
body: JSON.stringify(model),
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
if (!response.ok) {
|
|
157
|
+
if (response.status === 401) {
|
|
158
|
+
return {
|
|
159
|
+
ok: false,
|
|
160
|
+
error: "Unauthorized. Your session may have expired. Run `prismic login` again.",
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
if (response.status === 403) {
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
error: `Access denied. You may not have access to repository "${repo}".`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { ok: true, value: undefined };
|
|
173
|
+
} catch (error) {
|
|
174
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function updateCustomType(
|
|
179
|
+
repo: string,
|
|
180
|
+
model: CustomType,
|
|
181
|
+
): Promise<FetchResult<void>> {
|
|
182
|
+
const token = await readToken();
|
|
183
|
+
if (!token) {
|
|
184
|
+
return { ok: false, error: "Not authenticated" };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
188
|
+
const url = new URL("customtypes/update", baseUrl);
|
|
189
|
+
|
|
190
|
+
try {
|
|
191
|
+
const response = await fetch(url, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
headers: {
|
|
194
|
+
Authorization: `Bearer ${token}`,
|
|
195
|
+
repository: repo,
|
|
196
|
+
"Content-Type": "application/json",
|
|
197
|
+
},
|
|
198
|
+
body: JSON.stringify(model),
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
if (!response.ok) {
|
|
202
|
+
if (response.status === 401) {
|
|
203
|
+
return {
|
|
204
|
+
ok: false,
|
|
205
|
+
error: "Unauthorized. Your session may have expired. Run `prismic login` again.",
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (response.status === 403) {
|
|
209
|
+
return {
|
|
210
|
+
ok: false,
|
|
211
|
+
error: `Access denied. You may not have access to repository "${repo}".`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return { ok: true, value: undefined };
|
|
218
|
+
} catch (error) {
|
|
219
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export async function deleteCustomType(repo: string, id: string): Promise<FetchResult<void>> {
|
|
224
|
+
const token = await readToken();
|
|
225
|
+
if (!token) {
|
|
226
|
+
return { ok: false, error: "Not authenticated" };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
230
|
+
const url = new URL(`customtypes/${encodeURIComponent(id)}`, baseUrl);
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
const response = await fetch(url, {
|
|
234
|
+
method: "DELETE",
|
|
235
|
+
headers: {
|
|
236
|
+
Authorization: `Bearer ${token}`,
|
|
237
|
+
repository: repo,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (!response.ok) {
|
|
242
|
+
if (response.status === 401) {
|
|
243
|
+
return {
|
|
244
|
+
ok: false,
|
|
245
|
+
error: "Unauthorized. Your session may have expired. Run `prismic login` again.",
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (response.status === 403) {
|
|
249
|
+
return {
|
|
250
|
+
ok: false,
|
|
251
|
+
error: `Access denied. You may not have access to repository "${repo}".`,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { ok: true, value: undefined };
|
|
258
|
+
} catch (error) {
|
|
259
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
export async function insertSlice(repo: string, model: SharedSlice): Promise<FetchResult<void>> {
|
|
264
|
+
const token = await readToken();
|
|
265
|
+
if (!token) {
|
|
266
|
+
return { ok: false, error: "Not authenticated" };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
270
|
+
const url = new URL("slices/insert", baseUrl);
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const response = await fetch(url, {
|
|
274
|
+
method: "POST",
|
|
275
|
+
headers: {
|
|
276
|
+
Authorization: `Bearer ${token}`,
|
|
277
|
+
repository: repo,
|
|
278
|
+
"Content-Type": "application/json",
|
|
279
|
+
},
|
|
280
|
+
body: JSON.stringify(model),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
if (!response.ok) {
|
|
284
|
+
if (response.status === 401) {
|
|
285
|
+
return {
|
|
286
|
+
ok: false,
|
|
287
|
+
error: "Unauthorized. Your session may have expired. Run `prismic login` again.",
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (response.status === 403) {
|
|
291
|
+
return {
|
|
292
|
+
ok: false,
|
|
293
|
+
error: `Access denied. You may not have access to repository "${repo}".`,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return { ok: true, value: undefined };
|
|
300
|
+
} catch (error) {
|
|
301
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export async function updateSlice(repo: string, model: SharedSlice): Promise<FetchResult<void>> {
|
|
306
|
+
const token = await readToken();
|
|
307
|
+
if (!token) {
|
|
308
|
+
return { ok: false, error: "Not authenticated" };
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
312
|
+
const url = new URL("slices/update", baseUrl);
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const response = await fetch(url, {
|
|
316
|
+
method: "POST",
|
|
317
|
+
headers: {
|
|
318
|
+
Authorization: `Bearer ${token}`,
|
|
319
|
+
repository: repo,
|
|
320
|
+
"Content-Type": "application/json",
|
|
321
|
+
},
|
|
322
|
+
body: JSON.stringify(model),
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
if (!response.ok) {
|
|
326
|
+
if (response.status === 401) {
|
|
327
|
+
return {
|
|
328
|
+
ok: false,
|
|
329
|
+
error: "Unauthorized. Your session may have expired. Run `prismic login` again.",
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
if (response.status === 403) {
|
|
333
|
+
return {
|
|
334
|
+
ok: false,
|
|
335
|
+
error: `Access denied. You may not have access to repository "${repo}".`,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return { ok: true, value: undefined };
|
|
342
|
+
} catch (error) {
|
|
343
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export async function deleteSlice(repo: string, id: string): Promise<FetchResult<void>> {
|
|
348
|
+
const token = await readToken();
|
|
349
|
+
if (!token) {
|
|
350
|
+
return { ok: false, error: "Not authenticated" };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
354
|
+
const url = new URL(`slices/${encodeURIComponent(id)}`, baseUrl);
|
|
355
|
+
|
|
356
|
+
try {
|
|
357
|
+
const response = await fetch(url, {
|
|
358
|
+
method: "DELETE",
|
|
359
|
+
headers: {
|
|
360
|
+
Authorization: `Bearer ${token}`,
|
|
361
|
+
repository: repo,
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
if (!response.ok) {
|
|
366
|
+
if (response.status === 401) {
|
|
367
|
+
return {
|
|
368
|
+
ok: false,
|
|
369
|
+
error: "Unauthorized. Your session may have expired. Run `prismic login` again.",
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
if (response.status === 403) {
|
|
373
|
+
return {
|
|
374
|
+
ok: false,
|
|
375
|
+
error: `Access denied. You may not have access to repository "${repo}".`,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return { ok: true, value: undefined };
|
|
382
|
+
} catch (error) {
|
|
383
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
384
|
+
}
|
|
385
|
+
}
|