@angeloashmore/prismic-cli-poc 0.0.0-canary.1143872
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 +98 -0
- package/dist/index.mjs +2508 -0
- package/package.json +53 -0
- package/src/codegen-types.ts +82 -0
- package/src/codegen.ts +45 -0
- package/src/custom-type-add-field-boolean.ts +222 -0
- package/src/custom-type-add-field-color.ts +205 -0
- package/src/custom-type-add-field-date.ts +208 -0
- package/src/custom-type-add-field-embed.ts +205 -0
- package/src/custom-type-add-field-geo-point.ts +202 -0
- package/src/custom-type-add-field-group.ts +179 -0
- package/src/custom-type-add-field-image.ts +205 -0
- package/src/custom-type-add-field-key-text.ts +205 -0
- package/src/custom-type-add-field-link.ts +228 -0
- package/src/custom-type-add-field-number.ts +237 -0
- package/src/custom-type-add-field-rich-text.ts +229 -0
- package/src/custom-type-add-field-select.ts +211 -0
- package/src/custom-type-add-field-timestamp.ts +208 -0
- package/src/custom-type-add-field-uid.ts +188 -0
- package/src/custom-type-add-field.ts +116 -0
- package/src/custom-type-connect-slice.ts +214 -0
- package/src/custom-type-create.ts +112 -0
- package/src/custom-type-disconnect-slice.ts +171 -0
- package/src/custom-type-list.ts +110 -0
- package/src/custom-type-remove-field.ts +171 -0
- package/src/custom-type-remove.ts +138 -0
- package/src/custom-type-set-name.ts +138 -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/index.ts +132 -0
- package/src/init.ts +64 -0
- package/src/lib/auth.ts +83 -0
- package/src/lib/config.ts +111 -0
- package/src/lib/custom-types-api.ts +438 -0
- package/src/lib/field-path.ts +81 -0
- package/src/lib/file.ts +49 -0
- package/src/lib/framework.ts +143 -0
- package/src/lib/json.ts +3 -0
- package/src/lib/request.ts +116 -0
- package/src/lib/slice.ts +115 -0
- package/src/lib/string.ts +6 -0
- package/src/lib/url.ts +25 -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 +152 -0
- package/src/logout.ts +36 -0
- package/src/page-type-add-field-boolean.ts +238 -0
- package/src/page-type-add-field-color.ts +224 -0
- package/src/page-type-add-field-date.ts +227 -0
- package/src/page-type-add-field-embed.ts +224 -0
- package/src/page-type-add-field-geo-point.ts +221 -0
- package/src/page-type-add-field-group.ts +198 -0
- package/src/page-type-add-field-image.ts +224 -0
- package/src/page-type-add-field-key-text.ts +224 -0
- package/src/page-type-add-field-link.ts +247 -0
- package/src/page-type-add-field-number.ts +256 -0
- package/src/page-type-add-field-rich-text.ts +248 -0
- package/src/page-type-add-field-select.ts +230 -0
- package/src/page-type-add-field-timestamp.ts +227 -0
- package/src/page-type-add-field-uid.ts +207 -0
- package/src/page-type-add-field.ts +116 -0
- package/src/page-type-connect-slice.ts +214 -0
- package/src/page-type-create.ts +161 -0
- package/src/page-type-disconnect-slice.ts +171 -0
- package/src/page-type-list.ts +109 -0
- package/src/page-type-remove-field.ts +171 -0
- package/src/page-type-remove.ts +138 -0
- package/src/page-type-set-name.ts +138 -0
- package/src/page-type-set-repeatable.ts +147 -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 +242 -0
- package/src/push.ts +405 -0
- package/src/repo-create.ts +195 -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 +240 -0
- package/src/slice-add-field-color.ts +226 -0
- package/src/slice-add-field-date.ts +226 -0
- package/src/slice-add-field-embed.ts +226 -0
- package/src/slice-add-field-geo-point.ts +223 -0
- package/src/slice-add-field-group.ts +191 -0
- package/src/slice-add-field-image.ts +223 -0
- package/src/slice-add-field-key-text.ts +226 -0
- package/src/slice-add-field-link.ts +245 -0
- package/src/slice-add-field-number.ts +226 -0
- package/src/slice-add-field-rich-text.ts +250 -0
- package/src/slice-add-field-select.ts +232 -0
- package/src/slice-add-field-timestamp.ts +226 -0
- package/src/slice-add-field.ts +111 -0
- package/src/slice-add-variation.ts +139 -0
- package/src/slice-create.ts +203 -0
- package/src/slice-list-variations.ts +67 -0
- package/src/slice-list.ts +88 -0
- package/src/slice-remove-field.ts +122 -0
- package/src/slice-remove-variation.ts +112 -0
- package/src/slice-remove.ts +91 -0
- package/src/slice-rename.ts +122 -0
- package/src/slice-set-screenshot.ts +235 -0
- package/src/slice-view.ts +80 -0
- package/src/slice.ts +95 -0
- package/src/status.ts +873 -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,143 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
import { exists, findUpward } from "./file";
|
|
5
|
+
|
|
6
|
+
export type Framework = "next" | "nuxt" | "sveltekit";
|
|
7
|
+
|
|
8
|
+
export type FrameworkInfo = {
|
|
9
|
+
framework: Framework | undefined;
|
|
10
|
+
hasSrcDir: boolean;
|
|
11
|
+
projectRoot: URL;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const PackageJsonSchema = v.object({
|
|
15
|
+
dependencies: v.optional(v.record(v.string(), v.string())),
|
|
16
|
+
devDependencies: v.optional(v.record(v.string(), v.string())),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
export async function detectFrameworkInfo(): Promise<FrameworkInfo | undefined> {
|
|
20
|
+
const packageJsonPath = await findUpward("package.json");
|
|
21
|
+
if (!packageJsonPath) return undefined;
|
|
22
|
+
|
|
23
|
+
const projectRoot = new URL(".", packageJsonPath);
|
|
24
|
+
|
|
25
|
+
let framework: Framework | undefined;
|
|
26
|
+
try {
|
|
27
|
+
const contents = await readFile(packageJsonPath, "utf8");
|
|
28
|
+
const { dependencies = {}, devDependencies = {} } = v.parse(
|
|
29
|
+
PackageJsonSchema,
|
|
30
|
+
JSON.parse(contents),
|
|
31
|
+
);
|
|
32
|
+
const allDeps = { ...dependencies, ...devDependencies };
|
|
33
|
+
if ("next" in allDeps) framework = "next";
|
|
34
|
+
else if ("nuxt" in allDeps) framework = "nuxt";
|
|
35
|
+
else if ("@sveltejs/kit" in allDeps) framework = "sveltekit";
|
|
36
|
+
} catch {
|
|
37
|
+
// Continue with undefined framework
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let hasSrcDir = false;
|
|
41
|
+
if (framework === "next" || framework === "sveltekit") {
|
|
42
|
+
hasSrcDir = await exists(new URL("src/", projectRoot));
|
|
43
|
+
} else if (framework === "nuxt") {
|
|
44
|
+
hasSrcDir = await exists(new URL("app/", projectRoot));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return { framework, hasSrcDir, projectRoot };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getRequiredDependencies(framework: Framework | undefined): string[] {
|
|
51
|
+
switch (framework) {
|
|
52
|
+
case "next":
|
|
53
|
+
return ["@prismicio/client", "@prismicio/react", "@prismicio/next"];
|
|
54
|
+
case "nuxt":
|
|
55
|
+
return ["@nuxtjs/prismic"];
|
|
56
|
+
case "sveltekit":
|
|
57
|
+
return ["@prismicio/client", "@prismicio/svelte"];
|
|
58
|
+
default:
|
|
59
|
+
return ["@prismicio/client"];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function getClientFilePath(info: FrameworkInfo): string | null {
|
|
64
|
+
switch (info.framework) {
|
|
65
|
+
case "next":
|
|
66
|
+
return info.hasSrcDir ? "src/prismicio.ts" : "prismicio.ts";
|
|
67
|
+
case "nuxt":
|
|
68
|
+
return null; // Nuxt uses nuxt.config.ts instead
|
|
69
|
+
case "sveltekit":
|
|
70
|
+
return "src/lib/prismicio.ts";
|
|
71
|
+
default:
|
|
72
|
+
return "prismicio.ts";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function getSlicesDirectory(info: FrameworkInfo): string {
|
|
77
|
+
switch (info.framework) {
|
|
78
|
+
case "next":
|
|
79
|
+
return info.hasSrcDir ? "src/slices/" : "slices/";
|
|
80
|
+
case "nuxt":
|
|
81
|
+
return info.hasSrcDir ? "app/slices/" : "slices/";
|
|
82
|
+
case "sveltekit":
|
|
83
|
+
return "src/lib/slices/";
|
|
84
|
+
default:
|
|
85
|
+
return "slices/";
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function getSliceComponentExtensions(framework: Framework | undefined): string[] {
|
|
90
|
+
switch (framework) {
|
|
91
|
+
case "next":
|
|
92
|
+
return [".tsx", ".ts", ".jsx", ".js"];
|
|
93
|
+
case "nuxt":
|
|
94
|
+
return [".vue"];
|
|
95
|
+
case "sveltekit":
|
|
96
|
+
return [".svelte"];
|
|
97
|
+
default:
|
|
98
|
+
return [".tsx", ".ts", ".jsx", ".js"];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function getRoutePath(
|
|
103
|
+
info: FrameworkInfo,
|
|
104
|
+
route: string,
|
|
105
|
+
): { path: string; extensions: string[] } | null {
|
|
106
|
+
switch (info.framework) {
|
|
107
|
+
case "next": {
|
|
108
|
+
const base = info.hasSrcDir ? "src/app" : "app";
|
|
109
|
+
if (route === "/slice-simulator") {
|
|
110
|
+
return { path: `${base}/slice-simulator/page`, extensions: [".tsx", ".ts", ".jsx", ".js"] };
|
|
111
|
+
}
|
|
112
|
+
if (route === "/api/preview") {
|
|
113
|
+
return { path: `${base}/api/preview/route`, extensions: [".ts", ".js"] };
|
|
114
|
+
}
|
|
115
|
+
if (route === "/api/exit-preview") {
|
|
116
|
+
return { path: `${base}/api/exit-preview/route`, extensions: [".ts", ".js"] };
|
|
117
|
+
}
|
|
118
|
+
if (route === "/api/revalidate") {
|
|
119
|
+
return { path: `${base}/api/revalidate/route`, extensions: [".ts", ".js"] };
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
case "nuxt": {
|
|
124
|
+
if (route === "/slice-simulator") {
|
|
125
|
+
return { path: "pages/slice-simulator", extensions: [".vue"] };
|
|
126
|
+
}
|
|
127
|
+
// Preview endpoints are built-in for Nuxt
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
case "sveltekit": {
|
|
131
|
+
if (route === "/slice-simulator") {
|
|
132
|
+
return { path: "src/routes/slice-simulator/+page", extensions: [".svelte"] };
|
|
133
|
+
}
|
|
134
|
+
if (route === "/api/preview") {
|
|
135
|
+
return { path: "src/routes/api/preview/+server", extensions: [".ts", ".js"] };
|
|
136
|
+
}
|
|
137
|
+
// exit-preview not required for SvelteKit
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
default:
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
}
|
package/src/lib/json.ts
ADDED
|
@@ -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 {}
|
package/src/lib/slice.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
2
|
+
|
|
3
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
4
|
+
import * as v from "valibot";
|
|
5
|
+
|
|
6
|
+
import { exists, findUpward } from "./file";
|
|
7
|
+
|
|
8
|
+
export const SharedSliceSchema = v.object({
|
|
9
|
+
id: v.string(),
|
|
10
|
+
type: v.literal("SharedSlice"),
|
|
11
|
+
name: v.string(),
|
|
12
|
+
description: v.optional(v.string()),
|
|
13
|
+
variations: v.array(
|
|
14
|
+
v.object({
|
|
15
|
+
id: v.string(),
|
|
16
|
+
name: v.string(),
|
|
17
|
+
description: v.optional(v.string()),
|
|
18
|
+
docURL: v.optional(v.string()),
|
|
19
|
+
version: v.optional(v.string()),
|
|
20
|
+
imageUrl: v.optional(v.string()),
|
|
21
|
+
primary: v.optional(v.record(v.string(), v.unknown())),
|
|
22
|
+
items: v.optional(v.record(v.string(), v.unknown())),
|
|
23
|
+
}),
|
|
24
|
+
),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
type SliceModelResult =
|
|
28
|
+
| { ok: true; model: SharedSlice; modelPath: URL }
|
|
29
|
+
| { ok: false; error: string };
|
|
30
|
+
|
|
31
|
+
export async function findSliceModel(sliceId: string): Promise<SliceModelResult> {
|
|
32
|
+
const projectRoot = await findUpward("package.json");
|
|
33
|
+
if (!projectRoot) {
|
|
34
|
+
return { ok: false, error: "Could not find project root (no package.json found)" };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const slicesDirectory = await getSlicesDirectory();
|
|
38
|
+
|
|
39
|
+
// List all directories in slices folder
|
|
40
|
+
let entries: string[];
|
|
41
|
+
try {
|
|
42
|
+
entries = (await readdir(slicesDirectory, { withFileTypes: false })) as unknown as string[];
|
|
43
|
+
} catch {
|
|
44
|
+
return { ok: false, error: `No slices directory found at ${slicesDirectory.href}` };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Search for a slice with matching ID
|
|
48
|
+
for (const entry of entries) {
|
|
49
|
+
const modelPath = new URL(`${entry}/model.json`, slicesDirectory);
|
|
50
|
+
try {
|
|
51
|
+
const contents = await readFile(modelPath, "utf8");
|
|
52
|
+
const parsed = JSON.parse(contents);
|
|
53
|
+
if (parsed.id === sliceId) {
|
|
54
|
+
const result = v.safeParse(SharedSliceSchema, parsed);
|
|
55
|
+
if (!result.success) {
|
|
56
|
+
return { ok: false, error: `Invalid slice model at ${modelPath.href}` };
|
|
57
|
+
}
|
|
58
|
+
return { ok: true, model: result.output as SharedSlice, modelPath };
|
|
59
|
+
}
|
|
60
|
+
} catch {
|
|
61
|
+
// Skip directories without valid model.json
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
error: `Slice not found: ${sliceId}\n\nCreate it first with: prismic slice create ${sliceId}`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export async function getSlicesDirectory(): Promise<URL> {
|
|
72
|
+
const framework = await detectFramework();
|
|
73
|
+
const projectRoot = await findUpward("package.json");
|
|
74
|
+
if (!projectRoot) {
|
|
75
|
+
throw new Error("Could not find project root (no package.json found)");
|
|
76
|
+
}
|
|
77
|
+
const projectDir = new URL(".", projectRoot);
|
|
78
|
+
|
|
79
|
+
switch (framework) {
|
|
80
|
+
case "next": {
|
|
81
|
+
const hasSrcDir = await exists(new URL("src", projectDir));
|
|
82
|
+
if (hasSrcDir) return new URL("src/slices/", projectDir);
|
|
83
|
+
}
|
|
84
|
+
case "nuxt": {
|
|
85
|
+
const hasAppDir = await exists(new URL("app", projectDir));
|
|
86
|
+
if (hasAppDir) return new URL("app/slices/", projectDir);
|
|
87
|
+
}
|
|
88
|
+
case "sveltekit": {
|
|
89
|
+
return new URL("src/slices/", projectDir);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return new URL("slices/", projectDir);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const PackageJsonSchema = v.object({
|
|
96
|
+
dependencies: v.optional(v.record(v.string(), v.string())),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
type Framework = "next" | "nuxt" | "sveltekit";
|
|
100
|
+
|
|
101
|
+
async function detectFramework(): Promise<Framework | undefined> {
|
|
102
|
+
const packageJsonPath = await findUpward("package.json");
|
|
103
|
+
if (!packageJsonPath) return;
|
|
104
|
+
try {
|
|
105
|
+
const contents = await readFile(packageJsonPath, "utf8");
|
|
106
|
+
const { dependencies = {} } = v.parse(PackageJsonSchema, JSON.parse(contents));
|
|
107
|
+
if ("next" in dependencies) return "next";
|
|
108
|
+
if ("nuxt" in dependencies) return "nuxt";
|
|
109
|
+
if ("@sveltejs/kit" in dependencies) return "sveltekit";
|
|
110
|
+
} catch {}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export function pascalCase(input: string): string {
|
|
114
|
+
return input.toLowerCase().replace(/(^|[-_\s]+)(.)?/g, (_, __, c) => c?.toUpperCase() ?? "");
|
|
115
|
+
}
|
package/src/lib/url.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
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 function appendTrailingSlash(url: string | URL): URL {
|
|
22
|
+
const newURL = new URL(url);
|
|
23
|
+
if (!newURL.pathname.endsWith("/")) newURL.pathname += "/";
|
|
24
|
+
return newURL;
|
|
25
|
+
}
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
import { isAuthenticated } from "./lib/auth";
|
|
5
|
+
import { safeGetRepositoryFromConfig } from "./lib/config";
|
|
6
|
+
import { stringify } from "./lib/json";
|
|
7
|
+
import { ForbiddenRequestError, type ParsedRequestResponse, request } from "./lib/request";
|
|
8
|
+
import { getInternalApiUrl } from "./lib/url";
|
|
9
|
+
|
|
10
|
+
const HELP = `
|
|
11
|
+
List all locales in a Prismic repository.
|
|
12
|
+
|
|
13
|
+
By default, this command reads the repository from prismic.config.json at the
|
|
14
|
+
project root.
|
|
15
|
+
|
|
16
|
+
USAGE
|
|
17
|
+
prismic locale list [flags]
|
|
18
|
+
|
|
19
|
+
FLAGS
|
|
20
|
+
--json Output as JSON
|
|
21
|
+
-r, --repo string Repository domain
|
|
22
|
+
-h, --help Show help for command
|
|
23
|
+
|
|
24
|
+
LEARN MORE
|
|
25
|
+
Use \`prismic <command> <subcommand> --help\` for more information about a command.
|
|
26
|
+
`.trim();
|
|
27
|
+
|
|
28
|
+
export async function localeList(): Promise<void> {
|
|
29
|
+
const {
|
|
30
|
+
values: { help, repo = await safeGetRepositoryFromConfig(), json },
|
|
31
|
+
} = parseArgs({
|
|
32
|
+
args: process.argv.slice(4), // skip: node, script, "locale", "list"
|
|
33
|
+
options: {
|
|
34
|
+
json: { type: "boolean" },
|
|
35
|
+
repo: { type: "string", short: "r" },
|
|
36
|
+
help: { type: "boolean", short: "h" },
|
|
37
|
+
},
|
|
38
|
+
allowPositionals: false,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
if (help) {
|
|
42
|
+
console.info(HELP);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!repo) {
|
|
47
|
+
console.error("Missing prismic.config.json or --repo option");
|
|
48
|
+
process.exitCode = 1;
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const authenticated = await isAuthenticated();
|
|
53
|
+
if (!authenticated) {
|
|
54
|
+
handleUnauthenticated();
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const response = await getLocales(repo);
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
if (response.error instanceof ForbiddenRequestError) {
|
|
61
|
+
handleUnauthenticated();
|
|
62
|
+
} else if (v.isValiError(response.error)) {
|
|
63
|
+
console.error(
|
|
64
|
+
`Failed to list locales: Invalid response: ${stringify(response.error.issues)}`,
|
|
65
|
+
);
|
|
66
|
+
process.exitCode = 1;
|
|
67
|
+
} else {
|
|
68
|
+
console.error(`Failed to list locales: ${stringify(response.value)}`);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
}
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const locales = response.value.results;
|
|
75
|
+
if (json) {
|
|
76
|
+
console.info(stringify(locales));
|
|
77
|
+
} else {
|
|
78
|
+
for (const locale of locales) {
|
|
79
|
+
const defaultLabel = locale.isMaster ? " (default)" : "";
|
|
80
|
+
console.info(`${locale.id} ${locale.label}${defaultLabel}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const LocaleSchema = v.object({
|
|
86
|
+
id: v.string(),
|
|
87
|
+
label: v.string(),
|
|
88
|
+
customName: v.nullable(v.string()),
|
|
89
|
+
isMaster: v.boolean(),
|
|
90
|
+
});
|
|
91
|
+
export type Locale = v.InferOutput<typeof LocaleSchema>;
|
|
92
|
+
|
|
93
|
+
const GetLocalesResponseSchema = v.object({
|
|
94
|
+
results: v.array(LocaleSchema),
|
|
95
|
+
});
|
|
96
|
+
type GetLocalesResponse = v.InferOutput<typeof GetLocalesResponseSchema>;
|
|
97
|
+
|
|
98
|
+
export async function getLocales(repo: string): Promise<ParsedRequestResponse<GetLocalesResponse>> {
|
|
99
|
+
const url = new URL("/locale/repository/locales", await getInternalApiUrl());
|
|
100
|
+
url.searchParams.set("repository", repo);
|
|
101
|
+
return await request(url, { schema: GetLocalesResponseSchema });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function handleUnauthenticated() {
|
|
105
|
+
console.error("Not logged in. Run `prismic login` first.");
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
}
|