@angeloashmore/prismic-cli-poc 0.0.0-canary.2ff9563
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 +1996 -0
- package/package.json +52 -0
- package/src/custom-type-add-field-boolean.ts +171 -0
- package/src/custom-type-add-field-color.ts +158 -0
- package/src/custom-type-add-field-date.ts +161 -0
- package/src/custom-type-add-field-embed.ts +158 -0
- package/src/custom-type-add-field-geo-point.ts +155 -0
- package/src/custom-type-add-field-image.ts +158 -0
- package/src/custom-type-add-field-key-text.ts +158 -0
- package/src/custom-type-add-field-link.ts +180 -0
- package/src/custom-type-add-field-number.ts +190 -0
- package/src/custom-type-add-field-rich-text.ts +181 -0
- package/src/custom-type-add-field-select.ts +164 -0
- package/src/custom-type-add-field-timestamp.ts +161 -0
- package/src/custom-type-add-field-uid.ts +158 -0
- package/src/custom-type-add-field.ts +111 -0
- package/src/custom-type-connect-slice.ts +221 -0
- package/src/custom-type-create.ts +92 -0
- package/src/custom-type-disconnect-slice.ts +179 -0
- package/src/custom-type-list.ts +110 -0
- package/src/custom-type-remove-field.ts +161 -0
- package/src/custom-type-remove.ts +126 -0
- package/src/custom-type-set-name.ts +128 -0
- package/src/custom-type-view.ts +118 -0
- package/src/custom-type.ts +85 -0
- package/src/index.ts +100 -0
- package/src/init.ts +62 -0
- package/src/lib/auth.ts +60 -0
- package/src/lib/config.ts +111 -0
- package/src/lib/file.ts +49 -0
- package/src/lib/json.ts +3 -0
- package/src/lib/request.ts +116 -0
- package/src/lib/slice.ts +112 -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 +143 -0
- package/src/logout.ts +36 -0
- package/src/page-type-add-field-boolean.ts +171 -0
- package/src/page-type-add-field-color.ts +158 -0
- package/src/page-type-add-field-date.ts +161 -0
- package/src/page-type-add-field-embed.ts +158 -0
- package/src/page-type-add-field-geo-point.ts +155 -0
- package/src/page-type-add-field-image.ts +158 -0
- package/src/page-type-add-field-key-text.ts +158 -0
- package/src/page-type-add-field-link.ts +180 -0
- package/src/page-type-add-field-number.ts +190 -0
- package/src/page-type-add-field-rich-text.ts +181 -0
- package/src/page-type-add-field-select.ts +164 -0
- package/src/page-type-add-field-timestamp.ts +161 -0
- package/src/page-type-add-field-uid.ts +158 -0
- package/src/page-type-add-field.ts +111 -0
- package/src/page-type-connect-slice.ts +221 -0
- package/src/page-type-create.ts +93 -0
- package/src/page-type-disconnect-slice.ts +179 -0
- package/src/page-type-list.ts +109 -0
- package/src/page-type-remove-field.ts +161 -0
- package/src/page-type-remove.ts +126 -0
- package/src/page-type-set-name.ts +128 -0
- package/src/page-type-set-repeatable.ts +137 -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-list.ts +106 -0
- package/src/preview-remove.ts +109 -0
- package/src/preview-set-name.ts +137 -0
- package/src/preview.ts +60 -0
- package/src/repo-create.ts +136 -0
- package/src/repo-list.ts +100 -0
- package/src/repo-set-name.ts +102 -0
- package/src/repo-view.ts +113 -0
- package/src/repo.ts +60 -0
- package/src/slice-add-field-boolean.ts +150 -0
- package/src/slice-add-field-color.ts +137 -0
- package/src/slice-add-field-date.ts +137 -0
- package/src/slice-add-field-embed.ts +137 -0
- package/src/slice-add-field-geo-point.ts +134 -0
- package/src/slice-add-field-image.ts +134 -0
- package/src/slice-add-field-key-text.ts +137 -0
- package/src/slice-add-field-link.ts +155 -0
- package/src/slice-add-field-number.ts +137 -0
- package/src/slice-add-field-rich-text.ts +160 -0
- package/src/slice-add-field-select.ts +143 -0
- package/src/slice-add-field-timestamp.ts +137 -0
- package/src/slice-add-field.ts +106 -0
- package/src/slice-add-variation.ts +137 -0
- package/src/slice-create.ts +129 -0
- package/src/slice-list-variations.ts +67 -0
- package/src/slice-list.ts +88 -0
- package/src/slice-remove-field.ts +117 -0
- package/src/slice-remove-variation.ts +108 -0
- package/src/slice-remove.ts +81 -0
- package/src/slice-rename.ts +112 -0
- package/src/slice-view.ts +77 -0
- package/src/slice.ts +90 -0
- package/src/sync.ts +309 -0
- package/src/token-create.ts +185 -0
- package/src/token-delete.ts +161 -0
- package/src/token-list.ts +212 -0
- package/src/token-set-name.ts +165 -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,118 @@
|
|
|
1
|
+
import type { CustomType } from "@prismicio/types-internal/lib/customtypes";
|
|
2
|
+
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import * as v from "valibot";
|
|
6
|
+
|
|
7
|
+
import { findUpward } from "./lib/file";
|
|
8
|
+
|
|
9
|
+
const HELP = `
|
|
10
|
+
View details of a specific custom type.
|
|
11
|
+
|
|
12
|
+
USAGE
|
|
13
|
+
prismic custom-type view <type-id> [flags]
|
|
14
|
+
|
|
15
|
+
ARGUMENTS
|
|
16
|
+
type-id Custom type identifier (required)
|
|
17
|
+
|
|
18
|
+
FLAGS
|
|
19
|
+
--json Output as JSON
|
|
20
|
+
-h, --help Show help for command
|
|
21
|
+
|
|
22
|
+
EXAMPLES
|
|
23
|
+
prismic custom-type view settings
|
|
24
|
+
prismic custom-type view settings --json
|
|
25
|
+
`.trim();
|
|
26
|
+
|
|
27
|
+
const CustomTypeSchema = v.object({
|
|
28
|
+
id: v.string(),
|
|
29
|
+
label: v.string(),
|
|
30
|
+
repeatable: v.boolean(),
|
|
31
|
+
status: v.boolean(),
|
|
32
|
+
format: v.optional(v.string()),
|
|
33
|
+
json: v.record(v.string(), v.record(v.string(), v.unknown())),
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export async function customTypeView(): Promise<void> {
|
|
37
|
+
const {
|
|
38
|
+
values: { help, json },
|
|
39
|
+
positionals: [typeId],
|
|
40
|
+
} = parseArgs({
|
|
41
|
+
args: process.argv.slice(4), // skip: node, script, "custom-type", "view"
|
|
42
|
+
options: {
|
|
43
|
+
json: { type: "boolean" },
|
|
44
|
+
help: { type: "boolean", short: "h" },
|
|
45
|
+
},
|
|
46
|
+
allowPositionals: true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
if (help) {
|
|
50
|
+
console.info(HELP);
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (!typeId) {
|
|
55
|
+
console.error("Missing required argument: type-id\n");
|
|
56
|
+
console.error("Usage: prismic custom-type view <type-id>");
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const projectRoot = await findUpward("package.json");
|
|
62
|
+
if (!projectRoot) {
|
|
63
|
+
console.error("Could not find project root (no package.json found)");
|
|
64
|
+
process.exitCode = 1;
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const modelPath = new URL(`customtypes/${typeId}/index.json`, projectRoot);
|
|
69
|
+
|
|
70
|
+
let model: CustomType;
|
|
71
|
+
try {
|
|
72
|
+
const contents = await readFile(modelPath, "utf8");
|
|
73
|
+
const result = v.safeParse(CustomTypeSchema, JSON.parse(contents));
|
|
74
|
+
if (!result.success) {
|
|
75
|
+
console.error(`Invalid custom type model: ${modelPath.href}`);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
model = result.output as CustomType;
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
82
|
+
console.error(`Custom type not found: ${typeId}\n`);
|
|
83
|
+
console.error(`Create it first with: prismic custom-type create ${typeId}`);
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
console.error(`Failed to read custom type: ${error.message}`);
|
|
89
|
+
} else {
|
|
90
|
+
console.error("Failed to read custom type");
|
|
91
|
+
}
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check if this is actually a custom type (not a page type)
|
|
97
|
+
if (model.format === "page") {
|
|
98
|
+
console.error(`"${typeId}" is not a custom type (format: page)`);
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (json) {
|
|
104
|
+
console.info(JSON.stringify(model, null, 2));
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.info(`ID: ${model.id}`);
|
|
109
|
+
console.info(`Label: ${model.label}`);
|
|
110
|
+
console.info(`Repeatable: ${model.repeatable}`);
|
|
111
|
+
|
|
112
|
+
const tabs = Object.entries(model.json);
|
|
113
|
+
console.info(`\nTabs (${tabs.length}):`);
|
|
114
|
+
for (const [tabName, tabFields] of tabs) {
|
|
115
|
+
const fieldCount = Object.keys(tabFields).length;
|
|
116
|
+
console.info(` - ${tabName}: ${fieldCount} fields`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { customTypeAddField } from "./custom-type-add-field";
|
|
4
|
+
import { customTypeConnectSlice } from "./custom-type-connect-slice";
|
|
5
|
+
import { customTypeCreate } from "./custom-type-create";
|
|
6
|
+
import { customTypeDisconnectSlice } from "./custom-type-disconnect-slice";
|
|
7
|
+
import { customTypeList } from "./custom-type-list";
|
|
8
|
+
import { customTypeRemove } from "./custom-type-remove";
|
|
9
|
+
import { customTypeRemoveField } from "./custom-type-remove-field";
|
|
10
|
+
import { customTypeSetName } from "./custom-type-set-name";
|
|
11
|
+
import { customTypeView } from "./custom-type-view";
|
|
12
|
+
|
|
13
|
+
const HELP = `
|
|
14
|
+
Manage custom types in a Prismic repository.
|
|
15
|
+
|
|
16
|
+
USAGE
|
|
17
|
+
prismic custom-type <command> [flags]
|
|
18
|
+
|
|
19
|
+
COMMANDS
|
|
20
|
+
create Create a new custom type
|
|
21
|
+
list List all custom types
|
|
22
|
+
view View details of a custom type
|
|
23
|
+
remove Remove a custom type
|
|
24
|
+
set-name Change a custom type's display name
|
|
25
|
+
add-field Add a field to a custom type
|
|
26
|
+
remove-field Remove a field from a custom type
|
|
27
|
+
connect-slice Connect a shared slice to a custom type
|
|
28
|
+
disconnect-slice Disconnect a shared slice from a custom type
|
|
29
|
+
|
|
30
|
+
FLAGS
|
|
31
|
+
-h, --help Show help for command
|
|
32
|
+
|
|
33
|
+
LEARN MORE
|
|
34
|
+
Use \`prismic custom-type <command> --help\` for more information about a command.
|
|
35
|
+
`.trim();
|
|
36
|
+
|
|
37
|
+
export async function customType(): Promise<void> {
|
|
38
|
+
const {
|
|
39
|
+
positionals: [subcommand],
|
|
40
|
+
} = parseArgs({
|
|
41
|
+
args: process.argv.slice(3), // skip: node, script, "custom-type"
|
|
42
|
+
options: {
|
|
43
|
+
help: { type: "boolean", short: "h" },
|
|
44
|
+
},
|
|
45
|
+
allowPositionals: true,
|
|
46
|
+
strict: false,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
switch (subcommand) {
|
|
50
|
+
case "create":
|
|
51
|
+
await customTypeCreate();
|
|
52
|
+
break;
|
|
53
|
+
case "list":
|
|
54
|
+
await customTypeList();
|
|
55
|
+
break;
|
|
56
|
+
case "view":
|
|
57
|
+
await customTypeView();
|
|
58
|
+
break;
|
|
59
|
+
case "remove":
|
|
60
|
+
await customTypeRemove();
|
|
61
|
+
break;
|
|
62
|
+
case "set-name":
|
|
63
|
+
await customTypeSetName();
|
|
64
|
+
break;
|
|
65
|
+
case "add-field":
|
|
66
|
+
await customTypeAddField();
|
|
67
|
+
break;
|
|
68
|
+
case "remove-field":
|
|
69
|
+
await customTypeRemoveField();
|
|
70
|
+
break;
|
|
71
|
+
case "connect-slice":
|
|
72
|
+
await customTypeConnectSlice();
|
|
73
|
+
break;
|
|
74
|
+
case "disconnect-slice":
|
|
75
|
+
await customTypeDisconnectSlice();
|
|
76
|
+
break;
|
|
77
|
+
default: {
|
|
78
|
+
if (subcommand) {
|
|
79
|
+
console.error(`Unknown custom-type subcommand: ${subcommand}\n`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
}
|
|
82
|
+
console.info(HELP);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
|
|
5
|
+
import { customType } from "./custom-type";
|
|
6
|
+
import { init } from "./init";
|
|
7
|
+
import { locale } from "./locale";
|
|
8
|
+
import { login } from "./login";
|
|
9
|
+
import { logout } from "./logout";
|
|
10
|
+
import { pageType } from "./page-type";
|
|
11
|
+
import { preview } from "./preview";
|
|
12
|
+
import { repo } from "./repo";
|
|
13
|
+
import { slice } from "./slice";
|
|
14
|
+
import { sync } from "./sync";
|
|
15
|
+
import { token } from "./token";
|
|
16
|
+
import { webhook } from "./webhook";
|
|
17
|
+
import { whoami } from "./whoami";
|
|
18
|
+
|
|
19
|
+
const HELP = `
|
|
20
|
+
Prismic CLI for managing repositories and configurations.
|
|
21
|
+
|
|
22
|
+
USAGE
|
|
23
|
+
prismic <command> [flags]
|
|
24
|
+
|
|
25
|
+
COMMANDS
|
|
26
|
+
init Initialize a Prismic project
|
|
27
|
+
login Log in to Prismic
|
|
28
|
+
logout Log out of Prismic
|
|
29
|
+
whoami Show the currently logged in user
|
|
30
|
+
repo Manage Prismic repositories
|
|
31
|
+
locale Manage locales in a repository
|
|
32
|
+
page-type Manage page types in a repository
|
|
33
|
+
custom-type Manage custom types in a repository
|
|
34
|
+
slice Manage slices in a project
|
|
35
|
+
sync Sync types and slices from Prismic
|
|
36
|
+
preview Manage preview configurations
|
|
37
|
+
token Manage API tokens in a repository
|
|
38
|
+
webhook Manage webhooks in a repository
|
|
39
|
+
|
|
40
|
+
FLAGS
|
|
41
|
+
-h, --help Show help for command
|
|
42
|
+
|
|
43
|
+
LEARN MORE
|
|
44
|
+
Use \`prismic <command> --help\` for more information about a command.
|
|
45
|
+
`.trim();
|
|
46
|
+
|
|
47
|
+
const { positionals } = parseArgs({
|
|
48
|
+
options: { help: { type: "boolean", short: "h" } },
|
|
49
|
+
allowPositionals: true,
|
|
50
|
+
strict: false,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
switch (positionals[0]) {
|
|
54
|
+
case "init":
|
|
55
|
+
await init();
|
|
56
|
+
break;
|
|
57
|
+
case "login":
|
|
58
|
+
await login();
|
|
59
|
+
break;
|
|
60
|
+
case "logout":
|
|
61
|
+
await logout();
|
|
62
|
+
break;
|
|
63
|
+
case "whoami":
|
|
64
|
+
await whoami();
|
|
65
|
+
break;
|
|
66
|
+
case "repo":
|
|
67
|
+
await repo();
|
|
68
|
+
break;
|
|
69
|
+
case "locale":
|
|
70
|
+
await locale();
|
|
71
|
+
break;
|
|
72
|
+
case "page-type":
|
|
73
|
+
await pageType();
|
|
74
|
+
break;
|
|
75
|
+
case "custom-type":
|
|
76
|
+
await customType();
|
|
77
|
+
break;
|
|
78
|
+
case "slice":
|
|
79
|
+
await slice();
|
|
80
|
+
break;
|
|
81
|
+
case "sync":
|
|
82
|
+
await sync();
|
|
83
|
+
break;
|
|
84
|
+
case "preview":
|
|
85
|
+
await preview();
|
|
86
|
+
break;
|
|
87
|
+
case "token":
|
|
88
|
+
await token();
|
|
89
|
+
break;
|
|
90
|
+
case "webhook":
|
|
91
|
+
await webhook();
|
|
92
|
+
break;
|
|
93
|
+
default: {
|
|
94
|
+
if (positionals[0]) {
|
|
95
|
+
console.error(`Unknown command: ${positionals[0]}`);
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
}
|
|
98
|
+
console.info(HELP);
|
|
99
|
+
}
|
|
100
|
+
}
|
package/src/init.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { createConfig, readConfig, UnknownProjectRoot } from "./lib/config";
|
|
4
|
+
|
|
5
|
+
const HELP = `
|
|
6
|
+
Initialize a Prismic project by creating a prismic.config.json file.
|
|
7
|
+
|
|
8
|
+
Use this command to connect an existing Prismic repository to your project.
|
|
9
|
+
To create a new repository, use \`prismic repo create\` instead.
|
|
10
|
+
|
|
11
|
+
USAGE
|
|
12
|
+
prismic init [flags]
|
|
13
|
+
|
|
14
|
+
FLAGS
|
|
15
|
+
-r, --repo Repository name (required)
|
|
16
|
+
-h, --help Show help for command
|
|
17
|
+
|
|
18
|
+
LEARN MORE
|
|
19
|
+
Use \`prismic <command> --help\` for more information about a command.
|
|
20
|
+
`.trim();
|
|
21
|
+
|
|
22
|
+
export async function init(): Promise<void> {
|
|
23
|
+
const { values } = parseArgs({
|
|
24
|
+
args: process.argv.slice(3),
|
|
25
|
+
options: {
|
|
26
|
+
help: { type: "boolean", short: "h" },
|
|
27
|
+
repo: { type: "string", short: "r" },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (values.help) {
|
|
32
|
+
console.info(HELP);
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!values.repo) {
|
|
37
|
+
console.error("Missing required flag: --repo");
|
|
38
|
+
process.exitCode = 1;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const existingConfig = await readConfig();
|
|
43
|
+
if (existingConfig.ok) {
|
|
44
|
+
console.error("A prismic.config.json file already exists.");
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = await createConfig({ repositoryName: values.repo });
|
|
50
|
+
|
|
51
|
+
if (!result.ok) {
|
|
52
|
+
if (result.error instanceof UnknownProjectRoot) {
|
|
53
|
+
console.error("Could not find a package.json file. Run this command from a project directory.");
|
|
54
|
+
} else {
|
|
55
|
+
console.error("Failed to create config file.");
|
|
56
|
+
}
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.info(`Created prismic.config.json for repository "${values.repo}"`);
|
|
62
|
+
}
|
package/src/lib/auth.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { access, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { pathToFileURL } from "node:url";
|
|
4
|
+
|
|
5
|
+
import { appendTrailingSlash } from "./url";
|
|
6
|
+
|
|
7
|
+
const AUTH_FILE_PATH = new URL(".prismic", appendTrailingSlash(pathToFileURL(homedir())));
|
|
8
|
+
const DEFAULT_HOST = "https://prismic.io";
|
|
9
|
+
|
|
10
|
+
type AuthContents = {
|
|
11
|
+
token?: string;
|
|
12
|
+
host?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export async function saveToken(token: string, options?: { host?: string }): Promise<void> {
|
|
16
|
+
const contents: AuthContents = { token, host: options?.host };
|
|
17
|
+
await writeFile(AUTH_FILE_PATH, JSON.stringify(contents, null, 2));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function isAuthenticated(): Promise<boolean> {
|
|
21
|
+
const token = await readToken();
|
|
22
|
+
return Boolean(token);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function readToken(): Promise<string | undefined> {
|
|
26
|
+
const auth = await readAuthFile();
|
|
27
|
+
return auth?.token;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function readHost(): Promise<URL> {
|
|
31
|
+
try {
|
|
32
|
+
const auth = await readAuthFile();
|
|
33
|
+
if (!auth?.host) return new URL(DEFAULT_HOST);
|
|
34
|
+
return new URL(auth.host);
|
|
35
|
+
} catch {
|
|
36
|
+
return new URL(DEFAULT_HOST);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function readAuthFile(): Promise<AuthContents | undefined> {
|
|
41
|
+
try {
|
|
42
|
+
const contents = await readFile(AUTH_FILE_PATH, "utf-8");
|
|
43
|
+
return JSON.parse(contents);
|
|
44
|
+
} catch {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function removeToken(): Promise<boolean> {
|
|
50
|
+
try {
|
|
51
|
+
await access(AUTH_FILE_PATH);
|
|
52
|
+
} catch {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const auth = await readAuthFile();
|
|
57
|
+
if (!auth) return false;
|
|
58
|
+
await rm(AUTH_FILE_PATH);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
@@ -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
|
+
}
|
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,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 {}
|