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
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import * as v from "valibot";
|
|
2
|
+
|
|
3
|
+
import { readToken } from "./auth";
|
|
4
|
+
|
|
5
|
+
type CustomRequestInit = Omit<RequestInit, "body"> & {
|
|
6
|
+
body?: RequestInit["body"] | Record<PropertyKey, unknown>;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type RequestResponse<T> = SuccessfulRequestResponse<T> | FailedRequestResponse;
|
|
10
|
+
export type ParsedRequestResponse<T> =
|
|
11
|
+
| RequestResponse<T>
|
|
12
|
+
| { ok: false; value: unknown; error: v.ValiError<v.GenericSchema<T>> };
|
|
13
|
+
export type SuccessfulRequestResponse<T> = { ok: true; value: T };
|
|
14
|
+
export type FailedRequestResponse = {
|
|
15
|
+
ok: false;
|
|
16
|
+
value: unknown;
|
|
17
|
+
error: RequestError | ForbiddenRequestError | UnauthorizedRequestError;
|
|
18
|
+
};
|
|
19
|
+
export type FailedParsedRequestResponse<T> =
|
|
20
|
+
| FailedRequestResponse
|
|
21
|
+
| { ok: false; value: unknown; error: v.ValiError<v.GenericSchema<T>> };
|
|
22
|
+
|
|
23
|
+
export async function request<T>(
|
|
24
|
+
input: RequestInfo | URL,
|
|
25
|
+
init?: CustomRequestInit,
|
|
26
|
+
): Promise<RequestResponse<T>>;
|
|
27
|
+
export async function request<T>(
|
|
28
|
+
input: RequestInfo | URL,
|
|
29
|
+
init: CustomRequestInit & { schema: v.GenericSchema<T> },
|
|
30
|
+
): Promise<ParsedRequestResponse<T>>;
|
|
31
|
+
export async function request<T>(
|
|
32
|
+
input: RequestInfo | URL,
|
|
33
|
+
init: CustomRequestInit & { schema?: v.GenericSchema<T> } = {},
|
|
34
|
+
): Promise<ParsedRequestResponse<T>> {
|
|
35
|
+
const { credentials = "include" } = init;
|
|
36
|
+
|
|
37
|
+
const headers = new Headers(init.headers);
|
|
38
|
+
headers.set("Accept", "application/json");
|
|
39
|
+
if (credentials === "include") {
|
|
40
|
+
const token = await readToken();
|
|
41
|
+
if (token) headers.set("Cookie", `SESSION=fake_session; prismic-auth=${token}`);
|
|
42
|
+
}
|
|
43
|
+
if (!headers.has("Content-Type") && init.body) {
|
|
44
|
+
headers.set("Content-Type", "application/json");
|
|
45
|
+
}
|
|
46
|
+
if (init.body instanceof FormData) {
|
|
47
|
+
headers.delete("Content-Type");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const body =
|
|
51
|
+
headers.get("Content-Type") === "application/json"
|
|
52
|
+
? JSON.stringify(init.body)
|
|
53
|
+
: (init.body as RequestInit["body"]);
|
|
54
|
+
|
|
55
|
+
const response = await fetch(input, { ...init, body, headers });
|
|
56
|
+
|
|
57
|
+
const value = await response
|
|
58
|
+
.clone()
|
|
59
|
+
.json()
|
|
60
|
+
.catch(() => response.clone().text());
|
|
61
|
+
|
|
62
|
+
if (response.ok) {
|
|
63
|
+
if (!init?.schema) return { ok: true, value };
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const parsed = v.parse(init.schema, value);
|
|
67
|
+
return { ok: true, value: parsed };
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (v.isValiError<v.GenericSchema<T>>(error)) {
|
|
70
|
+
return { ok: false, value, error };
|
|
71
|
+
}
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
} else {
|
|
75
|
+
if (response.status === 401) {
|
|
76
|
+
const error = new UnauthorizedRequestError(response);
|
|
77
|
+
return { ok: false, value, error };
|
|
78
|
+
} else if (response.status === 403) {
|
|
79
|
+
const error = new ForbiddenRequestError(response);
|
|
80
|
+
return { ok: false, value, error };
|
|
81
|
+
} else {
|
|
82
|
+
const error = new RequestError(response);
|
|
83
|
+
return { ok: false, value, error };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export class RequestError extends Error {
|
|
89
|
+
name = "RequestError" as const;
|
|
90
|
+
response: Response;
|
|
91
|
+
|
|
92
|
+
constructor(response: Response) {
|
|
93
|
+
super(`fetch failed: ${response.url}`);
|
|
94
|
+
this.response = response;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
async text(): Promise<string> {
|
|
98
|
+
return this.response.clone().text();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async json(): Promise<unknown> {
|
|
102
|
+
return this.response.clone().json();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get status(): number {
|
|
106
|
+
return this.response.status;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
get statusText(): string {
|
|
110
|
+
return this.response.statusText;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export class ForbiddenRequestError extends RequestError {}
|
|
115
|
+
|
|
116
|
+
export class UnauthorizedRequestError extends RequestError {}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { homedir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
|
|
7
|
+
import packageJson from "../../package.json" with { type: "json" };
|
|
8
|
+
|
|
9
|
+
const SEGMENT_WRITE_KEY =
|
|
10
|
+
process.env.PRISMIC_ENV && process.env.PRISMIC_ENV !== "production"
|
|
11
|
+
? "Ng5oKJHCGpSWplZ9ymB7Pu7rm0sTDeiG"
|
|
12
|
+
: "cGjidifKefYb6EPaGaqpt8rQXkv5TD6P";
|
|
13
|
+
const SEGMENT_TRACK_URL = "https://api.segment.io/v1/track";
|
|
14
|
+
|
|
15
|
+
let enabled = false;
|
|
16
|
+
let anonymousId = "";
|
|
17
|
+
let authorization = "";
|
|
18
|
+
const appContext = { app: { name: packageJson.name, version: packageJson.version } };
|
|
19
|
+
const eventQueue: Array<{ event: string; properties: Record<string, unknown> }> = [];
|
|
20
|
+
|
|
21
|
+
export async function initSegment(): Promise<void> {
|
|
22
|
+
try {
|
|
23
|
+
enabled = await isTelemetryEnabled();
|
|
24
|
+
if (!enabled) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
anonymousId = randomUUID();
|
|
29
|
+
authorization = `Basic ${btoa(SEGMENT_WRITE_KEY + ":")}`;
|
|
30
|
+
process.on("exit", flushTelemetry);
|
|
31
|
+
} catch {
|
|
32
|
+
enabled = false;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function trackStart(command: string): void {
|
|
37
|
+
if (!enabled) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
eventQueue.push({
|
|
42
|
+
event: "Prismic CLI Start",
|
|
43
|
+
properties: {
|
|
44
|
+
commandType: command,
|
|
45
|
+
fullCommand: process.argv.join(" "),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function trackEnd(
|
|
51
|
+
command: string,
|
|
52
|
+
success: boolean,
|
|
53
|
+
error?: unknown,
|
|
54
|
+
): void {
|
|
55
|
+
if (!enabled) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const properties: Record<string, unknown> = {
|
|
60
|
+
commandType: command,
|
|
61
|
+
fullCommand: process.argv.join(" "),
|
|
62
|
+
success,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
if (error !== undefined) {
|
|
66
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
67
|
+
properties.error = message.slice(0, 512);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
eventQueue.push({ event: "Prismic CLI End", properties });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Spawns a detached subprocess to send queued telemetry events.
|
|
75
|
+
* The main process exits immediately; the subprocess handles HTTP delivery.
|
|
76
|
+
*/
|
|
77
|
+
function flushTelemetry(): void {
|
|
78
|
+
if (eventQueue.length === 0) {
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const payload = Buffer.from(
|
|
84
|
+
JSON.stringify({
|
|
85
|
+
url: SEGMENT_TRACK_URL,
|
|
86
|
+
authorization,
|
|
87
|
+
events: eventQueue.map((e) => ({
|
|
88
|
+
anonymousId,
|
|
89
|
+
event: e.event,
|
|
90
|
+
properties: { nodeVersion: process.versions.node, ...e.properties },
|
|
91
|
+
context: appContext,
|
|
92
|
+
timestamp: new Date().toISOString(),
|
|
93
|
+
})),
|
|
94
|
+
}),
|
|
95
|
+
).toString("base64");
|
|
96
|
+
|
|
97
|
+
const child = spawn(
|
|
98
|
+
process.execPath,
|
|
99
|
+
["--input-type=module", "-e", FLUSH_SCRIPT, payload],
|
|
100
|
+
{ detached: true, stdio: "ignore" },
|
|
101
|
+
);
|
|
102
|
+
child.unref();
|
|
103
|
+
} catch {
|
|
104
|
+
// Silent failure — never breaks the CLI
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
eventQueue.length = 0;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const FLUSH_SCRIPT = `
|
|
111
|
+
const {url, authorization, events} = JSON.parse(Buffer.from(process.argv[1], "base64"));
|
|
112
|
+
const h = {"Content-Type": "application/json", Authorization: authorization};
|
|
113
|
+
await Promise.allSettled(events.map(e => fetch(url, {method: "POST", headers: h, body: JSON.stringify(e)}).catch(() => {})));
|
|
114
|
+
`;
|
|
115
|
+
|
|
116
|
+
async function isTelemetryEnabled(): Promise<boolean> {
|
|
117
|
+
try {
|
|
118
|
+
// Check user-level .prismicrc
|
|
119
|
+
const userRc = await readJsonFile(join(homedir(), ".prismicrc"));
|
|
120
|
+
if (userRc?.telemetry === false) {
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Check project-level .prismicrc
|
|
125
|
+
const projectRc = await readJsonFile(join(process.cwd(), ".prismicrc"));
|
|
126
|
+
if (projectRc?.telemetry === false) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return true;
|
|
131
|
+
} catch {
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
async function readJsonFile(
|
|
137
|
+
path: string,
|
|
138
|
+
): Promise<Record<string, unknown> | undefined> {
|
|
139
|
+
try {
|
|
140
|
+
const contents = await readFile(path, "utf8");
|
|
141
|
+
return JSON.parse(contents);
|
|
142
|
+
} catch {
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import * as Sentry from "@sentry/node-core/light";
|
|
2
|
+
|
|
3
|
+
import packageJson from "../../package.json" with { type: "json" };
|
|
4
|
+
|
|
5
|
+
const SENTRY_DSN =
|
|
6
|
+
import.meta.env.PRISMIC_SENTRY_DSN ||
|
|
7
|
+
"https://e1886b1775bd397cd1afc60bfd2ebfc8@o146123.ingest.us.sentry.io/4510445143588864";
|
|
8
|
+
|
|
9
|
+
function isSentryEnabled(): boolean {
|
|
10
|
+
if (import.meta.env.PRISMIC_SENTRY_ENABLED === undefined) {
|
|
11
|
+
return import.meta.env.PROD;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return import.meta.env.PRISMIC_SENTRY_ENABLED === "true";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function setupSentry(): void {
|
|
18
|
+
try {
|
|
19
|
+
if (!isSentryEnabled()) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
Sentry.init({
|
|
24
|
+
dsn: SENTRY_DSN,
|
|
25
|
+
release: packageJson.version,
|
|
26
|
+
environment: import.meta.env.PRISMIC_SENTRY_ENVIRONMENT ?? "production",
|
|
27
|
+
defaultIntegrations: false,
|
|
28
|
+
integrations: [],
|
|
29
|
+
maxValueLength: 2_500,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
Sentry.setContext("Process", {
|
|
33
|
+
command: process.argv.join(" "),
|
|
34
|
+
cwd: process.cwd(),
|
|
35
|
+
});
|
|
36
|
+
} catch {
|
|
37
|
+
// Silent failure — never breaks the CLI
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export async function captureError(error: unknown): Promise<void> {
|
|
42
|
+
try {
|
|
43
|
+
if (!isSentryEnabled()) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
Sentry.captureException(
|
|
48
|
+
error,
|
|
49
|
+
error instanceof Error
|
|
50
|
+
? { extra: { cause: error.cause, fullCommand: process.argv.join(" ") } }
|
|
51
|
+
: {},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
await Sentry.flush();
|
|
55
|
+
} catch {
|
|
56
|
+
// Silent failure — never breaks the CLI
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Re-exports for future devtools-parity integration points
|
|
61
|
+
export const setUser = Sentry.setUser;
|
|
62
|
+
export const setTag = Sentry.setTag;
|
|
63
|
+
export const setContext = Sentry.setContext;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import baseDedent from "dedent";
|
|
2
|
+
|
|
3
|
+
export function humanReadable(id: string): string {
|
|
4
|
+
return id
|
|
5
|
+
.replace(/([a-z])([A-Z])/g, "$1 $2")
|
|
6
|
+
.replace(/[_-]+/g, " ")
|
|
7
|
+
.replace(/\b\w/g, (c) => c.toUpperCase());
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const dedent = baseDedent.withOptions({ alignValues: true });
|
package/src/lib/url.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { readHost } from "./auth";
|
|
2
|
+
|
|
3
|
+
export async function getRepoUrl(repo: string): Promise<URL> {
|
|
4
|
+
const host = await readHost();
|
|
5
|
+
host.hostname = `${repo}.${host.hostname}`;
|
|
6
|
+
return appendTrailingSlash(host);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export async function getInternalApiUrl(): Promise<URL> {
|
|
10
|
+
const host = await readHost();
|
|
11
|
+
host.hostname = `api.internal.${host.hostname}`;
|
|
12
|
+
return appendTrailingSlash(host);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function getUserServiceUrl(): Promise<URL> {
|
|
16
|
+
const host = await readHost();
|
|
17
|
+
host.hostname = `user-service.${host.hostname}`;
|
|
18
|
+
return appendTrailingSlash(host);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function getAuthUrl(): Promise<URL> {
|
|
22
|
+
const host = await readHost();
|
|
23
|
+
host.hostname = `auth.${host.hostname}`;
|
|
24
|
+
return appendTrailingSlash(host);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function appendTrailingSlash(url: string | URL): URL {
|
|
28
|
+
const newURL = new URL(url);
|
|
29
|
+
if (!newURL.pathname.endsWith("/")) newURL.pathname += "/";
|
|
30
|
+
return newURL;
|
|
31
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { isAuthenticated } from "./lib/auth";
|
|
4
|
+
import { safeGetRepositoryFromConfig } from "./lib/config";
|
|
5
|
+
import { stringify } from "./lib/json";
|
|
6
|
+
import { ForbiddenRequestError, request } from "./lib/request";
|
|
7
|
+
import { getRepoUrl } from "./lib/url";
|
|
8
|
+
|
|
9
|
+
const HELP = `
|
|
10
|
+
Add a new locale to a Prismic repository.
|
|
11
|
+
|
|
12
|
+
By default, this command reads the repository from prismic.config.json at the
|
|
13
|
+
project root.
|
|
14
|
+
|
|
15
|
+
USAGE
|
|
16
|
+
prismic locale add <code> [flags]
|
|
17
|
+
|
|
18
|
+
ARGUMENTS
|
|
19
|
+
<code> Locale code (e.g. fr-fr, es-es)
|
|
20
|
+
|
|
21
|
+
FLAGS
|
|
22
|
+
-n, --name string Custom display name (creates custom locale)
|
|
23
|
+
-r, --repo string Repository domain
|
|
24
|
+
-h, --help Show help for command
|
|
25
|
+
|
|
26
|
+
LEARN MORE
|
|
27
|
+
Use \`prismic <command> <subcommand> --help\` for more information about a command.
|
|
28
|
+
`.trim();
|
|
29
|
+
|
|
30
|
+
export async function localeAdd(): Promise<void> {
|
|
31
|
+
const {
|
|
32
|
+
values: { help, name, repo = await safeGetRepositoryFromConfig() },
|
|
33
|
+
positionals: [code],
|
|
34
|
+
} = parseArgs({
|
|
35
|
+
args: process.argv.slice(4), // skip: node, script, "locale", "add"
|
|
36
|
+
options: {
|
|
37
|
+
name: { type: "string", short: "n" },
|
|
38
|
+
repo: { type: "string", short: "r" },
|
|
39
|
+
help: { type: "boolean", short: "h" },
|
|
40
|
+
},
|
|
41
|
+
allowPositionals: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (help) {
|
|
45
|
+
console.info(HELP);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!code) {
|
|
50
|
+
console.error("Missing required argument: <code>");
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!repo) {
|
|
56
|
+
console.error("Missing prismic.config.json or --repo option");
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const authenticated = await isAuthenticated();
|
|
62
|
+
if (!authenticated) {
|
|
63
|
+
handleUnauthenticated();
|
|
64
|
+
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const response = name
|
|
69
|
+
? await addCustomLocale(repo, code, name)
|
|
70
|
+
: await addStandardLocale(repo, code);
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
if (
|
|
73
|
+
typeof response.value === "string" &&
|
|
74
|
+
response.value.includes("already existing languages")
|
|
75
|
+
) {
|
|
76
|
+
// Treat as success
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (response.error instanceof ForbiddenRequestError) {
|
|
81
|
+
handleUnauthenticated();
|
|
82
|
+
} else {
|
|
83
|
+
console.error(`Failed to add locale: ${stringify(response.value)}`);
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.info(`Locale added: ${code}`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function addStandardLocale(repo: string, code: string) {
|
|
94
|
+
const url = new URL("/app/settings/multilanguages", await getRepoUrl(repo));
|
|
95
|
+
return await request(url, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
body: { languages: [code] },
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function addCustomLocale(repo: string, code: string, name: string) {
|
|
102
|
+
const [langPart, regionPart] = code.split("-");
|
|
103
|
+
const url = new URL("/app/settings/multilanguages/custom", await getRepoUrl(repo));
|
|
104
|
+
return await request(url, {
|
|
105
|
+
method: "POST",
|
|
106
|
+
body: {
|
|
107
|
+
lang: { label: name, id: langPart || code },
|
|
108
|
+
region: { label: name, id: regionPart || langPart || code },
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function handleUnauthenticated() {
|
|
114
|
+
console.error("Not logged in. Run `prismic login` first.");
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
}
|