@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,7 @@ 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";
9
10
  import { stringify } from "./lib/json";
10
11
  import { humanReadable } from "./lib/string";
11
12
 
@@ -76,6 +77,22 @@ export async function customTypeAddFieldUid(): 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
+
89
+ // UID fields cannot be nested in groups
90
+ if (fieldPath.type === "nested") {
91
+ console.error("UID fields cannot be nested inside groups");
92
+ process.exitCode = 1;
93
+ return;
94
+ }
95
+
79
96
  // Find the custom type file
80
97
  const projectRoot = await findUpward("package.json");
81
98
  if (!projectRoot) {
@@ -5,6 +5,7 @@ import { customTypeAddFieldColor } from "./custom-type-add-field-color";
5
5
  import { customTypeAddFieldDate } from "./custom-type-add-field-date";
6
6
  import { customTypeAddFieldEmbed } from "./custom-type-add-field-embed";
7
7
  import { customTypeAddFieldGeoPoint } from "./custom-type-add-field-geo-point";
8
+ import { customTypeAddFieldGroup } from "./custom-type-add-field-group";
8
9
  import { customTypeAddFieldImage } from "./custom-type-add-field-image";
9
10
  import { customTypeAddFieldKeyText } from "./custom-type-add-field-key-text";
10
11
  import { customTypeAddFieldLink } from "./custom-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 customTypeAddField(): Promise<void> {
76
78
  case "geo-point":
77
79
  await customTypeAddFieldGeoPoint();
78
80
  break;
81
+ case "group":
82
+ await customTypeAddFieldGroup();
83
+ break;
79
84
  case "image":
80
85
  await customTypeAddFieldImage();
81
86
  break;
package/src/index.ts CHANGED
@@ -16,6 +16,7 @@ import { pull } from "./pull";
16
16
  import { push } from "./push";
17
17
  import { repo } from "./repo";
18
18
  import { slice } from "./slice";
19
+ import { skill } from "./skill";
19
20
  import { status } from "./status";
20
21
  import { token } from "./token";
21
22
  import { webhook } from "./webhook";
@@ -42,6 +43,7 @@ COMMANDS
42
43
  push Push types and slices to Prismic
43
44
  codegen Generate code from Prismic models
44
45
  docs Fetch documentation from Prismic
46
+ skill Manage Prismic AI skills
45
47
  preview Manage preview configurations
46
48
  token Manage API tokens in a repository
47
49
  webhook Manage webhooks in a repository
@@ -112,6 +114,9 @@ if (version) {
112
114
  case "docs":
113
115
  await docs();
114
116
  break;
117
+ case "skill":
118
+ await skill();
119
+ break;
115
120
  case "preview":
116
121
  await preview();
117
122
  break;
@@ -0,0 +1,81 @@
1
+ export type FieldPath =
2
+ | { type: "top-level"; fieldId: string }
3
+ | { type: "nested"; groupId: string; nestedFieldId: string };
4
+
5
+ export function parseFieldPath(fieldId: string): FieldPath {
6
+ const parts = fieldId.split(".");
7
+
8
+ if (parts.length === 1) {
9
+ return { type: "top-level", fieldId };
10
+ }
11
+
12
+ if (parts.length === 2) {
13
+ return { type: "nested", groupId: parts[0], nestedFieldId: parts[1] };
14
+ }
15
+
16
+ // More than 2 parts means nested groups which aren't supported
17
+ return { type: "nested", groupId: parts[0], nestedFieldId: parts.slice(1).join(".") };
18
+ }
19
+
20
+ export type GroupFieldResult =
21
+ | { ok: true; group: { config: { fields: Record<string, unknown> } } }
22
+ | { ok: false; error: string };
23
+
24
+ export function isGroupField(field: unknown): field is { type: "Group"; config: { fields: Record<string, unknown> } } {
25
+ return (
26
+ typeof field === "object" &&
27
+ field !== null &&
28
+ "type" in field &&
29
+ field.type === "Group" &&
30
+ "config" in field &&
31
+ typeof field.config === "object" &&
32
+ field.config !== null &&
33
+ "fields" in field.config
34
+ );
35
+ }
36
+
37
+ export function findGroupInTab(
38
+ tabFields: Record<string, unknown>,
39
+ groupId: string,
40
+ tabName: string,
41
+ ): GroupFieldResult {
42
+ const field = tabFields[groupId];
43
+
44
+ if (!field) {
45
+ return { ok: false, error: `Group "${groupId}" not found in tab "${tabName}"` };
46
+ }
47
+
48
+ if (!isGroupField(field)) {
49
+ return { ok: false, error: `Field "${groupId}" is not a group` };
50
+ }
51
+
52
+ return { ok: true, group: field };
53
+ }
54
+
55
+ export function findGroupInVariation(
56
+ primary: Record<string, unknown>,
57
+ groupId: string,
58
+ variationId: string,
59
+ ): GroupFieldResult {
60
+ const field = primary[groupId];
61
+
62
+ if (!field) {
63
+ return { ok: false, error: `Group "${groupId}" not found in variation "${variationId}"` };
64
+ }
65
+
66
+ if (!isGroupField(field)) {
67
+ return { ok: false, error: `Field "${groupId}" is not a group` };
68
+ }
69
+
70
+ return { ok: true, group: field };
71
+ }
72
+
73
+ export function validateNestedFieldPath(fieldPath: FieldPath): { ok: true } | { ok: false; error: string } {
74
+ if (fieldPath.type === "nested" && fieldPath.nestedFieldId.includes(".")) {
75
+ return {
76
+ ok: false,
77
+ error: `Nested groups not supported. Use: group.field`,
78
+ };
79
+ }
80
+ return { ok: true };
81
+ }
@@ -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,6 +45,17 @@ 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 pageTypeAddFieldBoolean(): Promise<void> {
47
60
  const {
48
61
  values: {
@@ -88,6 +101,15 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
88
101
  return;
89
102
  }
90
103
 
104
+ // Parse and validate field path
105
+ const fieldPath = parseFieldPath(fieldId);
106
+ const pathValidation = validateNestedFieldPath(fieldPath);
107
+ if (!pathValidation.ok) {
108
+ console.error(pathValidation.error);
109
+ process.exitCode = 1;
110
+ return;
111
+ }
112
+
91
113
  // Find the page type file
92
114
  const projectRoot = await findUpward("package.json");
93
115
  if (!projectRoot) {
@@ -134,20 +156,11 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
134
156
  model.json[targetTab] = {};
135
157
  }
136
158
 
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
159
  // Build field definition
147
160
  const fieldDefinition: BooleanField = {
148
161
  type: "Boolean",
149
162
  config: {
150
- label: label ?? humanReadable(fieldId),
163
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
151
164
  ...(defaultValue && { default_value: true }),
152
165
  ...(trueLabel && { placeholder_true: trueLabel }),
153
166
  ...(falseLabel && { placeholder_false: falseLabel }),
@@ -155,7 +168,36 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
155
168
  };
156
169
 
157
170
  // Add field to model
158
- model.json[targetTab][fieldId] = fieldDefinition;
171
+ if (fieldPath.type === "nested") {
172
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
173
+ if (!groupResult.ok) {
174
+ console.error(groupResult.error);
175
+ process.exitCode = 1;
176
+ return;
177
+ }
178
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
179
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
180
+ process.exitCode = 1;
181
+ return;
182
+ }
183
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
184
+ } else {
185
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
186
+ if (tabFields[fieldId]) {
187
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
188
+ process.exitCode = 1;
189
+ return;
190
+ }
191
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
192
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
193
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
194
+ process.exitCode = 1;
195
+ return;
196
+ }
197
+ }
198
+ }
199
+ model.json[targetTab][fieldId] = fieldDefinition;
200
+ }
159
201
 
160
202
  // Write updated model
161
203
  try {
@@ -170,7 +212,11 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
170
212
  return;
171
213
  }
172
214
 
173
- console.info(`Added field "${fieldId}" (Boolean) to "${targetTab}" tab in ${typeId}`);
215
+ if (fieldPath.type === "nested") {
216
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Boolean) to group "${fieldPath.groupId}" in ${typeId}`);
217
+ } else {
218
+ console.info(`Added field "${fieldId}" (Boolean) to "${targetTab}" tab in ${typeId}`);
219
+ }
174
220
 
175
221
  try {
176
222
  await buildTypes({ output: types });
@@ -181,5 +227,12 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
181
227
 
182
228
  console.info();
183
229
  console.info("Next: Add more fields with `prismic page-type add-field`");
184
- console.info(" Run `prismic status` when done to find next steps");
230
+
231
+ const frameworkInfo = await detectFrameworkInfo();
232
+ if (frameworkInfo?.framework) {
233
+ const docsPath = getDocsPath(frameworkInfo.framework);
234
+ console.info(
235
+ ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
236
+ );
237
+ }
185
238
  }
@@ -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
 
@@ -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 pageTypeAddFieldColor(): Promise<void> {
45
58
  const {
46
59
  values: { help, tab, label, placeholder, types },
@@ -76,6 +89,15 @@ export async function pageTypeAddFieldColor(): 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
+
79
101
  // Find the page type file
80
102
  const projectRoot = await findUpward("package.json");
81
103
  if (!projectRoot) {
@@ -122,26 +144,46 @@ export async function pageTypeAddFieldColor(): Promise<void> {
122
144
  model.json[targetTab] = {};
123
145
  }
124
146
 
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
147
  // Build field definition
135
148
  const fieldDefinition: Color = {
136
149
  type: "Color",
137
150
  config: {
138
- label: label ?? humanReadable(fieldId),
151
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
139
152
  ...(placeholder && { placeholder }),
140
153
  },
141
154
  };
142
155
 
143
156
  // Add field to model
144
- 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
+ }
145
187
 
146
188
  // Write updated model
147
189
  try {
@@ -156,7 +198,11 @@ export async function pageTypeAddFieldColor(): Promise<void> {
156
198
  return;
157
199
  }
158
200
 
159
- 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
+ }
160
206
 
161
207
  try {
162
208
  await buildTypes({ output: types });
@@ -167,5 +213,12 @@ export async function pageTypeAddFieldColor(): Promise<void> {
167
213
 
168
214
  console.info();
169
215
  console.info("Next: Add more fields with `prismic page-type add-field`");
170
- console.info(" Run `prismic status` when done to find next steps");
216
+
217
+ const frameworkInfo = await detectFrameworkInfo();
218
+ if (frameworkInfo?.framework) {
219
+ const docsPath = getDocsPath(frameworkInfo.framework);
220
+ console.info(
221
+ ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
222
+ );
223
+ }
171
224
  }
@@ -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 pageTypeAddFieldDate(): Promise<void> {
46
59
  const {
47
60
  values: { help, tab, label, placeholder, default: defaultValue, types },
@@ -78,6 +91,15 @@ export async function pageTypeAddFieldDate(): 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 pageTypeAddFieldDate(): 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: DateField = {
138
151
  type: "Date",
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 pageTypeAddFieldDate(): Promise<void> {
159
201
  return;
160
202
  }
161
203
 
162
- 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
+ }
163
209
 
164
210
  try {
165
211
  await buildTypes({ output: types });
@@ -170,5 +216,12 @@ export async function pageTypeAddFieldDate(): 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 { 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
 
@@ -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 pageTypeAddFieldEmbed(): Promise<void> {
45
58
  const {
46
59
  values: { help, tab, label, placeholder, types },
@@ -76,6 +89,15 @@ export async function pageTypeAddFieldEmbed(): 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
+
79
101
  // Find the page type file
80
102
  const projectRoot = await findUpward("package.json");
81
103
  if (!projectRoot) {
@@ -122,26 +144,46 @@ export async function pageTypeAddFieldEmbed(): Promise<void> {
122
144
  model.json[targetTab] = {};
123
145
  }
124
146
 
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
147
  // Build field definition
135
148
  const fieldDefinition: Embed = {
136
149
  type: "Embed",
137
150
  config: {
138
- label: label ?? humanReadable(fieldId),
151
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
139
152
  ...(placeholder && { placeholder }),
140
153
  },
141
154
  };
142
155
 
143
156
  // Add field to model
144
- 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
+ }
145
187
 
146
188
  // Write updated model
147
189
  try {
@@ -156,7 +198,11 @@ export async function pageTypeAddFieldEmbed(): Promise<void> {
156
198
  return;
157
199
  }
158
200
 
159
- 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
+ }
160
206
 
161
207
  try {
162
208
  await buildTypes({ output: types });
@@ -167,5 +213,12 @@ export async function pageTypeAddFieldEmbed(): Promise<void> {
167
213
 
168
214
  console.info();
169
215
  console.info("Next: Add more fields with `prismic page-type add-field`");
170
- console.info(" Run `prismic status` when done to find next steps");
216
+
217
+ const frameworkInfo = await detectFrameworkInfo();
218
+ if (frameworkInfo?.framework) {
219
+ const docsPath = getDocsPath(frameworkInfo.framework);
220
+ console.info(
221
+ ` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
222
+ );
223
+ }
171
224
  }