@angeloashmore/prismic-cli-poc 0.0.0-canary.fe51fbb → 0.0.0-pr.10.032f7ef
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/dist/index.mjs +229 -136
- package/package.json +1 -1
- package/src/custom-type-add-field-boolean.ts +49 -12
- package/src/custom-type-add-field-color.ts +46 -12
- package/src/custom-type-add-field-date.ts +46 -12
- package/src/custom-type-add-field-embed.ts +46 -12
- package/src/custom-type-add-field-geo-point.ts +46 -12
- package/src/custom-type-add-field-group.ts +179 -0
- package/src/custom-type-add-field-image.ts +46 -12
- package/src/custom-type-add-field-key-text.ts +46 -12
- package/src/custom-type-add-field-link.ts +46 -12
- package/src/custom-type-add-field-number.ts +46 -12
- package/src/custom-type-add-field-rich-text.ts +46 -12
- package/src/custom-type-add-field-select.ts +46 -12
- package/src/custom-type-add-field-timestamp.ts +46 -12
- package/src/custom-type-add-field-uid.ts +17 -0
- package/src/custom-type-add-field.ts +5 -0
- package/src/docs-fetch.ts +146 -0
- package/src/docs-list.ts +131 -0
- package/src/docs.ts +26 -121
- package/src/index.ts +1 -1
- package/src/lib/field-path.ts +81 -0
- package/src/page-type-add-field-boolean.ts +47 -13
- package/src/page-type-add-field-color.ts +47 -13
- package/src/page-type-add-field-date.ts +47 -13
- package/src/page-type-add-field-embed.ts +47 -13
- package/src/page-type-add-field-geo-point.ts +47 -13
- package/src/page-type-add-field-group.ts +198 -0
- package/src/page-type-add-field-image.ts +47 -13
- package/src/page-type-add-field-key-text.ts +47 -13
- package/src/page-type-add-field-link.ts +47 -13
- package/src/page-type-add-field-number.ts +47 -13
- package/src/page-type-add-field-rich-text.ts +47 -13
- package/src/page-type-add-field-select.ts +47 -13
- package/src/page-type-add-field-timestamp.ts +47 -13
- package/src/page-type-add-field-uid.ts +18 -1
- package/src/page-type-add-field.ts +5 -0
- package/src/page-type-create.ts +1 -1
- package/src/repo-create.ts +28 -1
- package/src/slice-add-field-boolean.ts +59 -16
- package/src/slice-add-field-color.ts +59 -16
- package/src/slice-add-field-date.ts +59 -16
- package/src/slice-add-field-embed.ts +59 -16
- package/src/slice-add-field-geo-point.ts +59 -16
- package/src/slice-add-field-group.ts +191 -0
- package/src/slice-add-field-image.ts +59 -16
- package/src/slice-add-field-key-text.ts +59 -16
- package/src/slice-add-field-link.ts +59 -16
- package/src/slice-add-field-number.ts +59 -16
- package/src/slice-add-field-rich-text.ts +59 -16
- package/src/slice-add-field-select.ts +59 -16
- package/src/slice-add-field-timestamp.ts +59 -16
- package/src/slice-add-field.ts +5 -0
- package/src/status.ts +1 -1
|
@@ -6,6 +6,7 @@ import * as v from "valibot";
|
|
|
6
6
|
|
|
7
7
|
import { buildTypes } from "./codegen-types";
|
|
8
8
|
import { findUpward } from "./lib/file";
|
|
9
|
+
import { parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
|
|
9
10
|
import { stringify } from "./lib/json";
|
|
10
11
|
import { humanReadable } from "./lib/string";
|
|
11
12
|
|
|
@@ -76,6 +77,22 @@ export async function customTypeAddFieldUid(): Promise<void> {
|
|
|
76
77
|
return;
|
|
77
78
|
}
|
|
78
79
|
|
|
80
|
+
// Parse and validate field path
|
|
81
|
+
const fieldPath = parseFieldPath(fieldId);
|
|
82
|
+
const pathValidation = validateNestedFieldPath(fieldPath);
|
|
83
|
+
if (!pathValidation.ok) {
|
|
84
|
+
console.error(pathValidation.error);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// UID fields cannot be nested in groups
|
|
90
|
+
if (fieldPath.type === "nested") {
|
|
91
|
+
console.error("UID fields cannot be nested inside groups");
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
79
96
|
// Find the custom type file
|
|
80
97
|
const projectRoot = await findUpward("package.json");
|
|
81
98
|
if (!projectRoot) {
|
|
@@ -5,6 +5,7 @@ import { customTypeAddFieldColor } from "./custom-type-add-field-color";
|
|
|
5
5
|
import { customTypeAddFieldDate } from "./custom-type-add-field-date";
|
|
6
6
|
import { customTypeAddFieldEmbed } from "./custom-type-add-field-embed";
|
|
7
7
|
import { customTypeAddFieldGeoPoint } from "./custom-type-add-field-geo-point";
|
|
8
|
+
import { customTypeAddFieldGroup } from "./custom-type-add-field-group";
|
|
8
9
|
import { customTypeAddFieldImage } from "./custom-type-add-field-image";
|
|
9
10
|
import { customTypeAddFieldKeyText } from "./custom-type-add-field-key-text";
|
|
10
11
|
import { customTypeAddFieldLink } from "./custom-type-add-field-link";
|
|
@@ -26,6 +27,7 @@ FIELD TYPES
|
|
|
26
27
|
date Date picker
|
|
27
28
|
embed Embed (oEmbed)
|
|
28
29
|
geo-point Geographic coordinates
|
|
30
|
+
group Repeatable group of fields
|
|
29
31
|
image Image
|
|
30
32
|
key-text Single-line text
|
|
31
33
|
link Any link type
|
|
@@ -76,6 +78,9 @@ export async function customTypeAddField(): Promise<void> {
|
|
|
76
78
|
case "geo-point":
|
|
77
79
|
await customTypeAddFieldGeoPoint();
|
|
78
80
|
break;
|
|
81
|
+
case "group":
|
|
82
|
+
await customTypeAddFieldGroup();
|
|
83
|
+
break;
|
|
79
84
|
case "image":
|
|
80
85
|
await customTypeAddFieldImage();
|
|
81
86
|
break;
|
|
@@ -0,0 +1,146 @@
|
|
|
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 fetch <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 fetch nextjs
|
|
17
|
+
prismic docs fetch 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
|
+
const pattern = anchor
|
|
59
|
+
.split("-")
|
|
60
|
+
.map((word) => word.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"))
|
|
61
|
+
.join("[\\s-]+");
|
|
62
|
+
return new RegExp(`^(#{1,6})\\s+${pattern}\\s*$`, "im");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function extractSection(
|
|
66
|
+
markdown: string,
|
|
67
|
+
anchor: string,
|
|
68
|
+
): { ok: true; content: string } | { ok: false; error: string } {
|
|
69
|
+
const lines = markdown.split("\n");
|
|
70
|
+
const headingPattern = anchorToHeadingPattern(anchor);
|
|
71
|
+
|
|
72
|
+
let startIndex = -1;
|
|
73
|
+
let headingLevel = 0;
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < lines.length; i++) {
|
|
76
|
+
const match = lines[i].match(headingPattern);
|
|
77
|
+
if (match) {
|
|
78
|
+
startIndex = i;
|
|
79
|
+
headingLevel = match[1].length;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (startIndex === -1) {
|
|
85
|
+
return { ok: false, error: `Anchor not found: #${anchor}` };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let endIndex = lines.length;
|
|
89
|
+
for (let i = startIndex + 1; i < lines.length; i++) {
|
|
90
|
+
const headingMatch = lines[i].match(/^(#{1,6})\s/);
|
|
91
|
+
if (headingMatch && headingMatch[1].length <= headingLevel) {
|
|
92
|
+
endIndex = i;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const content = lines.slice(startIndex, endIndex).join("\n").trim();
|
|
98
|
+
return { ok: true, content };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function docsFetch(): Promise<void> {
|
|
102
|
+
const {
|
|
103
|
+
positionals: [pathArg],
|
|
104
|
+
values: { help },
|
|
105
|
+
} = parseArgs({
|
|
106
|
+
args: process.argv.slice(4),
|
|
107
|
+
options: {
|
|
108
|
+
help: { type: "boolean", short: "h" },
|
|
109
|
+
},
|
|
110
|
+
allowPositionals: true,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
if (help) {
|
|
114
|
+
console.info(HELP);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (!pathArg) {
|
|
119
|
+
console.info(HELP);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const { path, anchor } = parsePathAndAnchor(pathArg);
|
|
124
|
+
const url = `https://prismic.io/docs/${path}.md`;
|
|
125
|
+
|
|
126
|
+
const fetchResult = await fetchMarkdown(url);
|
|
127
|
+
if (!fetchResult.ok) {
|
|
128
|
+
console.error(fetchResult.error);
|
|
129
|
+
process.exitCode = 1;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
let output = fetchResult.content;
|
|
134
|
+
|
|
135
|
+
if (anchor) {
|
|
136
|
+
const extractResult = extractSection(output, anchor);
|
|
137
|
+
if (!extractResult.ok) {
|
|
138
|
+
console.error(extractResult.error);
|
|
139
|
+
process.exitCode = 1;
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
output = extractResult.content;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.info(output);
|
|
146
|
+
}
|
package/src/docs-list.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
const HELP = `
|
|
4
|
+
List documentation pages from Prismic's docs site.
|
|
5
|
+
|
|
6
|
+
USAGE
|
|
7
|
+
prismic docs list [flags]
|
|
8
|
+
|
|
9
|
+
FLAGS
|
|
10
|
+
-h, --help Show help for command
|
|
11
|
+
|
|
12
|
+
EXAMPLES
|
|
13
|
+
prismic docs list
|
|
14
|
+
`.trim();
|
|
15
|
+
|
|
16
|
+
const ROOT_SITEMAP_URL = "https://prismic.io/docs/sitemap.xml";
|
|
17
|
+
|
|
18
|
+
function decodeXmlEntities(input: string): string {
|
|
19
|
+
return input
|
|
20
|
+
.replaceAll("&", "&")
|
|
21
|
+
.replaceAll("<", "<")
|
|
22
|
+
.replaceAll(">", ">")
|
|
23
|
+
.replaceAll(""", '"')
|
|
24
|
+
.replaceAll("'", "'");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function extractLocEntries(xml: string): string[] {
|
|
28
|
+
const locPattern = /<loc>(.*?)<\/loc>/g;
|
|
29
|
+
const entries: string[] = [];
|
|
30
|
+
let match = locPattern.exec(xml);
|
|
31
|
+
|
|
32
|
+
while (match) {
|
|
33
|
+
entries.push(decodeXmlEntities(match[1]).trim());
|
|
34
|
+
match = locPattern.exec(xml);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return entries.filter(Boolean);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function fetchXml(url: string): Promise<{ ok: true; xml: string } | { ok: false; error: string }> {
|
|
41
|
+
try {
|
|
42
|
+
const response = await fetch(url);
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
return {
|
|
45
|
+
ok: false,
|
|
46
|
+
error: `Failed to fetch sitemap: ${response.status} (${url})`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
ok: true,
|
|
52
|
+
xml: await response.text(),
|
|
53
|
+
};
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
56
|
+
return { ok: false, error: `Network error while fetching sitemap ${url}: ${message}` };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function toDocsPath(urlString: string): string | null {
|
|
61
|
+
try {
|
|
62
|
+
const url = new URL(urlString);
|
|
63
|
+
if (url.hostname !== "prismic.io") {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!url.pathname.startsWith("/docs/")) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return url.pathname.replace(/^\/docs\//, "").replace(/^\/+|\/+$/g, "");
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function docsList(): Promise<void> {
|
|
78
|
+
const {
|
|
79
|
+
values: { help },
|
|
80
|
+
} = parseArgs({
|
|
81
|
+
args: process.argv.slice(4),
|
|
82
|
+
options: {
|
|
83
|
+
help: { type: "boolean", short: "h" },
|
|
84
|
+
},
|
|
85
|
+
allowPositionals: true,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (help) {
|
|
89
|
+
console.info(HELP);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const rootResult = await fetchXml(ROOT_SITEMAP_URL);
|
|
94
|
+
if (!rootResult.ok) {
|
|
95
|
+
console.error(rootResult.error);
|
|
96
|
+
process.exitCode = 1;
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const nestedSitemapUrls = extractLocEntries(rootResult.xml);
|
|
101
|
+
if (nestedSitemapUrls.length === 0) {
|
|
102
|
+
console.error(`No nested sitemaps found in ${ROOT_SITEMAP_URL}`);
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const nestedResults = await Promise.all(nestedSitemapUrls.map((url) => fetchXml(url)));
|
|
108
|
+
const failedFetch = nestedResults.find((result) => !result.ok);
|
|
109
|
+
if (failedFetch && !failedFetch.ok) {
|
|
110
|
+
console.error(failedFetch.error);
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const paths = new Set<string>();
|
|
116
|
+
|
|
117
|
+
for (const nestedResult of nestedResults) {
|
|
118
|
+
if (!nestedResult.ok) continue;
|
|
119
|
+
|
|
120
|
+
for (const url of extractLocEntries(nestedResult.xml)) {
|
|
121
|
+
const path = toDocsPath(url);
|
|
122
|
+
if (path) {
|
|
123
|
+
paths.add(path);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (const path of [...paths].sort((a, b) => a.localeCompare(b))) {
|
|
129
|
+
console.info(path);
|
|
130
|
+
}
|
|
131
|
+
}
|
package/src/docs.ts
CHANGED
|
@@ -1,149 +1,54 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
+
import { docsFetch } from "./docs-fetch";
|
|
3
|
+
import { docsList } from "./docs-list";
|
|
2
4
|
|
|
3
5
|
const HELP = `
|
|
4
|
-
Fetch and
|
|
6
|
+
Fetch and list documentation from Prismic's docs site.
|
|
5
7
|
|
|
6
8
|
USAGE
|
|
7
|
-
prismic docs <
|
|
9
|
+
prismic docs <command> [flags]
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
+
COMMANDS
|
|
12
|
+
fetch Fetch and display a documentation page
|
|
13
|
+
list List documentation pages
|
|
11
14
|
|
|
12
15
|
FLAGS
|
|
13
16
|
-h, --help Show help for command
|
|
14
17
|
|
|
15
18
|
EXAMPLES
|
|
16
|
-
prismic docs nextjs
|
|
17
|
-
prismic docs nextjs#set-up-a-prismic-client
|
|
19
|
+
prismic docs fetch nextjs
|
|
20
|
+
prismic docs fetch nextjs#set-up-a-prismic-client
|
|
21
|
+
prismic docs list
|
|
18
22
|
|
|
19
23
|
LEARN MORE
|
|
20
|
-
|
|
24
|
+
Use \`prismic docs <command> --help\` for more information about a command.
|
|
21
25
|
`.trim();
|
|
22
26
|
|
|
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
27
|
export async function docs(): Promise<void> {
|
|
105
28
|
const {
|
|
106
|
-
positionals: [
|
|
107
|
-
values: { help },
|
|
29
|
+
positionals: [subcommand],
|
|
108
30
|
} = parseArgs({
|
|
109
31
|
args: process.argv.slice(3),
|
|
110
32
|
options: {
|
|
111
33
|
help: { type: "boolean", short: "h" },
|
|
112
34
|
},
|
|
113
35
|
allowPositionals: true,
|
|
36
|
+
strict: false,
|
|
114
37
|
});
|
|
115
38
|
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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;
|
|
39
|
+
switch (subcommand) {
|
|
40
|
+
case "fetch":
|
|
41
|
+
await docsFetch();
|
|
42
|
+
break;
|
|
43
|
+
case "list":
|
|
44
|
+
await docsList();
|
|
45
|
+
break;
|
|
46
|
+
default: {
|
|
47
|
+
if (subcommand) {
|
|
48
|
+
console.error(`Unknown docs subcommand: ${subcommand}\n`);
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
}
|
|
51
|
+
console.info(HELP);
|
|
144
52
|
}
|
|
145
|
-
output = extractResult.content;
|
|
146
53
|
}
|
|
147
|
-
|
|
148
|
-
console.info(output);
|
|
149
54
|
}
|
package/src/index.ts
CHANGED
|
@@ -41,7 +41,7 @@ COMMANDS
|
|
|
41
41
|
pull Pull types and slices from Prismic
|
|
42
42
|
push Push types and slices to Prismic
|
|
43
43
|
codegen Generate code from Prismic models
|
|
44
|
-
docs Fetch documentation from Prismic
|
|
44
|
+
docs Fetch and list documentation from Prismic
|
|
45
45
|
preview Manage preview configurations
|
|
46
46
|
token Manage API tokens in a repository
|
|
47
47
|
webhook Manage webhooks in a repository
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
export type FieldPath =
|
|
2
|
+
| { type: "top-level"; fieldId: string }
|
|
3
|
+
| { type: "nested"; groupId: string; nestedFieldId: string };
|
|
4
|
+
|
|
5
|
+
export function parseFieldPath(fieldId: string): FieldPath {
|
|
6
|
+
const parts = fieldId.split(".");
|
|
7
|
+
|
|
8
|
+
if (parts.length === 1) {
|
|
9
|
+
return { type: "top-level", fieldId };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (parts.length === 2) {
|
|
13
|
+
return { type: "nested", groupId: parts[0], nestedFieldId: parts[1] };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// More than 2 parts means nested groups which aren't supported
|
|
17
|
+
return { type: "nested", groupId: parts[0], nestedFieldId: parts.slice(1).join(".") };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export type GroupFieldResult =
|
|
21
|
+
| { ok: true; group: { config: { fields: Record<string, unknown> } } }
|
|
22
|
+
| { ok: false; error: string };
|
|
23
|
+
|
|
24
|
+
export function isGroupField(field: unknown): field is { type: "Group"; config: { fields: Record<string, unknown> } } {
|
|
25
|
+
return (
|
|
26
|
+
typeof field === "object" &&
|
|
27
|
+
field !== null &&
|
|
28
|
+
"type" in field &&
|
|
29
|
+
field.type === "Group" &&
|
|
30
|
+
"config" in field &&
|
|
31
|
+
typeof field.config === "object" &&
|
|
32
|
+
field.config !== null &&
|
|
33
|
+
"fields" in field.config
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function findGroupInTab(
|
|
38
|
+
tabFields: Record<string, unknown>,
|
|
39
|
+
groupId: string,
|
|
40
|
+
tabName: string,
|
|
41
|
+
): GroupFieldResult {
|
|
42
|
+
const field = tabFields[groupId];
|
|
43
|
+
|
|
44
|
+
if (!field) {
|
|
45
|
+
return { ok: false, error: `Group "${groupId}" not found in tab "${tabName}"` };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!isGroupField(field)) {
|
|
49
|
+
return { ok: false, error: `Field "${groupId}" is not a group` };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return { ok: true, group: field };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function findGroupInVariation(
|
|
56
|
+
primary: Record<string, unknown>,
|
|
57
|
+
groupId: string,
|
|
58
|
+
variationId: string,
|
|
59
|
+
): GroupFieldResult {
|
|
60
|
+
const field = primary[groupId];
|
|
61
|
+
|
|
62
|
+
if (!field) {
|
|
63
|
+
return { ok: false, error: `Group "${groupId}" not found in variation "${variationId}"` };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!isGroupField(field)) {
|
|
67
|
+
return { ok: false, error: `Field "${groupId}" is not a group` };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { ok: true, group: field };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function validateNestedFieldPath(fieldPath: FieldPath): { ok: true } | { ok: false; error: string } {
|
|
74
|
+
if (fieldPath.type === "nested" && fieldPath.nestedFieldId.includes(".")) {
|
|
75
|
+
return {
|
|
76
|
+
ok: false,
|
|
77
|
+
error: `Nested groups not supported. Use: group.field`,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return { ok: true };
|
|
81
|
+
}
|
|
@@ -6,6 +6,7 @@ import * as v from "valibot";
|
|
|
6
6
|
|
|
7
7
|
import { buildTypes } from "./codegen-types";
|
|
8
8
|
import { findUpward } from "./lib/file";
|
|
9
|
+
import { findGroupInTab, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
|
|
9
10
|
import { type Framework, detectFrameworkInfo } from "./lib/framework";
|
|
10
11
|
import { stringify } from "./lib/json";
|
|
11
12
|
import { humanReadable } from "./lib/string";
|
|
@@ -100,6 +101,15 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
|
|
|
100
101
|
return;
|
|
101
102
|
}
|
|
102
103
|
|
|
104
|
+
// Parse and validate field path
|
|
105
|
+
const fieldPath = parseFieldPath(fieldId);
|
|
106
|
+
const pathValidation = validateNestedFieldPath(fieldPath);
|
|
107
|
+
if (!pathValidation.ok) {
|
|
108
|
+
console.error(pathValidation.error);
|
|
109
|
+
process.exitCode = 1;
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
// Find the page type file
|
|
104
114
|
const projectRoot = await findUpward("package.json");
|
|
105
115
|
if (!projectRoot) {
|
|
@@ -146,20 +156,11 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
|
|
|
146
156
|
model.json[targetTab] = {};
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
// Check if field already exists in any tab
|
|
150
|
-
for (const [tabName, tabFields] of Object.entries(model.json)) {
|
|
151
|
-
if (tabFields[fieldId]) {
|
|
152
|
-
console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
|
|
153
|
-
process.exitCode = 1;
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
159
|
// Build field definition
|
|
159
160
|
const fieldDefinition: BooleanField = {
|
|
160
161
|
type: "Boolean",
|
|
161
162
|
config: {
|
|
162
|
-
label: label ?? humanReadable(fieldId),
|
|
163
|
+
label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
|
|
163
164
|
...(defaultValue && { default_value: true }),
|
|
164
165
|
...(trueLabel && { placeholder_true: trueLabel }),
|
|
165
166
|
...(falseLabel && { placeholder_false: falseLabel }),
|
|
@@ -167,7 +168,36 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
|
|
|
167
168
|
};
|
|
168
169
|
|
|
169
170
|
// Add field to model
|
|
170
|
-
|
|
171
|
+
if (fieldPath.type === "nested") {
|
|
172
|
+
const groupResult = findGroupInTab(model.json[targetTab], fieldPath.groupId, targetTab);
|
|
173
|
+
if (!groupResult.ok) {
|
|
174
|
+
console.error(groupResult.error);
|
|
175
|
+
process.exitCode = 1;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
|
|
179
|
+
console.error(`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`);
|
|
180
|
+
process.exitCode = 1;
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
|
|
184
|
+
} else {
|
|
185
|
+
for (const [tabName, tabFields] of Object.entries(model.json)) {
|
|
186
|
+
if (tabFields[fieldId]) {
|
|
187
|
+
console.error(`Field "${fieldId}" already exists in tab "${tabName}"`);
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
for (const [groupFieldId, groupField] of Object.entries(tabFields)) {
|
|
192
|
+
if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
|
|
193
|
+
console.error(`Field "${fieldId}" already exists in group "${groupFieldId}" in tab "${tabName}"`);
|
|
194
|
+
process.exitCode = 1;
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
model.json[targetTab][fieldId] = fieldDefinition;
|
|
200
|
+
}
|
|
171
201
|
|
|
172
202
|
// Write updated model
|
|
173
203
|
try {
|
|
@@ -182,7 +212,11 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
|
|
|
182
212
|
return;
|
|
183
213
|
}
|
|
184
214
|
|
|
185
|
-
|
|
215
|
+
if (fieldPath.type === "nested") {
|
|
216
|
+
console.info(`Added field "${fieldPath.nestedFieldId}" (Boolean) to group "${fieldPath.groupId}" in ${typeId}`);
|
|
217
|
+
} else {
|
|
218
|
+
console.info(`Added field "${fieldId}" (Boolean) to "${targetTab}" tab in ${typeId}`);
|
|
219
|
+
}
|
|
186
220
|
|
|
187
221
|
try {
|
|
188
222
|
await buildTypes({ output: types });
|
|
@@ -198,7 +232,7 @@ export async function pageTypeAddFieldBoolean(): Promise<void> {
|
|
|
198
232
|
if (frameworkInfo?.framework) {
|
|
199
233
|
const docsPath = getDocsPath(frameworkInfo.framework);
|
|
200
234
|
console.info(
|
|
201
|
-
` Run \`prismic docs ${docsPath}#write-page-components\` to learn how to implement a page file`,
|
|
235
|
+
` Run \`prismic docs fetch ${docsPath}#write-page-components\` to learn how to implement a page file`,
|
|
202
236
|
);
|
|
203
237
|
}
|
|
204
238
|
}
|