@angeloashmore/prismic-cli-poc 0.0.0-pr.8.d3fbd0b → 0.0.0-pr.9.5366ece

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