@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,81 @@
|
|
|
1
|
+
import { rm } from "node:fs/promises";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
|
|
4
|
+
import { findSliceModel } from "./lib/slice";
|
|
5
|
+
|
|
6
|
+
const HELP = `
|
|
7
|
+
Remove a slice from the project.
|
|
8
|
+
|
|
9
|
+
USAGE
|
|
10
|
+
prismic slice remove <slice-id> [flags]
|
|
11
|
+
|
|
12
|
+
ARGUMENTS
|
|
13
|
+
slice-id Slice identifier (required)
|
|
14
|
+
|
|
15
|
+
FLAGS
|
|
16
|
+
-y Confirm removal
|
|
17
|
+
-h, --help Show help for command
|
|
18
|
+
|
|
19
|
+
EXAMPLES
|
|
20
|
+
prismic slice remove MySlice
|
|
21
|
+
prismic slice remove MySlice -y
|
|
22
|
+
`.trim();
|
|
23
|
+
|
|
24
|
+
export async function sliceRemove(): Promise<void> {
|
|
25
|
+
const {
|
|
26
|
+
values: { help, y },
|
|
27
|
+
positionals: [sliceId],
|
|
28
|
+
} = parseArgs({
|
|
29
|
+
args: process.argv.slice(4), // skip: node, script, "slice", "remove"
|
|
30
|
+
options: {
|
|
31
|
+
y: { type: "boolean", short: "y" },
|
|
32
|
+
help: { type: "boolean", short: "h" },
|
|
33
|
+
},
|
|
34
|
+
allowPositionals: true,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (help) {
|
|
38
|
+
console.info(HELP);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!sliceId) {
|
|
43
|
+
console.error("Missing required argument: slice-id\n");
|
|
44
|
+
console.error("Usage: prismic slice remove <slice-id>");
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const result = await findSliceModel(sliceId);
|
|
50
|
+
if (!result.ok) {
|
|
51
|
+
console.error(result.error);
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { modelPath } = result;
|
|
57
|
+
const sliceDirectory = new URL(".", modelPath);
|
|
58
|
+
|
|
59
|
+
// Require -y flag to confirm deletion
|
|
60
|
+
if (!y) {
|
|
61
|
+
console.error(`Refusing to remove slice "${sliceId}" (this will delete the entire directory).`);
|
|
62
|
+
console.error("Re-run with -y to confirm.");
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Delete the slice directory
|
|
68
|
+
try {
|
|
69
|
+
await rm(sliceDirectory, { recursive: true });
|
|
70
|
+
} catch (error) {
|
|
71
|
+
if (error instanceof Error) {
|
|
72
|
+
console.error(`Failed to remove slice: ${error.message}`);
|
|
73
|
+
} else {
|
|
74
|
+
console.error("Failed to remove slice");
|
|
75
|
+
}
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.info(`Removed slice "${sliceId}"`);
|
|
81
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
|
|
4
|
+
import { stringify } from "./lib/json";
|
|
5
|
+
import { findSliceModel, getSlicesDirectory, pascalCase } from "./lib/slice";
|
|
6
|
+
|
|
7
|
+
const HELP = `
|
|
8
|
+
Rename a slice (updates name field, optionally id and directory).
|
|
9
|
+
|
|
10
|
+
USAGE
|
|
11
|
+
prismic slice rename <slice-id> <new-name> [flags]
|
|
12
|
+
|
|
13
|
+
ARGUMENTS
|
|
14
|
+
slice-id Current slice identifier (required)
|
|
15
|
+
new-name New display name (required)
|
|
16
|
+
|
|
17
|
+
FLAGS
|
|
18
|
+
--id string Also change the slice ID (renames directory)
|
|
19
|
+
-h, --help Show help for command
|
|
20
|
+
|
|
21
|
+
EXAMPLES
|
|
22
|
+
prismic slice rename MySlice "My New Name"
|
|
23
|
+
prismic slice rename MySlice "My New Name" --id NewSliceId
|
|
24
|
+
`.trim();
|
|
25
|
+
|
|
26
|
+
export async function sliceRename(): Promise<void> {
|
|
27
|
+
const {
|
|
28
|
+
values: { help, id: newId },
|
|
29
|
+
positionals: [sliceId, newName],
|
|
30
|
+
} = parseArgs({
|
|
31
|
+
args: process.argv.slice(4), // skip: node, script, "slice", "rename"
|
|
32
|
+
options: {
|
|
33
|
+
id: { type: "string" },
|
|
34
|
+
help: { type: "boolean", short: "h" },
|
|
35
|
+
},
|
|
36
|
+
allowPositionals: true,
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
if (help) {
|
|
40
|
+
console.info(HELP);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!sliceId) {
|
|
45
|
+
console.error("Missing required argument: slice-id\n");
|
|
46
|
+
console.error("Usage: prismic slice rename <slice-id> <new-name>");
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!newName) {
|
|
52
|
+
console.error("Missing required argument: new-name\n");
|
|
53
|
+
console.error("Usage: prismic slice rename <slice-id> <new-name>");
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = await findSliceModel(sliceId);
|
|
59
|
+
if (!result.ok) {
|
|
60
|
+
console.error(result.error);
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const { model, modelPath } = result;
|
|
66
|
+
|
|
67
|
+
// Update the model
|
|
68
|
+
model.name = newName;
|
|
69
|
+
if (newId) {
|
|
70
|
+
model.id = newId;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Write updated model
|
|
74
|
+
try {
|
|
75
|
+
await writeFile(modelPath, stringify(model));
|
|
76
|
+
} catch (error) {
|
|
77
|
+
if (error instanceof Error) {
|
|
78
|
+
console.error(`Failed to update slice: ${error.message}`);
|
|
79
|
+
} else {
|
|
80
|
+
console.error("Failed to update slice");
|
|
81
|
+
}
|
|
82
|
+
process.exitCode = 1;
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// If changing ID, also rename the directory
|
|
87
|
+
if (newId) {
|
|
88
|
+
const slicesDirectory = await getSlicesDirectory();
|
|
89
|
+
const currentDir = new URL(".", modelPath);
|
|
90
|
+
const newDir = new URL(pascalCase(newName) + "/", slicesDirectory);
|
|
91
|
+
|
|
92
|
+
if (currentDir.href !== newDir.href) {
|
|
93
|
+
try {
|
|
94
|
+
await rename(currentDir, newDir);
|
|
95
|
+
console.info(`Renamed slice "${sliceId}" to "${newId}" (${newName})`);
|
|
96
|
+
console.info(`Moved directory to ${newDir.href}`);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof Error) {
|
|
99
|
+
console.error(`Failed to rename directory: ${error.message}`);
|
|
100
|
+
} else {
|
|
101
|
+
console.error("Failed to rename directory");
|
|
102
|
+
}
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
} else {
|
|
107
|
+
console.info(`Renamed slice "${sliceId}" to "${newId}" (${newName})`);
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
console.info(`Renamed slice "${sliceId}" to "${newName}"`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { findSliceModel } from "./lib/slice";
|
|
4
|
+
|
|
5
|
+
const HELP = `
|
|
6
|
+
View details of a specific slice.
|
|
7
|
+
|
|
8
|
+
USAGE
|
|
9
|
+
prismic slice view <slice-id> [flags]
|
|
10
|
+
|
|
11
|
+
ARGUMENTS
|
|
12
|
+
slice-id Slice identifier (required)
|
|
13
|
+
|
|
14
|
+
FLAGS
|
|
15
|
+
--json Output as JSON
|
|
16
|
+
-h, --help Show help for command
|
|
17
|
+
|
|
18
|
+
EXAMPLES
|
|
19
|
+
prismic slice view MySlice
|
|
20
|
+
prismic slice view MySlice --json
|
|
21
|
+
`.trim();
|
|
22
|
+
|
|
23
|
+
export async function sliceView(): Promise<void> {
|
|
24
|
+
const {
|
|
25
|
+
values: { help, json },
|
|
26
|
+
positionals: [sliceId],
|
|
27
|
+
} = parseArgs({
|
|
28
|
+
args: process.argv.slice(4), // skip: node, script, "slice", "view"
|
|
29
|
+
options: {
|
|
30
|
+
json: { type: "boolean" },
|
|
31
|
+
help: { type: "boolean", short: "h" },
|
|
32
|
+
},
|
|
33
|
+
allowPositionals: true,
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
if (help) {
|
|
37
|
+
console.info(HELP);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!sliceId) {
|
|
42
|
+
console.error("Missing required argument: slice-id\n");
|
|
43
|
+
console.error("Usage: prismic slice view <slice-id>");
|
|
44
|
+
process.exitCode = 1;
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = await findSliceModel(sliceId);
|
|
49
|
+
if (!result.ok) {
|
|
50
|
+
console.error(result.error);
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const { model } = result;
|
|
56
|
+
|
|
57
|
+
if (json) {
|
|
58
|
+
console.info(JSON.stringify(model, null, 2));
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.info(`ID: ${model.id}`);
|
|
63
|
+
console.info(`Name: ${model.name}`);
|
|
64
|
+
if (model.description) {
|
|
65
|
+
console.info(`Description: ${model.description}`);
|
|
66
|
+
}
|
|
67
|
+
console.info(`Variations: ${model.variations.length}`);
|
|
68
|
+
|
|
69
|
+
console.info("\nVariations:");
|
|
70
|
+
for (const variation of model.variations) {
|
|
71
|
+
const primaryFields = Object.keys(variation.primary ?? {}).length;
|
|
72
|
+
const itemsFields = Object.keys(variation.items ?? {}).length;
|
|
73
|
+
console.info(
|
|
74
|
+
` - ${variation.id} (${variation.name}): ${primaryFields} primary fields, ${itemsFields} items fields`,
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/slice.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { sliceAddField } from "./slice-add-field";
|
|
4
|
+
import { sliceAddVariation } from "./slice-add-variation";
|
|
5
|
+
import { sliceCreate } from "./slice-create";
|
|
6
|
+
import { sliceList } from "./slice-list";
|
|
7
|
+
import { sliceListVariations } from "./slice-list-variations";
|
|
8
|
+
import { sliceRemove } from "./slice-remove";
|
|
9
|
+
import { sliceRemoveField } from "./slice-remove-field";
|
|
10
|
+
import { sliceRemoveVariation } from "./slice-remove-variation";
|
|
11
|
+
import { sliceRename } from "./slice-rename";
|
|
12
|
+
import { sliceView } from "./slice-view";
|
|
13
|
+
|
|
14
|
+
const HELP = `
|
|
15
|
+
Manage slices in a Prismic project.
|
|
16
|
+
|
|
17
|
+
USAGE
|
|
18
|
+
prismic slice <command> [flags]
|
|
19
|
+
|
|
20
|
+
COMMANDS
|
|
21
|
+
create Create a new slice
|
|
22
|
+
list List all slices
|
|
23
|
+
view View details of a slice
|
|
24
|
+
rename Rename a slice
|
|
25
|
+
remove Remove a slice
|
|
26
|
+
add-field Add a field to a slice
|
|
27
|
+
remove-field Remove a field from a slice
|
|
28
|
+
add-variation Add a variation to a slice
|
|
29
|
+
remove-variation Remove a variation from a slice
|
|
30
|
+
list-variations List all variations of a slice
|
|
31
|
+
|
|
32
|
+
FLAGS
|
|
33
|
+
-h, --help Show help for command
|
|
34
|
+
|
|
35
|
+
LEARN MORE
|
|
36
|
+
Use \`prismic slice <command> --help\` for more information about a command.
|
|
37
|
+
`.trim();
|
|
38
|
+
|
|
39
|
+
export async function slice(): Promise<void> {
|
|
40
|
+
const {
|
|
41
|
+
positionals: [subcommand],
|
|
42
|
+
} = parseArgs({
|
|
43
|
+
args: process.argv.slice(3), // skip: node, script, "slice"
|
|
44
|
+
options: {
|
|
45
|
+
help: { type: "boolean", short: "h" },
|
|
46
|
+
},
|
|
47
|
+
allowPositionals: true,
|
|
48
|
+
strict: false,
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
switch (subcommand) {
|
|
52
|
+
case "create":
|
|
53
|
+
await sliceCreate();
|
|
54
|
+
break;
|
|
55
|
+
case "list":
|
|
56
|
+
await sliceList();
|
|
57
|
+
break;
|
|
58
|
+
case "view":
|
|
59
|
+
await sliceView();
|
|
60
|
+
break;
|
|
61
|
+
case "rename":
|
|
62
|
+
await sliceRename();
|
|
63
|
+
break;
|
|
64
|
+
case "remove":
|
|
65
|
+
await sliceRemove();
|
|
66
|
+
break;
|
|
67
|
+
case "add-field":
|
|
68
|
+
await sliceAddField();
|
|
69
|
+
break;
|
|
70
|
+
case "remove-field":
|
|
71
|
+
await sliceRemoveField();
|
|
72
|
+
break;
|
|
73
|
+
case "add-variation":
|
|
74
|
+
await sliceAddVariation();
|
|
75
|
+
break;
|
|
76
|
+
case "remove-variation":
|
|
77
|
+
await sliceRemoveVariation();
|
|
78
|
+
break;
|
|
79
|
+
case "list-variations":
|
|
80
|
+
await sliceListVariations();
|
|
81
|
+
break;
|
|
82
|
+
default: {
|
|
83
|
+
if (subcommand) {
|
|
84
|
+
console.error(`Unknown slice subcommand: ${subcommand}\n`);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
}
|
|
87
|
+
console.info(HELP);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/sync.ts
ADDED
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import type { CustomType, SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
2
|
+
|
|
3
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import * as v from "valibot";
|
|
6
|
+
|
|
7
|
+
import { isAuthenticated, readHost, readToken } from "./lib/auth";
|
|
8
|
+
import { safeGetRepositoryFromConfig } from "./lib/config";
|
|
9
|
+
import { findUpward } from "./lib/file";
|
|
10
|
+
import { stringify } from "./lib/json";
|
|
11
|
+
import { getSlicesDirectory, pascalCase, SharedSliceSchema } from "./lib/slice";
|
|
12
|
+
|
|
13
|
+
const HELP = `
|
|
14
|
+
Sync custom types and slices from Prismic to local files.
|
|
15
|
+
|
|
16
|
+
By default, this command reads the repository from prismic.config.json at the
|
|
17
|
+
project root.
|
|
18
|
+
|
|
19
|
+
USAGE
|
|
20
|
+
prismic sync [flags]
|
|
21
|
+
|
|
22
|
+
FLAGS
|
|
23
|
+
-r, --repo string Repository domain
|
|
24
|
+
--dry-run Show what would be synced without writing files
|
|
25
|
+
--types-only Only sync custom types
|
|
26
|
+
--slices-only Only sync slices
|
|
27
|
+
--json Output as JSON
|
|
28
|
+
-h, --help Show help for command
|
|
29
|
+
|
|
30
|
+
EXAMPLES
|
|
31
|
+
prismic sync
|
|
32
|
+
prismic sync --repo my-repo
|
|
33
|
+
prismic sync --dry-run
|
|
34
|
+
prismic sync --types-only
|
|
35
|
+
`.trim();
|
|
36
|
+
|
|
37
|
+
const CustomTypeSchema = v.object({
|
|
38
|
+
id: v.string(),
|
|
39
|
+
label: v.optional(v.string()),
|
|
40
|
+
repeatable: v.boolean(),
|
|
41
|
+
status: v.boolean(),
|
|
42
|
+
format: v.optional(v.string()),
|
|
43
|
+
json: v.record(v.string(), v.unknown()),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export async function sync(): Promise<void> {
|
|
47
|
+
const {
|
|
48
|
+
values: { help, repo = await safeGetRepositoryFromConfig(), "dry-run": dryRun, "types-only": typesOnly, "slices-only": slicesOnly, json },
|
|
49
|
+
} = parseArgs({
|
|
50
|
+
args: process.argv.slice(3), // skip: node, script, "sync"
|
|
51
|
+
options: {
|
|
52
|
+
repo: { type: "string", short: "r" },
|
|
53
|
+
"dry-run": { type: "boolean" },
|
|
54
|
+
"types-only": { type: "boolean" },
|
|
55
|
+
"slices-only": { type: "boolean" },
|
|
56
|
+
json: { type: "boolean" },
|
|
57
|
+
help: { type: "boolean", short: "h" },
|
|
58
|
+
},
|
|
59
|
+
allowPositionals: false,
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
if (help) {
|
|
63
|
+
console.info(HELP);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!repo) {
|
|
68
|
+
console.error("Missing prismic.config.json or --repo option");
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Check authentication
|
|
74
|
+
if (!(await isAuthenticated())) {
|
|
75
|
+
console.error("Not logged in. Run `prismic login` first.");
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!json) {
|
|
81
|
+
console.info(`Syncing from repository: ${repo}\n`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Fetch remote data in parallel
|
|
85
|
+
const shouldFetchTypes = !slicesOnly;
|
|
86
|
+
const shouldFetchSlices = !typesOnly;
|
|
87
|
+
|
|
88
|
+
const [customTypesResult, slicesResult] = await Promise.all([
|
|
89
|
+
shouldFetchTypes ? fetchRemoteCustomTypes(repo) : Promise.resolve({ ok: true, value: [] } as const),
|
|
90
|
+
shouldFetchSlices ? fetchRemoteSlices(repo) : Promise.resolve({ ok: true, value: [] } as const),
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
if (!customTypesResult.ok) {
|
|
94
|
+
console.error(`Failed to fetch custom types: ${customTypesResult.error}`);
|
|
95
|
+
process.exitCode = 1;
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!slicesResult.ok) {
|
|
100
|
+
console.error(`Failed to fetch slices: ${slicesResult.error}`);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const customTypes = customTypesResult.value;
|
|
106
|
+
const slices = slicesResult.value;
|
|
107
|
+
|
|
108
|
+
if (!json) {
|
|
109
|
+
if (shouldFetchTypes) {
|
|
110
|
+
console.info(`Fetching custom types... ${customTypes.length} types`);
|
|
111
|
+
}
|
|
112
|
+
if (shouldFetchSlices) {
|
|
113
|
+
console.info(`Fetching slices... ${slices.length} slices`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Dry run - just show what would be synced
|
|
118
|
+
if (dryRun) {
|
|
119
|
+
if (json) {
|
|
120
|
+
console.info(stringify({ customTypes, slices }));
|
|
121
|
+
} else {
|
|
122
|
+
console.info("");
|
|
123
|
+
if (shouldFetchTypes && customTypes.length > 0) {
|
|
124
|
+
console.info("Would write custom types:");
|
|
125
|
+
for (const ct of customTypes) {
|
|
126
|
+
console.info(` customtypes/${ct.id}/index.json`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
if (shouldFetchSlices && slices.length > 0) {
|
|
130
|
+
const slicesDir = await getSlicesDirectory();
|
|
131
|
+
const relativeSlicesDir = getRelativePath(slicesDir);
|
|
132
|
+
console.info("Would write slices:");
|
|
133
|
+
for (const slice of slices) {
|
|
134
|
+
console.info(` ${relativeSlicesDir}${pascalCase(slice.name)}/model.json`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
console.info(`\nDry run complete: ${customTypes.length} custom types, ${slices.length} slices`);
|
|
138
|
+
}
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Find project root
|
|
143
|
+
const projectRoot = await findUpward("package.json");
|
|
144
|
+
if (!projectRoot) {
|
|
145
|
+
console.error("Could not find project root (no package.json found)");
|
|
146
|
+
process.exitCode = 1;
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const projectDir = new URL(".", projectRoot);
|
|
150
|
+
|
|
151
|
+
const writtenTypes: string[] = [];
|
|
152
|
+
const writtenSlices: string[] = [];
|
|
153
|
+
|
|
154
|
+
// Write custom types
|
|
155
|
+
if (shouldFetchTypes && customTypes.length > 0) {
|
|
156
|
+
if (!json) {
|
|
157
|
+
console.info("\nWriting custom types:");
|
|
158
|
+
}
|
|
159
|
+
const customTypesDir = new URL("customtypes/", projectDir);
|
|
160
|
+
|
|
161
|
+
for (const ct of customTypes) {
|
|
162
|
+
const typeDir = new URL(`${ct.id}/`, customTypesDir);
|
|
163
|
+
const modelPath = new URL("index.json", typeDir);
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
await mkdir(typeDir, { recursive: true });
|
|
167
|
+
await writeFile(modelPath, stringify(ct));
|
|
168
|
+
const relativePath = `customtypes/${ct.id}/index.json`;
|
|
169
|
+
writtenTypes.push(relativePath);
|
|
170
|
+
if (!json) {
|
|
171
|
+
console.info(` ${relativePath}`);
|
|
172
|
+
}
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`Failed to write custom type ${ct.id}: ${error instanceof Error ? error.message : error}`);
|
|
175
|
+
process.exitCode = 1;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Write slices
|
|
182
|
+
if (shouldFetchSlices && slices.length > 0) {
|
|
183
|
+
if (!json) {
|
|
184
|
+
console.info("\nWriting slices:");
|
|
185
|
+
}
|
|
186
|
+
const slicesDir = await getSlicesDirectory();
|
|
187
|
+
|
|
188
|
+
for (const slice of slices) {
|
|
189
|
+
const sliceDir = new URL(`${pascalCase(slice.name)}/`, slicesDir);
|
|
190
|
+
const modelPath = new URL("model.json", sliceDir);
|
|
191
|
+
|
|
192
|
+
try {
|
|
193
|
+
await mkdir(sliceDir, { recursive: true });
|
|
194
|
+
await writeFile(modelPath, stringify(slice));
|
|
195
|
+
const relativePath = `${getRelativePath(slicesDir)}${pascalCase(slice.name)}/model.json`;
|
|
196
|
+
writtenSlices.push(relativePath);
|
|
197
|
+
if (!json) {
|
|
198
|
+
console.info(` ${relativePath}`);
|
|
199
|
+
}
|
|
200
|
+
} catch (error) {
|
|
201
|
+
console.error(`Failed to write slice ${slice.name}: ${error instanceof Error ? error.message : error}`);
|
|
202
|
+
process.exitCode = 1;
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Output summary
|
|
209
|
+
if (json) {
|
|
210
|
+
console.info(stringify({ writtenTypes, writtenSlices }));
|
|
211
|
+
} else {
|
|
212
|
+
console.info(`\nSync complete: ${writtenTypes.length} custom types, ${writtenSlices.length} slices`);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
async function getCustomTypesApiUrl(): Promise<URL> {
|
|
217
|
+
const host = await readHost();
|
|
218
|
+
host.hostname = `customtypes.${host.hostname}`;
|
|
219
|
+
return host;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
type FetchResult<T> = { ok: true; value: T } | { ok: false; error: string };
|
|
223
|
+
|
|
224
|
+
async function fetchRemoteCustomTypes(repo: string): Promise<FetchResult<CustomType[]>> {
|
|
225
|
+
const token = await readToken();
|
|
226
|
+
if (!token) {
|
|
227
|
+
return { ok: false, error: "Not authenticated" };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
231
|
+
const url = new URL("customtypes", baseUrl);
|
|
232
|
+
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch(url, {
|
|
235
|
+
headers: {
|
|
236
|
+
Authorization: `Bearer ${token}`,
|
|
237
|
+
repository: repo,
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (!response.ok) {
|
|
242
|
+
if (response.status === 401) {
|
|
243
|
+
return { ok: false, error: "Unauthorized. Your session may have expired. Run `prismic login` again." };
|
|
244
|
+
}
|
|
245
|
+
if (response.status === 403) {
|
|
246
|
+
return { ok: false, error: `Access denied. You may not have access to repository "${repo}".` };
|
|
247
|
+
}
|
|
248
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const data = await response.json();
|
|
252
|
+
const result = v.safeParse(v.array(CustomTypeSchema), data);
|
|
253
|
+
if (!result.success) {
|
|
254
|
+
return { ok: false, error: "Invalid response from Custom Types API" };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return { ok: true, value: result.output as CustomType[] };
|
|
258
|
+
} catch (error) {
|
|
259
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async function fetchRemoteSlices(repo: string): Promise<FetchResult<SharedSlice[]>> {
|
|
264
|
+
const token = await readToken();
|
|
265
|
+
if (!token) {
|
|
266
|
+
return { ok: false, error: "Not authenticated" };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const baseUrl = await getCustomTypesApiUrl();
|
|
270
|
+
const url = new URL("slices", baseUrl);
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
const response = await fetch(url, {
|
|
274
|
+
headers: {
|
|
275
|
+
Authorization: `Bearer ${token}`,
|
|
276
|
+
repository: repo,
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
if (!response.ok) {
|
|
281
|
+
if (response.status === 401) {
|
|
282
|
+
return { ok: false, error: "Unauthorized. Your session may have expired. Run `prismic login` again." };
|
|
283
|
+
}
|
|
284
|
+
if (response.status === 403) {
|
|
285
|
+
return { ok: false, error: `Access denied. You may not have access to repository "${repo}".` };
|
|
286
|
+
}
|
|
287
|
+
return { ok: false, error: `API error: ${response.status} ${response.statusText}` };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const data = await response.json();
|
|
291
|
+
const result = v.safeParse(v.array(SharedSliceSchema), data);
|
|
292
|
+
if (!result.success) {
|
|
293
|
+
return { ok: false, error: "Invalid response from Custom Types API" };
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return { ok: true, value: result.output as SharedSlice[] };
|
|
297
|
+
} catch (error) {
|
|
298
|
+
return { ok: false, error: `Network error: ${error instanceof Error ? error.message : error}` };
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function getRelativePath(url: URL): string {
|
|
303
|
+
const cwd = process.cwd();
|
|
304
|
+
const path = url.pathname;
|
|
305
|
+
if (path.startsWith(cwd)) {
|
|
306
|
+
return path.slice(cwd.length + 1);
|
|
307
|
+
}
|
|
308
|
+
return path;
|
|
309
|
+
}
|