@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.
Files changed (54) hide show
  1. package/dist/index.mjs +229 -136
  2. package/package.json +1 -1
  3. package/src/custom-type-add-field-boolean.ts +49 -12
  4. package/src/custom-type-add-field-color.ts +46 -12
  5. package/src/custom-type-add-field-date.ts +46 -12
  6. package/src/custom-type-add-field-embed.ts +46 -12
  7. package/src/custom-type-add-field-geo-point.ts +46 -12
  8. package/src/custom-type-add-field-group.ts +179 -0
  9. package/src/custom-type-add-field-image.ts +46 -12
  10. package/src/custom-type-add-field-key-text.ts +46 -12
  11. package/src/custom-type-add-field-link.ts +46 -12
  12. package/src/custom-type-add-field-number.ts +46 -12
  13. package/src/custom-type-add-field-rich-text.ts +46 -12
  14. package/src/custom-type-add-field-select.ts +46 -12
  15. package/src/custom-type-add-field-timestamp.ts +46 -12
  16. package/src/custom-type-add-field-uid.ts +17 -0
  17. package/src/custom-type-add-field.ts +5 -0
  18. package/src/docs-fetch.ts +146 -0
  19. package/src/docs-list.ts +131 -0
  20. package/src/docs.ts +26 -121
  21. package/src/index.ts +1 -1
  22. package/src/lib/field-path.ts +81 -0
  23. package/src/page-type-add-field-boolean.ts +47 -13
  24. package/src/page-type-add-field-color.ts +47 -13
  25. package/src/page-type-add-field-date.ts +47 -13
  26. package/src/page-type-add-field-embed.ts +47 -13
  27. package/src/page-type-add-field-geo-point.ts +47 -13
  28. package/src/page-type-add-field-group.ts +198 -0
  29. package/src/page-type-add-field-image.ts +47 -13
  30. package/src/page-type-add-field-key-text.ts +47 -13
  31. package/src/page-type-add-field-link.ts +47 -13
  32. package/src/page-type-add-field-number.ts +47 -13
  33. package/src/page-type-add-field-rich-text.ts +47 -13
  34. package/src/page-type-add-field-select.ts +47 -13
  35. package/src/page-type-add-field-timestamp.ts +47 -13
  36. package/src/page-type-add-field-uid.ts +18 -1
  37. package/src/page-type-add-field.ts +5 -0
  38. package/src/page-type-create.ts +1 -1
  39. package/src/repo-create.ts +28 -1
  40. package/src/slice-add-field-boolean.ts +59 -16
  41. package/src/slice-add-field-color.ts +59 -16
  42. package/src/slice-add-field-date.ts +59 -16
  43. package/src/slice-add-field-embed.ts +59 -16
  44. package/src/slice-add-field-geo-point.ts +59 -16
  45. package/src/slice-add-field-group.ts +191 -0
  46. package/src/slice-add-field-image.ts +59 -16
  47. package/src/slice-add-field-key-text.ts +59 -16
  48. package/src/slice-add-field-link.ts +59 -16
  49. package/src/slice-add-field-number.ts +59 -16
  50. package/src/slice-add-field-rich-text.ts +59 -16
  51. package/src/slice-add-field-select.ts +59 -16
  52. package/src/slice-add-field-timestamp.ts +59 -16
  53. package/src/slice-add-field.ts +5 -0
  54. 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
+ }
@@ -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("&amp;", "&")
21
+ .replaceAll("&lt;", "<")
22
+ .replaceAll("&gt;", ">")
23
+ .replaceAll("&quot;", '"')
24
+ .replaceAll("&apos;", "'");
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 display documentation from Prismic's docs site.
6
+ Fetch and list documentation from Prismic's docs site.
5
7
 
6
8
  USAGE
7
- prismic docs <path> [flags]
9
+ prismic docs <command> [flags]
8
10
 
9
- ARGUMENTS
10
- path Documentation path with optional anchor (e.g., "nextjs" or "nextjs#set-up-a-prismic-client")
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
- Visit https://prismic.io/docs for the full documentation.
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: [pathArg],
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
- 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;
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
- model.json[targetTab][fieldId] = fieldDefinition;
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
- console.info(`Added field "${fieldId}" (Boolean) to "${targetTab}" tab in ${typeId}`);
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
  }