prismic 0.0.0-pr.28.59bf330
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 +69 -0
- package/dist/builders-hKD4IrLX-DsO7BUQw.mjs +97 -0
- package/dist/dist-B11B2hHn.mjs +1 -0
- package/dist/dist-DT8CtumB.mjs +1 -0
- package/dist/framework-CfjEoVk0.mjs +17 -0
- package/dist/index.mjs +2537 -0
- package/dist/nextjs-9z7YrSnS.mjs +312 -0
- package/dist/nuxt-KoJ61G2q.mjs +59 -0
- package/dist/sveltekit-DjXKCG78.mjs +226 -0
- package/package.json +58 -0
- package/src/codegen-types.ts +82 -0
- package/src/codegen.ts +45 -0
- package/src/custom-type-add-field-boolean.ts +185 -0
- package/src/custom-type-add-field-color.ts +168 -0
- package/src/custom-type-add-field-date.ts +171 -0
- package/src/custom-type-add-field-embed.ts +168 -0
- package/src/custom-type-add-field-geo-point.ts +165 -0
- package/src/custom-type-add-field-group.ts +142 -0
- package/src/custom-type-add-field-image.ts +168 -0
- package/src/custom-type-add-field-key-text.ts +168 -0
- package/src/custom-type-add-field-link.ts +191 -0
- package/src/custom-type-add-field-number.ts +200 -0
- package/src/custom-type-add-field-rich-text.ts +192 -0
- package/src/custom-type-add-field-select.ts +174 -0
- package/src/custom-type-add-field-timestamp.ts +171 -0
- package/src/custom-type-add-field-uid.ts +151 -0
- package/src/custom-type-add-field.ts +116 -0
- package/src/custom-type-connect-slice.ts +178 -0
- package/src/custom-type-create.ts +98 -0
- package/src/custom-type-disconnect-slice.ts +134 -0
- package/src/custom-type-list.ts +110 -0
- package/src/custom-type-remove-field.ts +135 -0
- package/src/custom-type-remove.ts +103 -0
- package/src/custom-type-set-name.ts +102 -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/env.d.ts +12 -0
- package/src/framework/index.ts +399 -0
- package/src/framework/nextjs.templates.ts +426 -0
- package/src/framework/nextjs.ts +216 -0
- package/src/framework/nuxt.templates.ts +74 -0
- package/src/framework/nuxt.ts +250 -0
- package/src/framework/sveltekit.templates.ts +278 -0
- package/src/framework/sveltekit.ts +241 -0
- package/src/index.ts +155 -0
- package/src/init.ts +173 -0
- package/src/lib/auth.ts +200 -0
- package/src/lib/browser.ts +11 -0
- package/src/lib/config.ts +111 -0
- package/src/lib/custom-types-api.ts +385 -0
- package/src/lib/field-path.ts +81 -0
- package/src/lib/file.ts +49 -0
- package/src/lib/json.ts +3 -0
- package/src/lib/packageJson.ts +35 -0
- package/src/lib/profile.ts +39 -0
- package/src/lib/request.ts +116 -0
- package/src/lib/segment.ts +145 -0
- package/src/lib/sentry.ts +63 -0
- package/src/lib/string.ts +10 -0
- package/src/lib/url.ts +31 -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 +45 -0
- package/src/logout.ts +36 -0
- package/src/page-type-add-field-boolean.ts +179 -0
- package/src/page-type-add-field-color.ts +165 -0
- package/src/page-type-add-field-date.ts +168 -0
- package/src/page-type-add-field-embed.ts +165 -0
- package/src/page-type-add-field-geo-point.ts +162 -0
- package/src/page-type-add-field-group.ts +139 -0
- package/src/page-type-add-field-image.ts +165 -0
- package/src/page-type-add-field-key-text.ts +165 -0
- package/src/page-type-add-field-link.ts +188 -0
- package/src/page-type-add-field-number.ts +197 -0
- package/src/page-type-add-field-rich-text.ts +189 -0
- package/src/page-type-add-field-select.ts +171 -0
- package/src/page-type-add-field-timestamp.ts +168 -0
- package/src/page-type-add-field-uid.ts +148 -0
- package/src/page-type-add-field.ts +116 -0
- package/src/page-type-connect-slice.ts +178 -0
- package/src/page-type-create.ts +128 -0
- package/src/page-type-disconnect-slice.ts +134 -0
- package/src/page-type-list.ts +109 -0
- package/src/page-type-remove-field.ts +135 -0
- package/src/page-type-remove.ts +103 -0
- package/src/page-type-set-name.ts +102 -0
- package/src/page-type-set-repeatable.ts +111 -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 +236 -0
- package/src/push.ts +409 -0
- package/src/repo-create.ts +175 -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 +219 -0
- package/src/slice-add-field-color.ts +205 -0
- package/src/slice-add-field-date.ts +205 -0
- package/src/slice-add-field-embed.ts +205 -0
- package/src/slice-add-field-geo-point.ts +202 -0
- package/src/slice-add-field-group.ts +170 -0
- package/src/slice-add-field-image.ts +202 -0
- package/src/slice-add-field-key-text.ts +205 -0
- package/src/slice-add-field-link.ts +224 -0
- package/src/slice-add-field-number.ts +205 -0
- package/src/slice-add-field-rich-text.ts +229 -0
- package/src/slice-add-field-select.ts +211 -0
- package/src/slice-add-field-timestamp.ts +205 -0
- package/src/slice-add-field.ts +111 -0
- package/src/slice-add-variation.ts +142 -0
- package/src/slice-create.ts +164 -0
- package/src/slice-list-variations.ts +71 -0
- package/src/slice-list.ts +60 -0
- package/src/slice-remove-field.ts +125 -0
- package/src/slice-remove-variation.ts +113 -0
- package/src/slice-remove.ts +92 -0
- package/src/slice-rename.ts +104 -0
- package/src/slice-set-screenshot.ts +239 -0
- package/src/slice-view.ts +83 -0
- package/src/slice.ts +95 -0
- package/src/status.ts +834 -0
- package/src/sync.ts +259 -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
package/src/sync.ts
ADDED
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { setTimeout } from "node:timers/promises";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
|
|
5
|
+
import { isAuthenticated } from "./lib/auth";
|
|
6
|
+
import { safeGetRepositoryFromConfig } from "./lib/config";
|
|
7
|
+
import { fetchRemoteCustomTypes, fetchRemoteSlices } from "./lib/custom-types-api";
|
|
8
|
+
import { type FrameworkAdapter, getFramework } from "./framework";
|
|
9
|
+
import { trackEnd } from "./lib/segment";
|
|
10
|
+
import { dedent } from "./lib/string";
|
|
11
|
+
|
|
12
|
+
const HELP = `
|
|
13
|
+
Sync slices, page types, and custom types from Prismic to local files.
|
|
14
|
+
|
|
15
|
+
Remote models are the source of truth. Local files are created, updated,
|
|
16
|
+
or deleted to match.
|
|
17
|
+
|
|
18
|
+
USAGE
|
|
19
|
+
prismic sync [flags]
|
|
20
|
+
|
|
21
|
+
FLAGS
|
|
22
|
+
-r, --repo string Repository domain
|
|
23
|
+
-w, --watch Watch for changes and sync continuously
|
|
24
|
+
-h, --help Show help for command
|
|
25
|
+
`.trim();
|
|
26
|
+
|
|
27
|
+
// 5 seconds balances responsiveness with API load
|
|
28
|
+
const POLL_INTERVAL_MS = 5000;
|
|
29
|
+
const MAX_BACKOFF_MS = 60000; // Cap backoff at 1 minute
|
|
30
|
+
const MAX_CONSECUTIVE_ERRORS = 10;
|
|
31
|
+
|
|
32
|
+
export async function sync(): Promise<void> {
|
|
33
|
+
const {
|
|
34
|
+
values: { help, repo = await safeGetRepositoryFromConfig(), watch },
|
|
35
|
+
} = parseArgs({
|
|
36
|
+
args: process.argv.slice(3), // skip: node, script, "sync"
|
|
37
|
+
options: {
|
|
38
|
+
repo: { type: "string", short: "r" },
|
|
39
|
+
watch: { type: "boolean", short: "w" },
|
|
40
|
+
help: { type: "boolean", short: "h" },
|
|
41
|
+
},
|
|
42
|
+
allowPositionals: false,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (help) {
|
|
46
|
+
console.info(HELP);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (!repo) {
|
|
51
|
+
console.error("Missing prismic.config.json or --repo option");
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!(await isAuthenticated())) {
|
|
57
|
+
console.error("Not logged in. Run `prismic login` first.");
|
|
58
|
+
process.exitCode = 1;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const framework = await getFramework();
|
|
63
|
+
if (!framework) {
|
|
64
|
+
console.error("Could not detect a supported framework (Next.js, Nuxt, or SvelteKit).");
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.info(`Syncing from repository: ${repo}`);
|
|
70
|
+
|
|
71
|
+
if (watch) {
|
|
72
|
+
await watchForChanges(repo, framework);
|
|
73
|
+
} else {
|
|
74
|
+
await syncSlices(repo, framework);
|
|
75
|
+
await syncCustomTypes(repo, framework);
|
|
76
|
+
|
|
77
|
+
console.info("Sync complete");
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function watchForChanges(repo: string, framework: FrameworkAdapter) {
|
|
82
|
+
const remoteSlicesResult = await fetchRemoteSlices(repo);
|
|
83
|
+
if (!remoteSlicesResult.ok) {
|
|
84
|
+
console.error(`Failed to fetch remote slices: ${remoteSlicesResult.error}`);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const initialRemoteSlices = remoteSlicesResult.value;
|
|
89
|
+
|
|
90
|
+
const remoteCustomTypesResult = await fetchRemoteCustomTypes(repo);
|
|
91
|
+
if (!remoteCustomTypesResult.ok) {
|
|
92
|
+
console.error(`Failed to fetch remote custom types: ${remoteCustomTypesResult.error}`);
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const initialRemoteCustomTypes = remoteCustomTypesResult.value;
|
|
97
|
+
|
|
98
|
+
await syncSlices(repo, framework);
|
|
99
|
+
await syncCustomTypes(repo, framework);
|
|
100
|
+
|
|
101
|
+
console.info(dedent`
|
|
102
|
+
Initial sync completed!
|
|
103
|
+
|
|
104
|
+
Watching for changes (polling every ${POLL_INTERVAL_MS / 1000}s),
|
|
105
|
+
Press Ctrl+C to stop
|
|
106
|
+
`);
|
|
107
|
+
|
|
108
|
+
let lastRemoteSlicesHash = hash(initialRemoteSlices);
|
|
109
|
+
let lastRemoteCustomTypesHash = hash(initialRemoteCustomTypes);
|
|
110
|
+
|
|
111
|
+
let consecutiveErrors = 0;
|
|
112
|
+
|
|
113
|
+
// Handle all common termination signals
|
|
114
|
+
process.on("SIGINT", shutdown); // Ctrl+C
|
|
115
|
+
process.on("SIGTERM", shutdown); // kill command
|
|
116
|
+
process.on("SIGHUP", shutdown); // terminal closed
|
|
117
|
+
process.on("SIGQUIT", shutdown); // Ctrl+\
|
|
118
|
+
if (process.platform === "win32") {
|
|
119
|
+
process.on("SIGBREAK", shutdown); // Windows Ctrl+Break
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
while (true) {
|
|
123
|
+
await setTimeout(exponentialMs(consecutiveErrors));
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const remoteSlicesResult = await fetchRemoteSlices(repo);
|
|
127
|
+
if (!remoteSlicesResult.ok) continue;
|
|
128
|
+
const remoteSlicesHash = hash(remoteSlicesResult.value);
|
|
129
|
+
const slicesChanged = remoteSlicesHash !== lastRemoteSlicesHash;
|
|
130
|
+
|
|
131
|
+
const remoteCustomTypesResult = await fetchRemoteCustomTypes(repo);
|
|
132
|
+
if (!remoteCustomTypesResult.ok) continue;
|
|
133
|
+
const remoteCustomTypesHash = hash(remoteCustomTypesResult.value);
|
|
134
|
+
const customTypesChanged = remoteCustomTypesHash !== lastRemoteCustomTypesHash;
|
|
135
|
+
|
|
136
|
+
if (slicesChanged || customTypesChanged) {
|
|
137
|
+
if (slicesChanged) {
|
|
138
|
+
await syncSlices(repo, framework);
|
|
139
|
+
lastRemoteSlicesHash = remoteSlicesHash;
|
|
140
|
+
}
|
|
141
|
+
if (customTypesChanged) {
|
|
142
|
+
await syncCustomTypes(repo, framework);
|
|
143
|
+
lastRemoteCustomTypesHash = remoteCustomTypesHash;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Reset error count on success
|
|
148
|
+
consecutiveErrors = 0;
|
|
149
|
+
} catch (error) {
|
|
150
|
+
consecutiveErrors++;
|
|
151
|
+
|
|
152
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
153
|
+
|
|
154
|
+
const nextDelay = Math.min(
|
|
155
|
+
POLL_INTERVAL_MS * Math.pow(2, consecutiveErrors - 1),
|
|
156
|
+
MAX_BACKOFF_MS,
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
console.error(`Error checking for changes: ${message}. Retrying in ${nextDelay / 1000}s...`);
|
|
160
|
+
|
|
161
|
+
if (consecutiveErrors >= MAX_CONSECUTIVE_ERRORS) {
|
|
162
|
+
throw new Error(`Too many consecutive errors (${MAX_CONSECUTIVE_ERRORS}), stopping watch.`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function syncSlices(repo: string, framework: FrameworkAdapter): Promise<void> {
|
|
169
|
+
const remoteSlicesResult = await fetchRemoteSlices(repo);
|
|
170
|
+
if (!remoteSlicesResult.ok) {
|
|
171
|
+
console.error(`Failed to fetch remote slices: ${remoteSlicesResult.error}`);
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
const remoteSlices = remoteSlicesResult.value;
|
|
176
|
+
const localSlices = await framework.getSlices();
|
|
177
|
+
|
|
178
|
+
// Handle slices update
|
|
179
|
+
for (const remoteSlice of remoteSlices) {
|
|
180
|
+
const localSlice = localSlices.find((slice) => slice.model.id === remoteSlice.id);
|
|
181
|
+
if (localSlice) {
|
|
182
|
+
await framework.updateSlice(remoteSlice);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Handle slices deletion
|
|
187
|
+
for (const localSlice of localSlices) {
|
|
188
|
+
const existsRemotely = remoteSlices.some((slice) => slice.id === localSlice.model.id);
|
|
189
|
+
if (!existsRemotely) {
|
|
190
|
+
await framework.deleteSlice(localSlice.model.id);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Handle slices creation
|
|
195
|
+
const defaultLibrary = await framework.getDefaultSliceLibrary();
|
|
196
|
+
for (const remoteSlice of remoteSlices) {
|
|
197
|
+
const existsLocally = localSlices.some((slice) => slice.model.id === remoteSlice.id);
|
|
198
|
+
if (!existsLocally) {
|
|
199
|
+
await framework.createSlice(remoteSlice, defaultLibrary);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export async function syncCustomTypes(repo: string, framework: FrameworkAdapter): Promise<void> {
|
|
205
|
+
const remoteCustomTypesResult = await fetchRemoteCustomTypes(repo);
|
|
206
|
+
if (!remoteCustomTypesResult.ok) {
|
|
207
|
+
console.error(`Failed to fetch remote custom types: ${remoteCustomTypesResult.error}`);
|
|
208
|
+
process.exitCode = 1;
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
const remoteCustomTypes = remoteCustomTypesResult.value;
|
|
212
|
+
const localCustomTypes = await framework.getCustomTypes();
|
|
213
|
+
|
|
214
|
+
// Handle custom types update
|
|
215
|
+
for (const remoteCustomType of remoteCustomTypes) {
|
|
216
|
+
const localCustomType = localCustomTypes.find(
|
|
217
|
+
(customType) => customType.model.id === remoteCustomType.id,
|
|
218
|
+
);
|
|
219
|
+
if (localCustomType) {
|
|
220
|
+
await framework.updateCustomType(remoteCustomType);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Handle custom types deletion
|
|
225
|
+
for (const localCustomType of localCustomTypes) {
|
|
226
|
+
const existsRemotely = remoteCustomTypes.some(
|
|
227
|
+
(customType) => customType.id === localCustomType.model.id,
|
|
228
|
+
);
|
|
229
|
+
if (!existsRemotely) {
|
|
230
|
+
await framework.deleteCustomType(localCustomType.model.id);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Handle custom types creation
|
|
235
|
+
for (const remoteCustomType of remoteCustomTypes) {
|
|
236
|
+
const existsLocally = localCustomTypes.some(
|
|
237
|
+
(customType) => customType.model.id === remoteCustomType.id,
|
|
238
|
+
);
|
|
239
|
+
if (!existsLocally) {
|
|
240
|
+
await framework.createCustomType(remoteCustomType);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function shutdown(): void {
|
|
246
|
+
console.info("Watch stopped. Goodbye!");
|
|
247
|
+
trackEnd("sync", true);
|
|
248
|
+
process.exit(0);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Exponential backoff: 5s, 10s, 20s, 40s, 60s (capped)
|
|
252
|
+
function exponentialMs(base: number): number {
|
|
253
|
+
if (base === 0) return POLL_INTERVAL_MS;
|
|
254
|
+
return Math.min(POLL_INTERVAL_MS * Math.pow(2, base - 1), MAX_BACKOFF_MS);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function hash(data: unknown): string {
|
|
258
|
+
return createHash("sha256").update(JSON.stringify(data)).digest("hex");
|
|
259
|
+
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
import { isAuthenticated } from "./lib/auth";
|
|
5
|
+
import { safeGetRepositoryFromConfig } from "./lib/config";
|
|
6
|
+
import { stringify } from "./lib/json";
|
|
7
|
+
import { ForbiddenRequestError, request, UnauthorizedRequestError } from "./lib/request";
|
|
8
|
+
import { getRepoUrl } from "./lib/url";
|
|
9
|
+
import {
|
|
10
|
+
type AccessToken,
|
|
11
|
+
AccessTokenSchema,
|
|
12
|
+
getAccessTokens,
|
|
13
|
+
type OAuthApp,
|
|
14
|
+
OAuthAppSchema,
|
|
15
|
+
type WriteToken,
|
|
16
|
+
WriteTokenSchema,
|
|
17
|
+
} from "./token-list";
|
|
18
|
+
|
|
19
|
+
const HELP = `
|
|
20
|
+
Create a new API token for a Prismic repository.
|
|
21
|
+
|
|
22
|
+
By default, this command reads the repository from prismic.config.json at the
|
|
23
|
+
project root.
|
|
24
|
+
|
|
25
|
+
USAGE
|
|
26
|
+
prismic token create [flags]
|
|
27
|
+
|
|
28
|
+
FLAGS
|
|
29
|
+
-w, --write Create a write token (Custom Types/Migration API)
|
|
30
|
+
-n, --name string Token name (default: "Prismic CLI")
|
|
31
|
+
--allow-releases Allow access to releases (access tokens only)
|
|
32
|
+
--json Output as JSON
|
|
33
|
+
-r, --repo string Repository domain
|
|
34
|
+
-h, --help Show help for command
|
|
35
|
+
|
|
36
|
+
LEARN MORE
|
|
37
|
+
Use \`prismic token <command> --help\` for more information about a command.
|
|
38
|
+
`.trim();
|
|
39
|
+
|
|
40
|
+
const DEFAULT_APP_NAME = "Prismic CLI";
|
|
41
|
+
|
|
42
|
+
export async function tokenCreate(): Promise<void> {
|
|
43
|
+
const {
|
|
44
|
+
values: {
|
|
45
|
+
help,
|
|
46
|
+
repo = await safeGetRepositoryFromConfig(),
|
|
47
|
+
json,
|
|
48
|
+
write,
|
|
49
|
+
name = DEFAULT_APP_NAME,
|
|
50
|
+
"allow-releases": allowReleases,
|
|
51
|
+
},
|
|
52
|
+
} = parseArgs({
|
|
53
|
+
args: process.argv.slice(4), // skip: node, script, "token", "create"
|
|
54
|
+
options: {
|
|
55
|
+
json: { type: "boolean" },
|
|
56
|
+
repo: { type: "string", short: "r" },
|
|
57
|
+
help: { type: "boolean", short: "h" },
|
|
58
|
+
write: { type: "boolean", short: "w" },
|
|
59
|
+
name: { type: "string", short: "n" },
|
|
60
|
+
"allow-releases": { type: "boolean" },
|
|
61
|
+
},
|
|
62
|
+
allowPositionals: false,
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (help) {
|
|
66
|
+
console.info(HELP);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (!repo) {
|
|
71
|
+
console.error("Missing prismic.config.json or --repo option");
|
|
72
|
+
process.exitCode = 1;
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (write && allowReleases) {
|
|
77
|
+
console.error("--allow-releases is only valid for access tokens (not with --write)");
|
|
78
|
+
process.exitCode = 1;
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const authenticated = await isAuthenticated();
|
|
83
|
+
if (!authenticated) {
|
|
84
|
+
handleUnauthenticated();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (write) {
|
|
89
|
+
const result = await createWriteToken(repo, name);
|
|
90
|
+
if (!result.ok) {
|
|
91
|
+
if (
|
|
92
|
+
result.error instanceof ForbiddenRequestError ||
|
|
93
|
+
result.error instanceof UnauthorizedRequestError
|
|
94
|
+
) {
|
|
95
|
+
handleUnauthenticated();
|
|
96
|
+
} else if (v.isValiError(result.error)) {
|
|
97
|
+
console.error(
|
|
98
|
+
`Failed to create write token: Invalid response: ${stringify(result.error.issues)}`,
|
|
99
|
+
);
|
|
100
|
+
process.exitCode = 1;
|
|
101
|
+
} else {
|
|
102
|
+
console.error(`Failed to create write token: ${stringify(result.value)}`);
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (json) {
|
|
109
|
+
console.info(stringify(result.value));
|
|
110
|
+
} else {
|
|
111
|
+
console.info(`Token created: ${result.value.token}`);
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
const scope = allowReleases ? "master+releases" : "master";
|
|
115
|
+
const result = await createAccessToken(repo, name, scope);
|
|
116
|
+
if (!result.ok) {
|
|
117
|
+
if (
|
|
118
|
+
result.error instanceof ForbiddenRequestError ||
|
|
119
|
+
result.error instanceof UnauthorizedRequestError
|
|
120
|
+
) {
|
|
121
|
+
handleUnauthenticated();
|
|
122
|
+
} else if (v.isValiError(result.error)) {
|
|
123
|
+
console.error(
|
|
124
|
+
`Failed to create access token: Invalid response: ${stringify(result.error.issues)}`,
|
|
125
|
+
);
|
|
126
|
+
process.exitCode = 1;
|
|
127
|
+
} else {
|
|
128
|
+
console.error(`Failed to create access token: ${stringify(result.value)}`);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
}
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (json) {
|
|
135
|
+
console.info(stringify(result.value));
|
|
136
|
+
} else {
|
|
137
|
+
console.info(`Token created: ${result.value.token}`);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
type CreateWriteTokenResult =
|
|
143
|
+
| { ok: true; value: WriteToken }
|
|
144
|
+
| { ok: false; value: unknown; error: Error | v.ValiError<typeof WriteTokenSchema> };
|
|
145
|
+
|
|
146
|
+
async function createWriteToken(repo: string, appName: string): Promise<CreateWriteTokenResult> {
|
|
147
|
+
const url = new URL("settings/security/token", await getRepoUrl(repo));
|
|
148
|
+
const response = await request(url, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
body: { app_name: appName },
|
|
151
|
+
schema: WriteTokenSchema,
|
|
152
|
+
});
|
|
153
|
+
return response;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
type CreateAccessTokenResult =
|
|
157
|
+
| { ok: true; value: AccessToken }
|
|
158
|
+
| { ok: false; value: unknown; error: Error | v.ValiError<typeof AccessTokenSchema> };
|
|
159
|
+
|
|
160
|
+
async function createAccessToken(
|
|
161
|
+
repo: string,
|
|
162
|
+
appName: string,
|
|
163
|
+
scope: "master" | "master+releases",
|
|
164
|
+
): Promise<CreateAccessTokenResult> {
|
|
165
|
+
// First, find or create an OAuth app with the given name
|
|
166
|
+
const appsResponse = await getAccessTokens(repo);
|
|
167
|
+
if (!appsResponse.ok) {
|
|
168
|
+
return appsResponse;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
let app = appsResponse.value.find((a: OAuthApp) => a.name === appName);
|
|
172
|
+
|
|
173
|
+
// Create OAuth app if it doesn't exist
|
|
174
|
+
if (!app) {
|
|
175
|
+
const createAppUrl = new URL("settings/security/oauthapp", await getRepoUrl(repo));
|
|
176
|
+
const createAppResponse = await request(createAppUrl, {
|
|
177
|
+
method: "POST",
|
|
178
|
+
body: { app_name: appName },
|
|
179
|
+
schema: OAuthAppSchema,
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
if (!createAppResponse.ok) {
|
|
183
|
+
return createAppResponse;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
app = createAppResponse.value;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Create the authorization token
|
|
190
|
+
const authUrl = new URL("settings/security/authorizations", await getRepoUrl(repo));
|
|
191
|
+
const authResponse = await request(authUrl, {
|
|
192
|
+
method: "POST",
|
|
193
|
+
body: { app: app.id, scope },
|
|
194
|
+
schema: AccessTokenSchema,
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
return authResponse;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleUnauthenticated(): void {
|
|
201
|
+
console.error("Not logged in. Run `prismic login` first.");
|
|
202
|
+
process.exitCode = 1;
|
|
203
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import * as v from "valibot";
|
|
3
|
+
|
|
4
|
+
import { isAuthenticated } from "./lib/auth";
|
|
5
|
+
import { safeGetRepositoryFromConfig } from "./lib/config";
|
|
6
|
+
import { stringify } from "./lib/json";
|
|
7
|
+
import { ForbiddenRequestError, request, UnauthorizedRequestError } from "./lib/request";
|
|
8
|
+
import { getRepoUrl } from "./lib/url";
|
|
9
|
+
import { type AccessToken, getAccessTokens, getWriteTokens, type WriteToken } from "./token-list";
|
|
10
|
+
|
|
11
|
+
const HELP = `
|
|
12
|
+
Delete a token from a Prismic repository.
|
|
13
|
+
|
|
14
|
+
By default, this command reads the repository from prismic.config.json at the
|
|
15
|
+
project root.
|
|
16
|
+
|
|
17
|
+
USAGE
|
|
18
|
+
prismic token delete <token> [flags]
|
|
19
|
+
|
|
20
|
+
ARGUMENTS
|
|
21
|
+
token The token value (or partial match)
|
|
22
|
+
|
|
23
|
+
FLAGS
|
|
24
|
+
-r, --repo string Repository domain
|
|
25
|
+
-h, --help Show help for command
|
|
26
|
+
|
|
27
|
+
LEARN MORE
|
|
28
|
+
Use \`prismic token <command> --help\` for more information about a command.
|
|
29
|
+
`.trim();
|
|
30
|
+
|
|
31
|
+
export async function tokenDelete(): Promise<void> {
|
|
32
|
+
const {
|
|
33
|
+
values: { help, repo = await safeGetRepositoryFromConfig() },
|
|
34
|
+
positionals: [tokenValue],
|
|
35
|
+
} = parseArgs({
|
|
36
|
+
args: process.argv.slice(4), // skip: node, script, "token", "delete"
|
|
37
|
+
options: {
|
|
38
|
+
repo: { type: "string", short: "r" },
|
|
39
|
+
help: { type: "boolean", short: "h" },
|
|
40
|
+
},
|
|
41
|
+
allowPositionals: true,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
if (help) {
|
|
45
|
+
console.info(HELP);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!tokenValue) {
|
|
50
|
+
console.error("Missing required argument: token");
|
|
51
|
+
process.exitCode = 1;
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!repo) {
|
|
56
|
+
console.error("Missing prismic.config.json or --repo option");
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const authenticated = await isAuthenticated();
|
|
62
|
+
if (!authenticated) {
|
|
63
|
+
handleUnauthenticated();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// First, find the token in access tokens or write tokens
|
|
68
|
+
const [accessResponse, writeResponse] = await Promise.all([
|
|
69
|
+
getAccessTokens(repo),
|
|
70
|
+
getWriteTokens(repo),
|
|
71
|
+
]);
|
|
72
|
+
|
|
73
|
+
if (!accessResponse.ok) {
|
|
74
|
+
if (
|
|
75
|
+
accessResponse.error instanceof ForbiddenRequestError ||
|
|
76
|
+
accessResponse.error instanceof UnauthorizedRequestError
|
|
77
|
+
) {
|
|
78
|
+
handleUnauthenticated();
|
|
79
|
+
} else if (v.isValiError(accessResponse.error)) {
|
|
80
|
+
console.error(
|
|
81
|
+
`Failed to list access tokens: Invalid response: ${stringify(accessResponse.error.issues)}`,
|
|
82
|
+
);
|
|
83
|
+
process.exitCode = 1;
|
|
84
|
+
} else {
|
|
85
|
+
console.error(`Failed to list access tokens: ${stringify(accessResponse.value)}`);
|
|
86
|
+
process.exitCode = 1;
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (!writeResponse.ok) {
|
|
92
|
+
if (
|
|
93
|
+
writeResponse.error instanceof ForbiddenRequestError ||
|
|
94
|
+
writeResponse.error instanceof UnauthorizedRequestError
|
|
95
|
+
) {
|
|
96
|
+
handleUnauthenticated();
|
|
97
|
+
} else if (v.isValiError(writeResponse.error)) {
|
|
98
|
+
console.error(
|
|
99
|
+
`Failed to list write tokens: Invalid response: ${stringify(writeResponse.error.issues)}`,
|
|
100
|
+
);
|
|
101
|
+
process.exitCode = 1;
|
|
102
|
+
} else {
|
|
103
|
+
console.error(`Failed to list write tokens: ${stringify(writeResponse.value)}`);
|
|
104
|
+
process.exitCode = 1;
|
|
105
|
+
}
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Find in access tokens
|
|
110
|
+
let foundAuth: AccessToken | undefined;
|
|
111
|
+
for (const app of accessResponse.value) {
|
|
112
|
+
for (const auth of app.wroom_auths) {
|
|
113
|
+
if (
|
|
114
|
+
auth.token === tokenValue ||
|
|
115
|
+
auth.token.startsWith(tokenValue) ||
|
|
116
|
+
auth.token.endsWith(tokenValue)
|
|
117
|
+
) {
|
|
118
|
+
foundAuth = auth;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (foundAuth) break;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (foundAuth) {
|
|
126
|
+
// Delete the authorization (preserves OAuth app)
|
|
127
|
+
const url = new URL(`settings/security/authorizations/${foundAuth.id}`, await getRepoUrl(repo));
|
|
128
|
+
const response = await request(url, { method: "DELETE" });
|
|
129
|
+
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
if (
|
|
132
|
+
response.error instanceof ForbiddenRequestError ||
|
|
133
|
+
response.error instanceof UnauthorizedRequestError
|
|
134
|
+
) {
|
|
135
|
+
handleUnauthenticated();
|
|
136
|
+
} else {
|
|
137
|
+
console.error(`Failed to delete token: ${stringify(response.value)}`);
|
|
138
|
+
process.exitCode = 1;
|
|
139
|
+
}
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.info("Token deleted");
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Find in write tokens
|
|
148
|
+
const foundWriteToken = writeResponse.value.tokens.find(
|
|
149
|
+
(t: WriteToken) =>
|
|
150
|
+
t.token === tokenValue || t.token.startsWith(tokenValue) || t.token.endsWith(tokenValue),
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
if (foundWriteToken) {
|
|
154
|
+
// Delete write token
|
|
155
|
+
const url = new URL(`settings/security/token/${foundWriteToken.token}`, await getRepoUrl(repo));
|
|
156
|
+
const response = await request(url, { method: "DELETE" });
|
|
157
|
+
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
if (
|
|
160
|
+
response.error instanceof ForbiddenRequestError ||
|
|
161
|
+
response.error instanceof UnauthorizedRequestError
|
|
162
|
+
) {
|
|
163
|
+
handleUnauthenticated();
|
|
164
|
+
} else {
|
|
165
|
+
console.error(`Failed to delete token: ${stringify(response.value)}`);
|
|
166
|
+
process.exitCode = 1;
|
|
167
|
+
}
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
console.info("Token deleted");
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
console.error(`Token not found: ${tokenValue}`);
|
|
176
|
+
process.exitCode = 1;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function handleUnauthenticated(): void {
|
|
180
|
+
console.error("Not logged in. Run `prismic login` first.");
|
|
181
|
+
process.exitCode = 1;
|
|
182
|
+
}
|