prismic 0.0.0-canary.2cfb4a8
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-CSAFppyU.mjs +97 -0
- package/dist/framework-AxGiKSX9.mjs +17 -0
- package/dist/index.mjs +87 -0
- package/dist/nextjs-D4viImqE.mjs +312 -0
- package/dist/nuxt-CbBJIJw1.mjs +59 -0
- package/dist/sveltekit-BuNy6sKm.mjs +226 -0
- package/package.json +58 -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 +90 -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/login.ts +45 -0
- package/src/logout.ts +36 -0
- package/src/sync.ts +259 -0
- package/src/whoami.ts +62 -0
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type FieldPath =
|
|
2
|
+
| { type: "top-level"; fieldId: string }
|
|
3
|
+
| { type: "nested"; groupId: string; nestedFieldId: string };
|
|
4
|
+
|
|
5
|
+
export function parseFieldPath(fieldId: string): FieldPath {
|
|
6
|
+
const parts = fieldId.split(".");
|
|
7
|
+
|
|
8
|
+
if (parts.length === 1) {
|
|
9
|
+
return { type: "top-level", fieldId };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (parts.length === 2) {
|
|
13
|
+
return { type: "nested", groupId: parts[0], nestedFieldId: parts[1] };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// More than 2 parts means nested groups which aren't supported
|
|
17
|
+
return { type: "nested", groupId: parts[0], nestedFieldId: parts.slice(1).join(".") };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type GroupFieldResult =
|
|
21
|
+
| { ok: true; group: { config: { fields: Record<string, unknown> } } }
|
|
22
|
+
| { ok: false; error: string };
|
|
23
|
+
|
|
24
|
+
export function isGroupField(field: unknown): field is { type: "Group"; config: { fields: Record<string, unknown> } } {
|
|
25
|
+
return (
|
|
26
|
+
typeof field === "object" &&
|
|
27
|
+
field !== null &&
|
|
28
|
+
"type" in field &&
|
|
29
|
+
field.type === "Group" &&
|
|
30
|
+
"config" in field &&
|
|
31
|
+
typeof field.config === "object" &&
|
|
32
|
+
field.config !== null &&
|
|
33
|
+
"fields" in field.config
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function findGroupInTab(
|
|
38
|
+
tabFields: Record<string, unknown>,
|
|
39
|
+
groupId: string,
|
|
40
|
+
tabName: string,
|
|
41
|
+
): GroupFieldResult {
|
|
42
|
+
const field = tabFields[groupId];
|
|
43
|
+
|
|
44
|
+
if (!field) {
|
|
45
|
+
return { ok: false, error: `Group "${groupId}" not found in tab "${tabName}"` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!isGroupField(field)) {
|
|
49
|
+
return { ok: false, error: `Field "${groupId}" is not a group` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { ok: true, group: field };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function findGroupInVariation(
|
|
56
|
+
primary: Record<string, unknown>,
|
|
57
|
+
groupId: string,
|
|
58
|
+
variationId: string,
|
|
59
|
+
): GroupFieldResult {
|
|
60
|
+
const field = primary[groupId];
|
|
61
|
+
|
|
62
|
+
if (!field) {
|
|
63
|
+
return { ok: false, error: `Group "${groupId}" not found in variation "${variationId}"` };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!isGroupField(field)) {
|
|
67
|
+
return { ok: false, error: `Field "${groupId}" is not a group` };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { ok: true, group: field };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function validateNestedFieldPath(fieldPath: FieldPath): { ok: true } | { ok: false; error: string } {
|
|
74
|
+
if (fieldPath.type === "nested" && fieldPath.nestedFieldId.includes(".")) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error: `Nested groups not supported. Use: group.field`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { ok: true };
|
|
81
|
+
}
|
package/src/lib/file.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { access } from "node:fs/promises";
|
|
2
|
+
import { pathToFileURL } from "node:url";
|
|
3
|
+
|
|
4
|
+
import { appendTrailingSlash } from "./url";
|
|
5
|
+
|
|
6
|
+
export async function findUpward(
|
|
7
|
+
name: string,
|
|
8
|
+
config: { start?: URL; stop?: URL | string } = {},
|
|
9
|
+
): Promise<URL | undefined> {
|
|
10
|
+
const { start = pathToFileURL(process.cwd()), stop } = config;
|
|
11
|
+
|
|
12
|
+
let dir = appendTrailingSlash(start);
|
|
13
|
+
|
|
14
|
+
while (true) {
|
|
15
|
+
const path = new URL(name, dir);
|
|
16
|
+
try {
|
|
17
|
+
await access(path);
|
|
18
|
+
return path;
|
|
19
|
+
} catch {}
|
|
20
|
+
|
|
21
|
+
if (typeof stop === "string") {
|
|
22
|
+
const stopPath = new URL(stop, dir);
|
|
23
|
+
try {
|
|
24
|
+
await access(stopPath);
|
|
25
|
+
return;
|
|
26
|
+
} catch {}
|
|
27
|
+
} else if (stop instanceof URL) {
|
|
28
|
+
if (stop.href === dir.href) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const parent = new URL("..", dir);
|
|
34
|
+
if (parent.href === dir.href) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
dir = parent;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function exists(path: URL): Promise<boolean> {
|
|
43
|
+
try {
|
|
44
|
+
await access(path);
|
|
45
|
+
return true;
|
|
46
|
+
} catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
package/src/lib/json.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import detectIndent from "detect-indent";
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
import { findUpward } from "./file";
|
|
5
|
+
|
|
6
|
+
export async function addDependencies(
|
|
7
|
+
dependencies: Record<string, string>,
|
|
8
|
+
): Promise<void> {
|
|
9
|
+
const packageJsonPath = await findUpward("package.json");
|
|
10
|
+
if (!packageJsonPath) {
|
|
11
|
+
throw new Error("No package.json found");
|
|
12
|
+
}
|
|
13
|
+
const raw = await readFile(packageJsonPath, "utf8");
|
|
14
|
+
const indent = detectIndent(raw).indent || "\t";
|
|
15
|
+
const packageJson = JSON.parse(raw);
|
|
16
|
+
packageJson.dependencies = Object.fromEntries(
|
|
17
|
+
Object.entries({ ...packageJson.dependencies, ...dependencies }).sort(
|
|
18
|
+
([a], [b]) => a.localeCompare(b),
|
|
19
|
+
),
|
|
20
|
+
);
|
|
21
|
+
await writeFile(
|
|
22
|
+
packageJsonPath,
|
|
23
|
+
JSON.stringify(packageJson, null, indent) + "\n",
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export async function getNpmPackageVersion(
|
|
28
|
+
name: string,
|
|
29
|
+
tag = "latest",
|
|
30
|
+
): Promise<string> {
|
|
31
|
+
const url = new URL(`${name}/${tag}`, "https://registry.npmjs.org/");
|
|
32
|
+
const res = await fetch(url);
|
|
33
|
+
const { version } = await res.json();
|
|
34
|
+
return version;
|
|
35
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { readToken } from "./auth";
|
|
4
|
+
import { getUserServiceUrl } from "./url";
|
|
5
|
+
|
|
6
|
+
const PrismicUserProfileSchema = v.object({
|
|
7
|
+
userId: v.string(),
|
|
8
|
+
shortId: v.string(),
|
|
9
|
+
intercomHash: v.string(),
|
|
10
|
+
email: v.string(),
|
|
11
|
+
firstName: v.string(),
|
|
12
|
+
lastName: v.string(),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export type PrismicUserProfile = v.InferOutput<typeof PrismicUserProfileSchema>;
|
|
16
|
+
|
|
17
|
+
export async function getProfile(): Promise<PrismicUserProfile> {
|
|
18
|
+
const token = await readToken();
|
|
19
|
+
if (!token) {
|
|
20
|
+
throw new Error("Not authenticated. Log in before trying again.");
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const baseUrl = await getUserServiceUrl();
|
|
24
|
+
const url = new URL("profile", baseUrl);
|
|
25
|
+
|
|
26
|
+
const response = await fetch(url, {
|
|
27
|
+
headers: {
|
|
28
|
+
Authorization: `Bearer ${token}`,
|
|
29
|
+
},
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
throw new Error("Failed to retrieve profile from the Prismic user service.");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const json = await response.json();
|
|
37
|
+
|
|
38
|
+
return v.parse(PrismicUserProfileSchema, json);
|
|
39
|
+
}
|