@angeloashmore/prismic-cli-poc 0.0.0-pr.3.219652e → 0.0.0-pr.4.484acb8

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 (44) hide show
  1. package/dist/index.mjs +99 -96
  2. package/package.json +1 -1
  3. package/src/custom-type-add-field-boolean.ts +4 -0
  4. package/src/custom-type-add-field-color.ts +4 -0
  5. package/src/custom-type-add-field-date.ts +4 -0
  6. package/src/custom-type-add-field-embed.ts +4 -0
  7. package/src/custom-type-add-field-geo-point.ts +4 -0
  8. package/src/custom-type-add-field-image.ts +4 -0
  9. package/src/custom-type-add-field-key-text.ts +4 -0
  10. package/src/custom-type-add-field-link.ts +4 -0
  11. package/src/custom-type-add-field-number.ts +4 -0
  12. package/src/custom-type-add-field-rich-text.ts +4 -0
  13. package/src/custom-type-add-field-select.ts +4 -0
  14. package/src/custom-type-add-field-timestamp.ts +4 -0
  15. package/src/custom-type-add-field-uid.ts +4 -0
  16. package/src/custom-type-create.ts +3 -0
  17. package/src/page-type-add-field-boolean.ts +4 -0
  18. package/src/page-type-add-field-color.ts +4 -0
  19. package/src/page-type-add-field-date.ts +4 -0
  20. package/src/page-type-add-field-embed.ts +4 -0
  21. package/src/page-type-add-field-geo-point.ts +4 -0
  22. package/src/page-type-add-field-image.ts +4 -0
  23. package/src/page-type-add-field-key-text.ts +4 -0
  24. package/src/page-type-add-field-link.ts +4 -0
  25. package/src/page-type-add-field-number.ts +4 -0
  26. package/src/page-type-add-field-rich-text.ts +4 -0
  27. package/src/page-type-add-field-select.ts +4 -0
  28. package/src/page-type-add-field-timestamp.ts +4 -0
  29. package/src/page-type-add-field-uid.ts +4 -0
  30. package/src/page-type-create.ts +3 -0
  31. package/src/slice-add-field-boolean.ts +4 -0
  32. package/src/slice-add-field-color.ts +4 -0
  33. package/src/slice-add-field-date.ts +4 -0
  34. package/src/slice-add-field-embed.ts +4 -0
  35. package/src/slice-add-field-geo-point.ts +4 -0
  36. package/src/slice-add-field-image.ts +4 -0
  37. package/src/slice-add-field-key-text.ts +4 -0
  38. package/src/slice-add-field-link.ts +4 -0
  39. package/src/slice-add-field-number.ts +4 -0
  40. package/src/slice-add-field-rich-text.ts +4 -0
  41. package/src/slice-add-field-select.ts +4 -0
  42. package/src/slice-add-field-timestamp.ts +4 -0
  43. package/src/slice-create.ts +3 -0
  44. package/src/status.ts +176 -31
package/src/status.ts CHANGED
@@ -14,6 +14,7 @@ import {
14
14
  } from "./lib/custom-types-api";
15
15
  import { exists } from "./lib/file";
16
16
  import {
17
+ type Framework,
17
18
  type FrameworkInfo,
18
19
  detectFrameworkInfo,
19
20
  getClientFilePath,
@@ -29,6 +30,9 @@ import { getWebhooks } from "./webhook-view";
29
30
  const HELP = `
30
31
  Show the status of the current Prismic project.
31
32
 
33
+ Includes a "Next:" step showing the most important action to take based on
34
+ project state.
35
+
32
36
  By default, this command reads the repository from prismic.config.json at the
33
37
  project root.
34
38
 
@@ -58,6 +62,135 @@ type StatusSection = {
58
62
  items: StatusItem[];
59
63
  };
60
64
 
65
+ type NextStep = {
66
+ message: string;
67
+ };
68
+
69
+ function getDocsUrl(framework: Framework | undefined): string {
70
+ switch (framework) {
71
+ case "next":
72
+ return "https://prismic.io/docs/nextjs/with-cli";
73
+ case "nuxt":
74
+ return "https://prismic.io/docs/nuxt/with-cli";
75
+ case "sveltekit":
76
+ return "https://prismic.io/docs/sveltekit/with-cli";
77
+ default:
78
+ return "https://prismic.io/docs";
79
+ }
80
+ }
81
+
82
+ function computeNextStep(
83
+ sections: StatusSection[],
84
+ frameworkInfo: FrameworkInfo,
85
+ typeStatuses: TypeWithStatus[],
86
+ sliceStatuses: TypeWithStatus[],
87
+ slicesWithMissingComponents: string[],
88
+ ): NextStep | undefined {
89
+ const docsUrl = getDocsUrl(frameworkInfo.framework);
90
+
91
+ // 1. Setup - missing dependencies
92
+ const setupSection = sections.find((s) => s.title === "Setup");
93
+ const missingDeps = setupSection?.items.filter((i) => !i.done && i.hint === "not installed");
94
+ if (missingDeps && missingDeps.length > 0) {
95
+ const depsList = missingDeps.map((d) => d.label).join(" ");
96
+ return { message: `Install Prismic packages with 'npm install ${depsList}'` };
97
+ }
98
+
99
+ // 2. Setup - missing client file
100
+ const missingClientFile = setupSection?.items.find((i) => !i.done && i.hint?.includes("client"));
101
+ if (missingClientFile) {
102
+ return { message: `Create a ${missingClientFile.label} file (see ${docsUrl})` };
103
+ }
104
+
105
+ // 3-7. Preview section (in order: local files, then remote config)
106
+ const previewSection = sections.find((s) => s.title === "Preview");
107
+ if (previewSection) {
108
+ // Local files first
109
+ const sliceSimRoute = previewSection.items.find(
110
+ (i) => i.label === "/slice-simulator route" && !i.done,
111
+ );
112
+ if (sliceSimRoute) {
113
+ return { message: `Create the /slice-simulator route (see ${docsUrl})` };
114
+ }
115
+
116
+ const apiPreview = previewSection.items.find(
117
+ (i) => i.label === "/api/preview endpoint" && !i.done,
118
+ );
119
+ if (apiPreview) {
120
+ return { message: `Create the /api/preview route (see ${docsUrl})` };
121
+ }
122
+
123
+ const exitPreview = previewSection.items.find(
124
+ (i) => i.label === "/api/exit-preview endpoint" && !i.done,
125
+ );
126
+ if (exitPreview) {
127
+ return { message: `Create the /api/exit-preview route (see ${docsUrl})` };
128
+ }
129
+
130
+ // Remote config
131
+ const simulatorUrl = previewSection.items.find(
132
+ (i) => i.label === "Slice simulator URL" && !i.done,
133
+ );
134
+ if (simulatorUrl) {
135
+ return { message: `Configure the slice simulator URL with 'prismic preview set-simulator'` };
136
+ }
137
+
138
+ const previewEnv = previewSection.items.find(
139
+ (i) => i.label === "Preview environment" && !i.done,
140
+ );
141
+ if (previewEnv) {
142
+ return { message: `Add a preview environment with 'prismic preview add'` };
143
+ }
144
+ }
145
+
146
+ // 8. Models to pull
147
+ const hasToPull =
148
+ typeStatuses.some((t) => t.status === "to_pull") ||
149
+ sliceStatuses.some((s) => s.status === "to_pull");
150
+ if (hasToPull) {
151
+ return { message: `Pull remote models with 'prismic pull'` };
152
+ }
153
+
154
+ // 9. Models to push
155
+ const hasToPush =
156
+ typeStatuses.some((t) => t.status === "to_push") ||
157
+ sliceStatuses.some((s) => s.status === "to_push");
158
+ if (hasToPush) {
159
+ return { message: `Push local models with 'prismic push'` };
160
+ }
161
+
162
+ // 10. Slice components to implement (first alphabetically)
163
+ if (slicesWithMissingComponents.length > 0) {
164
+ const sorted = [...slicesWithMissingComponents].sort();
165
+ const sliceName = sorted[0];
166
+ const slicesDir = getSlicesDirectory(frameworkInfo);
167
+ const ext = getSliceComponentExtensions(frameworkInfo.framework)[0];
168
+ const path = `${slicesDir}${sliceName}/index${ext}`;
169
+ return { message: `Implement the ${sliceName} slice component at ${path} (see ${docsUrl})` };
170
+ }
171
+
172
+ // 11-12. Deployment (Next.js only)
173
+ const deploymentSection = sections.find((s) => s.title === "Deployment");
174
+ if (deploymentSection) {
175
+ const revalidateEndpoint = deploymentSection.items.find(
176
+ (i) => i.label === "/api/revalidate endpoint" && !i.done,
177
+ );
178
+ if (revalidateEndpoint) {
179
+ return { message: `Create the /api/revalidate route for ISR (see ${docsUrl})` };
180
+ }
181
+
182
+ const webhook = deploymentSection.items.find(
183
+ (i) => i.label === "Revalidation webhook" && !i.done,
184
+ );
185
+ if (webhook) {
186
+ return { message: `Create a revalidation webhook with 'prismic webhook create'` };
187
+ }
188
+ }
189
+
190
+ // All complete
191
+ return undefined;
192
+ }
193
+
61
194
  export async function status(): Promise<void> {
62
195
  const {
63
196
  values: { help, repo = await safeGetRepositoryFromConfig() },
@@ -118,39 +251,42 @@ export async function status(): Promise<void> {
118
251
 
119
252
  // Print repository header
120
253
  const repoUrl = await getRepoUrl(repo);
121
- console.info(`Repository: ${repo}`);
122
- console.info(`URL: ${repoUrl.href}`);
123
-
124
- if (repoInfoResult.ok) {
125
- const accessLevel = formatApiAccess(repoInfoResult.value.api_access);
126
- console.info(`Content API Access: ${accessLevel}`);
127
- }
254
+ console.info(`Repository: ${repo}`);
255
+ console.info(`URL: ${repoUrl.href}`);
128
256
  console.info("");
129
257
 
130
258
  const sections: StatusSection[] = [];
131
259
 
260
+ // Track statuses for next step computation
261
+ let typeStatuses: TypeWithStatus[] = [];
262
+ let sliceStatuses: TypeWithStatus[] = [];
263
+ let slicesWithMissingComponents: string[] = [];
264
+
132
265
  // Setup section
133
266
  const setupSection = await buildSetupSection(frameworkInfo, installedDeps);
134
267
  sections.push(setupSection);
135
268
 
136
269
  // Types sections (Page Types and Custom Types)
137
270
  if (localTypesResult.ok && remoteTypesResult.ok) {
138
- const { pageTypes, customTypes } = buildTypeSections(
271
+ const { pageTypes, customTypes, allTypeStatuses } = buildTypeSections(
139
272
  localTypesResult.value,
140
273
  remoteTypesResult.value,
141
274
  );
142
275
  sections.push(pageTypes);
143
276
  sections.push(customTypes);
277
+ typeStatuses = allTypeStatuses;
144
278
  }
145
279
 
146
280
  // Slices section
147
281
  if (localSlicesResult.ok && remoteSlicesResult.ok) {
148
- const slicesSection = await buildSlicesSection(
149
- localSlicesResult.value,
150
- remoteSlicesResult.value,
151
- frameworkInfo,
152
- );
282
+ const {
283
+ section: slicesSection,
284
+ statuses,
285
+ missingComponents,
286
+ } = await buildSlicesSection(localSlicesResult.value, remoteSlicesResult.value, frameworkInfo);
153
287
  sections.push(slicesSection);
288
+ sliceStatuses = statuses;
289
+ slicesWithMissingComponents = missingComponents;
154
290
  }
155
291
 
156
292
  // Preview section
@@ -174,6 +310,18 @@ export async function status(): Promise<void> {
174
310
  for (const section of sections) {
175
311
  printSection(section);
176
312
  }
313
+
314
+ // Print next step
315
+ const nextStep = computeNextStep(
316
+ sections,
317
+ frameworkInfo,
318
+ typeStatuses,
319
+ sliceStatuses,
320
+ slicesWithMissingComponents,
321
+ );
322
+ if (nextStep) {
323
+ console.info(`Next: ${nextStep.message}`);
324
+ }
177
325
  }
178
326
 
179
327
  function printSection(section: StatusSection): void {
@@ -208,9 +356,8 @@ function printSection(section: StatusSection): void {
208
356
  console.info("");
209
357
  }
210
358
 
211
- // Repository Info
359
+ // Repository Info (from /core/repository)
212
360
  const RepositoryInfoSchema = v.object({
213
- api_access: v.optional(v.string()),
214
361
  simulator_url: v.optional(v.string()),
215
362
  });
216
363
  type RepositoryInfo = v.InferOutput<typeof RepositoryInfoSchema>;
@@ -226,19 +373,6 @@ async function fetchRepositoryInfo(
226
373
  return { ok: false };
227
374
  }
228
375
 
229
- function formatApiAccess(access: string | undefined): string {
230
- switch (access) {
231
- case "private":
232
- return "Private";
233
- case "open":
234
- return "Open";
235
- case "master_only":
236
- return "Master only";
237
- default:
238
- return access || "Unknown";
239
- }
240
- }
241
-
242
376
  // Previews
243
377
  const PreviewSchema = v.object({
244
378
  id: v.string(),
@@ -371,7 +505,7 @@ function computeTypeStatus<T extends { id: string }>(local: T[], remote: T[]): T
371
505
  function buildTypeSections(
372
506
  localTypes: CustomType[],
373
507
  remoteTypes: CustomType[],
374
- ): { pageTypes: StatusSection; customTypes: StatusSection } {
508
+ ): { pageTypes: StatusSection; customTypes: StatusSection; allTypeStatuses: TypeWithStatus[] } {
375
509
  const typeStatuses = computeTypeStatus(localTypes, remoteTypes);
376
510
 
377
511
  // Separate by format
@@ -404,6 +538,7 @@ function buildTypeSections(
404
538
  return {
405
539
  pageTypes: { title: "Page Types", items: pageTypeItems },
406
540
  customTypes: { title: "Custom Types", items: customTypeItems },
541
+ allTypeStatuses: typeStatuses,
407
542
  };
408
543
  }
409
544
 
@@ -423,9 +558,14 @@ async function buildSlicesSection(
423
558
  localSlices: SharedSlice[],
424
559
  remoteSlices: SharedSlice[],
425
560
  info: FrameworkInfo,
426
- ): Promise<StatusSection> {
561
+ ): Promise<{
562
+ section: StatusSection;
563
+ statuses: TypeWithStatus[];
564
+ missingComponents: string[];
565
+ }> {
427
566
  const sliceStatuses = computeTypeStatus(localSlices, remoteSlices);
428
567
  const items: StatusItem[] = [];
568
+ const missingComponents: string[] = [];
429
569
 
430
570
  const slicesDir = getSlicesDirectory(info);
431
571
  const extensions = getSliceComponentExtensions(info.framework);
@@ -446,6 +586,7 @@ async function buildSlicesSection(
446
586
  label: slice.label,
447
587
  hint: "missing component",
448
588
  });
589
+ missingComponents.push(slice.label);
449
590
  } else {
450
591
  items.push({
451
592
  done: false,
@@ -455,7 +596,11 @@ async function buildSlicesSection(
455
596
  }
456
597
  }
457
598
 
458
- return { title: "Slices", items };
599
+ return {
600
+ section: { title: "Slices", items },
601
+ statuses: sliceStatuses,
602
+ missingComponents,
603
+ };
459
604
  }
460
605
 
461
606
  async function checkSliceComponent(