@angeloashmore/prismic-cli-poc 0.0.0-canary.fe51fbb → 0.0.0-pr.10.032f7ef

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 (54) hide show
  1. package/dist/index.mjs +229 -136
  2. package/package.json +1 -1
  3. package/src/custom-type-add-field-boolean.ts +49 -12
  4. package/src/custom-type-add-field-color.ts +46 -12
  5. package/src/custom-type-add-field-date.ts +46 -12
  6. package/src/custom-type-add-field-embed.ts +46 -12
  7. package/src/custom-type-add-field-geo-point.ts +46 -12
  8. package/src/custom-type-add-field-group.ts +179 -0
  9. package/src/custom-type-add-field-image.ts +46 -12
  10. package/src/custom-type-add-field-key-text.ts +46 -12
  11. package/src/custom-type-add-field-link.ts +46 -12
  12. package/src/custom-type-add-field-number.ts +46 -12
  13. package/src/custom-type-add-field-rich-text.ts +46 -12
  14. package/src/custom-type-add-field-select.ts +46 -12
  15. package/src/custom-type-add-field-timestamp.ts +46 -12
  16. package/src/custom-type-add-field-uid.ts +17 -0
  17. package/src/custom-type-add-field.ts +5 -0
  18. package/src/docs-fetch.ts +146 -0
  19. package/src/docs-list.ts +131 -0
  20. package/src/docs.ts +26 -121
  21. package/src/index.ts +1 -1
  22. package/src/lib/field-path.ts +81 -0
  23. package/src/page-type-add-field-boolean.ts +47 -13
  24. package/src/page-type-add-field-color.ts +47 -13
  25. package/src/page-type-add-field-date.ts +47 -13
  26. package/src/page-type-add-field-embed.ts +47 -13
  27. package/src/page-type-add-field-geo-point.ts +47 -13
  28. package/src/page-type-add-field-group.ts +198 -0
  29. package/src/page-type-add-field-image.ts +47 -13
  30. package/src/page-type-add-field-key-text.ts +47 -13
  31. package/src/page-type-add-field-link.ts +47 -13
  32. package/src/page-type-add-field-number.ts +47 -13
  33. package/src/page-type-add-field-rich-text.ts +47 -13
  34. package/src/page-type-add-field-select.ts +47 -13
  35. package/src/page-type-add-field-timestamp.ts +47 -13
  36. package/src/page-type-add-field-uid.ts +18 -1
  37. package/src/page-type-add-field.ts +5 -0
  38. package/src/page-type-create.ts +1 -1
  39. package/src/repo-create.ts +28 -1
  40. package/src/slice-add-field-boolean.ts +59 -16
  41. package/src/slice-add-field-color.ts +59 -16
  42. package/src/slice-add-field-date.ts +59 -16
  43. package/src/slice-add-field-embed.ts +59 -16
  44. package/src/slice-add-field-geo-point.ts +59 -16
  45. package/src/slice-add-field-group.ts +191 -0
  46. package/src/slice-add-field-image.ts +59 -16
  47. package/src/slice-add-field-key-text.ts +59 -16
  48. package/src/slice-add-field-link.ts +59 -16
  49. package/src/slice-add-field-number.ts +59 -16
  50. package/src/slice-add-field-rich-text.ts +59 -16
  51. package/src/slice-add-field-select.ts +59 -16
  52. package/src/slice-add-field-timestamp.ts +59 -16
  53. package/src/slice-add-field.ts +5 -0
  54. package/src/status.ts +1 -1
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { type Framework, detectFrameworkInfo } from "./lib/framework";
10
11
  import { stringify } from "./lib/json";
11
12
  import { humanReadable } from "./lib/string";
@@ -88,6 +89,15 @@ export async function pageTypeAddFieldColor(): Promise<void> {
88
89
  return;
89
90
  }
90
91
 
92
+ // Parse and validate field path
93
+ const fieldPath = parseFieldPath(fieldId);
94
+ const pathValidation = validateNestedFieldPath(fieldPath);
95
+ if (!pathValidation.ok) {
96
+ console.error(pathValidation.error);
97
+ process.exitCode = 1;
98
+ return;
99
+ }
100
+
91
101
  // Find the page type file
92
102
  const projectRoot = await findUpward("package.json");
93
103
  if (!projectRoot) {
@@ -134,26 +144,46 @@ export async function pageTypeAddFieldColor(): Promise<void> {
134
144
  model.json[targetTab] = {};
135
145
  }
136
146
 
137
- // Check if field already exists in any tab
138
- for (const [tabName, tabFields] of Object.entries(model.json)) {
139
- if (tabFields[fieldId]) {
140
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
141
- process.exitCode = 1;
142
- return;
143
- }
144
- }
145
-
146
147
  // Build field definition
147
148
  const fieldDefinition: Color = {
148
149
  type: "Color",
149
150
  config: {
150
- label: label ?? humanReadable(fieldId),
151
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
151
152
  ...(placeholder && { placeholder }),
152
153
  },
153
154
  };
154
155
 
155
156
  // Add field to model
156
- model.json[targetTab][fieldId] = fieldDefinition;
157
+ if (fieldPath.type === "nested") {
158
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
159
+ if (!groupResult.ok) {
160
+ console.error(groupResult.error);
161
+ process.exitCode = 1;
162
+ return;
163
+ }
164
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
165
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
166
+ process.exitCode = 1;
167
+ return;
168
+ }
169
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
170
+ } else {
171
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
172
+ if (tabFields[fieldId]) {
173
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
178
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
179
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
180
+ process.exitCode = 1;
181
+ return;
182
+ }
183
+ }
184
+ }
185
+ model.json[targetTab][fieldId] = fieldDefinition;
186
+ }
157
187
 
158
188
  // Write updated model
159
189
  try {
@@ -168,7 +198,11 @@ export async function pageTypeAddFieldColor(): Promise<void> {
168
198
  return;
169
199
  }
170
200
 
171
- console.info(`Added field "${fieldId}" (Color) to "${targetTab}" tab in ${typeId}`);
201
+ if (fieldPath.type === "nested") {
202
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Color) to group "${fieldPath.groupId}" in ${typeId}`);
203
+ } else {
204
+ console.info(`Added field "${fieldId}" (Color) to "${targetTab}" tab in ${typeId}`);
205
+ }
172
206
 
173
207
  try {
174
208
  await buildTypes({ output: types });
@@ -184,7 +218,7 @@ export async function pageTypeAddFieldColor(): Promise<void> {
184
218
  if (frameworkInfo?.framework) {
185
219
  const docsPath = getDocsPath(frameworkInfo.framework);
186
220
  console.info(
187
- ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
221
+ ` Run \`prismic docs fetch ${docsPath}#write-page-components\` to learn how to implement a page file`,
188
222
  );
189
223
  }
190
224
  }
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { type Framework, detectFrameworkInfo } from "./lib/framework";
10
11
  import { stringify } from "./lib/json";
11
12
  import { humanReadable } from "./lib/string";
@@ -90,6 +91,15 @@ export async function pageTypeAddFieldDate(): Promise<void> {
90
91
  return;
91
92
  }
92
93
 
94
+ // Parse and validate field path
95
+ const fieldPath = parseFieldPath(fieldId);
96
+ const pathValidation = validateNestedFieldPath(fieldPath);
97
+ if (!pathValidation.ok) {
98
+ console.error(pathValidation.error);
99
+ process.exitCode = 1;
100
+ return;
101
+ }
102
+
93
103
  // Find the page type file
94
104
  const projectRoot = await findUpward("package.json");
95
105
  if (!projectRoot) {
@@ -136,27 +146,47 @@ export async function pageTypeAddFieldDate(): Promise<void> {
136
146
  model.json[targetTab] = {};
137
147
  }
138
148
 
139
- // Check if field already exists in any tab
140
- for (const [tabName, tabFields] of Object.entries(model.json)) {
141
- if (tabFields[fieldId]) {
142
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
143
- process.exitCode = 1;
144
- return;
145
- }
146
- }
147
-
148
149
  // Build field definition
149
150
  const fieldDefinition: DateField = {
150
151
  type: "Date",
151
152
  config: {
152
- label: label ?? humanReadable(fieldId),
153
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
153
154
  ...(placeholder && { placeholder }),
154
155
  ...(defaultValue && { default: defaultValue }),
155
156
  },
156
157
  };
157
158
 
158
159
  // Add field to model
159
- model.json[targetTab][fieldId] = fieldDefinition;
160
+ if (fieldPath.type === "nested") {
161
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
162
+ if (!groupResult.ok) {
163
+ console.error(groupResult.error);
164
+ process.exitCode = 1;
165
+ return;
166
+ }
167
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
168
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
169
+ process.exitCode = 1;
170
+ return;
171
+ }
172
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
173
+ } else {
174
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
175
+ if (tabFields[fieldId]) {
176
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
181
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
182
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
183
+ process.exitCode = 1;
184
+ return;
185
+ }
186
+ }
187
+ }
188
+ model.json[targetTab][fieldId] = fieldDefinition;
189
+ }
160
190
 
161
191
  // Write updated model
162
192
  try {
@@ -171,7 +201,11 @@ export async function pageTypeAddFieldDate(): Promise<void> {
171
201
  return;
172
202
  }
173
203
 
174
- console.info(`Added field "${fieldId}" (Date) to "${targetTab}" tab in ${typeId}`);
204
+ if (fieldPath.type === "nested") {
205
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Date) to group "${fieldPath.groupId}" in ${typeId}`);
206
+ } else {
207
+ console.info(`Added field "${fieldId}" (Date) to "${targetTab}" tab in ${typeId}`);
208
+ }
175
209
 
176
210
  try {
177
211
  await buildTypes({ output: types });
@@ -187,7 +221,7 @@ export async function pageTypeAddFieldDate(): Promise<void> {
187
221
  if (frameworkInfo?.framework) {
188
222
  const docsPath = getDocsPath(frameworkInfo.framework);
189
223
  console.info(
190
- ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
224
+ ` Run \`prismic docs fetch ${docsPath}#write-page-components\` to learn how to implement a page file`,
191
225
  );
192
226
  }
193
227
  }
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { type Framework, detectFrameworkInfo } from "./lib/framework";
10
11
  import { stringify } from "./lib/json";
11
12
  import { humanReadable } from "./lib/string";
@@ -88,6 +89,15 @@ export async function pageTypeAddFieldEmbed(): Promise<void> {
88
89
  return;
89
90
  }
90
91
 
92
+ // Parse and validate field path
93
+ const fieldPath = parseFieldPath(fieldId);
94
+ const pathValidation = validateNestedFieldPath(fieldPath);
95
+ if (!pathValidation.ok) {
96
+ console.error(pathValidation.error);
97
+ process.exitCode = 1;
98
+ return;
99
+ }
100
+
91
101
  // Find the page type file
92
102
  const projectRoot = await findUpward("package.json");
93
103
  if (!projectRoot) {
@@ -134,26 +144,46 @@ export async function pageTypeAddFieldEmbed(): Promise<void> {
134
144
  model.json[targetTab] = {};
135
145
  }
136
146
 
137
- // Check if field already exists in any tab
138
- for (const [tabName, tabFields] of Object.entries(model.json)) {
139
- if (tabFields[fieldId]) {
140
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
141
- process.exitCode = 1;
142
- return;
143
- }
144
- }
145
-
146
147
  // Build field definition
147
148
  const fieldDefinition: Embed = {
148
149
  type: "Embed",
149
150
  config: {
150
- label: label ?? humanReadable(fieldId),
151
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
151
152
  ...(placeholder && { placeholder }),
152
153
  },
153
154
  };
154
155
 
155
156
  // Add field to model
156
- model.json[targetTab][fieldId] = fieldDefinition;
157
+ if (fieldPath.type === "nested") {
158
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
159
+ if (!groupResult.ok) {
160
+ console.error(groupResult.error);
161
+ process.exitCode = 1;
162
+ return;
163
+ }
164
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
165
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
166
+ process.exitCode = 1;
167
+ return;
168
+ }
169
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
170
+ } else {
171
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
172
+ if (tabFields[fieldId]) {
173
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
178
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
179
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
180
+ process.exitCode = 1;
181
+ return;
182
+ }
183
+ }
184
+ }
185
+ model.json[targetTab][fieldId] = fieldDefinition;
186
+ }
157
187
 
158
188
  // Write updated model
159
189
  try {
@@ -168,7 +198,11 @@ export async function pageTypeAddFieldEmbed(): Promise<void> {
168
198
  return;
169
199
  }
170
200
 
171
- console.info(`Added field "${fieldId}" (Embed) to "${targetTab}" tab in ${typeId}`);
201
+ if (fieldPath.type === "nested") {
202
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Embed) to group "${fieldPath.groupId}" in ${typeId}`);
203
+ } else {
204
+ console.info(`Added field "${fieldId}" (Embed) to "${targetTab}" tab in ${typeId}`);
205
+ }
172
206
 
173
207
  try {
174
208
  await buildTypes({ output: types });
@@ -184,7 +218,7 @@ export async function pageTypeAddFieldEmbed(): Promise<void> {
184
218
  if (frameworkInfo?.framework) {
185
219
  const docsPath = getDocsPath(frameworkInfo.framework);
186
220
  console.info(
187
- ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
221
+ ` Run \`prismic docs fetch ${docsPath}#write-page-components\` to learn how to implement a page file`,
188
222
  );
189
223
  }
190
224
  }
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { type Framework, detectFrameworkInfo } from "./lib/framework";
10
11
  import { stringify } from "./lib/json";
11
12
  import { humanReadable } from "./lib/string";
@@ -86,6 +87,15 @@ export async function pageTypeAddFieldGeoPoint(): Promise<void> {
86
87
  return;
87
88
  }
88
89
 
90
+ // Parse and validate field path
91
+ const fieldPath = parseFieldPath(fieldId);
92
+ const pathValidation = validateNestedFieldPath(fieldPath);
93
+ if (!pathValidation.ok) {
94
+ console.error(pathValidation.error);
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+
89
99
  // Find the page type file
90
100
  const projectRoot = await findUpward("package.json");
91
101
  if (!projectRoot) {
@@ -132,25 +142,45 @@ export async function pageTypeAddFieldGeoPoint(): Promise<void> {
132
142
  model.json[targetTab] = {};
133
143
  }
134
144
 
135
- // Check if field already exists in any tab
136
- for (const [tabName, tabFields] of Object.entries(model.json)) {
137
- if (tabFields[fieldId]) {
138
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
139
- process.exitCode = 1;
140
- return;
141
- }
142
- }
143
-
144
145
  // Build field definition
145
146
  const fieldDefinition: GeoPoint = {
146
147
  type: "GeoPoint",
147
148
  config: {
148
- label: label ?? humanReadable(fieldId),
149
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
149
150
  },
150
151
  };
151
152
 
152
153
  // Add field to model
153
- model.json[targetTab][fieldId] = fieldDefinition;
154
+ if (fieldPath.type === "nested") {
155
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
156
+ if (!groupResult.ok) {
157
+ console.error(groupResult.error);
158
+ process.exitCode = 1;
159
+ return;
160
+ }
161
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
162
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
163
+ process.exitCode = 1;
164
+ return;
165
+ }
166
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
167
+ } else {
168
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
169
+ if (tabFields[fieldId]) {
170
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
171
+ process.exitCode = 1;
172
+ return;
173
+ }
174
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
175
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
176
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ }
181
+ }
182
+ model.json[targetTab][fieldId] = fieldDefinition;
183
+ }
154
184
 
155
185
  // Write updated model
156
186
  try {
@@ -165,7 +195,11 @@ export async function pageTypeAddFieldGeoPoint(): Promise<void> {
165
195
  return;
166
196
  }
167
197
 
168
- console.info(`Added field "${fieldId}" (GeoPoint) to "${targetTab}" tab in ${typeId}`);
198
+ if (fieldPath.type === "nested") {
199
+ console.info(`Added field "${fieldPath.nestedFieldId}" (GeoPoint) to group "${fieldPath.groupId}" in ${typeId}`);
200
+ } else {
201
+ console.info(`Added field "${fieldId}" (GeoPoint) to "${targetTab}" tab in ${typeId}`);
202
+ }
169
203
 
170
204
  try {
171
205
  await buildTypes({ output: types });
@@ -181,7 +215,7 @@ export async function pageTypeAddFieldGeoPoint(): Promise<void> {
181
215
  if (frameworkInfo?.framework) {
182
216
  const docsPath = getDocsPath(frameworkInfo.framework);
183
217
  console.info(
184
- ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
218
+ ` Run \`prismic docs fetch ${docsPath}#write-page-components\` to learn how to implement a page file`,
185
219
  );
186
220
  }
187
221
  }
@@ -0,0 +1,198 @@
1
+ import type { CustomType, Group } 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 { type Framework, detectFrameworkInfo } from "./lib/framework";
10
+ import { stringify } from "./lib/json";
11
+ import { humanReadable } from "./lib/string";
12
+
13
+ const HELP = `
14
+ Add a group field to an existing page type.
15
+
16
+ USAGE
17
+ prismic page-type add-field group <type-id> <field-id> [flags]
18
+
19
+ ARGUMENTS
20
+ type-id Page type identifier (required)
21
+ field-id Field identifier (required)
22
+
23
+ FLAGS
24
+ -t, --tab string Target tab (default: first existing tab, or "Main")
25
+ -l, --label string Display label for the field (inferred from field-id if omitted)
26
+ --non-repeatable Make this a non-repeating group (default: repeatable)
27
+ --types string Output file for generated types (default: "prismicio-types.d.ts")
28
+ -h, --help Show help for command
29
+
30
+ EXAMPLES
31
+ prismic page-type add-field group homepage buttons
32
+ prismic page-type add-field group article authors --non-repeatable
33
+ prismic page-type add-field group product variants --tab "Content"
34
+ `.trim();
35
+
36
+ const CustomTypeSchema = v.object({
37
+ id: v.string(),
38
+ label: v.string(),
39
+ repeatable: v.boolean(),
40
+ status: v.boolean(),
41
+ format: v.string(),
42
+ json: v.record(v.string(), v.record(v.string(), v.unknown())),
43
+ });
44
+
45
+ function getDocsPath(framework: Framework): string {
46
+ switch (framework) {
47
+ case "next":
48
+ return "nextjs/with-cli";
49
+ case "nuxt":
50
+ return "nuxt/with-cli";
51
+ case "sveltekit":
52
+ return "sveltekit/with-cli";
53
+ }
54
+ }
55
+
56
+ export async function pageTypeAddFieldGroup(): Promise<void> {
57
+ const {
58
+ values: { help, tab, label, "non-repeatable": nonRepeatable, types },
59
+ positionals: [typeId, fieldId],
60
+ } = parseArgs({
61
+ args: process.argv.slice(5), // skip: node, script, "page-type", "add-field", "group"
62
+ options: {
63
+ tab: { type: "string", short: "t" },
64
+ label: { type: "string", short: "l" },
65
+ "non-repeatable": { type: "boolean" },
66
+ types: { type: "string" },
67
+ help: { type: "boolean", short: "h" },
68
+ },
69
+ allowPositionals: true,
70
+ });
71
+
72
+ if (help) {
73
+ console.info(HELP);
74
+ return;
75
+ }
76
+
77
+ if (!typeId) {
78
+ console.error("Missing required argument: type-id\n");
79
+ console.error("Usage: prismic page-type add-field group <type-id> <field-id>");
80
+ process.exitCode = 1;
81
+ return;
82
+ }
83
+
84
+ if (!fieldId) {
85
+ console.error("Missing required argument: field-id\n");
86
+ console.error("Usage: prismic page-type add-field group <type-id> <field-id>");
87
+ process.exitCode = 1;
88
+ return;
89
+ }
90
+
91
+ // Groups cannot be nested
92
+ if (fieldId.includes(".")) {
93
+ console.error("Groups cannot be nested inside other groups");
94
+ process.exitCode = 1;
95
+ return;
96
+ }
97
+
98
+ // Find the page type file
99
+ const projectRoot = await findUpward("package.json");
100
+ if (!projectRoot) {
101
+ console.error("Could not find project root (no package.json found)");
102
+ process.exitCode = 1;
103
+ return;
104
+ }
105
+
106
+ const modelPath = new URL(`customtypes/${typeId}/index.json`, projectRoot);
107
+
108
+ // Read and parse the model
109
+ let model: CustomType;
110
+ try {
111
+ const contents = await readFile(modelPath, "utf8");
112
+ const result = v.safeParse(CustomTypeSchema, JSON.parse(contents));
113
+ if (!result.success) {
114
+ console.error(`Invalid page type model: ${modelPath.href}`);
115
+ process.exitCode = 1;
116
+ return;
117
+ }
118
+ model = result.output as CustomType;
119
+ } catch (error) {
120
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
121
+ console.error(`Page type not found: ${typeId}\n`);
122
+ console.error(`Create it first with: prismic page-type create ${typeId}`);
123
+ process.exitCode = 1;
124
+ return;
125
+ }
126
+ if (error instanceof Error) {
127
+ console.error(`Failed to read page type: ${error.message}`);
128
+ } else {
129
+ console.error("Failed to read page type");
130
+ }
131
+ process.exitCode = 1;
132
+ return;
133
+ }
134
+
135
+ // Determine target tab
136
+ const existingTabs = Object.keys(model.json);
137
+ const targetTab = tab ?? existingTabs[0] ?? "Main";
138
+
139
+ // Initialize tab if it doesn't exist
140
+ if (!model.json[targetTab]) {
141
+ model.json[targetTab] = {};
142
+ }
143
+
144
+ // Check if field already exists in any tab
145
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
146
+ if (tabFields[fieldId]) {
147
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
148
+ process.exitCode = 1;
149
+ return;
150
+ }
151
+ }
152
+
153
+ // Build field definition
154
+ const fieldDefinition: Group = {
155
+ type: "Group",
156
+ config: {
157
+ label: label ?? humanReadable(fieldId),
158
+ repeat: !nonRepeatable,
159
+ fields: {},
160
+ },
161
+ };
162
+
163
+ // Add field to model
164
+ model.json[targetTab][fieldId] = fieldDefinition;
165
+
166
+ // Write updated model
167
+ try {
168
+ await writeFile(modelPath, stringify(model));
169
+ } catch (error) {
170
+ if (error instanceof Error) {
171
+ console.error(`Failed to update page type: ${error.message}`);
172
+ } else {
173
+ console.error("Failed to update page type");
174
+ }
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+
179
+ console.info(`Added field "${fieldId}" (Group) to "${targetTab}" tab in ${typeId}`);
180
+
181
+ try {
182
+ await buildTypes({ output: types });
183
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
184
+ } catch (error) {
185
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
186
+ }
187
+
188
+ console.info();
189
+ console.info(`Next: Add fields to the group with \`prismic page-type add-field <type> ${typeId} ${fieldId}.<field-id>\``);
190
+
191
+ const frameworkInfo = await detectFrameworkInfo();
192
+ if (frameworkInfo?.framework) {
193
+ const docsPath = getDocsPath(frameworkInfo.framework);
194
+ console.info(
195
+ ` Run \`prismic docs fetch ${docsPath}#write-page-components\` to learn how to implement a page file`,
196
+ );
197
+ }
198
+ }