@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
@@ -4,6 +4,7 @@ import { writeFile } from "node:fs/promises";
4
4
  import { parseArgs } from "node:util";
5
5
 
6
6
  import { buildTypes } from "./codegen-types";
7
+ import { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
7
8
  import { type Framework, detectFrameworkInfo } from "./lib/framework";
8
9
  import { stringify } from "./lib/json";
9
10
  import { findSliceModel } from "./lib/slice";
@@ -89,6 +90,15 @@ export async function sliceAddFieldEmbed(): Promise<void> {
89
90
  return;
90
91
  }
91
92
 
93
+ // Parse and validate field path
94
+ const fieldPath = parseFieldPath(fieldId);
95
+ const pathValidation = validateNestedFieldPath(fieldPath);
96
+ if (!pathValidation.ok) {
97
+ console.error(pathValidation.error);
98
+ process.exitCode = 1;
99
+ return;
100
+ }
101
+
92
102
  // Find the slice model
93
103
  const result = await findSliceModel(sliceId);
94
104
  if (!result.ok) {
@@ -124,26 +134,53 @@ export async function sliceAddFieldEmbed(): Promise<void> {
124
134
  targetVariation.primary = {};
125
135
  }
126
136
 
127
- // Check if field already exists in any variation
128
- for (const v of model.variations) {
129
- if (v.primary?.[fieldId]) {
130
- console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
131
- process.exitCode = 1;
132
- return;
133
- }
134
- }
135
-
136
137
  // Build field definition
137
138
  const fieldDefinition: Embed = {
138
139
  type: "Embed",
139
140
  config: {
140
- label: label ?? humanReadable(fieldId),
141
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
141
142
  ...(placeholder && { placeholder }),
142
143
  },
143
144
  };
144
145
 
145
- // Add field to variation
146
- targetVariation.primary[fieldId] = fieldDefinition;
146
+ // Add field to variation (with nested field support)
147
+ if (fieldPath.type === "nested") {
148
+ const groupResult = findGroupInVariation(targetVariation.primary, fieldPath.groupId, targetVariation.id);
149
+ if (!groupResult.ok) {
150
+ console.error(groupResult.error);
151
+ process.exitCode = 1;
152
+ return;
153
+ }
154
+ // Check if nested field already exists
155
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
156
+ console.error(
157
+ `Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`,
158
+ );
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
163
+ } else {
164
+ // Check if field already exists in any variation (at top level or in groups)
165
+ for (const v of model.variations) {
166
+ if (v.primary?.[fieldId]) {
167
+ console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ // Also check inside groups
172
+ for (const [groupFieldId, groupField] of Object.entries(v.primary ?? {})) {
173
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
174
+ console.error(
175
+ `Field "${fieldId}" already exists in group "${groupFieldId}" in variation "${v.id}"`,
176
+ );
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ }
181
+ }
182
+ targetVariation.primary[fieldId] = fieldDefinition;
183
+ }
147
184
 
148
185
  // Write updated model
149
186
  try {
@@ -158,9 +195,15 @@ export async function sliceAddFieldEmbed(): Promise<void> {
158
195
  return;
159
196
  }
160
197
 
161
- console.info(
162
- `Added field "${fieldId}" (Embed) to "${targetVariation.id}" variation in ${sliceId}`,
163
- );
198
+ if (fieldPath.type === "nested") {
199
+ console.info(
200
+ `Added field "${fieldPath.nestedFieldId}" (Embed) to group "${fieldPath.groupId}" in ${sliceId}`,
201
+ );
202
+ } else {
203
+ console.info(
204
+ `Added field "${fieldId}" (Embed) to "${targetVariation.id}" variation in ${sliceId}`,
205
+ );
206
+ }
164
207
 
165
208
  try {
166
209
  await buildTypes({ output: types });
@@ -177,7 +220,7 @@ export async function sliceAddFieldEmbed(): Promise<void> {
177
220
  const docsPath = getDocsPath(frameworkInfo.framework);
178
221
  const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
179
222
  console.info(
180
- ` Run \`prismic docs ${docsPath}${anchor}\` to learn how to implement the slice's component`,
223
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
181
224
  );
182
225
  }
183
226
  }
@@ -4,6 +4,7 @@ import { writeFile } from "node:fs/promises";
4
4
  import { parseArgs } from "node:util";
5
5
 
6
6
  import { buildTypes } from "./codegen-types";
7
+ import { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
7
8
  import { type Framework, detectFrameworkInfo } from "./lib/framework";
8
9
  import { stringify } from "./lib/json";
9
10
  import { findSliceModel } from "./lib/slice";
@@ -87,6 +88,15 @@ export async function sliceAddFieldGeoPoint(): Promise<void> {
87
88
  return;
88
89
  }
89
90
 
91
+ // Parse and validate field path
92
+ const fieldPath = parseFieldPath(fieldId);
93
+ const pathValidation = validateNestedFieldPath(fieldPath);
94
+ if (!pathValidation.ok) {
95
+ console.error(pathValidation.error);
96
+ process.exitCode = 1;
97
+ return;
98
+ }
99
+
90
100
  // Find the slice model
91
101
  const result = await findSliceModel(sliceId);
92
102
  if (!result.ok) {
@@ -122,25 +132,52 @@ export async function sliceAddFieldGeoPoint(): Promise<void> {
122
132
  targetVariation.primary = {};
123
133
  }
124
134
 
125
- // Check if field already exists in any variation
126
- for (const v of model.variations) {
127
- if (v.primary?.[fieldId]) {
128
- console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
129
- process.exitCode = 1;
130
- return;
131
- }
132
- }
133
-
134
135
  // Build field definition
135
136
  const fieldDefinition: GeoPoint = {
136
137
  type: "GeoPoint",
137
138
  config: {
138
- label: label ?? humanReadable(fieldId),
139
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
139
140
  },
140
141
  };
141
142
 
142
- // Add field to variation
143
- targetVariation.primary[fieldId] = fieldDefinition;
143
+ // Add field to variation (with nested field support)
144
+ if (fieldPath.type === "nested") {
145
+ const groupResult = findGroupInVariation(targetVariation.primary, fieldPath.groupId, targetVariation.id);
146
+ if (!groupResult.ok) {
147
+ console.error(groupResult.error);
148
+ process.exitCode = 1;
149
+ return;
150
+ }
151
+ // Check if nested field already exists
152
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
153
+ console.error(
154
+ `Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`,
155
+ );
156
+ process.exitCode = 1;
157
+ return;
158
+ }
159
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
160
+ } else {
161
+ // Check if field already exists in any variation (at top level or in groups)
162
+ for (const v of model.variations) {
163
+ if (v.primary?.[fieldId]) {
164
+ console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
165
+ process.exitCode = 1;
166
+ return;
167
+ }
168
+ // Also check inside groups
169
+ for (const [groupFieldId, groupField] of Object.entries(v.primary ?? {})) {
170
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
171
+ console.error(
172
+ `Field "${fieldId}" already exists in group "${groupFieldId}" in variation "${v.id}"`,
173
+ );
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+ }
178
+ }
179
+ targetVariation.primary[fieldId] = fieldDefinition;
180
+ }
144
181
 
145
182
  // Write updated model
146
183
  try {
@@ -155,9 +192,15 @@ export async function sliceAddFieldGeoPoint(): Promise<void> {
155
192
  return;
156
193
  }
157
194
 
158
- console.info(
159
- `Added field "${fieldId}" (GeoPoint) to "${targetVariation.id}" variation in ${sliceId}`,
160
- );
195
+ if (fieldPath.type === "nested") {
196
+ console.info(
197
+ `Added field "${fieldPath.nestedFieldId}" (GeoPoint) to group "${fieldPath.groupId}" in ${sliceId}`,
198
+ );
199
+ } else {
200
+ console.info(
201
+ `Added field "${fieldId}" (GeoPoint) to "${targetVariation.id}" variation in ${sliceId}`,
202
+ );
203
+ }
161
204
 
162
205
  try {
163
206
  await buildTypes({ output: types });
@@ -174,7 +217,7 @@ export async function sliceAddFieldGeoPoint(): Promise<void> {
174
217
  const docsPath = getDocsPath(frameworkInfo.framework);
175
218
  const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
176
219
  console.info(
177
- ` Run \`prismic docs ${docsPath}${anchor}\` to learn how to implement the slice's component`,
220
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
178
221
  );
179
222
  }
180
223
  }
@@ -0,0 +1,191 @@
1
+ import type { Group, SharedSlice } from "@prismicio/types-internal/lib/customtypes";
2
+
3
+ import { writeFile } from "node:fs/promises";
4
+ import { parseArgs } from "node:util";
5
+
6
+ import { buildTypes } from "./codegen-types";
7
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
8
+ import { stringify } from "./lib/json";
9
+ import { findSliceModel } from "./lib/slice";
10
+ import { humanReadable } from "./lib/string";
11
+
12
+ const HELP = `
13
+ Add a group field to an existing slice.
14
+
15
+ USAGE
16
+ prismic slice add-field group <slice-id> <field-id> [flags]
17
+
18
+ ARGUMENTS
19
+ slice-id Slice identifier (required)
20
+ field-id Field identifier (required)
21
+
22
+ FLAGS
23
+ -v, --variation string Target variation (default: first variation)
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 slice add-field group my_slice buttons
31
+ prismic slice add-field group hero ctas --non-repeatable
32
+ prismic slice add-field group product variants --variation "withImage"
33
+ `.trim();
34
+
35
+ function getDocsPath(framework: Framework): string {
36
+ switch (framework) {
37
+ case "next":
38
+ return "nextjs/with-cli";
39
+ case "nuxt":
40
+ return "nuxt/with-cli";
41
+ case "sveltekit":
42
+ return "sveltekit/with-cli";
43
+ }
44
+ }
45
+
46
+ function getWriteComponentsAnchor(framework: Framework): string {
47
+ switch (framework) {
48
+ case "nuxt":
49
+ return "#write-vue-components";
50
+ case "sveltekit":
51
+ return "#write-svelte-components";
52
+ default:
53
+ return "#write-react-components";
54
+ }
55
+ }
56
+
57
+ export async function sliceAddFieldGroup(): Promise<void> {
58
+ const {
59
+ values: { help, variation, label, "non-repeatable": nonRepeatable, types },
60
+ positionals: [sliceId, fieldId],
61
+ } = parseArgs({
62
+ args: process.argv.slice(5), // skip: node, script, "slice", "add-field", "group"
63
+ options: {
64
+ variation: { type: "string", short: "v" },
65
+ label: { type: "string", short: "l" },
66
+ "non-repeatable": { type: "boolean" },
67
+ types: { type: "string" },
68
+ help: { type: "boolean", short: "h" },
69
+ },
70
+ allowPositionals: true,
71
+ });
72
+
73
+ if (help) {
74
+ console.info(HELP);
75
+ return;
76
+ }
77
+
78
+ if (!sliceId) {
79
+ console.error("Missing required argument: slice-id\n");
80
+ console.error("Usage: prismic slice add-field group <slice-id> <field-id>");
81
+ process.exitCode = 1;
82
+ return;
83
+ }
84
+
85
+ if (!fieldId) {
86
+ console.error("Missing required argument: field-id\n");
87
+ console.error("Usage: prismic slice add-field group <slice-id> <field-id>");
88
+ process.exitCode = 1;
89
+ return;
90
+ }
91
+
92
+ // Groups cannot be nested
93
+ if (fieldId.includes(".")) {
94
+ console.error("Groups cannot be nested inside other groups");
95
+ process.exitCode = 1;
96
+ return;
97
+ }
98
+
99
+ // Find the slice model
100
+ const result = await findSliceModel(sliceId);
101
+ if (!result.ok) {
102
+ console.error(result.error);
103
+ process.exitCode = 1;
104
+ return;
105
+ }
106
+
107
+ const { model, modelPath } = result;
108
+
109
+ // Check for variations
110
+ if (model.variations.length === 0) {
111
+ console.error(`Slice "${sliceId}" has no variations.\n`);
112
+ console.error("Add a variation first before adding fields.");
113
+ process.exitCode = 1;
114
+ return;
115
+ }
116
+
117
+ // Find target variation
118
+ const targetVariation = variation
119
+ ? model.variations.find((v) => v.id === variation)
120
+ : model.variations[0];
121
+
122
+ if (!targetVariation) {
123
+ console.error(`Variation "${variation}" not found in slice "${sliceId}"\n`);
124
+ console.error(`Available variations: ${model.variations.map((v) => v.id).join(", ")}`);
125
+ process.exitCode = 1;
126
+ return;
127
+ }
128
+
129
+ // Initialize primary if it doesn't exist
130
+ if (!targetVariation.primary) {
131
+ targetVariation.primary = {};
132
+ }
133
+
134
+ // Check if field already exists in any variation
135
+ for (const v of model.variations) {
136
+ if (v.primary?.[fieldId]) {
137
+ console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
138
+ process.exitCode = 1;
139
+ return;
140
+ }
141
+ }
142
+
143
+ // Build field definition
144
+ const fieldDefinition: Group = {
145
+ type: "Group",
146
+ config: {
147
+ label: label ?? humanReadable(fieldId),
148
+ repeat: !nonRepeatable,
149
+ fields: {},
150
+ },
151
+ };
152
+
153
+ // Add field to variation
154
+ targetVariation.primary[fieldId] = fieldDefinition;
155
+
156
+ // Write updated model
157
+ try {
158
+ await writeFile(modelPath, stringify(model as SharedSlice));
159
+ } catch (error) {
160
+ if (error instanceof Error) {
161
+ console.error(`Failed to update slice: ${error.message}`);
162
+ } else {
163
+ console.error("Failed to update slice");
164
+ }
165
+ process.exitCode = 1;
166
+ return;
167
+ }
168
+
169
+ console.info(
170
+ `Added field "${fieldId}" (Group) to "${targetVariation.id}" variation in ${sliceId}`,
171
+ );
172
+
173
+ try {
174
+ await buildTypes({ output: types });
175
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
176
+ } catch (error) {
177
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
178
+ }
179
+
180
+ console.info();
181
+ console.info(`Next: Add fields to the group with \`prismic slice add-field <type> ${sliceId} ${fieldId}.<field-id>\``);
182
+
183
+ const frameworkInfo = await detectFrameworkInfo();
184
+ if (frameworkInfo?.framework) {
185
+ const docsPath = getDocsPath(frameworkInfo.framework);
186
+ const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
187
+ console.info(
188
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
189
+ );
190
+ }
191
+ }
@@ -4,6 +4,7 @@ import { writeFile } from "node:fs/promises";
4
4
  import { parseArgs } from "node:util";
5
5
 
6
6
  import { buildTypes } from "./codegen-types";
7
+ import { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
7
8
  import { type Framework, detectFrameworkInfo } from "./lib/framework";
8
9
  import { stringify } from "./lib/json";
9
10
  import { findSliceModel } from "./lib/slice";
@@ -87,6 +88,15 @@ export async function sliceAddFieldImage(): Promise<void> {
87
88
  return;
88
89
  }
89
90
 
91
+ // Parse and validate field path
92
+ const fieldPath = parseFieldPath(fieldId);
93
+ const pathValidation = validateNestedFieldPath(fieldPath);
94
+ if (!pathValidation.ok) {
95
+ console.error(pathValidation.error);
96
+ process.exitCode = 1;
97
+ return;
98
+ }
99
+
90
100
  // Find the slice model
91
101
  const result = await findSliceModel(sliceId);
92
102
  if (!result.ok) {
@@ -122,25 +132,52 @@ export async function sliceAddFieldImage(): Promise<void> {
122
132
  targetVariation.primary = {};
123
133
  }
124
134
 
125
- // Check if field already exists in any variation
126
- for (const v of model.variations) {
127
- if (v.primary?.[fieldId]) {
128
- console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
129
- process.exitCode = 1;
130
- return;
131
- }
132
- }
133
-
134
135
  // Build field definition
135
136
  const fieldDefinition: Image = {
136
137
  type: "Image",
137
138
  config: {
138
- label: label ?? humanReadable(fieldId),
139
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
139
140
  },
140
141
  };
141
142
 
142
- // Add field to variation
143
- targetVariation.primary[fieldId] = fieldDefinition;
143
+ // Add field to variation (with nested field support)
144
+ if (fieldPath.type === "nested") {
145
+ const groupResult = findGroupInVariation(targetVariation.primary, fieldPath.groupId, targetVariation.id);
146
+ if (!groupResult.ok) {
147
+ console.error(groupResult.error);
148
+ process.exitCode = 1;
149
+ return;
150
+ }
151
+ // Check if nested field already exists
152
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
153
+ console.error(
154
+ `Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`,
155
+ );
156
+ process.exitCode = 1;
157
+ return;
158
+ }
159
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
160
+ } else {
161
+ // Check if field already exists in any variation (at top level or in groups)
162
+ for (const v of model.variations) {
163
+ if (v.primary?.[fieldId]) {
164
+ console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
165
+ process.exitCode = 1;
166
+ return;
167
+ }
168
+ // Also check inside groups
169
+ for (const [groupFieldId, groupField] of Object.entries(v.primary ?? {})) {
170
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
171
+ console.error(
172
+ `Field "${fieldId}" already exists in group "${groupFieldId}" in variation "${v.id}"`,
173
+ );
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+ }
178
+ }
179
+ targetVariation.primary[fieldId] = fieldDefinition;
180
+ }
144
181
 
145
182
  // Write updated model
146
183
  try {
@@ -155,9 +192,15 @@ export async function sliceAddFieldImage(): Promise<void> {
155
192
  return;
156
193
  }
157
194
 
158
- console.info(
159
- `Added field "${fieldId}" (Image) to "${targetVariation.id}" variation in ${sliceId}`,
160
- );
195
+ if (fieldPath.type === "nested") {
196
+ console.info(
197
+ `Added field "${fieldPath.nestedFieldId}" (Image) to group "${fieldPath.groupId}" in ${sliceId}`,
198
+ );
199
+ } else {
200
+ console.info(
201
+ `Added field "${fieldId}" (Image) to "${targetVariation.id}" variation in ${sliceId}`,
202
+ );
203
+ }
161
204
 
162
205
  try {
163
206
  await buildTypes({ output: types });
@@ -174,7 +217,7 @@ export async function sliceAddFieldImage(): Promise<void> {
174
217
  const docsPath = getDocsPath(frameworkInfo.framework);
175
218
  const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
176
219
  console.info(
177
- ` Run \`prismic docs ${docsPath}${anchor}\` to learn how to implement the slice's component`,
220
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
178
221
  );
179
222
  }
180
223
  }
@@ -4,6 +4,7 @@ import { writeFile } from "node:fs/promises";
4
4
  import { parseArgs } from "node:util";
5
5
 
6
6
  import { buildTypes } from "./codegen-types";
7
+ import { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
7
8
  import { type Framework, detectFrameworkInfo } from "./lib/framework";
8
9
  import { stringify } from "./lib/json";
9
10
  import { findSliceModel } from "./lib/slice";
@@ -89,6 +90,15 @@ export async function sliceAddFieldKeyText(): Promise<void> {
89
90
  return;
90
91
  }
91
92
 
93
+ // Parse and validate field path
94
+ const fieldPath = parseFieldPath(fieldId);
95
+ const pathValidation = validateNestedFieldPath(fieldPath);
96
+ if (!pathValidation.ok) {
97
+ console.error(pathValidation.error);
98
+ process.exitCode = 1;
99
+ return;
100
+ }
101
+
92
102
  // Find the slice model
93
103
  const result = await findSliceModel(sliceId);
94
104
  if (!result.ok) {
@@ -124,26 +134,53 @@ export async function sliceAddFieldKeyText(): Promise<void> {
124
134
  targetVariation.primary = {};
125
135
  }
126
136
 
127
- // Check if field already exists in any variation
128
- for (const v of model.variations) {
129
- if (v.primary?.[fieldId]) {
130
- console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
131
- process.exitCode = 1;
132
- return;
133
- }
134
- }
135
-
136
137
  // Build field definition
137
138
  const fieldDefinition: Text = {
138
139
  type: "Text",
139
140
  config: {
140
- label: label ?? humanReadable(fieldId),
141
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
141
142
  ...(placeholder && { placeholder }),
142
143
  },
143
144
  };
144
145
 
145
- // Add field to variation
146
- targetVariation.primary[fieldId] = fieldDefinition;
146
+ // Add field to variation (with nested field support)
147
+ if (fieldPath.type === "nested") {
148
+ const groupResult = findGroupInVariation(targetVariation.primary, fieldPath.groupId, targetVariation.id);
149
+ if (!groupResult.ok) {
150
+ console.error(groupResult.error);
151
+ process.exitCode = 1;
152
+ return;
153
+ }
154
+ // Check if nested field already exists
155
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
156
+ console.error(
157
+ `Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`,
158
+ );
159
+ process.exitCode = 1;
160
+ return;
161
+ }
162
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
163
+ } else {
164
+ // Check if field already exists in any variation (at top level or in groups)
165
+ for (const v of model.variations) {
166
+ if (v.primary?.[fieldId]) {
167
+ console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ // Also check inside groups
172
+ for (const [groupFieldId, groupField] of Object.entries(v.primary ?? {})) {
173
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
174
+ console.error(
175
+ `Field "${fieldId}" already exists in group "${groupFieldId}" in variation "${v.id}"`,
176
+ );
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ }
181
+ }
182
+ targetVariation.primary[fieldId] = fieldDefinition;
183
+ }
147
184
 
148
185
  // Write updated model
149
186
  try {
@@ -158,9 +195,15 @@ export async function sliceAddFieldKeyText(): Promise<void> {
158
195
  return;
159
196
  }
160
197
 
161
- console.info(
162
- `Added field "${fieldId}" (Text) to "${targetVariation.id}" variation in ${sliceId}`,
163
- );
198
+ if (fieldPath.type === "nested") {
199
+ console.info(
200
+ `Added field "${fieldPath.nestedFieldId}" (Text) to group "${fieldPath.groupId}" in ${sliceId}`,
201
+ );
202
+ } else {
203
+ console.info(
204
+ `Added field "${fieldId}" (Text) to "${targetVariation.id}" variation in ${sliceId}`,
205
+ );
206
+ }
164
207
 
165
208
  try {
166
209
  await buildTypes({ output: types });
@@ -177,7 +220,7 @@ export async function sliceAddFieldKeyText(): Promise<void> {
177
220
  const docsPath = getDocsPath(frameworkInfo.framework);
178
221
  const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
179
222
  console.info(
180
- ` Run \`prismic docs ${docsPath}${anchor}\` to learn how to implement the slice's component`,
223
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
181
224
  );
182
225
  }
183
226
  }