@angeloashmore/prismic-cli-poc 0.0.0-pr.8.b80fefa → 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
@@ -6,6 +6,8 @@ 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";
10
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
11
  import { stringify } from "./lib/json";
10
12
  import { humanReadable } from "./lib/string";
11
13
 
@@ -44,6 +46,17 @@ const CustomTypeSchema = v.object({
44
46
  json: v.record(v.string(), v.record(v.string(), v.unknown())),
45
47
  });
46
48
 
49
+ function getDocsPath(framework: Framework): string {
50
+ switch (framework) {
51
+ case "next":
52
+ return "nextjs/with-cli";
53
+ case "nuxt":
54
+ return "nuxt/with-cli";
55
+ case "sveltekit":
56
+ return "sveltekit/with-cli";
57
+ }
58
+ }
59
+
47
60
  export async function pageTypeAddFieldNumber(): Promise<void> {
48
61
  const {
49
62
  values: { help, tab, label, placeholder, min, max, step, types },
@@ -82,6 +95,15 @@ export async function pageTypeAddFieldNumber(): Promise<void> {
82
95
  return;
83
96
  }
84
97
 
98
+ // Parse and validate field path
99
+ const fieldPath = parseFieldPath(fieldId);
100
+ const pathValidation = validateNestedFieldPath(fieldPath);
101
+ if (!pathValidation.ok) {
102
+ console.error(pathValidation.error);
103
+ process.exitCode = 1;
104
+ return;
105
+ }
106
+
85
107
  // Parse numeric values
86
108
  const minValue = min !== undefined ? Number(min) : undefined;
87
109
  const maxValue = max !== undefined ? Number(max) : undefined;
@@ -151,20 +173,11 @@ export async function pageTypeAddFieldNumber(): Promise<void> {
151
173
  model.json[targetTab] = {};
152
174
  }
153
175
 
154
- // Check if field already exists in any tab
155
- for (const [tabName, tabFields] of Object.entries(model.json)) {
156
- if (tabFields[fieldId]) {
157
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
158
- process.exitCode = 1;
159
- return;
160
- }
161
- }
162
-
163
176
  // Build field definition
164
177
  const fieldDefinition: NumberField = {
165
178
  type: "Number",
166
179
  config: {
167
- label: label ?? humanReadable(fieldId),
180
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
168
181
  ...(placeholder && { placeholder }),
169
182
  ...(minValue !== undefined && { min: minValue }),
170
183
  ...(maxValue !== undefined && { max: maxValue }),
@@ -173,7 +186,36 @@ export async function pageTypeAddFieldNumber(): Promise<void> {
173
186
  };
174
187
 
175
188
  // Add field to model
176
- model.json[targetTab][fieldId] = fieldDefinition;
189
+ if (fieldPath.type === "nested") {
190
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
191
+ if (!groupResult.ok) {
192
+ console.error(groupResult.error);
193
+ process.exitCode = 1;
194
+ return;
195
+ }
196
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
197
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
198
+ process.exitCode = 1;
199
+ return;
200
+ }
201
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
202
+ } else {
203
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
204
+ if (tabFields[fieldId]) {
205
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
206
+ process.exitCode = 1;
207
+ return;
208
+ }
209
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
210
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
211
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
212
+ process.exitCode = 1;
213
+ return;
214
+ }
215
+ }
216
+ }
217
+ model.json[targetTab][fieldId] = fieldDefinition;
218
+ }
177
219
 
178
220
  // Write updated model
179
221
  try {
@@ -188,7 +230,11 @@ export async function pageTypeAddFieldNumber(): Promise<void> {
188
230
  return;
189
231
  }
190
232
 
191
- console.info(`Added field "${fieldId}" (Number) to "${targetTab}" tab in ${typeId}`);
233
+ if (fieldPath.type === "nested") {
234
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Number) to group "${fieldPath.groupId}" in ${typeId}`);
235
+ } else {
236
+ console.info(`Added field "${fieldId}" (Number) to "${targetTab}" tab in ${typeId}`);
237
+ }
192
238
 
193
239
  try {
194
240
  await buildTypes({ output: types });
@@ -199,5 +245,12 @@ export async function pageTypeAddFieldNumber(): Promise<void> {
199
245
 
200
246
  console.info();
201
247
  console.info("Next: Add more fields with `prismic page-type add-field`");
202
- console.info(" Run `prismic status` when done to find next steps");
248
+
249
+ const frameworkInfo = await detectFrameworkInfo();
250
+ if (frameworkInfo?.framework) {
251
+ const docsPath = getDocsPath(frameworkInfo.framework);
252
+ console.info(
253
+ ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
254
+ );
255
+ }
203
256
  }
@@ -6,6 +6,8 @@ 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";
10
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
11
  import { stringify } from "./lib/json";
10
12
  import { humanReadable } from "./lib/string";
11
13
 
@@ -50,6 +52,17 @@ const CustomTypeSchema = v.object({
50
52
  json: v.record(v.string(), v.record(v.string(), v.unknown())),
51
53
  });
52
54
 
55
+ function getDocsPath(framework: Framework): string {
56
+ switch (framework) {
57
+ case "next":
58
+ return "nextjs/with-cli";
59
+ case "nuxt":
60
+ return "nuxt/with-cli";
61
+ case "sveltekit":
62
+ return "sveltekit/with-cli";
63
+ }
64
+ }
65
+
53
66
  export async function pageTypeAddFieldRichText(): Promise<void> {
54
67
  const {
55
68
  values: {
@@ -97,6 +110,15 @@ export async function pageTypeAddFieldRichText(): Promise<void> {
97
110
  return;
98
111
  }
99
112
 
113
+ // Parse and validate field path
114
+ const fieldPath = parseFieldPath(fieldId);
115
+ const pathValidation = validateNestedFieldPath(fieldPath);
116
+ if (!pathValidation.ok) {
117
+ console.error(pathValidation.error);
118
+ process.exitCode = 1;
119
+ return;
120
+ }
121
+
100
122
  // Find the page type file
101
123
  const projectRoot = await findUpward("package.json");
102
124
  if (!projectRoot) {
@@ -143,20 +165,11 @@ export async function pageTypeAddFieldRichText(): Promise<void> {
143
165
  model.json[targetTab] = {};
144
166
  }
145
167
 
146
- // Check if field already exists in any tab
147
- for (const [tabName, tabFields] of Object.entries(model.json)) {
148
- if (tabFields[fieldId]) {
149
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
150
- process.exitCode = 1;
151
- return;
152
- }
153
- }
154
-
155
168
  // Build field definition
156
169
  const fieldDefinition: RichText = {
157
170
  type: "StructuredText",
158
171
  config: {
159
- label: label ?? humanReadable(fieldId),
172
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
160
173
  ...(placeholder && { placeholder }),
161
174
  ...(single && { single }),
162
175
  ...(multi && { multi }),
@@ -165,7 +178,36 @@ export async function pageTypeAddFieldRichText(): Promise<void> {
165
178
  };
166
179
 
167
180
  // Add field to model
168
- model.json[targetTab][fieldId] = fieldDefinition;
181
+ if (fieldPath.type === "nested") {
182
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
183
+ if (!groupResult.ok) {
184
+ console.error(groupResult.error);
185
+ process.exitCode = 1;
186
+ return;
187
+ }
188
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
189
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
190
+ process.exitCode = 1;
191
+ return;
192
+ }
193
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
194
+ } else {
195
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
196
+ if (tabFields[fieldId]) {
197
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
198
+ process.exitCode = 1;
199
+ return;
200
+ }
201
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
202
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
203
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
204
+ process.exitCode = 1;
205
+ return;
206
+ }
207
+ }
208
+ }
209
+ model.json[targetTab][fieldId] = fieldDefinition;
210
+ }
169
211
 
170
212
  // Write updated model
171
213
  try {
@@ -180,7 +222,11 @@ export async function pageTypeAddFieldRichText(): Promise<void> {
180
222
  return;
181
223
  }
182
224
 
183
- console.info(`Added field "${fieldId}" (StructuredText) to "${targetTab}" tab in ${typeId}`);
225
+ if (fieldPath.type === "nested") {
226
+ console.info(`Added field "${fieldPath.nestedFieldId}" (StructuredText) to group "${fieldPath.groupId}" in ${typeId}`);
227
+ } else {
228
+ console.info(`Added field "${fieldId}" (StructuredText) to "${targetTab}" tab in ${typeId}`);
229
+ }
184
230
 
185
231
  try {
186
232
  await buildTypes({ output: types });
@@ -191,5 +237,12 @@ export async function pageTypeAddFieldRichText(): Promise<void> {
191
237
 
192
238
  console.info();
193
239
  console.info("Next: Add more fields with `prismic page-type add-field`");
194
- console.info(" Run `prismic status` when done to find next steps");
240
+
241
+ const frameworkInfo = await detectFrameworkInfo();
242
+ if (frameworkInfo?.framework) {
243
+ const docsPath = getDocsPath(frameworkInfo.framework);
244
+ console.info(
245
+ ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
246
+ );
247
+ }
195
248
  }
@@ -6,6 +6,8 @@ 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";
10
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
11
  import { stringify } from "./lib/json";
10
12
  import { humanReadable } from "./lib/string";
11
13
 
@@ -43,17 +45,20 @@ const CustomTypeSchema = v.object({
43
45
  json: v.record(v.string(), v.record(v.string(), v.unknown())),
44
46
  });
45
47
 
48
+ function getDocsPath(framework: Framework): string {
49
+ switch (framework) {
50
+ case "next":
51
+ return "nextjs/with-cli";
52
+ case "nuxt":
53
+ return "nuxt/with-cli";
54
+ case "sveltekit":
55
+ return "sveltekit/with-cli";
56
+ }
57
+ }
58
+
46
59
  export async function pageTypeAddFieldSelect(): Promise<void> {
47
60
  const {
48
- values: {
49
- help,
50
- tab,
51
- label,
52
- placeholder,
53
- option,
54
- default: defaultValue,
55
- types,
56
- },
61
+ values: { help, tab, label, placeholder, option, default: defaultValue, types },
57
62
  positionals: [typeId, fieldId],
58
63
  } = parseArgs({
59
64
  args: process.argv.slice(5), // skip: node, script, "page-type", "add-field", "select"
@@ -88,6 +93,15 @@ export async function pageTypeAddFieldSelect(): Promise<void> {
88
93
  return;
89
94
  }
90
95
 
96
+ // Parse and validate field path
97
+ const fieldPath = parseFieldPath(fieldId);
98
+ const pathValidation = validateNestedFieldPath(fieldPath);
99
+ if (!pathValidation.ok) {
100
+ console.error(pathValidation.error);
101
+ process.exitCode = 1;
102
+ return;
103
+ }
104
+
91
105
  // Find the page type file
92
106
  const projectRoot = await findUpward("package.json");
93
107
  if (!projectRoot) {
@@ -134,20 +148,11 @@ export async function pageTypeAddFieldSelect(): Promise<void> {
134
148
  model.json[targetTab] = {};
135
149
  }
136
150
 
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
151
  // Build field definition
147
152
  const fieldDefinition: Select = {
148
153
  type: "Select",
149
154
  config: {
150
- label: label ?? humanReadable(fieldId),
155
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
151
156
  ...(placeholder && { placeholder }),
152
157
  ...(option && option.length > 0 && { options: option }),
153
158
  ...(defaultValue && { default_value: defaultValue }),
@@ -155,7 +160,36 @@ export async function pageTypeAddFieldSelect(): Promise<void> {
155
160
  };
156
161
 
157
162
  // Add field to model
158
- model.json[targetTab][fieldId] = fieldDefinition;
163
+ if (fieldPath.type === "nested") {
164
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
165
+ if (!groupResult.ok) {
166
+ console.error(groupResult.error);
167
+ process.exitCode = 1;
168
+ return;
169
+ }
170
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
171
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
172
+ process.exitCode = 1;
173
+ return;
174
+ }
175
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
176
+ } else {
177
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
178
+ if (tabFields[fieldId]) {
179
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
180
+ process.exitCode = 1;
181
+ return;
182
+ }
183
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
184
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
185
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ }
190
+ }
191
+ model.json[targetTab][fieldId] = fieldDefinition;
192
+ }
159
193
 
160
194
  // Write updated model
161
195
  try {
@@ -170,7 +204,11 @@ export async function pageTypeAddFieldSelect(): Promise<void> {
170
204
  return;
171
205
  }
172
206
 
173
- console.info(`Added field "${fieldId}" (Select) to "${targetTab}" tab in ${typeId}`);
207
+ if (fieldPath.type === "nested") {
208
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Select) to group "${fieldPath.groupId}" in ${typeId}`);
209
+ } else {
210
+ console.info(`Added field "${fieldId}" (Select) to "${targetTab}" tab in ${typeId}`);
211
+ }
174
212
 
175
213
  try {
176
214
  await buildTypes({ output: types });
@@ -181,5 +219,12 @@ export async function pageTypeAddFieldSelect(): Promise<void> {
181
219
 
182
220
  console.info();
183
221
  console.info("Next: Add more fields with `prismic page-type add-field`");
184
- console.info(" Run `prismic status` when done to find next steps");
222
+
223
+ const frameworkInfo = await detectFrameworkInfo();
224
+ if (frameworkInfo?.framework) {
225
+ const docsPath = getDocsPath(frameworkInfo.framework);
226
+ console.info(
227
+ ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
228
+ );
229
+ }
185
230
  }
@@ -6,6 +6,8 @@ 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";
10
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
11
  import { stringify } from "./lib/json";
10
12
  import { humanReadable } from "./lib/string";
11
13
 
@@ -42,6 +44,17 @@ const CustomTypeSchema = v.object({
42
44
  json: v.record(v.string(), v.record(v.string(), v.unknown())),
43
45
  });
44
46
 
47
+ function getDocsPath(framework: Framework): string {
48
+ switch (framework) {
49
+ case "next":
50
+ return "nextjs/with-cli";
51
+ case "nuxt":
52
+ return "nuxt/with-cli";
53
+ case "sveltekit":
54
+ return "sveltekit/with-cli";
55
+ }
56
+ }
57
+
45
58
  export async function pageTypeAddFieldTimestamp(): Promise<void> {
46
59
  const {
47
60
  values: { help, tab, label, placeholder, default: defaultValue, types },
@@ -78,6 +91,15 @@ export async function pageTypeAddFieldTimestamp(): Promise<void> {
78
91
  return;
79
92
  }
80
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
+
81
103
  // Find the page type file
82
104
  const projectRoot = await findUpward("package.json");
83
105
  if (!projectRoot) {
@@ -124,27 +146,47 @@ export async function pageTypeAddFieldTimestamp(): Promise<void> {
124
146
  model.json[targetTab] = {};
125
147
  }
126
148
 
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
149
  // Build field definition
137
150
  const fieldDefinition: Timestamp = {
138
151
  type: "Timestamp",
139
152
  config: {
140
- label: label ?? humanReadable(fieldId),
153
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
141
154
  ...(placeholder && { placeholder }),
142
155
  ...(defaultValue && { default: defaultValue }),
143
156
  },
144
157
  };
145
158
 
146
159
  // Add field to model
147
- 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
+ }
148
190
 
149
191
  // Write updated model
150
192
  try {
@@ -159,7 +201,11 @@ export async function pageTypeAddFieldTimestamp(): Promise<void> {
159
201
  return;
160
202
  }
161
203
 
162
- console.info(`Added field "${fieldId}" (Timestamp) to "${targetTab}" tab in ${typeId}`);
204
+ if (fieldPath.type === "nested") {
205
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Timestamp) to group "${fieldPath.groupId}" in ${typeId}`);
206
+ } else {
207
+ console.info(`Added field "${fieldId}" (Timestamp) to "${targetTab}" tab in ${typeId}`);
208
+ }
163
209
 
164
210
  try {
165
211
  await buildTypes({ output: types });
@@ -170,5 +216,12 @@ export async function pageTypeAddFieldTimestamp(): Promise<void> {
170
216
 
171
217
  console.info();
172
218
  console.info("Next: Add more fields with `prismic page-type add-field`");
173
- console.info(" Run `prismic status` when done to find next steps");
219
+
220
+ const frameworkInfo = await detectFrameworkInfo();
221
+ if (frameworkInfo?.framework) {
222
+ const docsPath = getDocsPath(frameworkInfo.framework);
223
+ console.info(
224
+ ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
225
+ );
226
+ }
174
227
  }
@@ -6,6 +6,8 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
10
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
11
  import { stringify } from "./lib/json";
10
12
  import { humanReadable } from "./lib/string";
11
13
 
@@ -41,6 +43,17 @@ const CustomTypeSchema = v.object({
41
43
  json: v.record(v.string(), v.record(v.string(), v.unknown())),
42
44
  });
43
45
 
46
+ function getDocsPath(framework: Framework): string {
47
+ switch (framework) {
48
+ case "next":
49
+ return "nextjs/with-cli";
50
+ case "nuxt":
51
+ return "nuxt/with-cli";
52
+ case "sveltekit":
53
+ return "sveltekit/with-cli";
54
+ }
55
+ }
56
+
44
57
  export async function pageTypeAddFieldUid(): Promise<void> {
45
58
  const {
46
59
  values: { help, tab, label, placeholder, types },
@@ -76,6 +89,22 @@ export async function pageTypeAddFieldUid(): Promise<void> {
76
89
  return;
77
90
  }
78
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
+
101
+ // UID fields cannot be nested in groups
102
+ if (fieldPath.type === "nested") {
103
+ console.error("UID fields cannot be nested inside groups");
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+
79
108
  // Find the page type file
80
109
  const projectRoot = await findUpward("package.json");
81
110
  if (!projectRoot) {
@@ -167,5 +196,12 @@ export async function pageTypeAddFieldUid(): Promise<void> {
167
196
 
168
197
  console.info();
169
198
  console.info("Next: Add more fields with `prismic page-type add-field`");
170
- console.info(" Run `prismic status` when done to find next steps");
199
+
200
+ const frameworkInfo = await detectFrameworkInfo();
201
+ if (frameworkInfo?.framework) {
202
+ const docsPath = getDocsPath(frameworkInfo.framework);
203
+ console.info(
204
+ ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
205
+ );
206
+ }
171
207
  }
@@ -5,6 +5,7 @@ import { pageTypeAddFieldColor } from "./page-type-add-field-color";
5
5
  import { pageTypeAddFieldDate } from "./page-type-add-field-date";
6
6
  import { pageTypeAddFieldEmbed } from "./page-type-add-field-embed";
7
7
  import { pageTypeAddFieldGeoPoint } from "./page-type-add-field-geo-point";
8
+ import { pageTypeAddFieldGroup } from "./page-type-add-field-group";
8
9
  import { pageTypeAddFieldImage } from "./page-type-add-field-image";
9
10
  import { pageTypeAddFieldKeyText } from "./page-type-add-field-key-text";
10
11
  import { pageTypeAddFieldLink } from "./page-type-add-field-link";
@@ -26,6 +27,7 @@ FIELD TYPES
26
27
  date Date picker
27
28
  embed Embed (oEmbed)
28
29
  geo-point Geographic coordinates
30
+ group Repeatable group of fields
29
31
  image Image
30
32
  key-text Single-line text
31
33
  link Any link type
@@ -76,6 +78,9 @@ export async function pageTypeAddField(): Promise<void> {
76
78
  case "geo-point":
77
79
  await pageTypeAddFieldGeoPoint();
78
80
  break;
81
+ case "group":
82
+ await pageTypeAddFieldGroup();
83
+ break;
79
84
  case "image":
80
85
  await pageTypeAddFieldImage();
81
86
  break;
@@ -5,6 +5,7 @@ import { parseArgs } from "node:util";
5
5
 
6
6
  import { buildTypes } from "./codegen-types";
7
7
  import { findUpward } from "./lib/file";
8
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
8
9
  import { stringify } from "./lib/json";
9
10
 
10
11
  const HELP = `
@@ -26,6 +27,21 @@ LEARN MORE
26
27
  Use \`prismic page-type <command> --help\` for more information about a command.
27
28
  `.trim();
28
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
+
29
45
  export async function pageTypeCreate(): Promise<void> {
30
46
  const {
31
47
  values: { help, name, single, types },
@@ -129,6 +145,15 @@ export async function pageTypeCreate(): Promise<void> {
129
145
 
130
146
  console.info();
131
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 ${docsPath}${anchor}\` to learn how to implement a page file`,
155
+ );
156
+ }
132
157
  }
133
158
 
134
159
  export function pascalCase(input: string): string {