@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.
Files changed (119) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/dist/index.mjs +1996 -0
  4. package/package.json +52 -0
  5. package/src/custom-type-add-field-boolean.ts +171 -0
  6. package/src/custom-type-add-field-color.ts +158 -0
  7. package/src/custom-type-add-field-date.ts +161 -0
  8. package/src/custom-type-add-field-embed.ts +158 -0
  9. package/src/custom-type-add-field-geo-point.ts +155 -0
  10. package/src/custom-type-add-field-image.ts +158 -0
  11. package/src/custom-type-add-field-key-text.ts +158 -0
  12. package/src/custom-type-add-field-link.ts +180 -0
  13. package/src/custom-type-add-field-number.ts +190 -0
  14. package/src/custom-type-add-field-rich-text.ts +181 -0
  15. package/src/custom-type-add-field-select.ts +164 -0
  16. package/src/custom-type-add-field-timestamp.ts +161 -0
  17. package/src/custom-type-add-field-uid.ts +158 -0
  18. package/src/custom-type-add-field.ts +111 -0
  19. package/src/custom-type-connect-slice.ts +221 -0
  20. package/src/custom-type-create.ts +92 -0
  21. package/src/custom-type-disconnect-slice.ts +179 -0
  22. package/src/custom-type-list.ts +110 -0
  23. package/src/custom-type-remove-field.ts +161 -0
  24. package/src/custom-type-remove.ts +126 -0
  25. package/src/custom-type-set-name.ts +128 -0
  26. package/src/custom-type-view.ts +118 -0
  27. package/src/custom-type.ts +85 -0
  28. package/src/index.ts +100 -0
  29. package/src/init.ts +62 -0
  30. package/src/lib/auth.ts +60 -0
  31. package/src/lib/config.ts +111 -0
  32. package/src/lib/file.ts +49 -0
  33. package/src/lib/json.ts +3 -0
  34. package/src/lib/request.ts +116 -0
  35. package/src/lib/slice.ts +112 -0
  36. package/src/lib/url.ts +25 -0
  37. package/src/locale-add.ts +116 -0
  38. package/src/locale-list.ts +107 -0
  39. package/src/locale-remove.ts +88 -0
  40. package/src/locale-set-default.ts +131 -0
  41. package/src/locale.ts +60 -0
  42. package/src/login.ts +143 -0
  43. package/src/logout.ts +36 -0
  44. package/src/page-type-add-field-boolean.ts +171 -0
  45. package/src/page-type-add-field-color.ts +158 -0
  46. package/src/page-type-add-field-date.ts +161 -0
  47. package/src/page-type-add-field-embed.ts +158 -0
  48. package/src/page-type-add-field-geo-point.ts +155 -0
  49. package/src/page-type-add-field-image.ts +158 -0
  50. package/src/page-type-add-field-key-text.ts +158 -0
  51. package/src/page-type-add-field-link.ts +180 -0
  52. package/src/page-type-add-field-number.ts +190 -0
  53. package/src/page-type-add-field-rich-text.ts +181 -0
  54. package/src/page-type-add-field-select.ts +164 -0
  55. package/src/page-type-add-field-timestamp.ts +161 -0
  56. package/src/page-type-add-field-uid.ts +158 -0
  57. package/src/page-type-add-field.ts +111 -0
  58. package/src/page-type-connect-slice.ts +221 -0
  59. package/src/page-type-create.ts +93 -0
  60. package/src/page-type-disconnect-slice.ts +179 -0
  61. package/src/page-type-list.ts +109 -0
  62. package/src/page-type-remove-field.ts +161 -0
  63. package/src/page-type-remove.ts +126 -0
  64. package/src/page-type-set-name.ts +128 -0
  65. package/src/page-type-set-repeatable.ts +137 -0
  66. package/src/page-type-view.ts +118 -0
  67. package/src/page-type.ts +90 -0
  68. package/src/preview-add.ts +126 -0
  69. package/src/preview-list.ts +106 -0
  70. package/src/preview-remove.ts +109 -0
  71. package/src/preview-set-name.ts +137 -0
  72. package/src/preview.ts +60 -0
  73. package/src/repo-create.ts +136 -0
  74. package/src/repo-list.ts +100 -0
  75. package/src/repo-set-name.ts +102 -0
  76. package/src/repo-view.ts +113 -0
  77. package/src/repo.ts +60 -0
  78. package/src/slice-add-field-boolean.ts +150 -0
  79. package/src/slice-add-field-color.ts +137 -0
  80. package/src/slice-add-field-date.ts +137 -0
  81. package/src/slice-add-field-embed.ts +137 -0
  82. package/src/slice-add-field-geo-point.ts +134 -0
  83. package/src/slice-add-field-image.ts +134 -0
  84. package/src/slice-add-field-key-text.ts +137 -0
  85. package/src/slice-add-field-link.ts +155 -0
  86. package/src/slice-add-field-number.ts +137 -0
  87. package/src/slice-add-field-rich-text.ts +160 -0
  88. package/src/slice-add-field-select.ts +143 -0
  89. package/src/slice-add-field-timestamp.ts +137 -0
  90. package/src/slice-add-field.ts +106 -0
  91. package/src/slice-add-variation.ts +137 -0
  92. package/src/slice-create.ts +129 -0
  93. package/src/slice-list-variations.ts +67 -0
  94. package/src/slice-list.ts +88 -0
  95. package/src/slice-remove-field.ts +117 -0
  96. package/src/slice-remove-variation.ts +108 -0
  97. package/src/slice-remove.ts +81 -0
  98. package/src/slice-rename.ts +112 -0
  99. package/src/slice-view.ts +77 -0
  100. package/src/slice.ts +90 -0
  101. package/src/sync.ts +309 -0
  102. package/src/token-create.ts +185 -0
  103. package/src/token-delete.ts +161 -0
  104. package/src/token-list.ts +212 -0
  105. package/src/token-set-name.ts +165 -0
  106. package/src/token.ts +60 -0
  107. package/src/webhook-add-header.ts +118 -0
  108. package/src/webhook-create.ts +152 -0
  109. package/src/webhook-disable.ts +109 -0
  110. package/src/webhook-enable.ts +132 -0
  111. package/src/webhook-list.ts +93 -0
  112. package/src/webhook-remove-header.ts +117 -0
  113. package/src/webhook-remove.ts +106 -0
  114. package/src/webhook-set-triggers.ts +148 -0
  115. package/src/webhook-status.ts +90 -0
  116. package/src/webhook-test.ts +106 -0
  117. package/src/webhook-view.ts +147 -0
  118. package/src/webhook.ts +95 -0
  119. 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
+ }