@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.
- package/LICENSE +202 -0
- package/README.md +98 -0
- package/dist/index.mjs +2508 -0
- package/package.json +53 -0
- package/src/codegen-types.ts +82 -0
- package/src/codegen.ts +45 -0
- package/src/custom-type-add-field-boolean.ts +222 -0
- package/src/custom-type-add-field-color.ts +205 -0
- package/src/custom-type-add-field-date.ts +208 -0
- package/src/custom-type-add-field-embed.ts +205 -0
- package/src/custom-type-add-field-geo-point.ts +202 -0
- package/src/custom-type-add-field-group.ts +179 -0
- package/src/custom-type-add-field-image.ts +205 -0
- package/src/custom-type-add-field-key-text.ts +205 -0
- package/src/custom-type-add-field-link.ts +228 -0
- package/src/custom-type-add-field-number.ts +237 -0
- package/src/custom-type-add-field-rich-text.ts +229 -0
- package/src/custom-type-add-field-select.ts +211 -0
- package/src/custom-type-add-field-timestamp.ts +208 -0
- package/src/custom-type-add-field-uid.ts +188 -0
- package/src/custom-type-add-field.ts +116 -0
- package/src/custom-type-connect-slice.ts +214 -0
- package/src/custom-type-create.ts +112 -0
- package/src/custom-type-disconnect-slice.ts +171 -0
- package/src/custom-type-list.ts +110 -0
- package/src/custom-type-remove-field.ts +171 -0
- package/src/custom-type-remove.ts +138 -0
- package/src/custom-type-set-name.ts +138 -0
- package/src/custom-type-view.ts +118 -0
- package/src/custom-type.ts +85 -0
- package/src/docs-fetch.ts +146 -0
- package/src/docs-list.ts +131 -0
- package/src/docs.ts +54 -0
- package/src/index.ts +132 -0
- package/src/init.ts +64 -0
- package/src/lib/auth.ts +83 -0
- package/src/lib/config.ts +111 -0
- package/src/lib/custom-types-api.ts +438 -0
- package/src/lib/field-path.ts +81 -0
- package/src/lib/file.ts +49 -0
- package/src/lib/framework.ts +143 -0
- package/src/lib/json.ts +3 -0
- package/src/lib/request.ts +116 -0
- package/src/lib/slice.ts +115 -0
- package/src/lib/string.ts +6 -0
- package/src/lib/url.ts +25 -0
- package/src/locale-add.ts +116 -0
- package/src/locale-list.ts +107 -0
- package/src/locale-remove.ts +88 -0
- package/src/locale-set-default.ts +131 -0
- package/src/locale.ts +60 -0
- package/src/login.ts +152 -0
- package/src/logout.ts +36 -0
- package/src/page-type-add-field-boolean.ts +238 -0
- package/src/page-type-add-field-color.ts +224 -0
- package/src/page-type-add-field-date.ts +227 -0
- package/src/page-type-add-field-embed.ts +224 -0
- package/src/page-type-add-field-geo-point.ts +221 -0
- package/src/page-type-add-field-group.ts +198 -0
- package/src/page-type-add-field-image.ts +224 -0
- package/src/page-type-add-field-key-text.ts +224 -0
- package/src/page-type-add-field-link.ts +247 -0
- package/src/page-type-add-field-number.ts +256 -0
- package/src/page-type-add-field-rich-text.ts +248 -0
- package/src/page-type-add-field-select.ts +230 -0
- package/src/page-type-add-field-timestamp.ts +227 -0
- package/src/page-type-add-field-uid.ts +207 -0
- package/src/page-type-add-field.ts +116 -0
- package/src/page-type-connect-slice.ts +214 -0
- package/src/page-type-create.ts +161 -0
- package/src/page-type-disconnect-slice.ts +171 -0
- package/src/page-type-list.ts +109 -0
- package/src/page-type-remove-field.ts +171 -0
- package/src/page-type-remove.ts +138 -0
- package/src/page-type-set-name.ts +138 -0
- package/src/page-type-set-repeatable.ts +147 -0
- package/src/page-type-view.ts +118 -0
- package/src/page-type.ts +90 -0
- package/src/preview-add.ts +126 -0
- package/src/preview-get-simulator.ts +104 -0
- package/src/preview-list.ts +106 -0
- package/src/preview-remove-simulator.ts +80 -0
- package/src/preview-remove.ts +109 -0
- package/src/preview-set-name.ts +137 -0
- package/src/preview-set-simulator.ts +116 -0
- package/src/preview.ts +75 -0
- package/src/pull.ts +242 -0
- package/src/push.ts +405 -0
- package/src/repo-create.ts +195 -0
- package/src/repo-get-access.ts +86 -0
- package/src/repo-list.ts +100 -0
- package/src/repo-set-access.ts +100 -0
- package/src/repo-set-name.ts +102 -0
- package/src/repo-view.ts +113 -0
- package/src/repo.ts +70 -0
- package/src/slice-add-field-boolean.ts +240 -0
- package/src/slice-add-field-color.ts +226 -0
- package/src/slice-add-field-date.ts +226 -0
- package/src/slice-add-field-embed.ts +226 -0
- package/src/slice-add-field-geo-point.ts +223 -0
- package/src/slice-add-field-group.ts +191 -0
- package/src/slice-add-field-image.ts +223 -0
- package/src/slice-add-field-key-text.ts +226 -0
- package/src/slice-add-field-link.ts +245 -0
- package/src/slice-add-field-number.ts +226 -0
- package/src/slice-add-field-rich-text.ts +250 -0
- package/src/slice-add-field-select.ts +232 -0
- package/src/slice-add-field-timestamp.ts +226 -0
- package/src/slice-add-field.ts +111 -0
- package/src/slice-add-variation.ts +139 -0
- package/src/slice-create.ts +203 -0
- package/src/slice-list-variations.ts +67 -0
- package/src/slice-list.ts +88 -0
- package/src/slice-remove-field.ts +122 -0
- package/src/slice-remove-variation.ts +112 -0
- package/src/slice-remove.ts +91 -0
- package/src/slice-rename.ts +122 -0
- package/src/slice-set-screenshot.ts +235 -0
- package/src/slice-view.ts +80 -0
- package/src/slice.ts +95 -0
- package/src/status.ts +873 -0
- package/src/token-create.ts +203 -0
- package/src/token-delete.ts +182 -0
- package/src/token-list.ts +223 -0
- package/src/token-set-name.ts +193 -0
- package/src/token.ts +60 -0
- package/src/webhook-add-header.ts +118 -0
- package/src/webhook-create.ts +152 -0
- package/src/webhook-disable.ts +109 -0
- package/src/webhook-enable.ts +132 -0
- package/src/webhook-list.ts +93 -0
- package/src/webhook-remove-header.ts +117 -0
- package/src/webhook-remove.ts +106 -0
- package/src/webhook-set-triggers.ts +148 -0
- package/src/webhook-status.ts +90 -0
- package/src/webhook-test.ts +106 -0
- package/src/webhook-view.ts +147 -0
- package/src/webhook.ts +95 -0
- package/src/whoami.ts +62 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import type { SharedSlice, Timestamp } 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 timestamp (date and time) field to an existing slice.
|
|
15
|
+
|
|
16
|
+
USAGE
|
|
17
|
+
prismic slice add-field timestamp <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 timestamp my_slice created_at
|
|
32
|
+
prismic slice add-field timestamp event start_time --label "Event Start"
|
|
33
|
+
prismic slice add-field timestamp schedule meeting_time --variation "detailed"
|
|
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 sliceAddFieldTimestamp(): 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", "timestamp"
|
|
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 timestamp <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 timestamp <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: Timestamp = {
|
|
139
|
+
type: "Timestamp",
|
|
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}" (Timestamp) to group "${fieldPath.groupId}" in ${sliceId}`,
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
console.info(
|
|
204
|
+
`Added field "${fieldId}" (Timestamp) 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,111 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { sliceAddFieldBoolean } from "./slice-add-field-boolean";
|
|
4
|
+
import { sliceAddFieldColor } from "./slice-add-field-color";
|
|
5
|
+
import { sliceAddFieldDate } from "./slice-add-field-date";
|
|
6
|
+
import { sliceAddFieldEmbed } from "./slice-add-field-embed";
|
|
7
|
+
import { sliceAddFieldGeoPoint } from "./slice-add-field-geo-point";
|
|
8
|
+
import { sliceAddFieldGroup } from "./slice-add-field-group";
|
|
9
|
+
import { sliceAddFieldImage } from "./slice-add-field-image";
|
|
10
|
+
import { sliceAddFieldKeyText } from "./slice-add-field-key-text";
|
|
11
|
+
import { sliceAddFieldLink } from "./slice-add-field-link";
|
|
12
|
+
import { sliceAddFieldNumber } from "./slice-add-field-number";
|
|
13
|
+
import { sliceAddFieldRichText } from "./slice-add-field-rich-text";
|
|
14
|
+
import { sliceAddFieldSelect } from "./slice-add-field-select";
|
|
15
|
+
import { sliceAddFieldTimestamp } from "./slice-add-field-timestamp";
|
|
16
|
+
|
|
17
|
+
const HELP = `
|
|
18
|
+
Add a field to an existing slice.
|
|
19
|
+
|
|
20
|
+
USAGE
|
|
21
|
+
prismic slice add-field <field-type> <slice-id> <field-id> [flags]
|
|
22
|
+
|
|
23
|
+
FIELD TYPES
|
|
24
|
+
boolean Boolean toggle
|
|
25
|
+
color Color picker
|
|
26
|
+
date Date picker
|
|
27
|
+
embed Embed (oEmbed)
|
|
28
|
+
geo-point Geographic coordinates
|
|
29
|
+
group Repeatable group of fields
|
|
30
|
+
image Image
|
|
31
|
+
key-text Single-line text
|
|
32
|
+
link Any link type
|
|
33
|
+
number Number
|
|
34
|
+
rich-text Rich text editor
|
|
35
|
+
select Dropdown select
|
|
36
|
+
timestamp Date and time
|
|
37
|
+
|
|
38
|
+
FLAGS
|
|
39
|
+
-h, --help Show help for command
|
|
40
|
+
|
|
41
|
+
LEARN MORE
|
|
42
|
+
Use \`prismic slice add-field <field-type> --help\` for more information.
|
|
43
|
+
|
|
44
|
+
EXAMPLES
|
|
45
|
+
prismic slice add-field key-text my_slice title --label "Title"
|
|
46
|
+
prismic slice add-field link my_slice cta --allow-text
|
|
47
|
+
prismic slice add-field rich-text my_slice body --multi "paragraph,heading2,strong,em"
|
|
48
|
+
prismic slice add-field select my_slice layout --option "full" --option "sidebar"
|
|
49
|
+
`.trim();
|
|
50
|
+
|
|
51
|
+
export async function sliceAddField(): Promise<void> {
|
|
52
|
+
const {
|
|
53
|
+
positionals: [fieldType],
|
|
54
|
+
} = parseArgs({
|
|
55
|
+
args: process.argv.slice(4), // skip: node, script, "slice", "add-field"
|
|
56
|
+
options: {
|
|
57
|
+
help: { type: "boolean", short: "h" },
|
|
58
|
+
},
|
|
59
|
+
allowPositionals: true,
|
|
60
|
+
strict: false,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
switch (fieldType) {
|
|
64
|
+
case "boolean":
|
|
65
|
+
await sliceAddFieldBoolean();
|
|
66
|
+
break;
|
|
67
|
+
case "color":
|
|
68
|
+
await sliceAddFieldColor();
|
|
69
|
+
break;
|
|
70
|
+
case "date":
|
|
71
|
+
await sliceAddFieldDate();
|
|
72
|
+
break;
|
|
73
|
+
case "embed":
|
|
74
|
+
await sliceAddFieldEmbed();
|
|
75
|
+
break;
|
|
76
|
+
case "geo-point":
|
|
77
|
+
await sliceAddFieldGeoPoint();
|
|
78
|
+
break;
|
|
79
|
+
case "group":
|
|
80
|
+
await sliceAddFieldGroup();
|
|
81
|
+
break;
|
|
82
|
+
case "image":
|
|
83
|
+
await sliceAddFieldImage();
|
|
84
|
+
break;
|
|
85
|
+
case "key-text":
|
|
86
|
+
await sliceAddFieldKeyText();
|
|
87
|
+
break;
|
|
88
|
+
case "link":
|
|
89
|
+
await sliceAddFieldLink();
|
|
90
|
+
break;
|
|
91
|
+
case "number":
|
|
92
|
+
await sliceAddFieldNumber();
|
|
93
|
+
break;
|
|
94
|
+
case "rich-text":
|
|
95
|
+
await sliceAddFieldRichText();
|
|
96
|
+
break;
|
|
97
|
+
case "select":
|
|
98
|
+
await sliceAddFieldSelect();
|
|
99
|
+
break;
|
|
100
|
+
case "timestamp":
|
|
101
|
+
await sliceAddFieldTimestamp();
|
|
102
|
+
break;
|
|
103
|
+
default: {
|
|
104
|
+
if (fieldType) {
|
|
105
|
+
console.error(`Unknown field type: ${fieldType}\n`);
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
}
|
|
108
|
+
console.info(HELP);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { 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 { stringify } from "./lib/json";
|
|
8
|
+
import { findSliceModel, pascalCase } from "./lib/slice";
|
|
9
|
+
|
|
10
|
+
const HELP = `
|
|
11
|
+
Add a new variation to a slice.
|
|
12
|
+
|
|
13
|
+
USAGE
|
|
14
|
+
prismic slice add-variation <slice-id> <variation-id> [flags]
|
|
15
|
+
|
|
16
|
+
ARGUMENTS
|
|
17
|
+
slice-id Slice identifier (required)
|
|
18
|
+
variation-id New variation identifier (required)
|
|
19
|
+
|
|
20
|
+
FLAGS
|
|
21
|
+
--name string Display name for the variation
|
|
22
|
+
--copy-from string Copy fields from an existing variation
|
|
23
|
+
--types string Output file for generated types (default: "prismicio-types.d.ts")
|
|
24
|
+
-h, --help Show help for command
|
|
25
|
+
|
|
26
|
+
EXAMPLES
|
|
27
|
+
prismic slice add-variation MySlice withImage
|
|
28
|
+
prismic slice add-variation MySlice withImage --name "With Image"
|
|
29
|
+
prismic slice add-variation MySlice withImage --copy-from default
|
|
30
|
+
`.trim();
|
|
31
|
+
|
|
32
|
+
export async function sliceAddVariation(): Promise<void> {
|
|
33
|
+
const {
|
|
34
|
+
values: { help, name, "copy-from": copyFrom, types },
|
|
35
|
+
positionals: [sliceId, variationId],
|
|
36
|
+
} = parseArgs({
|
|
37
|
+
args: process.argv.slice(4), // skip: node, script, "slice", "add-variation"
|
|
38
|
+
options: {
|
|
39
|
+
name: { type: "string" },
|
|
40
|
+
"copy-from": { type: "string" },
|
|
41
|
+
types: { type: "string" },
|
|
42
|
+
help: { type: "boolean", short: "h" },
|
|
43
|
+
},
|
|
44
|
+
allowPositionals: true,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
if (help) {
|
|
48
|
+
console.info(HELP);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!sliceId) {
|
|
53
|
+
console.error("Missing required argument: slice-id\n");
|
|
54
|
+
console.error("Usage: prismic slice add-variation <slice-id> <variation-id>");
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!variationId) {
|
|
60
|
+
console.error("Missing required argument: variation-id\n");
|
|
61
|
+
console.error("Usage: prismic slice add-variation <slice-id> <variation-id>");
|
|
62
|
+
process.exitCode = 1;
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const result = await findSliceModel(sliceId);
|
|
67
|
+
if (!result.ok) {
|
|
68
|
+
console.error(result.error);
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const { model, modelPath } = result;
|
|
74
|
+
|
|
75
|
+
// Check if variation already exists
|
|
76
|
+
if (model.variations.some((v) => v.id === variationId)) {
|
|
77
|
+
console.error(`Variation "${variationId}" already exists in slice "${sliceId}"`);
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Build new variation
|
|
83
|
+
let newVariation: SharedSlice["variations"][number];
|
|
84
|
+
|
|
85
|
+
if (copyFrom) {
|
|
86
|
+
const sourceVariation = model.variations.find((v) => v.id === copyFrom);
|
|
87
|
+
if (!sourceVariation) {
|
|
88
|
+
console.error(`Source variation not found: ${copyFrom}`);
|
|
89
|
+
console.error(`Available variations: ${model.variations.map((v) => v.id).join(", ")}`);
|
|
90
|
+
process.exitCode = 1;
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
newVariation = {
|
|
95
|
+
...structuredClone(sourceVariation),
|
|
96
|
+
id: variationId,
|
|
97
|
+
name: name ?? pascalCase(variationId),
|
|
98
|
+
};
|
|
99
|
+
} else {
|
|
100
|
+
newVariation = {
|
|
101
|
+
id: variationId,
|
|
102
|
+
name: name ?? pascalCase(variationId),
|
|
103
|
+
description: variationId,
|
|
104
|
+
imageUrl: "",
|
|
105
|
+
docURL: "",
|
|
106
|
+
version: "initial",
|
|
107
|
+
primary: {},
|
|
108
|
+
items: {},
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Add variation to model
|
|
113
|
+
const updatedModel = {
|
|
114
|
+
...model,
|
|
115
|
+
variations: [...model.variations, newVariation],
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// Write updated model
|
|
119
|
+
try {
|
|
120
|
+
await writeFile(modelPath, stringify(updatedModel as SharedSlice));
|
|
121
|
+
} catch (error) {
|
|
122
|
+
if (error instanceof Error) {
|
|
123
|
+
console.error(`Failed to update slice: ${error.message}`);
|
|
124
|
+
} else {
|
|
125
|
+
console.error("Failed to update slice");
|
|
126
|
+
}
|
|
127
|
+
process.exitCode = 1;
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.info(`Added variation "${variationId}" to slice "${sliceId}"`);
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
await buildTypes({ output: types });
|
|
135
|
+
console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import type { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
2
|
+
|
|
3
|
+
import { mkdir, 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 { isAuthenticated } from "./lib/auth";
|
|
9
|
+
import { safeGetRepositoryFromConfig } from "./lib/config";
|
|
10
|
+
import { exists, findUpward } from "./lib/file";
|
|
11
|
+
import { stringify } from "./lib/json";
|
|
12
|
+
import { uploadScreenshot } from "./slice-set-screenshot";
|
|
13
|
+
|
|
14
|
+
const HELP = `
|
|
15
|
+
Create a new slice in a Prismic project.
|
|
16
|
+
|
|
17
|
+
USAGE
|
|
18
|
+
prismic slice create <id> [flags]
|
|
19
|
+
|
|
20
|
+
ARGUMENTS
|
|
21
|
+
id Slice identifier (required)
|
|
22
|
+
|
|
23
|
+
FLAGS
|
|
24
|
+
-n, --name string Display name for the slice
|
|
25
|
+
--screenshot string Path to screenshot image for default variation
|
|
26
|
+
-r, --repo string Repository name (required for screenshot)
|
|
27
|
+
--types string Output file for generated types (default: "prismicio-types.d.ts")
|
|
28
|
+
-h, --help Show help for command
|
|
29
|
+
|
|
30
|
+
LEARN MORE
|
|
31
|
+
Use \`prismic slice <command> --help\` for more information about a command.
|
|
32
|
+
`.trim();
|
|
33
|
+
|
|
34
|
+
export async function sliceCreate(): Promise<void> {
|
|
35
|
+
const {
|
|
36
|
+
values: { help, name, types, screenshot, repo: repoFlag },
|
|
37
|
+
positionals: [id],
|
|
38
|
+
} = parseArgs({
|
|
39
|
+
args: process.argv.slice(4), // skip: node, script, "slice", "create"
|
|
40
|
+
options: {
|
|
41
|
+
name: { type: "string", short: "n" },
|
|
42
|
+
screenshot: { type: "string" },
|
|
43
|
+
repo: { type: "string", short: "r" },
|
|
44
|
+
types: { type: "string" },
|
|
45
|
+
help: { type: "boolean", short: "h" },
|
|
46
|
+
},
|
|
47
|
+
allowPositionals: true,
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (help) {
|
|
51
|
+
console.info(HELP);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!id) {
|
|
56
|
+
console.error("Missing required argument: id");
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle screenshot upload if provided
|
|
62
|
+
let screenshotUrl: string | undefined;
|
|
63
|
+
if (screenshot) {
|
|
64
|
+
// Check authentication
|
|
65
|
+
const authenticated = await isAuthenticated();
|
|
66
|
+
if (!authenticated) {
|
|
67
|
+
console.error("You must be logged in to upload a screenshot.");
|
|
68
|
+
console.error("Run `prismic login` to authenticate.");
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Resolve repository
|
|
74
|
+
const repo = repoFlag ?? (await safeGetRepositoryFromConfig());
|
|
75
|
+
if (!repo) {
|
|
76
|
+
console.error("Could not determine repository for screenshot upload.");
|
|
77
|
+
console.error("Use --repo flag or run from a directory with prismic.config.json");
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Read and upload the screenshot
|
|
83
|
+
let imageData: Buffer;
|
|
84
|
+
try {
|
|
85
|
+
imageData = await readFile(screenshot);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
if (error instanceof Error) {
|
|
88
|
+
console.error(`Failed to read screenshot file: ${error.message}`);
|
|
89
|
+
} else {
|
|
90
|
+
console.error("Failed to read screenshot file");
|
|
91
|
+
}
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
screenshotUrl = await uploadScreenshot({
|
|
98
|
+
data: imageData,
|
|
99
|
+
repo,
|
|
100
|
+
sliceId: id,
|
|
101
|
+
variationId: "default",
|
|
102
|
+
filename: screenshot,
|
|
103
|
+
});
|
|
104
|
+
} catch (error) {
|
|
105
|
+
if (error instanceof Error) {
|
|
106
|
+
console.error(`Failed to upload screenshot: ${error.message}`);
|
|
107
|
+
} else {
|
|
108
|
+
console.error("Failed to upload screenshot");
|
|
109
|
+
}
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const model: SharedSlice = {
|
|
116
|
+
id,
|
|
117
|
+
type: "SharedSlice",
|
|
118
|
+
name: name ?? pascalCase(id),
|
|
119
|
+
description: "",
|
|
120
|
+
variations: [
|
|
121
|
+
{
|
|
122
|
+
id: "default",
|
|
123
|
+
name: "Default",
|
|
124
|
+
description: "Default",
|
|
125
|
+
imageUrl: screenshotUrl ?? "",
|
|
126
|
+
docURL: "",
|
|
127
|
+
version: "initial",
|
|
128
|
+
primary: {},
|
|
129
|
+
items: {},
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const slicesDirectory = await getSlicesDirectory();
|
|
135
|
+
const sliceDirectory = new URL(pascalCase(model.name) + "/", slicesDirectory);
|
|
136
|
+
const modelPath = new URL("model.json", sliceDirectory);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
await mkdir(new URL(".", modelPath), { recursive: true });
|
|
140
|
+
await writeFile(modelPath, stringify(model));
|
|
141
|
+
} catch (error) {
|
|
142
|
+
if (error instanceof Error) {
|
|
143
|
+
console.error(`Failed to create slice: ${error.message}`);
|
|
144
|
+
} else {
|
|
145
|
+
console.error(`Failed to create slice`);
|
|
146
|
+
}
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.info(`Created slice at ${modelPath.href}`);
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
await buildTypes({ output: types });
|
|
155
|
+
console.info(`Updated types in ${types ?? "prismicio-types.d.ts"}`);
|
|
156
|
+
} catch (error) {
|
|
157
|
+
console.warn(`Could not generate types: ${error instanceof Error ? error.message : error}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
console.info();
|
|
161
|
+
console.info("Next: Add fields with `prismic slice add-field`");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function getSlicesDirectory(): Promise<URL> {
|
|
165
|
+
const framework = await detectFramework();
|
|
166
|
+
const projectRoot = await findUpward("package.json");
|
|
167
|
+
switch (framework) {
|
|
168
|
+
case "next": {
|
|
169
|
+
const hasSrcDir = await exists(new URL("src", projectRoot));
|
|
170
|
+
if (hasSrcDir) return new URL("src/slices/", projectRoot);
|
|
171
|
+
}
|
|
172
|
+
case "nuxt": {
|
|
173
|
+
const hasAppDir = await exists(new URL("app", projectRoot));
|
|
174
|
+
if (hasAppDir) return new URL("app/slices/", projectRoot);
|
|
175
|
+
}
|
|
176
|
+
case "sveltekit": {
|
|
177
|
+
return new URL("src/slices/", projectRoot);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
return new URL("slices/", projectRoot);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const PackageJsonSchema = v.object({
|
|
184
|
+
dependencies: v.optional(v.record(v.string(), v.string())),
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
type Framework = "next" | "nuxt" | "sveltekit";
|
|
188
|
+
|
|
189
|
+
async function detectFramework(): Promise<Framework | undefined> {
|
|
190
|
+
const packageJsonPath = await findUpward("package.json");
|
|
191
|
+
if (!packageJsonPath) return;
|
|
192
|
+
try {
|
|
193
|
+
const contents = await readFile(packageJsonPath, "utf8");
|
|
194
|
+
const { dependencies = {} } = v.parse(PackageJsonSchema, JSON.parse(contents));
|
|
195
|
+
if ("next" in dependencies) return "next";
|
|
196
|
+
if ("nuxt" in dependencies) return "nuxt";
|
|
197
|
+
if ("@sveltejs/kit" in dependencies) return "sveltekit";
|
|
198
|
+
} catch {}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function pascalCase(input: string): string {
|
|
202
|
+
return input.toLowerCase().replace(/(^|[-_\s]+)(.)?/g, (_, __, c) => c?.toUpperCase() ?? "");
|
|
203
|
+
}
|