@angeloashmore/prismic-cli-poc 0.0.0-canary.1143872

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 (139) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/dist/index.mjs +2508 -0
  4. package/package.json +53 -0
  5. package/src/codegen-types.ts +82 -0
  6. package/src/codegen.ts +45 -0
  7. package/src/custom-type-add-field-boolean.ts +222 -0
  8. package/src/custom-type-add-field-color.ts +205 -0
  9. package/src/custom-type-add-field-date.ts +208 -0
  10. package/src/custom-type-add-field-embed.ts +205 -0
  11. package/src/custom-type-add-field-geo-point.ts +202 -0
  12. package/src/custom-type-add-field-group.ts +179 -0
  13. package/src/custom-type-add-field-image.ts +205 -0
  14. package/src/custom-type-add-field-key-text.ts +205 -0
  15. package/src/custom-type-add-field-link.ts +228 -0
  16. package/src/custom-type-add-field-number.ts +237 -0
  17. package/src/custom-type-add-field-rich-text.ts +229 -0
  18. package/src/custom-type-add-field-select.ts +211 -0
  19. package/src/custom-type-add-field-timestamp.ts +208 -0
  20. package/src/custom-type-add-field-uid.ts +188 -0
  21. package/src/custom-type-add-field.ts +116 -0
  22. package/src/custom-type-connect-slice.ts +214 -0
  23. package/src/custom-type-create.ts +112 -0
  24. package/src/custom-type-disconnect-slice.ts +171 -0
  25. package/src/custom-type-list.ts +110 -0
  26. package/src/custom-type-remove-field.ts +171 -0
  27. package/src/custom-type-remove.ts +138 -0
  28. package/src/custom-type-set-name.ts +138 -0
  29. package/src/custom-type-view.ts +118 -0
  30. package/src/custom-type.ts +85 -0
  31. package/src/docs-fetch.ts +146 -0
  32. package/src/docs-list.ts +131 -0
  33. package/src/docs.ts +54 -0
  34. package/src/index.ts +132 -0
  35. package/src/init.ts +64 -0
  36. package/src/lib/auth.ts +83 -0
  37. package/src/lib/config.ts +111 -0
  38. package/src/lib/custom-types-api.ts +438 -0
  39. package/src/lib/field-path.ts +81 -0
  40. package/src/lib/file.ts +49 -0
  41. package/src/lib/framework.ts +143 -0
  42. package/src/lib/json.ts +3 -0
  43. package/src/lib/request.ts +116 -0
  44. package/src/lib/slice.ts +115 -0
  45. package/src/lib/string.ts +6 -0
  46. package/src/lib/url.ts +25 -0
  47. package/src/locale-add.ts +116 -0
  48. package/src/locale-list.ts +107 -0
  49. package/src/locale-remove.ts +88 -0
  50. package/src/locale-set-default.ts +131 -0
  51. package/src/locale.ts +60 -0
  52. package/src/login.ts +152 -0
  53. package/src/logout.ts +36 -0
  54. package/src/page-type-add-field-boolean.ts +238 -0
  55. package/src/page-type-add-field-color.ts +224 -0
  56. package/src/page-type-add-field-date.ts +227 -0
  57. package/src/page-type-add-field-embed.ts +224 -0
  58. package/src/page-type-add-field-geo-point.ts +221 -0
  59. package/src/page-type-add-field-group.ts +198 -0
  60. package/src/page-type-add-field-image.ts +224 -0
  61. package/src/page-type-add-field-key-text.ts +224 -0
  62. package/src/page-type-add-field-link.ts +247 -0
  63. package/src/page-type-add-field-number.ts +256 -0
  64. package/src/page-type-add-field-rich-text.ts +248 -0
  65. package/src/page-type-add-field-select.ts +230 -0
  66. package/src/page-type-add-field-timestamp.ts +227 -0
  67. package/src/page-type-add-field-uid.ts +207 -0
  68. package/src/page-type-add-field.ts +116 -0
  69. package/src/page-type-connect-slice.ts +214 -0
  70. package/src/page-type-create.ts +161 -0
  71. package/src/page-type-disconnect-slice.ts +171 -0
  72. package/src/page-type-list.ts +109 -0
  73. package/src/page-type-remove-field.ts +171 -0
  74. package/src/page-type-remove.ts +138 -0
  75. package/src/page-type-set-name.ts +138 -0
  76. package/src/page-type-set-repeatable.ts +147 -0
  77. package/src/page-type-view.ts +118 -0
  78. package/src/page-type.ts +90 -0
  79. package/src/preview-add.ts +126 -0
  80. package/src/preview-get-simulator.ts +104 -0
  81. package/src/preview-list.ts +106 -0
  82. package/src/preview-remove-simulator.ts +80 -0
  83. package/src/preview-remove.ts +109 -0
  84. package/src/preview-set-name.ts +137 -0
  85. package/src/preview-set-simulator.ts +116 -0
  86. package/src/preview.ts +75 -0
  87. package/src/pull.ts +242 -0
  88. package/src/push.ts +405 -0
  89. package/src/repo-create.ts +195 -0
  90. package/src/repo-get-access.ts +86 -0
  91. package/src/repo-list.ts +100 -0
  92. package/src/repo-set-access.ts +100 -0
  93. package/src/repo-set-name.ts +102 -0
  94. package/src/repo-view.ts +113 -0
  95. package/src/repo.ts +70 -0
  96. package/src/slice-add-field-boolean.ts +240 -0
  97. package/src/slice-add-field-color.ts +226 -0
  98. package/src/slice-add-field-date.ts +226 -0
  99. package/src/slice-add-field-embed.ts +226 -0
  100. package/src/slice-add-field-geo-point.ts +223 -0
  101. package/src/slice-add-field-group.ts +191 -0
  102. package/src/slice-add-field-image.ts +223 -0
  103. package/src/slice-add-field-key-text.ts +226 -0
  104. package/src/slice-add-field-link.ts +245 -0
  105. package/src/slice-add-field-number.ts +226 -0
  106. package/src/slice-add-field-rich-text.ts +250 -0
  107. package/src/slice-add-field-select.ts +232 -0
  108. package/src/slice-add-field-timestamp.ts +226 -0
  109. package/src/slice-add-field.ts +111 -0
  110. package/src/slice-add-variation.ts +139 -0
  111. package/src/slice-create.ts +203 -0
  112. package/src/slice-list-variations.ts +67 -0
  113. package/src/slice-list.ts +88 -0
  114. package/src/slice-remove-field.ts +122 -0
  115. package/src/slice-remove-variation.ts +112 -0
  116. package/src/slice-remove.ts +91 -0
  117. package/src/slice-rename.ts +122 -0
  118. package/src/slice-set-screenshot.ts +235 -0
  119. package/src/slice-view.ts +80 -0
  120. package/src/slice.ts +95 -0
  121. package/src/status.ts +873 -0
  122. package/src/token-create.ts +203 -0
  123. package/src/token-delete.ts +182 -0
  124. package/src/token-list.ts +223 -0
  125. package/src/token-set-name.ts +193 -0
  126. package/src/token.ts +60 -0
  127. package/src/webhook-add-header.ts +118 -0
  128. package/src/webhook-create.ts +152 -0
  129. package/src/webhook-disable.ts +109 -0
  130. package/src/webhook-enable.ts +132 -0
  131. package/src/webhook-list.ts +93 -0
  132. package/src/webhook-remove-header.ts +117 -0
  133. package/src/webhook-remove.ts +106 -0
  134. package/src/webhook-set-triggers.ts +148 -0
  135. package/src/webhook-status.ts +90 -0
  136. package/src/webhook-test.ts +106 -0
  137. package/src/webhook-view.ts +147 -0
  138. package/src/webhook.ts +95 -0
  139. package/src/whoami.ts +62 -0
@@ -0,0 +1,240 @@
1
+ import type { BooleanField, 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 { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
8
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
+ import { stringify } from "./lib/json";
10
+ import { findSliceModel } from "./lib/slice";
11
+ import { humanReadable } from "./lib/string";
12
+
13
+ const HELP = `
14
+ Add a boolean (toggle) field to an existing slice.
15
+
16
+ USAGE
17
+ prismic slice add-field boolean <slice-id> <field-id> [flags]
18
+
19
+ ARGUMENTS
20
+ slice-id Slice identifier (required)
21
+ field-id Field identifier (required)
22
+
23
+ FLAGS
24
+ -v, --variation string Target variation (default: first variation)
25
+ -l, --label string Display label for the field (inferred from field-id if omitted)
26
+ --default Set default value to true
27
+ --true-label string Label shown when toggle is on
28
+ --false-label string Label shown when toggle is off
29
+ --types string Output file for generated types (default: "prismicio-types.d.ts")
30
+ -h, --help Show help for command
31
+
32
+ EXAMPLES
33
+ prismic slice add-field boolean my_slice featured
34
+ prismic slice add-field boolean hero show_overlay --default
35
+ prismic slice add-field boolean product available --true-label "In Stock" --false-label "Out of Stock"
36
+ `.trim();
37
+
38
+ function getDocsPath(framework: Framework): string {
39
+ switch (framework) {
40
+ case "next":
41
+ return "nextjs/with-cli";
42
+ case "nuxt":
43
+ return "nuxt/with-cli";
44
+ case "sveltekit":
45
+ return "sveltekit/with-cli";
46
+ }
47
+ }
48
+
49
+ function getWriteComponentsAnchor(framework: Framework): string {
50
+ switch (framework) {
51
+ case "nuxt":
52
+ return "#write-vue-components";
53
+ case "sveltekit":
54
+ return "#write-svelte-components";
55
+ default:
56
+ return "#write-react-components";
57
+ }
58
+ }
59
+
60
+ export async function sliceAddFieldBoolean(): Promise<void> {
61
+ const {
62
+ values: {
63
+ help,
64
+ variation,
65
+ label,
66
+ default: defaultValue,
67
+ "true-label": trueLabel,
68
+ "false-label": falseLabel,
69
+ types,
70
+ },
71
+ positionals: [sliceId, fieldId],
72
+ } = parseArgs({
73
+ args: process.argv.slice(5), // skip: node, script, "slice", "add-field", "boolean"
74
+ options: {
75
+ variation: { type: "string", short: "v" },
76
+ label: { type: "string", short: "l" },
77
+ default: { type: "boolean" },
78
+ "true-label": { type: "string" },
79
+ "false-label": { type: "string" },
80
+ types: { type: "string" },
81
+ help: { type: "boolean", short: "h" },
82
+ },
83
+ allowPositionals: true,
84
+ });
85
+
86
+ if (help) {
87
+ console.info(HELP);
88
+ return;
89
+ }
90
+
91
+ if (!sliceId) {
92
+ console.error("Missing required argument: slice-id\n");
93
+ console.error("Usage: prismic slice add-field boolean <slice-id> <field-id>");
94
+ process.exitCode = 1;
95
+ return;
96
+ }
97
+
98
+ if (!fieldId) {
99
+ console.error("Missing required argument: field-id\n");
100
+ console.error("Usage: prismic slice add-field boolean <slice-id> <field-id>");
101
+ process.exitCode = 1;
102
+ return;
103
+ }
104
+
105
+ // Parse and validate field path
106
+ const fieldPath = parseFieldPath(fieldId);
107
+ const pathValidation = validateNestedFieldPath(fieldPath);
108
+ if (!pathValidation.ok) {
109
+ console.error(pathValidation.error);
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+
114
+ // Find the slice model
115
+ const result = await findSliceModel(sliceId);
116
+ if (!result.ok) {
117
+ console.error(result.error);
118
+ process.exitCode = 1;
119
+ return;
120
+ }
121
+
122
+ const { model, modelPath } = result;
123
+
124
+ // Check for variations
125
+ if (model.variations.length === 0) {
126
+ console.error(`Slice "${sliceId}" has no variations.\n`);
127
+ console.error("Add a variation first before adding fields.");
128
+ process.exitCode = 1;
129
+ return;
130
+ }
131
+
132
+ // Find target variation
133
+ const targetVariation = variation
134
+ ? model.variations.find((v) => v.id === variation)
135
+ : model.variations[0];
136
+
137
+ if (!targetVariation) {
138
+ console.error(`Variation "${variation}" not found in slice "${sliceId}"\n`);
139
+ console.error(`Available variations: ${model.variations.map((v) => v.id).join(", ")}`);
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+
144
+ // Initialize primary if it doesn't exist
145
+ if (!targetVariation.primary) {
146
+ targetVariation.primary = {};
147
+ }
148
+
149
+ // Build field definition
150
+ const fieldDefinition: BooleanField = {
151
+ type: "Boolean",
152
+ config: {
153
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
154
+ ...(defaultValue && { default_value: true }),
155
+ ...(trueLabel && { placeholder_true: trueLabel }),
156
+ ...(falseLabel && { placeholder_false: falseLabel }),
157
+ },
158
+ };
159
+
160
+ // Add field to variation (with nested field support)
161
+ if (fieldPath.type === "nested") {
162
+ const groupResult = findGroupInVariation(targetVariation.primary, fieldPath.groupId, targetVariation.id);
163
+ if (!groupResult.ok) {
164
+ console.error(groupResult.error);
165
+ process.exitCode = 1;
166
+ return;
167
+ }
168
+ // Check if nested field already exists
169
+ if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
170
+ console.error(
171
+ `Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`,
172
+ );
173
+ process.exitCode = 1;
174
+ return;
175
+ }
176
+ groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
177
+ } else {
178
+ // Check if field already exists in any variation (at top level or in groups)
179
+ for (const v of model.variations) {
180
+ if (v.primary?.[fieldId]) {
181
+ console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
182
+ process.exitCode = 1;
183
+ return;
184
+ }
185
+ // Also check inside groups
186
+ for (const [groupFieldId, groupField] of Object.entries(v.primary ?? {})) {
187
+ if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
188
+ console.error(
189
+ `Field "${fieldId}" already exists in group "${groupFieldId}" in variation "${v.id}"`,
190
+ );
191
+ process.exitCode = 1;
192
+ return;
193
+ }
194
+ }
195
+ }
196
+ targetVariation.primary[fieldId] = fieldDefinition;
197
+ }
198
+
199
+ // Write updated model
200
+ try {
201
+ await writeFile(modelPath, stringify(model as SharedSlice));
202
+ } catch (error) {
203
+ if (error instanceof Error) {
204
+ console.error(`Failed to update slice: ${error.message}`);
205
+ } else {
206
+ console.error("Failed to update slice");
207
+ }
208
+ process.exitCode = 1;
209
+ return;
210
+ }
211
+
212
+ if (fieldPath.type === "nested") {
213
+ console.info(
214
+ `Added field "${fieldPath.nestedFieldId}" (Boolean) to group "${fieldPath.groupId}" in ${sliceId}`,
215
+ );
216
+ } else {
217
+ console.info(
218
+ `Added field "${fieldId}" (Boolean) to "${targetVariation.id}" variation in ${sliceId}`,
219
+ );
220
+ }
221
+
222
+ try {
223
+ await buildTypes({ output: types });
224
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
225
+ } catch (error) {
226
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
227
+ }
228
+
229
+ console.info();
230
+ console.info("Next: Add more fields with `prismic slice add-field`");
231
+
232
+ const frameworkInfo = await detectFrameworkInfo();
233
+ if (frameworkInfo?.framework) {
234
+ const docsPath = getDocsPath(frameworkInfo.framework);
235
+ const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
236
+ console.info(
237
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
238
+ );
239
+ }
240
+ }
@@ -0,0 +1,226 @@
1
+ import type { Color, 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 { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
8
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
+ import { stringify } from "./lib/json";
10
+ import { findSliceModel } from "./lib/slice";
11
+ import { humanReadable } from "./lib/string";
12
+
13
+ const HELP = `
14
+ Add a color picker field to an existing slice.
15
+
16
+ USAGE
17
+ prismic slice add-field color <slice-id> <field-id> [flags]
18
+
19
+ ARGUMENTS
20
+ slice-id Slice identifier (required)
21
+ field-id Field identifier (required)
22
+
23
+ FLAGS
24
+ -v, --variation string Target variation (default: first variation)
25
+ -l, --label string Display label for the field (inferred from field-id if omitted)
26
+ -p, --placeholder string Placeholder text
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 slice add-field color my_slice background_color
32
+ prismic slice add-field color hero accent --label "Accent Color"
33
+ prismic slice add-field color banner theme_color --variation "dark"
34
+ `.trim();
35
+
36
+ function getDocsPath(framework: Framework): string {
37
+ switch (framework) {
38
+ case "next":
39
+ return "nextjs/with-cli";
40
+ case "nuxt":
41
+ return "nuxt/with-cli";
42
+ case "sveltekit":
43
+ return "sveltekit/with-cli";
44
+ }
45
+ }
46
+
47
+ function getWriteComponentsAnchor(framework: Framework): string {
48
+ switch (framework) {
49
+ case "nuxt":
50
+ return "#write-vue-components";
51
+ case "sveltekit":
52
+ return "#write-svelte-components";
53
+ default:
54
+ return "#write-react-components";
55
+ }
56
+ }
57
+
58
+ export async function sliceAddFieldColor(): Promise<void> {
59
+ const {
60
+ values: { help, variation, label, placeholder, types },
61
+ positionals: [sliceId, fieldId],
62
+ } = parseArgs({
63
+ args: process.argv.slice(5), // skip: node, script, "slice", "add-field", "color"
64
+ options: {
65
+ variation: { type: "string", short: "v" },
66
+ label: { type: "string", short: "l" },
67
+ placeholder: { type: "string", short: "p" },
68
+ types: { type: "string" },
69
+ help: { type: "boolean", short: "h" },
70
+ },
71
+ allowPositionals: true,
72
+ });
73
+
74
+ if (help) {
75
+ console.info(HELP);
76
+ return;
77
+ }
78
+
79
+ if (!sliceId) {
80
+ console.error("Missing required argument: slice-id\n");
81
+ console.error("Usage: prismic slice add-field color <slice-id> <field-id>");
82
+ process.exitCode = 1;
83
+ return;
84
+ }
85
+
86
+ if (!fieldId) {
87
+ console.error("Missing required argument: field-id\n");
88
+ console.error("Usage: prismic slice add-field color <slice-id> <field-id>");
89
+ process.exitCode = 1;
90
+ return;
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
+
102
+ // Find the slice model
103
+ const result = await findSliceModel(sliceId);
104
+ if (!result.ok) {
105
+ console.error(result.error);
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+
110
+ const { model, modelPath } = result;
111
+
112
+ // Check for variations
113
+ if (model.variations.length === 0) {
114
+ console.error(`Slice "${sliceId}" has no variations.\n`);
115
+ console.error("Add a variation first before adding fields.");
116
+ process.exitCode = 1;
117
+ return;
118
+ }
119
+
120
+ // Find target variation
121
+ const targetVariation = variation
122
+ ? model.variations.find((v) => v.id === variation)
123
+ : model.variations[0];
124
+
125
+ if (!targetVariation) {
126
+ console.error(`Variation "${variation}" not found in slice "${sliceId}"\n`);
127
+ console.error(`Available variations: ${model.variations.map((v) => v.id).join(", ")}`);
128
+ process.exitCode = 1;
129
+ return;
130
+ }
131
+
132
+ // Initialize primary if it doesn't exist
133
+ if (!targetVariation.primary) {
134
+ targetVariation.primary = {};
135
+ }
136
+
137
+ // Build field definition
138
+ const fieldDefinition: Color = {
139
+ type: "Color",
140
+ config: {
141
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
142
+ ...(placeholder && { placeholder }),
143
+ },
144
+ };
145
+
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
+ }
184
+
185
+ // Write updated model
186
+ try {
187
+ await writeFile(modelPath, stringify(model as SharedSlice));
188
+ } catch (error) {
189
+ if (error instanceof Error) {
190
+ console.error(`Failed to update slice: ${error.message}`);
191
+ } else {
192
+ console.error("Failed to update slice");
193
+ }
194
+ process.exitCode = 1;
195
+ return;
196
+ }
197
+
198
+ if (fieldPath.type === "nested") {
199
+ console.info(
200
+ `Added field "${fieldPath.nestedFieldId}" (Color) to group "${fieldPath.groupId}" in ${sliceId}`,
201
+ );
202
+ } else {
203
+ console.info(
204
+ `Added field "${fieldId}" (Color) to "${targetVariation.id}" variation in ${sliceId}`,
205
+ );
206
+ }
207
+
208
+ try {
209
+ await buildTypes({ output: types });
210
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
211
+ } catch (error) {
212
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
213
+ }
214
+
215
+ console.info();
216
+ console.info("Next: Add more fields with `prismic slice add-field`");
217
+
218
+ const frameworkInfo = await detectFrameworkInfo();
219
+ if (frameworkInfo?.framework) {
220
+ const docsPath = getDocsPath(frameworkInfo.framework);
221
+ const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
222
+ console.info(
223
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
224
+ );
225
+ }
226
+ }
@@ -0,0 +1,226 @@
1
+ import type { Date as DateField, 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 { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
8
+ import { type Framework, detectFrameworkInfo } from "./lib/framework";
9
+ import { stringify } from "./lib/json";
10
+ import { findSliceModel } from "./lib/slice";
11
+ import { humanReadable } from "./lib/string";
12
+
13
+ const HELP = `
14
+ Add a date picker field to an existing slice.
15
+
16
+ USAGE
17
+ prismic slice add-field date <slice-id> <field-id> [flags]
18
+
19
+ ARGUMENTS
20
+ slice-id Slice identifier (required)
21
+ field-id Field identifier (required)
22
+
23
+ FLAGS
24
+ -v, --variation string Target variation (default: first variation)
25
+ -l, --label string Display label for the field (inferred from field-id if omitted)
26
+ -p, --placeholder string Placeholder text
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 slice add-field date my_slice publish_date
32
+ prismic slice add-field date event start_date --label "Start Date"
33
+ prismic slice add-field date promo end_date --variation "countdown"
34
+ `.trim();
35
+
36
+ function getDocsPath(framework: Framework): string {
37
+ switch (framework) {
38
+ case "next":
39
+ return "nextjs/with-cli";
40
+ case "nuxt":
41
+ return "nuxt/with-cli";
42
+ case "sveltekit":
43
+ return "sveltekit/with-cli";
44
+ }
45
+ }
46
+
47
+ function getWriteComponentsAnchor(framework: Framework): string {
48
+ switch (framework) {
49
+ case "nuxt":
50
+ return "#write-vue-components";
51
+ case "sveltekit":
52
+ return "#write-svelte-components";
53
+ default:
54
+ return "#write-react-components";
55
+ }
56
+ }
57
+
58
+ export async function sliceAddFieldDate(): Promise<void> {
59
+ const {
60
+ values: { help, variation, label, placeholder, types },
61
+ positionals: [sliceId, fieldId],
62
+ } = parseArgs({
63
+ args: process.argv.slice(5), // skip: node, script, "slice", "add-field", "date"
64
+ options: {
65
+ variation: { type: "string", short: "v" },
66
+ label: { type: "string", short: "l" },
67
+ placeholder: { type: "string", short: "p" },
68
+ types: { type: "string" },
69
+ help: { type: "boolean", short: "h" },
70
+ },
71
+ allowPositionals: true,
72
+ });
73
+
74
+ if (help) {
75
+ console.info(HELP);
76
+ return;
77
+ }
78
+
79
+ if (!sliceId) {
80
+ console.error("Missing required argument: slice-id\n");
81
+ console.error("Usage: prismic slice add-field date <slice-id> <field-id>");
82
+ process.exitCode = 1;
83
+ return;
84
+ }
85
+
86
+ if (!fieldId) {
87
+ console.error("Missing required argument: field-id\n");
88
+ console.error("Usage: prismic slice add-field date <slice-id> <field-id>");
89
+ process.exitCode = 1;
90
+ return;
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
+
102
+ // Find the slice model
103
+ const result = await findSliceModel(sliceId);
104
+ if (!result.ok) {
105
+ console.error(result.error);
106
+ process.exitCode = 1;
107
+ return;
108
+ }
109
+
110
+ const { model, modelPath } = result;
111
+
112
+ // Check for variations
113
+ if (model.variations.length === 0) {
114
+ console.error(`Slice "${sliceId}" has no variations.\n`);
115
+ console.error("Add a variation first before adding fields.");
116
+ process.exitCode = 1;
117
+ return;
118
+ }
119
+
120
+ // Find target variation
121
+ const targetVariation = variation
122
+ ? model.variations.find((v) => v.id === variation)
123
+ : model.variations[0];
124
+
125
+ if (!targetVariation) {
126
+ console.error(`Variation "${variation}" not found in slice "${sliceId}"\n`);
127
+ console.error(`Available variations: ${model.variations.map((v) => v.id).join(", ")}`);
128
+ process.exitCode = 1;
129
+ return;
130
+ }
131
+
132
+ // Initialize primary if it doesn't exist
133
+ if (!targetVariation.primary) {
134
+ targetVariation.primary = {};
135
+ }
136
+
137
+ // Build field definition
138
+ const fieldDefinition: DateField = {
139
+ type: "Date",
140
+ config: {
141
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
142
+ ...(placeholder && { placeholder }),
143
+ },
144
+ };
145
+
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
+ }
184
+
185
+ // Write updated model
186
+ try {
187
+ await writeFile(modelPath, stringify(model as SharedSlice));
188
+ } catch (error) {
189
+ if (error instanceof Error) {
190
+ console.error(`Failed to update slice: ${error.message}`);
191
+ } else {
192
+ console.error("Failed to update slice");
193
+ }
194
+ process.exitCode = 1;
195
+ return;
196
+ }
197
+
198
+ if (fieldPath.type === "nested") {
199
+ console.info(
200
+ `Added field "${fieldPath.nestedFieldId}" (Date) to group "${fieldPath.groupId}" in ${sliceId}`,
201
+ );
202
+ } else {
203
+ console.info(
204
+ `Added field "${fieldId}" (Date) to "${targetVariation.id}" variation in ${sliceId}`,
205
+ );
206
+ }
207
+
208
+ try {
209
+ await buildTypes({ output: types });
210
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
211
+ } catch (error) {
212
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
213
+ }
214
+
215
+ console.info();
216
+ console.info("Next: Add more fields with `prismic slice add-field`");
217
+
218
+ const frameworkInfo = await detectFrameworkInfo();
219
+ if (frameworkInfo?.framework) {
220
+ const docsPath = getDocsPath(frameworkInfo.framework);
221
+ const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
222
+ console.info(
223
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
224
+ );
225
+ }
226
+ }