@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.
Files changed (139) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/dist/index.mjs +2508 -0
  4. package/package.json +53 -0
  5. package/src/codegen-types.ts +82 -0
  6. package/src/codegen.ts +45 -0
  7. package/src/custom-type-add-field-boolean.ts +222 -0
  8. package/src/custom-type-add-field-color.ts +205 -0
  9. package/src/custom-type-add-field-date.ts +208 -0
  10. package/src/custom-type-add-field-embed.ts +205 -0
  11. package/src/custom-type-add-field-geo-point.ts +202 -0
  12. package/src/custom-type-add-field-group.ts +179 -0
  13. package/src/custom-type-add-field-image.ts +205 -0
  14. package/src/custom-type-add-field-key-text.ts +205 -0
  15. package/src/custom-type-add-field-link.ts +228 -0
  16. package/src/custom-type-add-field-number.ts +237 -0
  17. package/src/custom-type-add-field-rich-text.ts +229 -0
  18. package/src/custom-type-add-field-select.ts +211 -0
  19. package/src/custom-type-add-field-timestamp.ts +208 -0
  20. package/src/custom-type-add-field-uid.ts +188 -0
  21. package/src/custom-type-add-field.ts +116 -0
  22. package/src/custom-type-connect-slice.ts +214 -0
  23. package/src/custom-type-create.ts +112 -0
  24. package/src/custom-type-disconnect-slice.ts +171 -0
  25. package/src/custom-type-list.ts +110 -0
  26. package/src/custom-type-remove-field.ts +171 -0
  27. package/src/custom-type-remove.ts +138 -0
  28. package/src/custom-type-set-name.ts +138 -0
  29. package/src/custom-type-view.ts +118 -0
  30. package/src/custom-type.ts +85 -0
  31. package/src/docs-fetch.ts +146 -0
  32. package/src/docs-list.ts +131 -0
  33. package/src/docs.ts +54 -0
  34. package/src/index.ts +132 -0
  35. package/src/init.ts +64 -0
  36. package/src/lib/auth.ts +83 -0
  37. package/src/lib/config.ts +111 -0
  38. package/src/lib/custom-types-api.ts +438 -0
  39. package/src/lib/field-path.ts +81 -0
  40. package/src/lib/file.ts +49 -0
  41. package/src/lib/framework.ts +143 -0
  42. package/src/lib/json.ts +3 -0
  43. package/src/lib/request.ts +116 -0
  44. package/src/lib/slice.ts +115 -0
  45. package/src/lib/string.ts +6 -0
  46. package/src/lib/url.ts +25 -0
  47. package/src/locale-add.ts +116 -0
  48. package/src/locale-list.ts +107 -0
  49. package/src/locale-remove.ts +88 -0
  50. package/src/locale-set-default.ts +131 -0
  51. package/src/locale.ts +60 -0
  52. package/src/login.ts +152 -0
  53. package/src/logout.ts +36 -0
  54. package/src/page-type-add-field-boolean.ts +238 -0
  55. package/src/page-type-add-field-color.ts +224 -0
  56. package/src/page-type-add-field-date.ts +227 -0
  57. package/src/page-type-add-field-embed.ts +224 -0
  58. package/src/page-type-add-field-geo-point.ts +221 -0
  59. package/src/page-type-add-field-group.ts +198 -0
  60. package/src/page-type-add-field-image.ts +224 -0
  61. package/src/page-type-add-field-key-text.ts +224 -0
  62. package/src/page-type-add-field-link.ts +247 -0
  63. package/src/page-type-add-field-number.ts +256 -0
  64. package/src/page-type-add-field-rich-text.ts +248 -0
  65. package/src/page-type-add-field-select.ts +230 -0
  66. package/src/page-type-add-field-timestamp.ts +227 -0
  67. package/src/page-type-add-field-uid.ts +207 -0
  68. package/src/page-type-add-field.ts +116 -0
  69. package/src/page-type-connect-slice.ts +214 -0
  70. package/src/page-type-create.ts +161 -0
  71. package/src/page-type-disconnect-slice.ts +171 -0
  72. package/src/page-type-list.ts +109 -0
  73. package/src/page-type-remove-field.ts +171 -0
  74. package/src/page-type-remove.ts +138 -0
  75. package/src/page-type-set-name.ts +138 -0
  76. package/src/page-type-set-repeatable.ts +147 -0
  77. package/src/page-type-view.ts +118 -0
  78. package/src/page-type.ts +90 -0
  79. package/src/preview-add.ts +126 -0
  80. package/src/preview-get-simulator.ts +104 -0
  81. package/src/preview-list.ts +106 -0
  82. package/src/preview-remove-simulator.ts +80 -0
  83. package/src/preview-remove.ts +109 -0
  84. package/src/preview-set-name.ts +137 -0
  85. package/src/preview-set-simulator.ts +116 -0
  86. package/src/preview.ts +75 -0
  87. package/src/pull.ts +242 -0
  88. package/src/push.ts +405 -0
  89. package/src/repo-create.ts +195 -0
  90. package/src/repo-get-access.ts +86 -0
  91. package/src/repo-list.ts +100 -0
  92. package/src/repo-set-access.ts +100 -0
  93. package/src/repo-set-name.ts +102 -0
  94. package/src/repo-view.ts +113 -0
  95. package/src/repo.ts +70 -0
  96. package/src/slice-add-field-boolean.ts +240 -0
  97. package/src/slice-add-field-color.ts +226 -0
  98. package/src/slice-add-field-date.ts +226 -0
  99. package/src/slice-add-field-embed.ts +226 -0
  100. package/src/slice-add-field-geo-point.ts +223 -0
  101. package/src/slice-add-field-group.ts +191 -0
  102. package/src/slice-add-field-image.ts +223 -0
  103. package/src/slice-add-field-key-text.ts +226 -0
  104. package/src/slice-add-field-link.ts +245 -0
  105. package/src/slice-add-field-number.ts +226 -0
  106. package/src/slice-add-field-rich-text.ts +250 -0
  107. package/src/slice-add-field-select.ts +232 -0
  108. package/src/slice-add-field-timestamp.ts +226 -0
  109. package/src/slice-add-field.ts +111 -0
  110. package/src/slice-add-variation.ts +139 -0
  111. package/src/slice-create.ts +203 -0
  112. package/src/slice-list-variations.ts +67 -0
  113. package/src/slice-list.ts +88 -0
  114. package/src/slice-remove-field.ts +122 -0
  115. package/src/slice-remove-variation.ts +112 -0
  116. package/src/slice-remove.ts +91 -0
  117. package/src/slice-rename.ts +122 -0
  118. package/src/slice-set-screenshot.ts +235 -0
  119. package/src/slice-view.ts +80 -0
  120. package/src/slice.ts +95 -0
  121. package/src/status.ts +873 -0
  122. package/src/token-create.ts +203 -0
  123. package/src/token-delete.ts +182 -0
  124. package/src/token-list.ts +223 -0
  125. package/src/token-set-name.ts +193 -0
  126. package/src/token.ts +60 -0
  127. package/src/webhook-add-header.ts +118 -0
  128. package/src/webhook-create.ts +152 -0
  129. package/src/webhook-disable.ts +109 -0
  130. package/src/webhook-enable.ts +132 -0
  131. package/src/webhook-list.ts +93 -0
  132. package/src/webhook-remove-header.ts +117 -0
  133. package/src/webhook-remove.ts +106 -0
  134. package/src/webhook-set-triggers.ts +148 -0
  135. package/src/webhook-status.ts +90 -0
  136. package/src/webhook-test.ts +106 -0
  137. package/src/webhook-view.ts +147 -0
  138. package/src/webhook.ts +95 -0
  139. package/src/whoami.ts +62 -0
@@ -0,0 +1,116 @@
1
+ import { parseArgs } from "node:util";
2
+
3
+ import { pageTypeAddFieldBoolean } from "./page-type-add-field-boolean";
4
+ import { pageTypeAddFieldColor } from "./page-type-add-field-color";
5
+ import { pageTypeAddFieldDate } from "./page-type-add-field-date";
6
+ import { pageTypeAddFieldEmbed } from "./page-type-add-field-embed";
7
+ import { pageTypeAddFieldGeoPoint } from "./page-type-add-field-geo-point";
8
+ import { pageTypeAddFieldGroup } from "./page-type-add-field-group";
9
+ import { pageTypeAddFieldImage } from "./page-type-add-field-image";
10
+ import { pageTypeAddFieldKeyText } from "./page-type-add-field-key-text";
11
+ import { pageTypeAddFieldLink } from "./page-type-add-field-link";
12
+ import { pageTypeAddFieldNumber } from "./page-type-add-field-number";
13
+ import { pageTypeAddFieldRichText } from "./page-type-add-field-rich-text";
14
+ import { pageTypeAddFieldSelect } from "./page-type-add-field-select";
15
+ import { pageTypeAddFieldTimestamp } from "./page-type-add-field-timestamp";
16
+ import { pageTypeAddFieldUid } from "./page-type-add-field-uid";
17
+
18
+ const HELP = `
19
+ Add a field to an existing page type.
20
+
21
+ USAGE
22
+ prismic page-type add-field <field-type> <type-id> <field-id> [flags]
23
+
24
+ FIELD TYPES
25
+ boolean Boolean toggle
26
+ color Color picker
27
+ date Date picker
28
+ embed Embed (oEmbed)
29
+ geo-point Geographic coordinates
30
+ group Repeatable group of fields
31
+ image Image
32
+ key-text Single-line text
33
+ link Any link type
34
+ number Number
35
+ rich-text Rich text editor
36
+ select Dropdown select
37
+ timestamp Date and time
38
+ uid Unique identifier
39
+
40
+ FLAGS
41
+ -h, --help Show help for command
42
+
43
+ LEARN MORE
44
+ Use \`prismic page-type add-field <field-type> --help\` for more information.
45
+
46
+ EXAMPLES
47
+ prismic page-type add-field key-text homepage meta_title --tab "SEO"
48
+ prismic page-type add-field link homepage button --allow-text
49
+ prismic page-type add-field rich-text homepage body --multi "paragraph,heading2,strong,em"
50
+ prismic page-type add-field select homepage layout --option "full" --option "sidebar"
51
+ `.trim();
52
+
53
+ export async function pageTypeAddField(): Promise<void> {
54
+ const {
55
+ positionals: [fieldType],
56
+ } = parseArgs({
57
+ args: process.argv.slice(4), // skip: node, script, "page-type", "add-field"
58
+ options: {
59
+ help: { type: "boolean", short: "h" },
60
+ },
61
+ allowPositionals: true,
62
+ strict: false,
63
+ });
64
+
65
+ switch (fieldType) {
66
+ case "boolean":
67
+ await pageTypeAddFieldBoolean();
68
+ break;
69
+ case "color":
70
+ await pageTypeAddFieldColor();
71
+ break;
72
+ case "date":
73
+ await pageTypeAddFieldDate();
74
+ break;
75
+ case "embed":
76
+ await pageTypeAddFieldEmbed();
77
+ break;
78
+ case "geo-point":
79
+ await pageTypeAddFieldGeoPoint();
80
+ break;
81
+ case "group":
82
+ await pageTypeAddFieldGroup();
83
+ break;
84
+ case "image":
85
+ await pageTypeAddFieldImage();
86
+ break;
87
+ case "key-text":
88
+ await pageTypeAddFieldKeyText();
89
+ break;
90
+ case "link":
91
+ await pageTypeAddFieldLink();
92
+ break;
93
+ case "number":
94
+ await pageTypeAddFieldNumber();
95
+ break;
96
+ case "rich-text":
97
+ await pageTypeAddFieldRichText();
98
+ break;
99
+ case "select":
100
+ await pageTypeAddFieldSelect();
101
+ break;
102
+ case "timestamp":
103
+ await pageTypeAddFieldTimestamp();
104
+ break;
105
+ case "uid":
106
+ await pageTypeAddFieldUid();
107
+ break;
108
+ default: {
109
+ if (fieldType) {
110
+ console.error(`Unknown field type: ${fieldType}\n`);
111
+ process.exitCode = 1;
112
+ }
113
+ console.info(HELP);
114
+ }
115
+ }
116
+ }
@@ -0,0 +1,214 @@
1
+ import type {
2
+ CustomType,
3
+ DynamicSlices,
4
+ SharedSliceRef,
5
+ } from "@prismicio/types-internal/lib/customtypes";
6
+
7
+ import { readFile, writeFile } from "node:fs/promises";
8
+ import { parseArgs } from "node:util";
9
+ import * as v from "valibot";
10
+
11
+ import { buildTypes } from "./codegen-types";
12
+ import { findUpward } from "./lib/file";
13
+ import { stringify } from "./lib/json";
14
+ import { findSliceModel } from "./lib/slice";
15
+
16
+ const HELP = `
17
+ Connect a shared slice to a page type's slice zone.
18
+
19
+ USAGE
20
+ prismic page-type connect-slice <type-id> <slice-id> [flags]
21
+
22
+ ARGUMENTS
23
+ type-id Page type identifier (required)
24
+ slice-id Slice identifier (required)
25
+
26
+ FLAGS
27
+ -z, --slice-zone string Target slice zone field ID (default: "slices")
28
+ --types string Output file for generated types (default: "prismicio-types.d.ts")
29
+ -h, --help Show help for command
30
+
31
+ EXAMPLES
32
+ prismic page-type connect-slice homepage CallToAction
33
+ prismic page-type connect-slice homepage CallToAction --slice-zone slices
34
+ prismic page-type connect-slice article HeroSection -z body
35
+ `.trim();
36
+
37
+ const CustomTypeSchema = v.object({
38
+ id: v.string(),
39
+ label: v.string(),
40
+ repeatable: v.boolean(),
41
+ status: v.boolean(),
42
+ format: v.string(),
43
+ json: v.record(v.string(), v.record(v.string(), v.unknown())),
44
+ });
45
+
46
+ export async function pageTypeConnectSlice(): Promise<void> {
47
+ const {
48
+ values: { help, "slice-zone": sliceZoneId, types },
49
+ positionals: [typeId, sliceId],
50
+ } = parseArgs({
51
+ args: process.argv.slice(4), // skip: node, script, "page-type", "connect-slice"
52
+ options: {
53
+ "slice-zone": { type: "string", short: "z" },
54
+ types: { type: "string" },
55
+ help: { type: "boolean", short: "h" },
56
+ },
57
+ allowPositionals: true,
58
+ });
59
+
60
+ if (help) {
61
+ console.info(HELP);
62
+ return;
63
+ }
64
+
65
+ if (!typeId) {
66
+ console.error("Missing required argument: type-id\n");
67
+ console.error("Usage: prismic page-type connect-slice <type-id> <slice-id>");
68
+ process.exitCode = 1;
69
+ return;
70
+ }
71
+
72
+ if (!sliceId) {
73
+ console.error("Missing required argument: slice-id\n");
74
+ console.error("Usage: prismic page-type connect-slice <type-id> <slice-id>");
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+
79
+ // Verify the slice exists
80
+ const sliceResult = await findSliceModel(sliceId);
81
+ if (!sliceResult.ok) {
82
+ console.error(sliceResult.error);
83
+ process.exitCode = 1;
84
+ return;
85
+ }
86
+
87
+ // Find the page type file
88
+ const projectRoot = await findUpward("package.json");
89
+ if (!projectRoot) {
90
+ console.error("Could not find project root (no package.json found)");
91
+ process.exitCode = 1;
92
+ return;
93
+ }
94
+
95
+ const modelPath = new URL(`customtypes/${typeId}/index.json`, projectRoot);
96
+
97
+ // Read and parse the model
98
+ let model: CustomType;
99
+ try {
100
+ const contents = await readFile(modelPath, "utf8");
101
+ const result = v.safeParse(CustomTypeSchema, JSON.parse(contents));
102
+ if (!result.success) {
103
+ console.error(`Invalid page type model: ${modelPath.href}`);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+ model = result.output as CustomType;
108
+ } catch (error) {
109
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
110
+ console.error(`Page type not found: ${typeId}\n`);
111
+ console.error(`Create it first with: prismic page-type create ${typeId}`);
112
+ process.exitCode = 1;
113
+ return;
114
+ }
115
+ if (error instanceof Error) {
116
+ console.error(`Failed to read page type: ${error.message}`);
117
+ } else {
118
+ console.error("Failed to read page type");
119
+ }
120
+ process.exitCode = 1;
121
+ return;
122
+ }
123
+
124
+ const targetSliceZoneId = sliceZoneId ?? "slices";
125
+
126
+ // Find existing slice zone or create a new one
127
+ let sliceZone: DynamicSlices | undefined;
128
+ let sliceZoneFieldId: string | undefined;
129
+
130
+ // Search all tabs for a Slices field matching the target ID
131
+ for (const [, tabFields] of Object.entries(model.json)) {
132
+ for (const [fieldId, field] of Object.entries(tabFields)) {
133
+ if ((field as { type?: string }).type === "Slices" && fieldId === targetSliceZoneId) {
134
+ sliceZone = field as DynamicSlices;
135
+ sliceZoneFieldId = fieldId;
136
+ break;
137
+ }
138
+ }
139
+ if (sliceZone) break;
140
+ }
141
+
142
+ // Handle slice zone not found
143
+ if (!sliceZone) {
144
+ if (sliceZoneId) {
145
+ // User specified a slice zone that doesn't exist
146
+ console.error(`Slice zone "${sliceZoneId}" not found in page type "${typeId}"`);
147
+ process.exitCode = 1;
148
+ return;
149
+ }
150
+
151
+ // Create a new slice zone in the first tab
152
+ const existingTabs = Object.keys(model.json);
153
+ const targetTab = existingTabs[0] ?? "Main";
154
+
155
+ // Initialize tab if it doesn't exist
156
+ if (!model.json[targetTab]) {
157
+ model.json[targetTab] = {};
158
+ }
159
+
160
+ const newSliceZone: DynamicSlices = {
161
+ type: "Slices",
162
+ fieldset: "Slice Zone",
163
+ config: {
164
+ choices: {},
165
+ },
166
+ };
167
+
168
+ model.json[targetTab][targetSliceZoneId] = newSliceZone;
169
+ sliceZone = newSliceZone;
170
+ sliceZoneFieldId = targetSliceZoneId;
171
+ }
172
+
173
+ // Ensure config and choices exist
174
+ if (!sliceZone.config) {
175
+ sliceZone.config = { choices: {} };
176
+ }
177
+ if (!sliceZone.config.choices) {
178
+ sliceZone.config.choices = {};
179
+ }
180
+
181
+ // Check if slice is already connected
182
+ if (sliceId in sliceZone.config.choices) {
183
+ console.info(
184
+ `Slice "${sliceId}" is already connected to slice zone "${sliceZoneFieldId}" in ${typeId}`,
185
+ );
186
+ return;
187
+ }
188
+
189
+ // Add the slice reference
190
+ const sliceRef: SharedSliceRef = { type: "SharedSlice" };
191
+ sliceZone.config.choices[sliceId] = sliceRef;
192
+
193
+ // Write updated model
194
+ try {
195
+ await writeFile(modelPath, stringify(model));
196
+ } catch (error) {
197
+ if (error instanceof Error) {
198
+ console.error(`Failed to update page type: ${error.message}`);
199
+ } else {
200
+ console.error("Failed to update page type");
201
+ }
202
+ process.exitCode = 1;
203
+ return;
204
+ }
205
+
206
+ console.info(`Connected slice "${sliceId}" to slice zone "${sliceZoneFieldId}" in ${typeId}`);
207
+
208
+ try {
209
+ await buildTypes({ output: types });
210
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
211
+ } catch (error) {
212
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
213
+ }
214
+ }
@@ -0,0 +1,161 @@
1
+ import type { CustomType } from "@prismicio/types-internal/lib/customtypes";
2
+
3
+ import { mkdir, writeFile } from "node:fs/promises";
4
+ import { parseArgs } from "node:util";
5
+
6
+ import { buildTypes } from "./codegen-types";
7
+ import { findUpward } from "./lib/file";
8
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
+ import { stringify } from "./lib/json";
10
+
11
+ const HELP = `
12
+ Create a new page type in a Prismic repository.
13
+
14
+ USAGE
15
+ prismic page-type create <id> [flags]
16
+
17
+ ARGUMENTS
18
+ id Page type identifier (required)
19
+
20
+ FLAGS
21
+ -n, --name string Display name for the page type
22
+ --single Create as a singleton (non-repeatable) type
23
+ --types string Output file for generated types (default: "prismicio-types.d.ts")
24
+ -h, --help Show help for command
25
+
26
+ LEARN MORE
27
+ Use \`prismic page-type <command> --help\` for more information about a command.
28
+ `.trim();
29
+
30
+ function getDocsPath(framework: Framework): string {
31
+ switch (framework) {
32
+ case "next":
33
+ return "nextjs/with-cli";
34
+ case "nuxt":
35
+ return "nuxt/with-cli";
36
+ case "sveltekit":
37
+ return "sveltekit/with-cli";
38
+ }
39
+ }
40
+
41
+ function getWritePageComponentsAnchor(_framework: Framework): string {
42
+ return "#write-page-components";
43
+ }
44
+
45
+ export async function pageTypeCreate(): Promise<void> {
46
+ const {
47
+ values: { help, name, single, types },
48
+ positionals: [id],
49
+ } = parseArgs({
50
+ args: process.argv.slice(4), // skip: node, script, "page-type", "create"
51
+ options: {
52
+ name: { type: "string", short: "n" },
53
+ single: { type: "boolean" },
54
+ types: { type: "string" },
55
+ help: { type: "boolean", short: "h" },
56
+ },
57
+ allowPositionals: true,
58
+ });
59
+
60
+ if (help) {
61
+ console.info(HELP);
62
+ return;
63
+ }
64
+
65
+ if (!id) {
66
+ console.error("Missing required argument: id");
67
+ process.exitCode = 1;
68
+ return;
69
+ }
70
+
71
+ const model = {
72
+ id,
73
+ label: name ?? pascalCase(id),
74
+ repeatable: !single,
75
+ status: true,
76
+ format: "page",
77
+ json: {
78
+ Main: single
79
+ ? {}
80
+ : {
81
+ uid: {
82
+ type: "UID",
83
+ config: { label: "UID", placeholder: "" },
84
+ },
85
+ },
86
+ "SEO & Metadata": {
87
+ meta_title: {
88
+ type: "Text",
89
+ config: {
90
+ label: "Meta Title",
91
+ placeholder: "A title of the page used for social media and search engines",
92
+ },
93
+ },
94
+ meta_description: {
95
+ type: "Text",
96
+ config: {
97
+ label: "Meta Description",
98
+ placeholder: "A brief summary of the page",
99
+ },
100
+ },
101
+ meta_image: {
102
+ type: "Image",
103
+ config: {
104
+ label: "Meta Image",
105
+ constraint: { width: 2400, height: 1260 },
106
+ thumbnails: [],
107
+ },
108
+ },
109
+ },
110
+ },
111
+ } satisfies CustomType;
112
+
113
+ const projectRoot = await findUpward("package.json");
114
+ if (!projectRoot) {
115
+ console.error("Could not find project root (no package.json found)");
116
+ process.exitCode = 1;
117
+ return;
118
+ }
119
+
120
+ const customTypesDirectory = new URL("customtypes/", projectRoot);
121
+ const typeDirectory = new URL(id + "/", customTypesDirectory);
122
+ const modelPath = new URL("index.json", typeDirectory);
123
+
124
+ try {
125
+ await mkdir(new URL(".", modelPath), { recursive: true });
126
+ await writeFile(modelPath, stringify(model));
127
+ } catch (error) {
128
+ if (error instanceof Error) {
129
+ console.error(`Failed to create page type: ${error.message}`);
130
+ } else {
131
+ console.error(`Failed to create page type`);
132
+ }
133
+ process.exitCode = 1;
134
+ return;
135
+ }
136
+
137
+ console.info(`Created page type at ${modelPath.href}`);
138
+
139
+ try {
140
+ await buildTypes({ output: types });
141
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
142
+ } catch (error) {
143
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
144
+ }
145
+
146
+ console.info();
147
+ console.info("Next: Add fields with `prismic page-type add-field`");
148
+
149
+ const frameworkInfo = await detectFrameworkInfo();
150
+ if (frameworkInfo?.framework) {
151
+ const docsPath = getDocsPath(frameworkInfo.framework);
152
+ const anchor = getWritePageComponentsAnchor(frameworkInfo.framework);
153
+ console.info(
154
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement a page file`,
155
+ );
156
+ }
157
+ }
158
+
159
+ export function pascalCase(input: string): string {
160
+ return input.toLowerCase().replace(/(^|[-_\s]+)(.)?/g, (_, __, c) => c?.toUpperCase() ?? "");
161
+ }
@@ -0,0 +1,171 @@
1
+ import type { CustomType, DynamicSlices } from "@prismicio/types-internal/lib/customtypes";
2
+
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { parseArgs } from "node:util";
5
+ import * as v from "valibot";
6
+
7
+ import { buildTypes } from "./codegen-types";
8
+ import { findUpward } from "./lib/file";
9
+ import { stringify } from "./lib/json";
10
+
11
+ const HELP = `
12
+ Disconnect a shared slice from a page type's slice zone.
13
+
14
+ USAGE
15
+ prismic page-type disconnect-slice <type-id> <slice-id> [flags]
16
+
17
+ ARGUMENTS
18
+ type-id Page type identifier (required)
19
+ slice-id Slice identifier (required)
20
+
21
+ FLAGS
22
+ -z, --slice-zone string Target slice zone field ID (default: "slices")
23
+ --types string Output file for generated types (default: "prismicio-types.d.ts")
24
+ -h, --help Show help for command
25
+
26
+ EXAMPLES
27
+ prismic page-type disconnect-slice homepage CallToAction
28
+ prismic page-type disconnect-slice homepage CallToAction --slice-zone slices
29
+ prismic page-type disconnect-slice article HeroSection -z body
30
+ `.trim();
31
+
32
+ const CustomTypeSchema = v.object({
33
+ id: v.string(),
34
+ label: v.string(),
35
+ repeatable: v.boolean(),
36
+ status: v.boolean(),
37
+ format: v.string(),
38
+ json: v.record(v.string(), v.record(v.string(), v.unknown())),
39
+ });
40
+
41
+ export async function pageTypeDisconnectSlice(): Promise<void> {
42
+ const {
43
+ values: { help, "slice-zone": sliceZoneId, types },
44
+ positionals: [typeId, sliceId],
45
+ } = parseArgs({
46
+ args: process.argv.slice(4), // skip: node, script, "page-type", "disconnect-slice"
47
+ options: {
48
+ "slice-zone": { type: "string", short: "z" },
49
+ types: { type: "string" },
50
+ help: { type: "boolean", short: "h" },
51
+ },
52
+ allowPositionals: true,
53
+ });
54
+
55
+ if (help) {
56
+ console.info(HELP);
57
+ return;
58
+ }
59
+
60
+ if (!typeId) {
61
+ console.error("Missing required argument: type-id\n");
62
+ console.error("Usage: prismic page-type disconnect-slice <type-id> <slice-id>");
63
+ process.exitCode = 1;
64
+ return;
65
+ }
66
+
67
+ if (!sliceId) {
68
+ console.error("Missing required argument: slice-id\n");
69
+ console.error("Usage: prismic page-type disconnect-slice <type-id> <slice-id>");
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+
74
+ // Find the page type file
75
+ const projectRoot = await findUpward("package.json");
76
+ if (!projectRoot) {
77
+ console.error("Could not find project root (no package.json found)");
78
+ process.exitCode = 1;
79
+ return;
80
+ }
81
+
82
+ const modelPath = new URL(`customtypes/${typeId}/index.json`, projectRoot);
83
+
84
+ // Read and parse the model
85
+ let model: CustomType;
86
+ try {
87
+ const contents = await readFile(modelPath, "utf8");
88
+ const result = v.safeParse(CustomTypeSchema, JSON.parse(contents));
89
+ if (!result.success) {
90
+ console.error(`Invalid page type model: ${modelPath.href}`);
91
+ process.exitCode = 1;
92
+ return;
93
+ }
94
+ model = result.output as CustomType;
95
+ } catch (error) {
96
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
97
+ console.error(`Page type not found: ${typeId}\n`);
98
+ console.error(`Create it first with: prismic page-type create ${typeId}`);
99
+ process.exitCode = 1;
100
+ return;
101
+ }
102
+ if (error instanceof Error) {
103
+ console.error(`Failed to read page type: ${error.message}`);
104
+ } else {
105
+ console.error("Failed to read page type");
106
+ }
107
+ process.exitCode = 1;
108
+ return;
109
+ }
110
+
111
+ const targetSliceZoneId = sliceZoneId ?? "slices";
112
+
113
+ // Find existing slice zone
114
+ let sliceZone: DynamicSlices | undefined;
115
+ let sliceZoneFieldId: string | undefined;
116
+
117
+ // Search all tabs for a Slices field matching the target ID
118
+ for (const [, tabFields] of Object.entries(model.json)) {
119
+ for (const [fieldId, field] of Object.entries(tabFields)) {
120
+ if ((field as { type?: string }).type === "Slices" && fieldId === targetSliceZoneId) {
121
+ sliceZone = field as DynamicSlices;
122
+ sliceZoneFieldId = fieldId;
123
+ break;
124
+ }
125
+ }
126
+ if (sliceZone) break;
127
+ }
128
+
129
+ // Slice zone must exist for disconnect
130
+ if (!sliceZone) {
131
+ console.error(`Slice zone "${targetSliceZoneId}" not found in page type "${typeId}"`);
132
+ process.exitCode = 1;
133
+ return;
134
+ }
135
+
136
+ // Check if slice is connected
137
+ if (!sliceZone.config?.choices || !(sliceId in sliceZone.config.choices)) {
138
+ console.error(
139
+ `Slice "${sliceId}" is not connected to slice zone "${sliceZoneFieldId}" in ${typeId}`,
140
+ );
141
+ process.exitCode = 1;
142
+ return;
143
+ }
144
+
145
+ // Remove the slice reference
146
+ delete sliceZone.config.choices[sliceId];
147
+
148
+ // Write updated model
149
+ try {
150
+ await writeFile(modelPath, stringify(model));
151
+ } catch (error) {
152
+ if (error instanceof Error) {
153
+ console.error(`Failed to update page type: ${error.message}`);
154
+ } else {
155
+ console.error("Failed to update page type");
156
+ }
157
+ process.exitCode = 1;
158
+ return;
159
+ }
160
+
161
+ console.info(
162
+ `Disconnected slice "${sliceId}" from slice zone "${sliceZoneFieldId}" in ${typeId}`,
163
+ );
164
+
165
+ try {
166
+ await buildTypes({ output: types });
167
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
168
+ } catch (error) {
169
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
170
+ }
171
+ }