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/status.ts
ADDED
|
@@ -0,0 +1,834 @@
|
|
|
1
|
+
import type { CustomType, SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
2
|
+
|
|
3
|
+
import { readFile } from "node:fs/promises";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
import * as v from "valibot";
|
|
6
|
+
|
|
7
|
+
import { isAuthenticated } from "./lib/auth";
|
|
8
|
+
import { safeGetRepositoryFromConfig } from "./lib/config";
|
|
9
|
+
import { fetchRemoteCustomTypes, fetchRemoteSlices } from "./lib/custom-types-api";
|
|
10
|
+
import { exists } from "./lib/file";
|
|
11
|
+
import {
|
|
12
|
+
type FrameworkAdapter,
|
|
13
|
+
getClientSetupAnchor,
|
|
14
|
+
getDocsPath,
|
|
15
|
+
getPreviewSetupAnchor,
|
|
16
|
+
getWriteComponentsAnchor,
|
|
17
|
+
requireFramework,
|
|
18
|
+
} from "./framework";
|
|
19
|
+
import { request } from "./lib/request";
|
|
20
|
+
import { getRepoUrl } from "./lib/url";
|
|
21
|
+
import { getWebhooks } from "./webhook-view";
|
|
22
|
+
|
|
23
|
+
const HELP = `
|
|
24
|
+
Show the status of the current Prismic project.
|
|
25
|
+
|
|
26
|
+
Each section with incomplete items includes "Next steps:" with actionable
|
|
27
|
+
instructions.
|
|
28
|
+
|
|
29
|
+
By default, this command reads the repository from prismic.config.json at the
|
|
30
|
+
project root.
|
|
31
|
+
|
|
32
|
+
USAGE
|
|
33
|
+
prismic status [flags]
|
|
34
|
+
|
|
35
|
+
FLAGS
|
|
36
|
+
-r, --repo string Repository domain
|
|
37
|
+
-h, --help Show help for command
|
|
38
|
+
|
|
39
|
+
LEARN MORE
|
|
40
|
+
Use \`prismic <command> --help\` for more information about a command.
|
|
41
|
+
`.trim();
|
|
42
|
+
|
|
43
|
+
// Symbols for checkboxes
|
|
44
|
+
const CHECK = "\u2713";
|
|
45
|
+
const CIRCLE = "\u25CB";
|
|
46
|
+
|
|
47
|
+
type StatusItem = {
|
|
48
|
+
done: boolean;
|
|
49
|
+
label: string;
|
|
50
|
+
hint?: string;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
type NextStep = {
|
|
54
|
+
action: string;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
type StatusSection = {
|
|
58
|
+
title: string;
|
|
59
|
+
items: StatusItem[];
|
|
60
|
+
nextSteps?: NextStep[];
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function getDocsRef(docsPath: string, anchor?: string): string {
|
|
64
|
+
if (!docsPath) return "";
|
|
65
|
+
const fullPath = anchor ? `${docsPath}${anchor}` : docsPath;
|
|
66
|
+
return `\`prismic docs fetch ${fullPath}\``;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Next-step builder functions
|
|
70
|
+
|
|
71
|
+
function buildSetupNextSteps(items: StatusItem[], framework: FrameworkAdapter): NextStep[] {
|
|
72
|
+
const nextSteps: NextStep[] = [];
|
|
73
|
+
const docsPath = getDocsPath(framework.id);
|
|
74
|
+
|
|
75
|
+
// Missing dependencies
|
|
76
|
+
const missingDeps = items.filter((i) => !i.done && i.hint === "not installed");
|
|
77
|
+
if (missingDeps.length > 0) {
|
|
78
|
+
const depsList = missingDeps.map((d) => d.label).join(" ");
|
|
79
|
+
nextSteps.push({
|
|
80
|
+
action: `Install dependencies: Run \`npm install ${depsList}\``,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Missing client file
|
|
85
|
+
const missingClientFile = items.find((i) => !i.done && i.hint?.includes("client"));
|
|
86
|
+
if (missingClientFile) {
|
|
87
|
+
const docsRef = getDocsRef(docsPath, getClientSetupAnchor(framework.id));
|
|
88
|
+
nextSteps.push({
|
|
89
|
+
action: `Create Prismic client file: Run ${docsRef} and create the file as shown`,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return nextSteps;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function buildTypesNextSteps(statuses: TypeWithStatus[]): NextStep[] {
|
|
97
|
+
const nextSteps: NextStep[] = [];
|
|
98
|
+
|
|
99
|
+
const hasToPush = statuses.some((t) => t.status === "to_push");
|
|
100
|
+
const hasToPull = statuses.some((t) => t.status === "to_pull");
|
|
101
|
+
|
|
102
|
+
if (hasToPush) {
|
|
103
|
+
nextSteps.push({
|
|
104
|
+
action: "Push local models to Prismic: Run `prismic push`",
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (hasToPull) {
|
|
109
|
+
nextSteps.push({
|
|
110
|
+
action: "Pull remote models from Prismic: Run `prismic pull`",
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return nextSteps;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function buildSlicesNextSteps(
|
|
118
|
+
statuses: TypeWithStatus[],
|
|
119
|
+
missingComponents: string[],
|
|
120
|
+
slicesReadyToConnect: string[],
|
|
121
|
+
framework: FrameworkAdapter,
|
|
122
|
+
): NextStep[] {
|
|
123
|
+
const nextSteps: NextStep[] = [];
|
|
124
|
+
const docsPath = getDocsPath(framework.id);
|
|
125
|
+
|
|
126
|
+
if (missingComponents.length > 0) {
|
|
127
|
+
const docsRef = getDocsRef(docsPath, getWriteComponentsAnchor(framework.id));
|
|
128
|
+
nextSteps.push({
|
|
129
|
+
action: `Implement slice components: Run ${docsRef} and create each component file`,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const hasToPull = statuses.some((t) => t.status === "to_pull");
|
|
134
|
+
const hasToPush = statuses.some((t) => t.status === "to_push");
|
|
135
|
+
|
|
136
|
+
if (hasToPull) {
|
|
137
|
+
nextSteps.push({
|
|
138
|
+
action: "Pull remote models from Prismic: Run `prismic pull`",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Slices should be connected to page types before pushing
|
|
143
|
+
if (slicesReadyToConnect.length > 0) {
|
|
144
|
+
const sorted = [...slicesReadyToConnect].sort();
|
|
145
|
+
const sliceName = sorted[0];
|
|
146
|
+
nextSteps.push({
|
|
147
|
+
action: `Connect slice to page type: Run \`prismic page-type connect-slice <type-id> ${sliceName}\``,
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (hasToPush) {
|
|
152
|
+
nextSteps.push({
|
|
153
|
+
action: "Push local models to Prismic: Run `prismic push`",
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return nextSteps;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function buildPreviewNextSteps(items: StatusItem[], framework: FrameworkAdapter): NextStep[] {
|
|
161
|
+
const nextSteps: NextStep[] = [];
|
|
162
|
+
const docsPath = getDocsPath(framework.id);
|
|
163
|
+
|
|
164
|
+
// Check for missing /slice-simulator route
|
|
165
|
+
const sliceSimRoute = items.find((i) => i.label === "/slice-simulator route" && !i.done);
|
|
166
|
+
if (sliceSimRoute) {
|
|
167
|
+
const docsRef = getDocsRef(docsPath, "#set-up-live-previewing");
|
|
168
|
+
nextSteps.push({
|
|
169
|
+
action: `Create /slice-simulator route: Run ${docsRef} and create the route file as shown`,
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check for missing simulator URL config
|
|
174
|
+
const simulatorUrl = items.find((i) => i.label === "Slice simulator URL" && !i.done);
|
|
175
|
+
if (simulatorUrl) {
|
|
176
|
+
nextSteps.push({
|
|
177
|
+
action: "Configure slice simulator URL: Run `prismic preview set-simulator`",
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Check for missing preview endpoints (combine /api/preview and /api/exit-preview)
|
|
182
|
+
const apiPreview = items.find((i) => i.label === "/api/preview endpoint" && !i.done);
|
|
183
|
+
const exitPreview = items.find((i) => i.label === "/api/exit-preview endpoint" && !i.done);
|
|
184
|
+
if (apiPreview || exitPreview) {
|
|
185
|
+
const docsRef = getDocsRef(docsPath, getPreviewSetupAnchor(framework.id));
|
|
186
|
+
nextSteps.push({
|
|
187
|
+
action: `Create preview endpoints: Run ${docsRef} and create the endpoint files as shown`,
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Check for missing preview environment
|
|
192
|
+
const previewEnv = items.find((i) => i.label === "Preview environment" && !i.done);
|
|
193
|
+
if (previewEnv) {
|
|
194
|
+
nextSteps.push({
|
|
195
|
+
action: "Add preview environment: Run `prismic preview add`",
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return nextSteps;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function buildDeploymentNextSteps(items: StatusItem[], framework: FrameworkAdapter): NextStep[] {
|
|
203
|
+
const nextSteps: NextStep[] = [];
|
|
204
|
+
const docsPath = getDocsPath(framework.id);
|
|
205
|
+
|
|
206
|
+
// Check for missing /api/revalidate endpoint
|
|
207
|
+
const revalidateEndpoint = items.find((i) => i.label === "/api/revalidate endpoint" && !i.done);
|
|
208
|
+
if (revalidateEndpoint) {
|
|
209
|
+
const docsRef = getDocsRef(docsPath, "#handle-content-changes");
|
|
210
|
+
nextSteps.push({
|
|
211
|
+
action: `Create /api/revalidate endpoint: Run ${docsRef} and create the endpoint as shown`,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Check for missing revalidation webhook
|
|
216
|
+
const webhook = items.find((i) => i.label === "Revalidation webhook" && !i.done);
|
|
217
|
+
if (webhook) {
|
|
218
|
+
nextSteps.push({
|
|
219
|
+
action: "Create revalidation webhook: Run `prismic webhook create`",
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return nextSteps;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export async function status(): Promise<void> {
|
|
227
|
+
const {
|
|
228
|
+
values: { help, repo = await safeGetRepositoryFromConfig() },
|
|
229
|
+
} = parseArgs({
|
|
230
|
+
args: process.argv.slice(3), // skip: node, script, "status"
|
|
231
|
+
options: {
|
|
232
|
+
repo: { type: "string", short: "r" },
|
|
233
|
+
help: { type: "boolean", short: "h" },
|
|
234
|
+
},
|
|
235
|
+
allowPositionals: false,
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
if (help) {
|
|
239
|
+
console.info(HELP);
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (!repo) {
|
|
244
|
+
console.error("Missing prismic.config.json or --repo option");
|
|
245
|
+
process.exitCode = 1;
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
const authenticated = await isAuthenticated();
|
|
250
|
+
if (!authenticated) {
|
|
251
|
+
console.error("Not logged in. Run `prismic login` first.");
|
|
252
|
+
process.exitCode = 1;
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Get framework adapter for reading local models
|
|
257
|
+
const framework = await requireFramework();
|
|
258
|
+
if (!framework) return;
|
|
259
|
+
|
|
260
|
+
const projectRoot = await framework.getProjectRoot();
|
|
261
|
+
|
|
262
|
+
// Gather all status data in parallel
|
|
263
|
+
const [
|
|
264
|
+
repoInfoResult,
|
|
265
|
+
previewsResult,
|
|
266
|
+
webhooksResult,
|
|
267
|
+
localCustomTypeResults,
|
|
268
|
+
remoteTypesResult,
|
|
269
|
+
localSliceResults,
|
|
270
|
+
remoteSlicesResult,
|
|
271
|
+
installedDeps,
|
|
272
|
+
] = await Promise.all([
|
|
273
|
+
fetchRepositoryInfo(repo),
|
|
274
|
+
fetchPreviews(repo),
|
|
275
|
+
getWebhooks(repo),
|
|
276
|
+
framework.getCustomTypes(),
|
|
277
|
+
fetchRemoteCustomTypes(repo),
|
|
278
|
+
framework.getSlices(),
|
|
279
|
+
fetchRemoteSlices(repo),
|
|
280
|
+
getInstalledDependencies(projectRoot),
|
|
281
|
+
]);
|
|
282
|
+
|
|
283
|
+
const localTypesResult = {
|
|
284
|
+
ok: true as const,
|
|
285
|
+
value: localCustomTypeResults.map((ct) => ct.model as unknown as CustomType),
|
|
286
|
+
};
|
|
287
|
+
const localSlicesResult = {
|
|
288
|
+
ok: true as const,
|
|
289
|
+
value: localSliceResults.map((s) => s.model as unknown as SharedSlice),
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
// Print repository header
|
|
293
|
+
const repoUrl = await getRepoUrl(repo);
|
|
294
|
+
console.info(`Repository: ${repo}`);
|
|
295
|
+
console.info(`URL: ${repoUrl.href}`);
|
|
296
|
+
console.info("");
|
|
297
|
+
|
|
298
|
+
const sections: StatusSection[] = [];
|
|
299
|
+
|
|
300
|
+
// Setup section
|
|
301
|
+
const setupSection = await buildSetupSection(framework, installedDeps);
|
|
302
|
+
setupSection.nextSteps = buildSetupNextSteps(setupSection.items, framework);
|
|
303
|
+
sections.push(setupSection);
|
|
304
|
+
|
|
305
|
+
// Types sections (Page Types and Custom Types)
|
|
306
|
+
if (localTypesResult.ok && remoteTypesResult.ok) {
|
|
307
|
+
const { pageTypes, customTypes, pageTypeStatuses, customTypeStatuses } = buildTypeSections(
|
|
308
|
+
localTypesResult.value,
|
|
309
|
+
remoteTypesResult.value,
|
|
310
|
+
);
|
|
311
|
+
pageTypes.nextSteps = buildTypesNextSteps(pageTypeStatuses);
|
|
312
|
+
customTypes.nextSteps = buildTypesNextSteps(customTypeStatuses);
|
|
313
|
+
sections.push(pageTypes);
|
|
314
|
+
sections.push(customTypes);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Slices section
|
|
318
|
+
if (localSlicesResult.ok && remoteSlicesResult.ok) {
|
|
319
|
+
const {
|
|
320
|
+
section: slicesSection,
|
|
321
|
+
statuses,
|
|
322
|
+
missingComponents,
|
|
323
|
+
slicesReadyToConnect,
|
|
324
|
+
} = await buildSlicesSection(
|
|
325
|
+
localSlicesResult.value,
|
|
326
|
+
remoteSlicesResult.value,
|
|
327
|
+
framework,
|
|
328
|
+
localTypesResult.ok ? localTypesResult.value : [],
|
|
329
|
+
);
|
|
330
|
+
slicesSection.nextSteps = buildSlicesNextSteps(
|
|
331
|
+
statuses,
|
|
332
|
+
missingComponents,
|
|
333
|
+
slicesReadyToConnect,
|
|
334
|
+
framework,
|
|
335
|
+
);
|
|
336
|
+
sections.push(slicesSection);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Preview section
|
|
340
|
+
const previewSection = await buildPreviewSection(
|
|
341
|
+
framework,
|
|
342
|
+
previewsResult.ok ? previewsResult.value : undefined,
|
|
343
|
+
repoInfoResult.ok ? repoInfoResult.value.simulator_url : undefined,
|
|
344
|
+
);
|
|
345
|
+
previewSection.nextSteps = buildPreviewNextSteps(previewSection.items, framework);
|
|
346
|
+
sections.push(previewSection);
|
|
347
|
+
|
|
348
|
+
// Deployment section (Next.js only)
|
|
349
|
+
if (framework.id === "next") {
|
|
350
|
+
const deploymentSection = await buildDeploymentSection(
|
|
351
|
+
framework,
|
|
352
|
+
webhooksResult.ok ? webhooksResult.value : [],
|
|
353
|
+
);
|
|
354
|
+
deploymentSection.nextSteps = buildDeploymentNextSteps(deploymentSection.items, framework);
|
|
355
|
+
sections.push(deploymentSection);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Print all sections
|
|
359
|
+
for (const section of sections) {
|
|
360
|
+
printSection(section);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function printSection(section: StatusSection): void {
|
|
365
|
+
const remaining = section.items.filter((item) => !item.done).length;
|
|
366
|
+
const header = remaining > 0 ? `${section.title} (${remaining} remaining)` : section.title;
|
|
367
|
+
console.info(header);
|
|
368
|
+
|
|
369
|
+
// Group completed items together
|
|
370
|
+
const completed = section.items.filter((item) => item.done);
|
|
371
|
+
const incomplete = section.items.filter((item) => !item.done);
|
|
372
|
+
|
|
373
|
+
// Print completed items on one line if there are multiple
|
|
374
|
+
if (completed.length > 0) {
|
|
375
|
+
if (completed.length === 1) {
|
|
376
|
+
const item = completed[0];
|
|
377
|
+
const hint = item.hint ? ` \u2014 ${item.hint}` : "";
|
|
378
|
+
console.info(` ${CHECK} ${item.label}${hint}`);
|
|
379
|
+
} else {
|
|
380
|
+
const labels = completed.map((item) => item.label).join(", ");
|
|
381
|
+
const allSameHint = completed.every((item) => item.hint === completed[0].hint);
|
|
382
|
+
const hint = allSameHint && completed[0].hint ? ` \u2014 ${completed[0].hint}` : "";
|
|
383
|
+
console.info(` ${CHECK} ${labels}${hint}`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// Print incomplete items individually
|
|
388
|
+
for (const item of incomplete) {
|
|
389
|
+
const hint = item.hint ? ` \u2014 ${item.hint}` : "";
|
|
390
|
+
console.info(` ${CIRCLE} ${item.label}${hint}`);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Print next steps if there are any
|
|
394
|
+
if (section.nextSteps && section.nextSteps.length > 0) {
|
|
395
|
+
console.info("");
|
|
396
|
+
console.info(" Next steps:");
|
|
397
|
+
for (const step of section.nextSteps) {
|
|
398
|
+
console.info(` - ${step.action}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
console.info("");
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Repository Info (from /core/repository)
|
|
406
|
+
const RepositoryInfoSchema = v.object({
|
|
407
|
+
simulator_url: v.optional(v.string()),
|
|
408
|
+
});
|
|
409
|
+
type RepositoryInfo = v.InferOutput<typeof RepositoryInfoSchema>;
|
|
410
|
+
|
|
411
|
+
async function fetchRepositoryInfo(
|
|
412
|
+
repo: string,
|
|
413
|
+
): Promise<{ ok: true; value: RepositoryInfo } | { ok: false }> {
|
|
414
|
+
const url = new URL("/core/repository", await getRepoUrl(repo));
|
|
415
|
+
const result = await request(url, { schema: RepositoryInfoSchema });
|
|
416
|
+
if (result.ok) {
|
|
417
|
+
return { ok: true, value: result.value };
|
|
418
|
+
}
|
|
419
|
+
return { ok: false };
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Previews
|
|
423
|
+
const PreviewSchema = v.object({
|
|
424
|
+
id: v.string(),
|
|
425
|
+
label: v.string(),
|
|
426
|
+
url: v.string(),
|
|
427
|
+
});
|
|
428
|
+
const PreviewsResponseSchema = v.object({
|
|
429
|
+
results: v.array(PreviewSchema),
|
|
430
|
+
});
|
|
431
|
+
type Preview = v.InferOutput<typeof PreviewSchema>;
|
|
432
|
+
|
|
433
|
+
async function fetchPreviews(
|
|
434
|
+
repo: string,
|
|
435
|
+
): Promise<{ ok: true; value: Preview[] } | { ok: false }> {
|
|
436
|
+
const url = new URL("/core/repository/preview_configs", await getRepoUrl(repo));
|
|
437
|
+
const result = await request(url, { schema: PreviewsResponseSchema });
|
|
438
|
+
if (result.ok) {
|
|
439
|
+
return { ok: true, value: result.value.results };
|
|
440
|
+
}
|
|
441
|
+
return { ok: false };
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Dependencies
|
|
445
|
+
const PackageJsonSchema = v.object({
|
|
446
|
+
dependencies: v.optional(v.record(v.string(), v.string())),
|
|
447
|
+
devDependencies: v.optional(v.record(v.string(), v.string())),
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
async function getInstalledDependencies(projectRoot: URL): Promise<Set<string>> {
|
|
451
|
+
const packageJsonPath = new URL("package.json", projectRoot);
|
|
452
|
+
try {
|
|
453
|
+
const contents = await readFile(packageJsonPath, "utf8");
|
|
454
|
+
const { dependencies = {}, devDependencies = {} } = v.parse(
|
|
455
|
+
PackageJsonSchema,
|
|
456
|
+
JSON.parse(contents),
|
|
457
|
+
);
|
|
458
|
+
return new Set([...Object.keys(dependencies), ...Object.keys(devDependencies)]);
|
|
459
|
+
} catch {
|
|
460
|
+
return new Set();
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Setup Section
|
|
465
|
+
async function buildSetupSection(
|
|
466
|
+
framework: FrameworkAdapter,
|
|
467
|
+
installedDeps: Set<string>,
|
|
468
|
+
): Promise<StatusSection> {
|
|
469
|
+
const items: StatusItem[] = [];
|
|
470
|
+
|
|
471
|
+
// Check required dependencies
|
|
472
|
+
const requiredDeps = Object.keys(await framework.getDependencies());
|
|
473
|
+
for (const dep of requiredDeps) {
|
|
474
|
+
items.push({
|
|
475
|
+
done: installedDeps.has(dep),
|
|
476
|
+
label: dep,
|
|
477
|
+
hint: installedDeps.has(dep) ? "installed" : "not installed",
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
// Check client file
|
|
482
|
+
const projectRoot = await framework.getProjectRoot();
|
|
483
|
+
const clientFilePath = await framework.getClientFilePath();
|
|
484
|
+
if (clientFilePath) {
|
|
485
|
+
const clientFileExists = await exists(new URL(clientFilePath, projectRoot));
|
|
486
|
+
items.push({
|
|
487
|
+
done: clientFileExists,
|
|
488
|
+
label: clientFilePath,
|
|
489
|
+
hint: clientFileExists ? undefined : "create Prismic client file",
|
|
490
|
+
});
|
|
491
|
+
} else if (framework.id === "nuxt") {
|
|
492
|
+
// Check nuxt.config.ts for prismic config
|
|
493
|
+
const nuxtConfigExists = await checkNuxtPrismicConfig(projectRoot);
|
|
494
|
+
items.push({
|
|
495
|
+
done: nuxtConfigExists,
|
|
496
|
+
label: "nuxt.config.ts",
|
|
497
|
+
hint: nuxtConfigExists ? "prismic configured" : "add @nuxtjs/prismic to modules",
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
return { title: "Setup", items };
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async function checkNuxtPrismicConfig(projectRoot: URL): Promise<boolean> {
|
|
505
|
+
const configPath = new URL("nuxt.config.ts", projectRoot);
|
|
506
|
+
try {
|
|
507
|
+
const contents = await readFile(configPath, "utf8");
|
|
508
|
+
return contents.includes("@nuxtjs/prismic") || contents.includes("prismic:");
|
|
509
|
+
} catch {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// Types Sections
|
|
515
|
+
type TypeStatus = "in_sync" | "to_push" | "to_pull";
|
|
516
|
+
|
|
517
|
+
type TypeWithStatus = {
|
|
518
|
+
id: string;
|
|
519
|
+
label: string;
|
|
520
|
+
status: TypeStatus;
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
function computeTypeStatus<T extends { id: string }>(local: T[], remote: T[]): TypeWithStatus[] {
|
|
524
|
+
const localById = new Map(local.map((item) => [item.id, item]));
|
|
525
|
+
const remoteById = new Map(remote.map((item) => [item.id, item]));
|
|
526
|
+
const result: TypeWithStatus[] = [];
|
|
527
|
+
|
|
528
|
+
// Check local items
|
|
529
|
+
for (const localItem of local) {
|
|
530
|
+
const label = (localItem as { label?: string }).label || localItem.id;
|
|
531
|
+
const remoteItem = remoteById.get(localItem.id);
|
|
532
|
+
if (!remoteItem) {
|
|
533
|
+
result.push({ id: localItem.id, label, status: "to_push" });
|
|
534
|
+
} else if (JSON.stringify(localItem) !== JSON.stringify(remoteItem)) {
|
|
535
|
+
result.push({ id: localItem.id, label, status: "to_push" });
|
|
536
|
+
} else {
|
|
537
|
+
result.push({ id: localItem.id, label, status: "in_sync" });
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// Check remote items not in local
|
|
542
|
+
for (const remoteItem of remote) {
|
|
543
|
+
if (!localById.has(remoteItem.id)) {
|
|
544
|
+
const label = (remoteItem as { label?: string }).label || remoteItem.id;
|
|
545
|
+
result.push({ id: remoteItem.id, label, status: "to_pull" });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return result;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
function buildTypeSections(
|
|
553
|
+
localTypes: CustomType[],
|
|
554
|
+
remoteTypes: CustomType[],
|
|
555
|
+
): {
|
|
556
|
+
pageTypes: StatusSection;
|
|
557
|
+
customTypes: StatusSection;
|
|
558
|
+
pageTypeStatuses: TypeWithStatus[];
|
|
559
|
+
customTypeStatuses: TypeWithStatus[];
|
|
560
|
+
} {
|
|
561
|
+
const typeStatuses = computeTypeStatus(localTypes, remoteTypes);
|
|
562
|
+
|
|
563
|
+
// Separate by format
|
|
564
|
+
const pageTypeStatuses = typeStatuses.filter((t) => {
|
|
565
|
+
const localType = localTypes.find((lt) => lt.id === t.id);
|
|
566
|
+
const remoteType = remoteTypes.find((rt) => rt.id === t.id);
|
|
567
|
+
const type = localType || remoteType;
|
|
568
|
+
return type && (type as { format?: string }).format === "page";
|
|
569
|
+
});
|
|
570
|
+
|
|
571
|
+
const customTypeStatuses = typeStatuses.filter((t) => {
|
|
572
|
+
const localType = localTypes.find((lt) => lt.id === t.id);
|
|
573
|
+
const remoteType = remoteTypes.find((rt) => rt.id === t.id);
|
|
574
|
+
const type = localType || remoteType;
|
|
575
|
+
return !type || (type as { format?: string }).format !== "page";
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
const pageTypeItems: StatusItem[] = pageTypeStatuses.map((t) => ({
|
|
579
|
+
done: t.status === "in_sync",
|
|
580
|
+
label: t.label,
|
|
581
|
+
hint: statusToHint(t.status),
|
|
582
|
+
}));
|
|
583
|
+
|
|
584
|
+
const customTypeItems: StatusItem[] = customTypeStatuses.map((t) => ({
|
|
585
|
+
done: t.status === "in_sync",
|
|
586
|
+
label: t.label,
|
|
587
|
+
hint: statusToHint(t.status),
|
|
588
|
+
}));
|
|
589
|
+
|
|
590
|
+
return {
|
|
591
|
+
pageTypes: { title: "Page Types", items: pageTypeItems },
|
|
592
|
+
customTypes: { title: "Custom Types", items: customTypeItems },
|
|
593
|
+
pageTypeStatuses,
|
|
594
|
+
customTypeStatuses,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
function statusToHint(status: TypeStatus): string | undefined {
|
|
599
|
+
switch (status) {
|
|
600
|
+
case "in_sync":
|
|
601
|
+
return "in sync";
|
|
602
|
+
case "to_push":
|
|
603
|
+
return "to push";
|
|
604
|
+
case "to_pull":
|
|
605
|
+
return "to pull";
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Slices Section
|
|
610
|
+
function sliceHasFields(slice: SharedSlice): boolean {
|
|
611
|
+
for (const variation of slice.variations) {
|
|
612
|
+
const primaryFields = Object.keys(variation.primary ?? {});
|
|
613
|
+
const itemFields = Object.keys(variation.items ?? {});
|
|
614
|
+
if (primaryFields.length > 0 || itemFields.length > 0) {
|
|
615
|
+
return true;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function isSliceConnectedToAnyType(sliceId: string, localTypes: CustomType[]): boolean {
|
|
622
|
+
for (const type of localTypes) {
|
|
623
|
+
for (const tabFields of Object.values(type.json)) {
|
|
624
|
+
for (const field of Object.values(tabFields as Record<string, unknown>)) {
|
|
625
|
+
const typedField = field as {
|
|
626
|
+
type?: string;
|
|
627
|
+
config?: { choices?: Record<string, unknown> };
|
|
628
|
+
};
|
|
629
|
+
if (typedField.type === "Slices" && typedField.config?.choices?.[sliceId]) {
|
|
630
|
+
return true;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
return false;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async function buildSlicesSection(
|
|
639
|
+
localSlices: SharedSlice[],
|
|
640
|
+
remoteSlices: SharedSlice[],
|
|
641
|
+
framework: FrameworkAdapter,
|
|
642
|
+
localTypes: CustomType[],
|
|
643
|
+
): Promise<{
|
|
644
|
+
section: StatusSection;
|
|
645
|
+
statuses: TypeWithStatus[];
|
|
646
|
+
missingComponents: string[];
|
|
647
|
+
slicesReadyToConnect: string[];
|
|
648
|
+
}> {
|
|
649
|
+
const sliceStatuses = computeTypeStatus(localSlices, remoteSlices);
|
|
650
|
+
const items: StatusItem[] = [];
|
|
651
|
+
const missingComponents: string[] = [];
|
|
652
|
+
const slicesReadyToConnect: string[] = [];
|
|
653
|
+
|
|
654
|
+
const projectRoot = await framework.getProjectRoot();
|
|
655
|
+
const slicesDir = await framework.getSlicesDirectoryPath();
|
|
656
|
+
const extensions = framework.getSliceComponentExtensions();
|
|
657
|
+
|
|
658
|
+
for (const slice of sliceStatuses) {
|
|
659
|
+
const localSlice = localSlices.find((s) => s.id === slice.id);
|
|
660
|
+
|
|
661
|
+
// Track slices that have fields but aren't connected to any type
|
|
662
|
+
// These should be connected before pushing
|
|
663
|
+
if (localSlice) {
|
|
664
|
+
const hasFields = sliceHasFields(localSlice);
|
|
665
|
+
const isConnected = isSliceConnectedToAnyType(slice.id, localTypes);
|
|
666
|
+
|
|
667
|
+
if (hasFields && !isConnected) {
|
|
668
|
+
slicesReadyToConnect.push(slice.label);
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
// Check if component is implemented
|
|
673
|
+
const componentExists = await checkSliceComponent(projectRoot, slicesDir, slice.id, extensions);
|
|
674
|
+
|
|
675
|
+
if (slice.status === "in_sync" && componentExists) {
|
|
676
|
+
items.push({
|
|
677
|
+
done: true,
|
|
678
|
+
label: slice.label,
|
|
679
|
+
hint: "component implemented",
|
|
680
|
+
});
|
|
681
|
+
} else if (slice.status === "in_sync" && !componentExists) {
|
|
682
|
+
items.push({
|
|
683
|
+
done: false,
|
|
684
|
+
label: slice.label,
|
|
685
|
+
hint: "missing component",
|
|
686
|
+
});
|
|
687
|
+
missingComponents.push(slice.label);
|
|
688
|
+
} else {
|
|
689
|
+
items.push({
|
|
690
|
+
done: false,
|
|
691
|
+
label: slice.label,
|
|
692
|
+
hint: statusToHint(slice.status),
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return {
|
|
698
|
+
section: { title: "Slices", items },
|
|
699
|
+
statuses: sliceStatuses,
|
|
700
|
+
missingComponents,
|
|
701
|
+
slicesReadyToConnect,
|
|
702
|
+
};
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function checkSliceComponent(
|
|
706
|
+
projectRoot: URL,
|
|
707
|
+
slicesDir: string,
|
|
708
|
+
sliceId: string,
|
|
709
|
+
extensions: string[],
|
|
710
|
+
): Promise<boolean> {
|
|
711
|
+
// Convert slice ID to PascalCase for folder name
|
|
712
|
+
const sliceName = pascalCase(sliceId);
|
|
713
|
+
|
|
714
|
+
for (const ext of extensions) {
|
|
715
|
+
const componentPath = new URL(`${slicesDir}${sliceName}/index${ext}`, projectRoot);
|
|
716
|
+
if (await exists(componentPath)) {
|
|
717
|
+
return true;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return false;
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function pascalCase(input: string): string {
|
|
724
|
+
return input.toLowerCase().replace(/(^|[-_\s]+)(.)?/g, (_, __, c) => c?.toUpperCase() ?? "");
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
// Preview Section
|
|
728
|
+
async function buildPreviewSection(
|
|
729
|
+
framework: FrameworkAdapter,
|
|
730
|
+
previews: Preview[] | undefined,
|
|
731
|
+
simulatorUrl: string | undefined,
|
|
732
|
+
): Promise<StatusSection> {
|
|
733
|
+
const items: StatusItem[] = [];
|
|
734
|
+
const projectRoot = await framework.getProjectRoot();
|
|
735
|
+
|
|
736
|
+
// Check simulator URL configured
|
|
737
|
+
items.push({
|
|
738
|
+
done: Boolean(simulatorUrl),
|
|
739
|
+
label: "Slice simulator URL",
|
|
740
|
+
hint: simulatorUrl ? "configured" : "run `prismic preview set-simulator`",
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// Check slice-simulator route
|
|
744
|
+
const sliceSimRoute = await framework.getRoutePath("/slice-simulator");
|
|
745
|
+
if (sliceSimRoute) {
|
|
746
|
+
const routeExists = await checkRouteExists(projectRoot, sliceSimRoute);
|
|
747
|
+
items.push({
|
|
748
|
+
done: routeExists,
|
|
749
|
+
label: "/slice-simulator route",
|
|
750
|
+
hint: routeExists ? undefined : "create route for Page Builder",
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
// Check preview environment
|
|
755
|
+
const hasPreviewEnv = previews && previews.length > 0;
|
|
756
|
+
items.push({
|
|
757
|
+
done: Boolean(hasPreviewEnv),
|
|
758
|
+
label: "Preview environment",
|
|
759
|
+
hint: hasPreviewEnv ? undefined : "run `prismic preview add`",
|
|
760
|
+
});
|
|
761
|
+
|
|
762
|
+
// Check /api/preview endpoint (skip for Nuxt - built-in)
|
|
763
|
+
if (framework.id !== "nuxt") {
|
|
764
|
+
const previewRoute = await framework.getRoutePath("/api/preview");
|
|
765
|
+
if (previewRoute) {
|
|
766
|
+
const routeExists = await checkRouteExists(projectRoot, previewRoute);
|
|
767
|
+
items.push({
|
|
768
|
+
done: routeExists,
|
|
769
|
+
label: "/api/preview endpoint",
|
|
770
|
+
hint: routeExists ? undefined : "create preview endpoint",
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
// Check /api/exit-preview endpoint (Next.js only)
|
|
776
|
+
if (framework.id === "next") {
|
|
777
|
+
const exitPreviewRoute = await framework.getRoutePath("/api/exit-preview");
|
|
778
|
+
if (exitPreviewRoute) {
|
|
779
|
+
const routeExists = await checkRouteExists(projectRoot, exitPreviewRoute);
|
|
780
|
+
items.push({
|
|
781
|
+
done: routeExists,
|
|
782
|
+
label: "/api/exit-preview endpoint",
|
|
783
|
+
hint: routeExists ? undefined : "create exit-preview endpoint",
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return { title: "Preview", items };
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
async function checkRouteExists(
|
|
792
|
+
projectRoot: URL,
|
|
793
|
+
route: { path: string; extensions: string[] },
|
|
794
|
+
): Promise<boolean> {
|
|
795
|
+
for (const ext of route.extensions) {
|
|
796
|
+
const fullPath = new URL(`${route.path}${ext}`, projectRoot);
|
|
797
|
+
if (await exists(fullPath)) {
|
|
798
|
+
return true;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return false;
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// Deployment Section (Next.js only)
|
|
805
|
+
async function buildDeploymentSection(
|
|
806
|
+
framework: FrameworkAdapter,
|
|
807
|
+
webhooks: Array<{ config: { url: string; active: boolean } }>,
|
|
808
|
+
): Promise<StatusSection> {
|
|
809
|
+
const items: StatusItem[] = [];
|
|
810
|
+
const projectRoot = await framework.getProjectRoot();
|
|
811
|
+
|
|
812
|
+
// Check /api/revalidate endpoint
|
|
813
|
+
const revalidateRoute = await framework.getRoutePath("/api/revalidate");
|
|
814
|
+
if (revalidateRoute) {
|
|
815
|
+
const routeExists = await checkRouteExists(projectRoot, revalidateRoute);
|
|
816
|
+
items.push({
|
|
817
|
+
done: routeExists,
|
|
818
|
+
label: "/api/revalidate endpoint",
|
|
819
|
+
hint: routeExists ? undefined : "create for ISR",
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// Check revalidation webhook
|
|
824
|
+
const hasRevalidationWebhook = webhooks.some(
|
|
825
|
+
(w) => w.config.active && w.config.url.toLowerCase().includes("revalidate"),
|
|
826
|
+
);
|
|
827
|
+
items.push({
|
|
828
|
+
done: hasRevalidationWebhook,
|
|
829
|
+
label: "Revalidation webhook",
|
|
830
|
+
hint: hasRevalidationWebhook ? "configured" : "run `prismic webhook create`",
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
return { title: "Deployment", items };
|
|
834
|
+
}
|