@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 { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { stringify } from "./lib/json";
10
11
  import { humanReadable } from "./lib/string";
11
12
 
@@ -76,6 +77,15 @@ export async function customTypeAddFieldImage(): Promise<void> {
76
77
  return;
77
78
  }
78
79
 
80
+ // Parse and validate field path
81
+ const fieldPath = parseFieldPath(fieldId);
82
+ const pathValidation = validateNestedFieldPath(fieldPath);
83
+ if (!pathValidation.ok) {
84
+ console.error(pathValidation.error);
85
+ process.exitCode = 1;
86
+ return;
87
+ }
88
+
79
89
  // Find the custom type file
80
90
  const projectRoot = await findUpward("package.json");
81
91
  if (!projectRoot) {
@@ -122,26 +132,46 @@ export async function customTypeAddFieldImage(): Promise<void> {
122
132
  model.json[targetTab] = {};
123
133
  }
124
134
 
125
- // Check if field already exists in any tab
126
- for (const [tabName, tabFields] of Object.entries(model.json)) {
127
- if (tabFields[fieldId]) {
128
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
129
- process.exitCode = 1;
130
- return;
131
- }
132
- }
133
-
134
135
  // Build field definition
135
136
  const fieldDefinition: 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
  ...(placeholder && { placeholder }),
140
141
  },
141
142
  };
142
143
 
143
144
  // Add field to model
144
- model.json[targetTab][fieldId] = fieldDefinition;
145
+ if (fieldPath.type === "nested") {
146
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
147
+ if (!groupResult.ok) {
148
+ console.error(groupResult.error);
149
+ process.exitCode = 1;
150
+ return;
151
+ }
152
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
153
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
154
+ process.exitCode = 1;
155
+ return;
156
+ }
157
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
158
+ } else {
159
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
160
+ if (tabFields[fieldId]) {
161
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
162
+ process.exitCode = 1;
163
+ return;
164
+ }
165
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
166
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
167
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ }
172
+ }
173
+ model.json[targetTab][fieldId] = fieldDefinition;
174
+ }
145
175
 
146
176
  // Write updated model
147
177
  try {
@@ -156,7 +186,11 @@ export async function customTypeAddFieldImage(): Promise<void> {
156
186
  return;
157
187
  }
158
188
 
159
- console.info(`Added field "${fieldId}" (Image) to "${targetTab}" tab in ${typeId}`);
189
+ if (fieldPath.type === "nested") {
190
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Image) to group "${fieldPath.groupId}" in ${typeId}`);
191
+ } else {
192
+ console.info(`Added field "${fieldId}" (Image) to "${targetTab}" tab in ${typeId}`);
193
+ }
160
194
 
161
195
  try {
162
196
  await buildTypes({ output: types });
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { stringify } from "./lib/json";
10
11
  import { humanReadable } from "./lib/string";
11
12
 
@@ -76,6 +77,15 @@ export async function customTypeAddFieldKeyText(): Promise<void> {
76
77
  return;
77
78
  }
78
79
 
80
+ // Parse and validate field path
81
+ const fieldPath = parseFieldPath(fieldId);
82
+ const pathValidation = validateNestedFieldPath(fieldPath);
83
+ if (!pathValidation.ok) {
84
+ console.error(pathValidation.error);
85
+ process.exitCode = 1;
86
+ return;
87
+ }
88
+
79
89
  // Find the custom type file
80
90
  const projectRoot = await findUpward("package.json");
81
91
  if (!projectRoot) {
@@ -122,26 +132,46 @@ export async function customTypeAddFieldKeyText(): Promise<void> {
122
132
  model.json[targetTab] = {};
123
133
  }
124
134
 
125
- // Check if field already exists in any tab
126
- for (const [tabName, tabFields] of Object.entries(model.json)) {
127
- if (tabFields[fieldId]) {
128
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
129
- process.exitCode = 1;
130
- return;
131
- }
132
- }
133
-
134
135
  // Build field definition
135
136
  const fieldDefinition: Text = {
136
137
  type: "Text",
137
138
  config: {
138
- label: label ?? humanReadable(fieldId),
139
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
139
140
  ...(placeholder && { placeholder }),
140
141
  },
141
142
  };
142
143
 
143
144
  // Add field to model
144
- model.json[targetTab][fieldId] = fieldDefinition;
145
+ if (fieldPath.type === "nested") {
146
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
147
+ if (!groupResult.ok) {
148
+ console.error(groupResult.error);
149
+ process.exitCode = 1;
150
+ return;
151
+ }
152
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
153
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
154
+ process.exitCode = 1;
155
+ return;
156
+ }
157
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
158
+ } else {
159
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
160
+ if (tabFields[fieldId]) {
161
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
162
+ process.exitCode = 1;
163
+ return;
164
+ }
165
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
166
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
167
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ }
172
+ }
173
+ model.json[targetTab][fieldId] = fieldDefinition;
174
+ }
145
175
 
146
176
  // Write updated model
147
177
  try {
@@ -156,7 +186,11 @@ export async function customTypeAddFieldKeyText(): Promise<void> {
156
186
  return;
157
187
  }
158
188
 
159
- console.info(`Added field "${fieldId}" (Text) to "${targetTab}" tab in ${typeId}`);
189
+ if (fieldPath.type === "nested") {
190
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Text) to group "${fieldPath.groupId}" in ${typeId}`);
191
+ } else {
192
+ console.info(`Added field "${fieldId}" (Text) to "${targetTab}" tab in ${typeId}`);
193
+ }
160
194
 
161
195
  try {
162
196
  await buildTypes({ output: types });
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { stringify } from "./lib/json";
10
11
  import { humanReadable } from "./lib/string";
11
12
 
@@ -95,6 +96,15 @@ export async function customTypeAddFieldLink(): Promise<void> {
95
96
  return;
96
97
  }
97
98
 
99
+ // Parse and validate field path
100
+ const fieldPath = parseFieldPath(fieldId);
101
+ const pathValidation = validateNestedFieldPath(fieldPath);
102
+ if (!pathValidation.ok) {
103
+ console.error(pathValidation.error);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+
98
108
  // Find the custom type file
99
109
  const projectRoot = await findUpward("package.json");
100
110
  if (!projectRoot) {
@@ -141,20 +151,11 @@ export async function customTypeAddFieldLink(): Promise<void> {
141
151
  model.json[targetTab] = {};
142
152
  }
143
153
 
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
154
  // Build field definition
154
155
  const fieldDefinition: Link = {
155
156
  type: "Link",
156
157
  config: {
157
- label: label ?? humanReadable(fieldId),
158
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
158
159
  ...(placeholder && { placeholder }),
159
160
  ...(variation && variation.length > 0 && { variants: variation }),
160
161
  ...(allowText && { allowText: true }),
@@ -164,7 +165,36 @@ export async function customTypeAddFieldLink(): Promise<void> {
164
165
  };
165
166
 
166
167
  // Add field to model
167
- model.json[targetTab][fieldId] = fieldDefinition;
168
+ if (fieldPath.type === "nested") {
169
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
170
+ if (!groupResult.ok) {
171
+ console.error(groupResult.error);
172
+ process.exitCode = 1;
173
+ return;
174
+ }
175
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
176
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
177
+ process.exitCode = 1;
178
+ return;
179
+ }
180
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
181
+ } else {
182
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
183
+ if (tabFields[fieldId]) {
184
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
185
+ process.exitCode = 1;
186
+ return;
187
+ }
188
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
189
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
190
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
191
+ process.exitCode = 1;
192
+ return;
193
+ }
194
+ }
195
+ }
196
+ model.json[targetTab][fieldId] = fieldDefinition;
197
+ }
168
198
 
169
199
  // Write updated model
170
200
  try {
@@ -179,7 +209,11 @@ export async function customTypeAddFieldLink(): Promise<void> {
179
209
  return;
180
210
  }
181
211
 
182
- console.info(`Added field "${fieldId}" (Link) to "${targetTab}" tab in ${typeId}`);
212
+ if (fieldPath.type === "nested") {
213
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Link) to group "${fieldPath.groupId}" in ${typeId}`);
214
+ } else {
215
+ console.info(`Added field "${fieldId}" (Link) to "${targetTab}" tab in ${typeId}`);
216
+ }
183
217
 
184
218
  try {
185
219
  await buildTypes({ output: types });
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { stringify } from "./lib/json";
10
11
  import { humanReadable } from "./lib/string";
11
12
 
@@ -82,6 +83,15 @@ export async function customTypeAddFieldNumber(): Promise<void> {
82
83
  return;
83
84
  }
84
85
 
86
+ // Parse and validate field path
87
+ const fieldPath = parseFieldPath(fieldId);
88
+ const pathValidation = validateNestedFieldPath(fieldPath);
89
+ if (!pathValidation.ok) {
90
+ console.error(pathValidation.error);
91
+ process.exitCode = 1;
92
+ return;
93
+ }
94
+
85
95
  // Parse numeric values
86
96
  const minValue = min !== undefined ? Number(min) : undefined;
87
97
  const maxValue = max !== undefined ? Number(max) : undefined;
@@ -151,20 +161,11 @@ export async function customTypeAddFieldNumber(): Promise<void> {
151
161
  model.json[targetTab] = {};
152
162
  }
153
163
 
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
164
  // Build field definition
164
165
  const fieldDefinition: NumberField = {
165
166
  type: "Number",
166
167
  config: {
167
- label: label ?? humanReadable(fieldId),
168
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
168
169
  ...(placeholder && { placeholder }),
169
170
  ...(minValue !== undefined && { min: minValue }),
170
171
  ...(maxValue !== undefined && { max: maxValue }),
@@ -173,7 +174,36 @@ export async function customTypeAddFieldNumber(): Promise<void> {
173
174
  };
174
175
 
175
176
  // Add field to model
176
- model.json[targetTab][fieldId] = fieldDefinition;
177
+ if (fieldPath.type === "nested") {
178
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
179
+ if (!groupResult.ok) {
180
+ console.error(groupResult.error);
181
+ process.exitCode = 1;
182
+ return;
183
+ }
184
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
185
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
190
+ } else {
191
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
192
+ if (tabFields[fieldId]) {
193
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
194
+ process.exitCode = 1;
195
+ return;
196
+ }
197
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
198
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
199
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
200
+ process.exitCode = 1;
201
+ return;
202
+ }
203
+ }
204
+ }
205
+ model.json[targetTab][fieldId] = fieldDefinition;
206
+ }
177
207
 
178
208
  // Write updated model
179
209
  try {
@@ -188,7 +218,11 @@ export async function customTypeAddFieldNumber(): Promise<void> {
188
218
  return;
189
219
  }
190
220
 
191
- console.info(`Added field "${fieldId}" (Number) to "${targetTab}" tab in ${typeId}`);
221
+ if (fieldPath.type === "nested") {
222
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Number) to group "${fieldPath.groupId}" in ${typeId}`);
223
+ } else {
224
+ console.info(`Added field "${fieldId}" (Number) to "${targetTab}" tab in ${typeId}`);
225
+ }
192
226
 
193
227
  try {
194
228
  await buildTypes({ output: types });
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { stringify } from "./lib/json";
10
11
  import { humanReadable } from "./lib/string";
11
12
 
@@ -97,6 +98,15 @@ export async function customTypeAddFieldRichText(): Promise<void> {
97
98
  return;
98
99
  }
99
100
 
101
+ // Parse and validate field path
102
+ const fieldPath = parseFieldPath(fieldId);
103
+ const pathValidation = validateNestedFieldPath(fieldPath);
104
+ if (!pathValidation.ok) {
105
+ console.error(pathValidation.error);
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+
100
110
  // Find the custom type file
101
111
  const projectRoot = await findUpward("package.json");
102
112
  if (!projectRoot) {
@@ -143,20 +153,11 @@ export async function customTypeAddFieldRichText(): Promise<void> {
143
153
  model.json[targetTab] = {};
144
154
  }
145
155
 
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
156
  // Build field definition
156
157
  const fieldDefinition: RichText = {
157
158
  type: "StructuredText",
158
159
  config: {
159
- label: label ?? humanReadable(fieldId),
160
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
160
161
  ...(placeholder && { placeholder }),
161
162
  ...(single && { single }),
162
163
  ...(multi && { multi }),
@@ -165,7 +166,36 @@ export async function customTypeAddFieldRichText(): Promise<void> {
165
166
  };
166
167
 
167
168
  // Add field to model
168
- model.json[targetTab][fieldId] = fieldDefinition;
169
+ if (fieldPath.type === "nested") {
170
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
171
+ if (!groupResult.ok) {
172
+ console.error(groupResult.error);
173
+ process.exitCode = 1;
174
+ return;
175
+ }
176
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
177
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
178
+ process.exitCode = 1;
179
+ return;
180
+ }
181
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
182
+ } else {
183
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
184
+ if (tabFields[fieldId]) {
185
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
186
+ process.exitCode = 1;
187
+ return;
188
+ }
189
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
190
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
191
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
192
+ process.exitCode = 1;
193
+ return;
194
+ }
195
+ }
196
+ }
197
+ model.json[targetTab][fieldId] = fieldDefinition;
198
+ }
169
199
 
170
200
  // Write updated model
171
201
  try {
@@ -180,7 +210,11 @@ export async function customTypeAddFieldRichText(): Promise<void> {
180
210
  return;
181
211
  }
182
212
 
183
- console.info(`Added field "${fieldId}" (StructuredText) to "${targetTab}" tab in ${typeId}`);
213
+ if (fieldPath.type === "nested") {
214
+ console.info(`Added field "${fieldPath.nestedFieldId}" (StructuredText) to group "${fieldPath.groupId}" in ${typeId}`);
215
+ } else {
216
+ console.info(`Added field "${fieldId}" (StructuredText) to "${targetTab}" tab in ${typeId}`);
217
+ }
184
218
 
185
219
  try {
186
220
  await buildTypes({ output: types });
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { stringify } from "./lib/json";
10
11
  import { humanReadable } from "./lib/string";
11
12
 
@@ -45,15 +46,7 @@ const CustomTypeSchema = v.object({
45
46
 
46
47
  export async function customTypeAddFieldSelect(): Promise<void> {
47
48
  const {
48
- values: {
49
- help,
50
- tab,
51
- label,
52
- placeholder,
53
- option,
54
- default: defaultValue,
55
- types,
56
- },
49
+ values: { help, tab, label, placeholder, option, default: defaultValue, types },
57
50
  positionals: [typeId, fieldId],
58
51
  } = parseArgs({
59
52
  args: process.argv.slice(5), // skip: node, script, "custom-type", "add-field", "select"
@@ -88,6 +81,15 @@ export async function customTypeAddFieldSelect(): Promise<void> {
88
81
  return;
89
82
  }
90
83
 
84
+ // Parse and validate field path
85
+ const fieldPath = parseFieldPath(fieldId);
86
+ const pathValidation = validateNestedFieldPath(fieldPath);
87
+ if (!pathValidation.ok) {
88
+ console.error(pathValidation.error);
89
+ process.exitCode = 1;
90
+ return;
91
+ }
92
+
91
93
  // Find the custom type file
92
94
  const projectRoot = await findUpward("package.json");
93
95
  if (!projectRoot) {
@@ -134,20 +136,11 @@ export async function customTypeAddFieldSelect(): Promise<void> {
134
136
  model.json[targetTab] = {};
135
137
  }
136
138
 
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
139
  // Build field definition
147
140
  const fieldDefinition: Select = {
148
141
  type: "Select",
149
142
  config: {
150
- label: label ?? humanReadable(fieldId),
143
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
151
144
  ...(placeholder && { placeholder }),
152
145
  ...(option && option.length > 0 && { options: option }),
153
146
  ...(defaultValue && { default_value: defaultValue }),
@@ -155,7 +148,36 @@ export async function customTypeAddFieldSelect(): Promise<void> {
155
148
  };
156
149
 
157
150
  // Add field to model
158
- model.json[targetTab][fieldId] = fieldDefinition;
151
+ if (fieldPath.type === "nested") {
152
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
153
+ if (!groupResult.ok) {
154
+ console.error(groupResult.error);
155
+ process.exitCode = 1;
156
+ return;
157
+ }
158
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
159
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
160
+ process.exitCode = 1;
161
+ return;
162
+ }
163
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
164
+ } else {
165
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
166
+ if (tabFields[fieldId]) {
167
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
168
+ process.exitCode = 1;
169
+ return;
170
+ }
171
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
172
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
173
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+ }
178
+ }
179
+ model.json[targetTab][fieldId] = fieldDefinition;
180
+ }
159
181
 
160
182
  // Write updated model
161
183
  try {
@@ -170,7 +192,11 @@ export async function customTypeAddFieldSelect(): Promise<void> {
170
192
  return;
171
193
  }
172
194
 
173
- console.info(`Added field "${fieldId}" (Select) to "${targetTab}" tab in ${typeId}`);
195
+ if (fieldPath.type === "nested") {
196
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Select) to group "${fieldPath.groupId}" in ${typeId}`);
197
+ } else {
198
+ console.info(`Added field "${fieldId}" (Select) to "${targetTab}" tab in ${typeId}`);
199
+ }
174
200
 
175
201
  try {
176
202
  await buildTypes({ output: types });
@@ -6,6 +6,7 @@ import * as v from "valibot";
6
6
 
7
7
  import { buildTypes } from "./codegen-types";
8
8
  import { findUpward } from "./lib/file";
9
+ import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
9
10
  import { stringify } from "./lib/json";
10
11
  import { humanReadable } from "./lib/string";
11
12
 
@@ -78,6 +79,15 @@ export async function customTypeAddFieldTimestamp(): Promise<void> {
78
79
  return;
79
80
  }
80
81
 
82
+ // Parse and validate field path
83
+ const fieldPath = parseFieldPath(fieldId);
84
+ const pathValidation = validateNestedFieldPath(fieldPath);
85
+ if (!pathValidation.ok) {
86
+ console.error(pathValidation.error);
87
+ process.exitCode = 1;
88
+ return;
89
+ }
90
+
81
91
  // Find the custom type file
82
92
  const projectRoot = await findUpward("package.json");
83
93
  if (!projectRoot) {
@@ -124,27 +134,47 @@ export async function customTypeAddFieldTimestamp(): Promise<void> {
124
134
  model.json[targetTab] = {};
125
135
  }
126
136
 
127
- // Check if field already exists in any tab
128
- for (const [tabName, tabFields] of Object.entries(model.json)) {
129
- if (tabFields[fieldId]) {
130
- console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
131
- process.exitCode = 1;
132
- return;
133
- }
134
- }
135
-
136
137
  // Build field definition
137
138
  const fieldDefinition: Timestamp = {
138
139
  type: "Timestamp",
139
140
  config: {
140
- label: label ?? humanReadable(fieldId),
141
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
141
142
  ...(placeholder && { placeholder }),
142
143
  ...(defaultValue && { default: defaultValue }),
143
144
  },
144
145
  };
145
146
 
146
147
  // Add field to model
147
- model.json[targetTab][fieldId] = fieldDefinition;
148
+ if (fieldPath.type === "nested") {
149
+ const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
150
+ if (!groupResult.ok) {
151
+ console.error(groupResult.error);
152
+ process.exitCode = 1;
153
+ return;
154
+ }
155
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
156
+ console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
157
+ process.exitCode = 1;
158
+ return;
159
+ }
160
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
161
+ } else {
162
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
163
+ if (tabFields[fieldId]) {
164
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
165
+ process.exitCode = 1;
166
+ return;
167
+ }
168
+ for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
169
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
170
+ console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
171
+ process.exitCode = 1;
172
+ return;
173
+ }
174
+ }
175
+ }
176
+ model.json[targetTab][fieldId] = fieldDefinition;
177
+ }
148
178
 
149
179
  // Write updated model
150
180
  try {
@@ -159,7 +189,11 @@ export async function customTypeAddFieldTimestamp(): Promise<void> {
159
189
  return;
160
190
  }
161
191
 
162
- console.info(`Added field "${fieldId}" (Timestamp) to "${targetTab}" tab in ${typeId}`);
192
+ if (fieldPath.type === "nested") {
193
+ console.info(`Added field "${fieldPath.nestedFieldId}" (Timestamp) to group "${fieldPath.groupId}" in ${typeId}`);
194
+ } else {
195
+ console.info(`Added field "${fieldId}" (Timestamp) to "${targetTab}" tab in ${typeId}`);
196
+ }
163
197
 
164
198
  try {
165
199
  await buildTypes({ output: types });