@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,226 @@
1
+ import type { Embed, 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 an embed (oEmbed) field to an existing slice.
15
+
16
+ USAGE
17
+ prismic slice add-field embed <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 embed my_slice video
32
+ prismic slice add-field embed gallery media --label "Media Embed"
33
+ prismic slice add-field embed social tweet --variation "twitter"
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 sliceAddFieldEmbed(): 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", "embed"
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 embed <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 embed <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: Embed = {
139
+ type: "Embed",
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}" (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
+ }
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,223 @@
1
+ import type { GeoPoint, 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 geographic coordinates field to an existing slice.
15
+
16
+ USAGE
17
+ prismic slice add-field geo-point <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
+ --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 geo-point my_slice location
31
+ prismic slice add-field geo-point store coordinates --label "Store Location"
32
+ prismic slice add-field geo-point map marker --variation "interactive"
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 sliceAddFieldGeoPoint(): Promise<void> {
58
+ const {
59
+ values: { help, variation, label, types },
60
+ positionals: [sliceId, fieldId],
61
+ } = parseArgs({
62
+ args: process.argv.slice(5), // skip: node, script, "slice", "add-field", "geo-point"
63
+ options: {
64
+ variation: { type: "string", short: "v" },
65
+ label: { type: "string", short: "l" },
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 (!sliceId) {
78
+ console.error("Missing required argument: slice-id\n");
79
+ console.error("Usage: prismic slice add-field geo-point <slice-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 slice add-field geo-point <slice-id> <field-id>");
87
+ process.exitCode = 1;
88
+ return;
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
+
100
+ // Find the slice model
101
+ const result = await findSliceModel(sliceId);
102
+ if (!result.ok) {
103
+ console.error(result.error);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+
108
+ const { model, modelPath } = result;
109
+
110
+ // Check for variations
111
+ if (model.variations.length === 0) {
112
+ console.error(`Slice "${sliceId}" has no variations.\n`);
113
+ console.error("Add a variation first before adding fields.");
114
+ process.exitCode = 1;
115
+ return;
116
+ }
117
+
118
+ // Find target variation
119
+ const targetVariation = variation
120
+ ? model.variations.find((v) => v.id === variation)
121
+ : model.variations[0];
122
+
123
+ if (!targetVariation) {
124
+ console.error(`Variation "${variation}" not found in slice "${sliceId}"\n`);
125
+ console.error(`Available variations: ${model.variations.map((v) => v.id).join(", ")}`);
126
+ process.exitCode = 1;
127
+ return;
128
+ }
129
+
130
+ // Initialize primary if it doesn't exist
131
+ if (!targetVariation.primary) {
132
+ targetVariation.primary = {};
133
+ }
134
+
135
+ // Build field definition
136
+ const fieldDefinition: GeoPoint = {
137
+ type: "GeoPoint",
138
+ config: {
139
+ label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
140
+ },
141
+ };
142
+
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
+ }
181
+
182
+ // Write updated model
183
+ try {
184
+ await writeFile(modelPath, stringify(model as SharedSlice));
185
+ } catch (error) {
186
+ if (error instanceof Error) {
187
+ console.error(`Failed to update slice: ${error.message}`);
188
+ } else {
189
+ console.error("Failed to update slice");
190
+ }
191
+ process.exitCode = 1;
192
+ return;
193
+ }
194
+
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
+ }
204
+
205
+ try {
206
+ await buildTypes({ output: types });
207
+ console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
208
+ } catch (error) {
209
+ console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
210
+ }
211
+
212
+ console.info();
213
+ console.info("Next: Add more fields with `prismic slice add-field`");
214
+
215
+ const frameworkInfo = await detectFrameworkInfo();
216
+ if (frameworkInfo?.framework) {
217
+ const docsPath = getDocsPath(frameworkInfo.framework);
218
+ const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
219
+ console.info(
220
+ ` Run \`prismic docs fetch ${docsPath}${anchor}\` to learn how to implement the slice's component`,
221
+ );
222
+ }
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
+ }