@angeloashmore/prismic-cli-poc 0.0.0-canary.5da487c → 0.0.0-canary.63deeeb

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@angeloashmore/prismic-cli-poc",
3
- "version": "0.0.0-canary.5da487c",
3
+ "version": "0.0.0-canary.63deeeb",
4
4
  "description": "A proof-of-concept developer CLI for Prismic.",
5
5
  "keywords": [
6
6
  "prismic",
package/src/docs.ts ADDED
@@ -0,0 +1,149 @@
1
+ import { parseArgs } from "node:util";
2
+
3
+ const HELP = `
4
+ Fetch and display documentation from Prismic's docs site.
5
+
6
+ USAGE
7
+ prismic docs <path> [flags]
8
+
9
+ ARGUMENTS
10
+ path Documentation path with optional anchor (e.g., "nextjs" or "nextjs#set-up-a-prismic-client")
11
+
12
+ FLAGS
13
+ -h, --help Show help for command
14
+
15
+ EXAMPLES
16
+ prismic docs nextjs
17
+ prismic docs nextjs#set-up-a-prismic-client
18
+
19
+ LEARN MORE
20
+ Visit https://prismic.io/docs for the full documentation.
21
+ `.trim();
22
+
23
+ function parsePathAndAnchor(input: string): { path: string; anchor?: string } {
24
+ const hashIndex = input.indexOf("#");
25
+ if (hashIndex === -1) {
26
+ return { path: input };
27
+ }
28
+ return {
29
+ path: input.slice(0, hashIndex),
30
+ anchor: input.slice(hashIndex + 1),
31
+ };
32
+ }
33
+
34
+ async function fetchMarkdown(
35
+ url: string,
36
+ ): Promise<{ ok: true; content: string } | { ok: false; error: string }> {
37
+ try {
38
+ const response = await fetch(url);
39
+ if (response.status === 404) {
40
+ return { ok: false, error: `Documentation not found: ${url}` };
41
+ }
42
+ if (!response.ok) {
43
+ return {
44
+ ok: false,
45
+ error: `Failed to fetch documentation: ${response.status}`,
46
+ };
47
+ }
48
+ const content = await response.text();
49
+ return { ok: true, content };
50
+ } catch (error) {
51
+ const message = error instanceof Error ? error.message : String(error);
52
+ return { ok: false, error: `Network error: ${message}` };
53
+ }
54
+ }
55
+
56
+ function anchorToHeadingPattern(anchor: string): RegExp {
57
+ // Convert kebab-case anchor to a pattern that matches the heading text
58
+ // Each hyphen/space becomes a flexible match for hyphens or spaces
59
+ const pattern = anchor
60
+ .split("-")
61
+ .map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
62
+ .join("[\\s-]+");
63
+ return new RegExp(`^(#{1,6})\\s+${pattern}\\s*$`, "im");
64
+ }
65
+
66
+ function extractSection(
67
+ markdown: string,
68
+ anchor: string,
69
+ ): { ok: true; content: string } | { ok: false; error: string } {
70
+ const lines = markdown.split("\n");
71
+ const headingPattern = anchorToHeadingPattern(anchor);
72
+
73
+ let startIndex = -1;
74
+ let headingLevel = 0;
75
+
76
+ // Find the matching heading
77
+ for (let i = 0; i < lines.length; i++) {
78
+ const match = lines[i].match(headingPattern);
79
+ if (match) {
80
+ startIndex = i;
81
+ headingLevel = match[1].length;
82
+ break;
83
+ }
84
+ }
85
+
86
+ if (startIndex === -1) {
87
+ return { ok: false, error: `Anchor not found: #${anchor}` };
88
+ }
89
+
90
+ // Find the end of this section (next heading of equal or lower level number)
91
+ let endIndex = lines.length;
92
+ for (let i = startIndex + 1; i < lines.length; i++) {
93
+ const headingMatch = lines[i].match(/^(#{1,6})\s/);
94
+ if (headingMatch && headingMatch[1].length <= headingLevel) {
95
+ endIndex = i;
96
+ break;
97
+ }
98
+ }
99
+
100
+ const content = lines.slice(startIndex, endIndex).join("\n").trim();
101
+ return { ok: true, content };
102
+ }
103
+
104
+ export async function docs(): Promise<void> {
105
+ const {
106
+ positionals: [pathArg],
107
+ values: { help },
108
+ } = parseArgs({
109
+ args: process.argv.slice(3),
110
+ options: {
111
+ help: { type: "boolean", short: "h" },
112
+ },
113
+ allowPositionals: true,
114
+ });
115
+
116
+ if (help) {
117
+ console.info(HELP);
118
+ return;
119
+ }
120
+
121
+ if (!pathArg) {
122
+ console.info(HELP);
123
+ return;
124
+ }
125
+
126
+ const { path, anchor } = parsePathAndAnchor(pathArg);
127
+ const url = `https://prismic.io/docs/${path}.md`;
128
+
129
+ const fetchResult = await fetchMarkdown(url);
130
+ if (!fetchResult.ok) {
131
+ console.error(fetchResult.error);
132
+ process.exitCode = 1;
133
+ return;
134
+ }
135
+
136
+ let output = fetchResult.content;
137
+
138
+ if (anchor) {
139
+ const extractResult = extractSection(output, anchor);
140
+ if (!extractResult.ok) {
141
+ console.error(extractResult.error);
142
+ process.exitCode = 1;
143
+ return;
144
+ }
145
+ output = extractResult.content;
146
+ }
147
+
148
+ console.info(output);
149
+ }
package/src/index.ts CHANGED
@@ -5,6 +5,7 @@ import { parseArgs } from "node:util";
5
5
  import packageJson from "../package.json" with { type: "json" };
6
6
  import { codegen } from "./codegen";
7
7
  import { customType } from "./custom-type";
8
+ import { docs } from "./docs";
8
9
  import { init } from "./init";
9
10
  import { locale } from "./locale";
10
11
  import { login } from "./login";
@@ -40,6 +41,7 @@ COMMANDS
40
41
  pull Pull types and slices from Prismic
41
42
  push Push types and slices to Prismic
42
43
  codegen Generate code from Prismic models
44
+ docs Fetch documentation from Prismic
43
45
  preview Manage preview configurations
44
46
  token Manage API tokens in a repository
45
47
  webhook Manage webhooks in a repository
@@ -107,6 +109,9 @@ if (version) {
107
109
  case "codegen":
108
110
  await codegen();
109
111
  break;
112
+ case "docs":
113
+ await docs();
114
+ break;
110
115
  case "preview":
111
116
  await preview();
112
117
  break;
package/src/status.ts CHANGED
@@ -66,16 +66,53 @@ type NextStep = {
66
66
  message: string;
67
67
  };
68
68
 
69
- function getDocsUrl(framework: Framework | undefined): string {
69
+ function getDocsPath(framework: Framework | undefined): string {
70
70
  switch (framework) {
71
71
  case "next":
72
- return "https://prismic.io/docs/nextjs/with-cli";
72
+ return "nextjs/with-cli";
73
73
  case "nuxt":
74
- return "https://prismic.io/docs/nuxt/with-cli";
74
+ return "nuxt/with-cli";
75
75
  case "sveltekit":
76
- return "https://prismic.io/docs/sveltekit/with-cli";
76
+ return "sveltekit/with-cli";
77
77
  default:
78
- return "https://prismic.io/docs";
78
+ return "";
79
+ }
80
+ }
81
+
82
+ function getDocsRef(docsPath: string, anchor?: string): string {
83
+ if (!docsPath) return "";
84
+ const fullPath = anchor ? `${docsPath}${anchor}` : docsPath;
85
+ return ` (run \`prismic docs ${fullPath}\`)`;
86
+ }
87
+
88
+ function getClientSetupAnchor(framework: Framework | undefined): string {
89
+ switch (framework) {
90
+ case "nuxt":
91
+ return "#configure-the-modules-prismic-client";
92
+ default:
93
+ return "#set-up-a-prismic-client";
94
+ }
95
+ }
96
+
97
+ function getPreviewSetupAnchor(framework: Framework | undefined): string {
98
+ switch (framework) {
99
+ case "next":
100
+ return "#set-up-previews-in-next-js";
101
+ case "sveltekit":
102
+ return "#set-up-previews-in-sveltekit";
103
+ default:
104
+ return "";
105
+ }
106
+ }
107
+
108
+ function getWriteComponentsAnchor(framework: Framework | undefined): string {
109
+ switch (framework) {
110
+ case "nuxt":
111
+ return "#write-vue-components";
112
+ case "sveltekit":
113
+ return "#write-svelte-components";
114
+ default:
115
+ return "#write-react-components";
79
116
  }
80
117
  }
81
118
 
@@ -86,7 +123,7 @@ function computeNextStep(
86
123
  sliceStatuses: TypeWithStatus[],
87
124
  slicesWithMissingComponents: string[],
88
125
  ): NextStep | undefined {
89
- const docsUrl = getDocsUrl(frameworkInfo.framework);
126
+ const docsPath = getDocsPath(frameworkInfo.framework);
90
127
 
91
128
  // 1. Setup - missing dependencies
92
129
  const setupSection = sections.find((s) => s.title === "Setup");
@@ -99,7 +136,7 @@ function computeNextStep(
99
136
  // 2. Setup - missing client file
100
137
  const missingClientFile = setupSection?.items.find((i) => !i.done && i.hint?.includes("client"));
101
138
  if (missingClientFile) {
102
- return { message: `Create a ${missingClientFile.label} file (see ${docsUrl})` };
139
+ return { message: `Create a ${missingClientFile.label} file${getDocsRef(docsPath, getClientSetupAnchor(frameworkInfo.framework))}` };
103
140
  }
104
141
 
105
142
  // 3-7. Preview section (in order: local files, then remote config)
@@ -110,21 +147,21 @@ function computeNextStep(
110
147
  (i) => i.label === "/slice-simulator route" && !i.done,
111
148
  );
112
149
  if (sliceSimRoute) {
113
- return { message: `Create the /slice-simulator route (see ${docsUrl})` };
150
+ return { message: `Create the /slice-simulator route${getDocsRef(docsPath, "#set-up-live-previewing")}` };
114
151
  }
115
152
 
116
153
  const apiPreview = previewSection.items.find(
117
154
  (i) => i.label === "/api/preview endpoint" && !i.done,
118
155
  );
119
156
  if (apiPreview) {
120
- return { message: `Create the /api/preview route (see ${docsUrl})` };
157
+ return { message: `Create the /api/preview route${getDocsRef(docsPath, getPreviewSetupAnchor(frameworkInfo.framework))}` };
121
158
  }
122
159
 
123
160
  const exitPreview = previewSection.items.find(
124
161
  (i) => i.label === "/api/exit-preview endpoint" && !i.done,
125
162
  );
126
163
  if (exitPreview) {
127
- return { message: `Create the /api/exit-preview route (see ${docsUrl})` };
164
+ return { message: `Create the /api/exit-preview route${getDocsRef(docsPath, getPreviewSetupAnchor(frameworkInfo.framework))}` };
128
165
  }
129
166
 
130
167
  // Remote config
@@ -166,7 +203,7 @@ function computeNextStep(
166
203
  const slicesDir = getSlicesDirectory(frameworkInfo);
167
204
  const ext = getSliceComponentExtensions(frameworkInfo.framework)[0];
168
205
  const path = `${slicesDir}${sliceName}/index${ext}`;
169
- return { message: `Implement the ${sliceName} slice component at ${path} (see ${docsUrl})` };
206
+ return { message: `Implement the ${sliceName} slice component at ${path}${getDocsRef(docsPath, getWriteComponentsAnchor(frameworkInfo.framework))}` };
170
207
  }
171
208
 
172
209
  // 11-12. Deployment (Next.js only)
@@ -176,7 +213,7 @@ function computeNextStep(
176
213
  (i) => i.label === "/api/revalidate endpoint" && !i.done,
177
214
  );
178
215
  if (revalidateEndpoint) {
179
- return { message: `Create the /api/revalidate route for ISR (see ${docsUrl})` };
216
+ return { message: `Create the /api/revalidate route for ISR${getDocsRef(docsPath, "#handle-content-changes")}` };
180
217
  }
181
218
 
182
219
  const webhook = deploymentSection.items.find(