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