@angeloashmore/prismic-cli-poc 0.0.0-canary.2ff9563

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 (119) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +98 -0
  3. package/dist/index.mjs +1996 -0
  4. package/package.json +52 -0
  5. package/src/custom-type-add-field-boolean.ts +171 -0
  6. package/src/custom-type-add-field-color.ts +158 -0
  7. package/src/custom-type-add-field-date.ts +161 -0
  8. package/src/custom-type-add-field-embed.ts +158 -0
  9. package/src/custom-type-add-field-geo-point.ts +155 -0
  10. package/src/custom-type-add-field-image.ts +158 -0
  11. package/src/custom-type-add-field-key-text.ts +158 -0
  12. package/src/custom-type-add-field-link.ts +180 -0
  13. package/src/custom-type-add-field-number.ts +190 -0
  14. package/src/custom-type-add-field-rich-text.ts +181 -0
  15. package/src/custom-type-add-field-select.ts +164 -0
  16. package/src/custom-type-add-field-timestamp.ts +161 -0
  17. package/src/custom-type-add-field-uid.ts +158 -0
  18. package/src/custom-type-add-field.ts +111 -0
  19. package/src/custom-type-connect-slice.ts +221 -0
  20. package/src/custom-type-create.ts +92 -0
  21. package/src/custom-type-disconnect-slice.ts +179 -0
  22. package/src/custom-type-list.ts +110 -0
  23. package/src/custom-type-remove-field.ts +161 -0
  24. package/src/custom-type-remove.ts +126 -0
  25. package/src/custom-type-set-name.ts +128 -0
  26. package/src/custom-type-view.ts +118 -0
  27. package/src/custom-type.ts +85 -0
  28. package/src/index.ts +100 -0
  29. package/src/init.ts +62 -0
  30. package/src/lib/auth.ts +60 -0
  31. package/src/lib/config.ts +111 -0
  32. package/src/lib/file.ts +49 -0
  33. package/src/lib/json.ts +3 -0
  34. package/src/lib/request.ts +116 -0
  35. package/src/lib/slice.ts +112 -0
  36. package/src/lib/url.ts +25 -0
  37. package/src/locale-add.ts +116 -0
  38. package/src/locale-list.ts +107 -0
  39. package/src/locale-remove.ts +88 -0
  40. package/src/locale-set-default.ts +131 -0
  41. package/src/locale.ts +60 -0
  42. package/src/login.ts +143 -0
  43. package/src/logout.ts +36 -0
  44. package/src/page-type-add-field-boolean.ts +171 -0
  45. package/src/page-type-add-field-color.ts +158 -0
  46. package/src/page-type-add-field-date.ts +161 -0
  47. package/src/page-type-add-field-embed.ts +158 -0
  48. package/src/page-type-add-field-geo-point.ts +155 -0
  49. package/src/page-type-add-field-image.ts +158 -0
  50. package/src/page-type-add-field-key-text.ts +158 -0
  51. package/src/page-type-add-field-link.ts +180 -0
  52. package/src/page-type-add-field-number.ts +190 -0
  53. package/src/page-type-add-field-rich-text.ts +181 -0
  54. package/src/page-type-add-field-select.ts +164 -0
  55. package/src/page-type-add-field-timestamp.ts +161 -0
  56. package/src/page-type-add-field-uid.ts +158 -0
  57. package/src/page-type-add-field.ts +111 -0
  58. package/src/page-type-connect-slice.ts +221 -0
  59. package/src/page-type-create.ts +93 -0
  60. package/src/page-type-disconnect-slice.ts +179 -0
  61. package/src/page-type-list.ts +109 -0
  62. package/src/page-type-remove-field.ts +161 -0
  63. package/src/page-type-remove.ts +126 -0
  64. package/src/page-type-set-name.ts +128 -0
  65. package/src/page-type-set-repeatable.ts +137 -0
  66. package/src/page-type-view.ts +118 -0
  67. package/src/page-type.ts +90 -0
  68. package/src/preview-add.ts +126 -0
  69. package/src/preview-list.ts +106 -0
  70. package/src/preview-remove.ts +109 -0
  71. package/src/preview-set-name.ts +137 -0
  72. package/src/preview.ts +60 -0
  73. package/src/repo-create.ts +136 -0
  74. package/src/repo-list.ts +100 -0
  75. package/src/repo-set-name.ts +102 -0
  76. package/src/repo-view.ts +113 -0
  77. package/src/repo.ts +60 -0
  78. package/src/slice-add-field-boolean.ts +150 -0
  79. package/src/slice-add-field-color.ts +137 -0
  80. package/src/slice-add-field-date.ts +137 -0
  81. package/src/slice-add-field-embed.ts +137 -0
  82. package/src/slice-add-field-geo-point.ts +134 -0
  83. package/src/slice-add-field-image.ts +134 -0
  84. package/src/slice-add-field-key-text.ts +137 -0
  85. package/src/slice-add-field-link.ts +155 -0
  86. package/src/slice-add-field-number.ts +137 -0
  87. package/src/slice-add-field-rich-text.ts +160 -0
  88. package/src/slice-add-field-select.ts +143 -0
  89. package/src/slice-add-field-timestamp.ts +137 -0
  90. package/src/slice-add-field.ts +106 -0
  91. package/src/slice-add-variation.ts +137 -0
  92. package/src/slice-create.ts +129 -0
  93. package/src/slice-list-variations.ts +67 -0
  94. package/src/slice-list.ts +88 -0
  95. package/src/slice-remove-field.ts +117 -0
  96. package/src/slice-remove-variation.ts +108 -0
  97. package/src/slice-remove.ts +81 -0
  98. package/src/slice-rename.ts +112 -0
  99. package/src/slice-view.ts +77 -0
  100. package/src/slice.ts +90 -0
  101. package/src/sync.ts +309 -0
  102. package/src/token-create.ts +185 -0
  103. package/src/token-delete.ts +161 -0
  104. package/src/token-list.ts +212 -0
  105. package/src/token-set-name.ts +165 -0
  106. package/src/token.ts +60 -0
  107. package/src/webhook-add-header.ts +118 -0
  108. package/src/webhook-create.ts +152 -0
  109. package/src/webhook-disable.ts +109 -0
  110. package/src/webhook-enable.ts +132 -0
  111. package/src/webhook-list.ts +93 -0
  112. package/src/webhook-remove-header.ts +117 -0
  113. package/src/webhook-remove.ts +106 -0
  114. package/src/webhook-set-triggers.ts +148 -0
  115. package/src/webhook-status.ts +90 -0
  116. package/src/webhook-test.ts +106 -0
  117. package/src/webhook-view.ts +147 -0
  118. package/src/webhook.ts +95 -0
  119. package/src/whoami.ts +62 -0
@@ -0,0 +1,180 @@
1
+ import type { CustomType, Link } from "@prismicio/types-internal/lib/customtypes";
2
+
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { parseArgs } from "node:util";
5
+ import * as v from "valibot";
6
+
7
+ import { findUpward } from "./lib/file";
8
+ import { stringify } from "./lib/json";
9
+
10
+ const HELP = `
11
+ Add a link field to an existing page type.
12
+
13
+ USAGE
14
+ prismic page-type add-field link <type-id> <field-id> [flags]
15
+
16
+ ARGUMENTS
17
+ type-id Page type identifier (required)
18
+ field-id Field identifier (required)
19
+
20
+ FLAGS
21
+ -t, --tab string Target tab (default: first existing tab, or "Main")
22
+ -l, --label string Display label for the field
23
+ -p, --placeholder string Placeholder text
24
+ --variation string Slice variations (can be used multiple times)
25
+ --allow-text Allow text with link
26
+ --allow-target-blank Allow opening link in new tab
27
+ --repeatable Allow multiple links
28
+ -h, --help Show help for command
29
+
30
+ EXAMPLES
31
+ prismic page-type add-field link homepage button
32
+ prismic page-type add-field link homepage cta --allow-text
33
+ prismic page-type add-field link homepage cta --variation Primary --variation Secondary
34
+ prismic page-type add-field link homepage links --repeatable
35
+ `.trim();
36
+
37
+ const CustomTypeSchema = v.object({
38
+ id: v.string(),
39
+ label: v.string(),
40
+ repeatable: v.boolean(),
41
+ status: v.boolean(),
42
+ format: v.string(),
43
+ json: v.record(v.string(), v.record(v.string(), v.unknown())),
44
+ });
45
+
46
+ export async function pageTypeAddFieldLink(): Promise<void> {
47
+ const {
48
+ values: {
49
+ help,
50
+ tab,
51
+ label,
52
+ placeholder,
53
+ variation,
54
+ "allow-text": allowText,
55
+ "allow-target-blank": allowTargetBlank,
56
+ repeatable,
57
+ },
58
+ positionals: [typeId, fieldId],
59
+ } = parseArgs({
60
+ args: process.argv.slice(5), // skip: node, script, "page-type", "add-field", "link"
61
+ options: {
62
+ tab: { type: "string", short: "t" },
63
+ label: { type: "string", short: "l" },
64
+ placeholder: { type: "string", short: "p" },
65
+ variation: { type: "string", multiple: true },
66
+ "allow-text": { type: "boolean" },
67
+ "allow-target-blank": { type: "boolean" },
68
+ repeatable: { type: "boolean" },
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 (!typeId) {
80
+ console.error("Missing required argument: type-id\n");
81
+ console.error("Usage: prismic page-type add-field link <type-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 page-type add-field link <type-id> <field-id>");
89
+ process.exitCode = 1;
90
+ return;
91
+ }
92
+
93
+ // Find the page type file
94
+ const projectRoot = await findUpward("package.json");
95
+ if (!projectRoot) {
96
+ console.error("Could not find project root (no package.json found)");
97
+ process.exitCode = 1;
98
+ return;
99
+ }
100
+
101
+ const modelPath = new URL(`customtypes/${typeId}/index.json`, projectRoot);
102
+
103
+ // Read and parse the model
104
+ let model: CustomType;
105
+ try {
106
+ const contents = await readFile(modelPath, "utf8");
107
+ const result = v.safeParse(CustomTypeSchema, JSON.parse(contents));
108
+ if (!result.success) {
109
+ console.error(`Invalid page type model: ${modelPath.href}`);
110
+ process.exitCode = 1;
111
+ return;
112
+ }
113
+ model = result.output as CustomType;
114
+ } catch (error) {
115
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
116
+ console.error(`Page type not found: ${typeId}\n`);
117
+ console.error(`Create it first with: prismic page-type create ${typeId}`);
118
+ process.exitCode = 1;
119
+ return;
120
+ }
121
+ if (error instanceof Error) {
122
+ console.error(`Failed to read page type: ${error.message}`);
123
+ } else {
124
+ console.error("Failed to read page type");
125
+ }
126
+ process.exitCode = 1;
127
+ return;
128
+ }
129
+
130
+ // Determine target tab
131
+ const existingTabs = Object.keys(model.json);
132
+ const targetTab = tab ?? existingTabs[0] ?? "Main";
133
+
134
+ // Initialize tab if it doesn't exist
135
+ if (!model.json[targetTab]) {
136
+ model.json[targetTab] = {};
137
+ }
138
+
139
+ // Check if field already exists in any tab
140
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
141
+ if (tabFields[fieldId]) {
142
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
143
+ process.exitCode = 1;
144
+ return;
145
+ }
146
+ }
147
+
148
+ // Build field definition
149
+ const fieldDefinition: Link = {
150
+ type: "Link",
151
+ config: {
152
+ ...(label && { label }),
153
+ ...(placeholder && { placeholder }),
154
+ ...(variation && variation.length > 0 && { variants: variation }),
155
+ ...(allowText && { allowText: true }),
156
+ ...(allowTargetBlank && { allowTargetBlank: true }),
157
+ ...(repeatable && { repeat: true }),
158
+ },
159
+ };
160
+
161
+ // Add field to model
162
+ model.json[targetTab][fieldId] = fieldDefinition;
163
+
164
+ // Write updated model
165
+ try {
166
+ await writeFile(modelPath, stringify(model));
167
+ } catch (error) {
168
+ if (error instanceof Error) {
169
+ console.error(`Failed to update page type: ${error.message}`);
170
+ } else {
171
+ console.error("Failed to update page type");
172
+ }
173
+ process.exitCode = 1;
174
+ return;
175
+ }
176
+
177
+ console.info(
178
+ `Added field "${fieldId}" (Link) to "${targetTab}" tab in ${typeId}`,
179
+ );
180
+ }
@@ -0,0 +1,190 @@
1
+ import type { CustomType, Number as NumberField } from "@prismicio/types-internal/lib/customtypes";
2
+
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { parseArgs } from "node:util";
5
+ import * as v from "valibot";
6
+
7
+ import { findUpward } from "./lib/file";
8
+ import { stringify } from "./lib/json";
9
+
10
+ const HELP = `
11
+ Add a number field to an existing page type.
12
+
13
+ USAGE
14
+ prismic page-type add-field number <type-id> <field-id> [flags]
15
+
16
+ ARGUMENTS
17
+ type-id Page type identifier (required)
18
+ field-id Field identifier (required)
19
+
20
+ FLAGS
21
+ -t, --tab string Target tab (default: first existing tab, or "Main")
22
+ -l, --label string Display label for the field
23
+ -p, --placeholder string Placeholder text
24
+ --min number Minimum value
25
+ --max number Maximum value
26
+ --step number Step increment
27
+ -h, --help Show help for command
28
+
29
+ EXAMPLES
30
+ prismic page-type add-field number homepage price
31
+ prismic page-type add-field number product quantity --min 0 --max 100
32
+ prismic page-type add-field number settings rating --min 1 --max 5 --step 1
33
+ `.trim();
34
+
35
+ const CustomTypeSchema = v.object({
36
+ id: v.string(),
37
+ label: v.string(),
38
+ repeatable: v.boolean(),
39
+ status: v.boolean(),
40
+ format: v.string(),
41
+ json: v.record(v.string(), v.record(v.string(), v.unknown())),
42
+ });
43
+
44
+ export async function pageTypeAddFieldNumber(): Promise<void> {
45
+ const {
46
+ values: { help, tab, label, placeholder, min, max, step },
47
+ positionals: [typeId, fieldId],
48
+ } = parseArgs({
49
+ args: process.argv.slice(5), // skip: node, script, "page-type", "add-field", "number"
50
+ options: {
51
+ tab: { type: "string", short: "t" },
52
+ label: { type: "string", short: "l" },
53
+ placeholder: { type: "string", short: "p" },
54
+ min: { type: "string" },
55
+ max: { type: "string" },
56
+ step: { type: "string" },
57
+ help: { type: "boolean", short: "h" },
58
+ },
59
+ allowPositionals: true,
60
+ });
61
+
62
+ if (help) {
63
+ console.info(HELP);
64
+ return;
65
+ }
66
+
67
+ if (!typeId) {
68
+ console.error("Missing required argument: type-id\n");
69
+ console.error("Usage: prismic page-type add-field number <type-id> <field-id>");
70
+ process.exitCode = 1;
71
+ return;
72
+ }
73
+
74
+ if (!fieldId) {
75
+ console.error("Missing required argument: field-id\n");
76
+ console.error("Usage: prismic page-type add-field number <type-id> <field-id>");
77
+ process.exitCode = 1;
78
+ return;
79
+ }
80
+
81
+ // Parse numeric values
82
+ const minValue = min !== undefined ? Number(min) : undefined;
83
+ const maxValue = max !== undefined ? Number(max) : undefined;
84
+ const stepValue = step !== undefined ? Number(step) : undefined;
85
+
86
+ if (min !== undefined && Number.isNaN(minValue)) {
87
+ console.error("Invalid --min value: must be a number");
88
+ process.exitCode = 1;
89
+ return;
90
+ }
91
+
92
+ if (max !== undefined && Number.isNaN(maxValue)) {
93
+ console.error("Invalid --max value: must be a number");
94
+ process.exitCode = 1;
95
+ return;
96
+ }
97
+
98
+ if (step !== undefined && Number.isNaN(stepValue)) {
99
+ console.error("Invalid --step value: must be a number");
100
+ process.exitCode = 1;
101
+ return;
102
+ }
103
+
104
+ // Find the page type file
105
+ const projectRoot = await findUpward("package.json");
106
+ if (!projectRoot) {
107
+ console.error("Could not find project root (no package.json found)");
108
+ process.exitCode = 1;
109
+ return;
110
+ }
111
+
112
+ const modelPath = new URL(`customtypes/${typeId}/index.json`, projectRoot);
113
+
114
+ // Read and parse the model
115
+ let model: CustomType;
116
+ try {
117
+ const contents = await readFile(modelPath, "utf8");
118
+ const result = v.safeParse(CustomTypeSchema, JSON.parse(contents));
119
+ if (!result.success) {
120
+ console.error(`Invalid page type model: ${modelPath.href}`);
121
+ process.exitCode = 1;
122
+ return;
123
+ }
124
+ model = result.output as CustomType;
125
+ } catch (error) {
126
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
127
+ console.error(`Page type not found: ${typeId}\n`);
128
+ console.error(`Create it first with: prismic page-type create ${typeId}`);
129
+ process.exitCode = 1;
130
+ return;
131
+ }
132
+ if (error instanceof Error) {
133
+ console.error(`Failed to read page type: ${error.message}`);
134
+ } else {
135
+ console.error("Failed to read page type");
136
+ }
137
+ process.exitCode = 1;
138
+ return;
139
+ }
140
+
141
+ // Determine target tab
142
+ const existingTabs = Object.keys(model.json);
143
+ const targetTab = tab ?? existingTabs[0] ?? "Main";
144
+
145
+ // Initialize tab if it doesn't exist
146
+ if (!model.json[targetTab]) {
147
+ model.json[targetTab] = {};
148
+ }
149
+
150
+ // Check if field already exists in any tab
151
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
152
+ if (tabFields[fieldId]) {
153
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
154
+ process.exitCode = 1;
155
+ return;
156
+ }
157
+ }
158
+
159
+ // Build field definition
160
+ const fieldDefinition: NumberField = {
161
+ type: "Number",
162
+ config: {
163
+ ...(label && { label }),
164
+ ...(placeholder && { placeholder }),
165
+ ...(minValue !== undefined && { min: minValue }),
166
+ ...(maxValue !== undefined && { max: maxValue }),
167
+ ...(stepValue !== undefined && { step: stepValue }),
168
+ },
169
+ };
170
+
171
+ // Add field to model
172
+ model.json[targetTab][fieldId] = fieldDefinition;
173
+
174
+ // Write updated model
175
+ try {
176
+ await writeFile(modelPath, stringify(model));
177
+ } catch (error) {
178
+ if (error instanceof Error) {
179
+ console.error(`Failed to update page type: ${error.message}`);
180
+ } else {
181
+ console.error("Failed to update page type");
182
+ }
183
+ process.exitCode = 1;
184
+ return;
185
+ }
186
+
187
+ console.info(
188
+ `Added field "${fieldId}" (Number) to "${targetTab}" tab in ${typeId}`,
189
+ );
190
+ }
@@ -0,0 +1,181 @@
1
+ import type { CustomType, RichText } from "@prismicio/types-internal/lib/customtypes";
2
+
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { parseArgs } from "node:util";
5
+ import * as v from "valibot";
6
+
7
+ import { findUpward } from "./lib/file";
8
+ import { stringify } from "./lib/json";
9
+
10
+ const HELP = `
11
+ Add a rich text field to an existing page type.
12
+
13
+ USAGE
14
+ prismic page-type add-field rich-text <type-id> <field-id> [flags]
15
+
16
+ ARGUMENTS
17
+ type-id Page type identifier (required)
18
+ field-id Field identifier (required)
19
+
20
+ FLAGS
21
+ -t, --tab string Target tab (default: first existing tab, or "Main")
22
+ -l, --label string Display label for the field
23
+ -p, --placeholder string Placeholder text
24
+ --single string Allowed block types for single-line (comma-separated)
25
+ --multi string Allowed block types for multi-line (comma-separated)
26
+ --allow-target-blank Allow opening links in new tab
27
+ -h, --help Show help for command
28
+
29
+ BLOCK TYPES
30
+ heading1, heading2, heading3, heading4, heading5, heading6,
31
+ paragraph, strong, em, preformatted, hyperlink, image, embed,
32
+ list-item, o-list-item, rtl
33
+
34
+ EXAMPLES
35
+ prismic page-type add-field rich-text homepage body
36
+ prismic page-type add-field rich-text article content --multi "paragraph,heading2,heading3,strong,em,hyperlink"
37
+ prismic page-type add-field rich-text page tagline --single "heading1"
38
+ prismic page-type add-field rich-text blog post --multi "paragraph,strong,em,hyperlink" --allow-target-blank
39
+ `.trim();
40
+
41
+ const CustomTypeSchema = v.object({
42
+ id: v.string(),
43
+ label: v.string(),
44
+ repeatable: v.boolean(),
45
+ status: v.boolean(),
46
+ format: v.string(),
47
+ json: v.record(v.string(), v.record(v.string(), v.unknown())),
48
+ });
49
+
50
+ export async function pageTypeAddFieldRichText(): Promise<void> {
51
+ const {
52
+ values: {
53
+ help,
54
+ tab,
55
+ label,
56
+ placeholder,
57
+ single,
58
+ multi,
59
+ "allow-target-blank": allowTargetBlank,
60
+ },
61
+ positionals: [typeId, fieldId],
62
+ } = parseArgs({
63
+ args: process.argv.slice(5), // skip: node, script, "page-type", "add-field", "rich-text"
64
+ options: {
65
+ tab: { type: "string", short: "t" },
66
+ label: { type: "string", short: "l" },
67
+ placeholder: { type: "string", short: "p" },
68
+ single: { type: "string" },
69
+ multi: { type: "string" },
70
+ "allow-target-blank": { type: "boolean" },
71
+ help: { type: "boolean", short: "h" },
72
+ },
73
+ allowPositionals: true,
74
+ });
75
+
76
+ if (help) {
77
+ console.info(HELP);
78
+ return;
79
+ }
80
+
81
+ if (!typeId) {
82
+ console.error("Missing required argument: type-id\n");
83
+ console.error("Usage: prismic page-type add-field rich-text <type-id> <field-id>");
84
+ process.exitCode = 1;
85
+ return;
86
+ }
87
+
88
+ if (!fieldId) {
89
+ console.error("Missing required argument: field-id\n");
90
+ console.error("Usage: prismic page-type add-field rich-text <type-id> <field-id>");
91
+ process.exitCode = 1;
92
+ return;
93
+ }
94
+
95
+ // Find the page type file
96
+ const projectRoot = await findUpward("package.json");
97
+ if (!projectRoot) {
98
+ console.error("Could not find project root (no package.json found)");
99
+ process.exitCode = 1;
100
+ return;
101
+ }
102
+
103
+ const modelPath = new URL(`customtypes/${typeId}/index.json`, projectRoot);
104
+
105
+ // Read and parse the model
106
+ let model: CustomType;
107
+ try {
108
+ const contents = await readFile(modelPath, "utf8");
109
+ const result = v.safeParse(CustomTypeSchema, JSON.parse(contents));
110
+ if (!result.success) {
111
+ console.error(`Invalid page type model: ${modelPath.href}`);
112
+ process.exitCode = 1;
113
+ return;
114
+ }
115
+ model = result.output as CustomType;
116
+ } catch (error) {
117
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
118
+ console.error(`Page type not found: ${typeId}\n`);
119
+ console.error(`Create it first with: prismic page-type create ${typeId}`);
120
+ process.exitCode = 1;
121
+ return;
122
+ }
123
+ if (error instanceof Error) {
124
+ console.error(`Failed to read page type: ${error.message}`);
125
+ } else {
126
+ console.error("Failed to read page type");
127
+ }
128
+ process.exitCode = 1;
129
+ return;
130
+ }
131
+
132
+ // Determine target tab
133
+ const existingTabs = Object.keys(model.json);
134
+ const targetTab = tab ?? existingTabs[0] ?? "Main";
135
+
136
+ // Initialize tab if it doesn't exist
137
+ if (!model.json[targetTab]) {
138
+ model.json[targetTab] = {};
139
+ }
140
+
141
+ // Check if field already exists in any tab
142
+ for (const [tabName, tabFields] of Object.entries(model.json)) {
143
+ if (tabFields[fieldId]) {
144
+ console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
145
+ process.exitCode = 1;
146
+ return;
147
+ }
148
+ }
149
+
150
+ // Build field definition
151
+ const fieldDefinition: RichText = {
152
+ type: "StructuredText",
153
+ config: {
154
+ ...(label && { label }),
155
+ ...(placeholder && { placeholder }),
156
+ ...(single && { single }),
157
+ ...(multi && { multi }),
158
+ ...(allowTargetBlank && { allowTargetBlank: true }),
159
+ },
160
+ };
161
+
162
+ // Add field to model
163
+ model.json[targetTab][fieldId] = fieldDefinition;
164
+
165
+ // Write updated model
166
+ try {
167
+ await writeFile(modelPath, stringify(model));
168
+ } catch (error) {
169
+ if (error instanceof Error) {
170
+ console.error(`Failed to update page type: ${error.message}`);
171
+ } else {
172
+ console.error("Failed to update page type");
173
+ }
174
+ process.exitCode = 1;
175
+ return;
176
+ }
177
+
178
+ console.info(
179
+ `Added field "${fieldId}" (StructuredText) to "${targetTab}" tab in ${typeId}`,
180
+ );
181
+ }
@@ -0,0 +1,164 @@
1
+ import type { CustomType, Select } from "@prismicio/types-internal/lib/customtypes";
2
+
3
+ import { readFile, writeFile } from "node:fs/promises";
4
+ import { parseArgs } from "node:util";
5
+ import * as v from "valibot";
6
+
7
+ import { findUpward } from "./lib/file";
8
+ import { stringify } from "./lib/json";
9
+
10
+ const HELP = `
11
+ Add a select (dropdown) field to an existing page type.
12
+
13
+ USAGE
14
+ prismic page-type add-field select <type-id> <field-id> [flags]
15
+
16
+ ARGUMENTS
17
+ type-id Page type identifier (required)
18
+ field-id Field identifier (required)
19
+
20
+ FLAGS
21
+ -t, --tab string Target tab (default: first existing tab, or "Main")
22
+ -l, --label string Display label for the field
23
+ -p, --placeholder string Placeholder text
24
+ --option string Add an option (can be used multiple times)
25
+ --default string Default selected value
26
+ -h, --help Show help for command
27
+
28
+ EXAMPLES
29
+ prismic page-type add-field select homepage layout --option "full" --option "sidebar"
30
+ prismic page-type add-field select product size --option "small" --option "medium" --option "large" --default "medium"
31
+ prismic page-type add-field select article status --option "draft" --option "published" --label "Status"
32
+ `.trim();
33
+
34
+ const CustomTypeSchema = v.object({
35
+ id: v.string(),
36
+ label: v.string(),
37
+ repeatable: v.boolean(),
38
+ status: v.boolean(),
39
+ format: v.string(),
40
+ json: v.record(v.string(), v.record(v.string(), v.unknown())),
41
+ });
42
+
43
+ export async function pageTypeAddFieldSelect(): Promise<void> {
44
+ const {
45
+ values: { help, tab, label, placeholder, option, default: defaultValue },
46
+ positionals: [typeId, fieldId],
47
+ } = parseArgs({
48
+ args: process.argv.slice(5), // skip: node, script, "page-type", "add-field", "select"
49
+ options: {
50
+ tab: { type: "string", short: "t" },
51
+ label: { type: "string", short: "l" },
52
+ placeholder: { type: "string", short: "p" },
53
+ option: { type: "string", multiple: true },
54
+ default: { type: "string" },
55
+ help: { type: "boolean", short: "h" },
56
+ },
57
+ allowPositionals: true,
58
+ });
59
+
60
+ if (help) {
61
+ console.info(HELP);
62
+ return;
63
+ }
64
+
65
+ if (!typeId) {
66
+ console.error("Missing required argument: type-id\n");
67
+ console.error("Usage: prismic page-type add-field select <type-id> <field-id>");
68
+ process.exitCode = 1;
69
+ return;
70
+ }
71
+
72
+ if (!fieldId) {
73
+ console.error("Missing required argument: field-id\n");
74
+ console.error("Usage: prismic page-type add-field select <type-id> <field-id>");
75
+ process.exitCode = 1;
76
+ return;
77
+ }
78
+
79
+ // Find the page type file
80
+ const projectRoot = await findUpward("package.json");
81
+ if (!projectRoot) {
82
+ console.error("Could not find project root (no package.json found)");
83
+ process.exitCode = 1;
84
+ return;
85
+ }
86
+
87
+ const modelPath = new URL(`customtypes/${typeId}/index.json`, projectRoot);
88
+
89
+ // Read and parse the model
90
+ let model: CustomType;
91
+ try {
92
+ const contents = await readFile(modelPath, "utf8");
93
+ const result = v.safeParse(CustomTypeSchema, JSON.parse(contents));
94
+ if (!result.success) {
95
+ console.error(`Invalid page type model: ${modelPath.href}`);
96
+ process.exitCode = 1;
97
+ return;
98
+ }
99
+ model = result.output as CustomType;
100
+ } catch (error) {
101
+ if (error instanceof Error && "code" in error && error.code === "ENOENT") {
102
+ console.error(`Page type not found: ${typeId}\n`);
103
+ console.error(`Create it first with: prismic page-type create ${typeId}`);
104
+ process.exitCode = 1;
105
+ return;
106
+ }
107
+ if (error instanceof Error) {
108
+ console.error(`Failed to read page type: ${error.message}`);
109
+ } else {
110
+ console.error("Failed to read page type");
111
+ }
112
+ process.exitCode = 1;
113
+ return;
114
+ }
115
+
116
+ // Determine target tab
117
+ const existingTabs = Object.keys(model.json);
118
+ const targetTab = tab ?? existingTabs[0] ?? "Main";
119
+
120
+ // Initialize tab if it doesn't exist
121
+ if (!model.json[targetTab]) {
122
+ model.json[targetTab] = {};
123
+ }
124
+
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
+ // Build field definition
135
+ const fieldDefinition: Select = {
136
+ type: "Select",
137
+ config: {
138
+ ...(label && { label }),
139
+ ...(placeholder && { placeholder }),
140
+ ...(option && option.length > 0 && { options: option }),
141
+ ...(defaultValue && { default_value: defaultValue }),
142
+ },
143
+ };
144
+
145
+ // Add field to model
146
+ model.json[targetTab][fieldId] = fieldDefinition;
147
+
148
+ // Write updated model
149
+ try {
150
+ await writeFile(modelPath, stringify(model));
151
+ } catch (error) {
152
+ if (error instanceof Error) {
153
+ console.error(`Failed to update page type: ${error.message}`);
154
+ } else {
155
+ console.error("Failed to update page type");
156
+ }
157
+ process.exitCode = 1;
158
+ return;
159
+ }
160
+
161
+ console.info(
162
+ `Added field "${fieldId}" (Select) to "${targetTab}" tab in ${typeId}`,
163
+ );
164
+ }