@angeloashmore/prismic-cli-poc 0.0.0-pr.8.b80fefa → 0.0.0-pr.9.5366ece
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 +302 -168
- 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 +47 -21
- 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/index.ts +5 -0
- package/src/lib/field-path.ts +81 -0
- package/src/page-type-add-field-boolean.ts +66 -13
- package/src/page-type-add-field-color.ts +66 -13
- package/src/page-type-add-field-date.ts +66 -13
- package/src/page-type-add-field-embed.ts +66 -13
- package/src/page-type-add-field-geo-point.ts +66 -13
- package/src/page-type-add-field-group.ts +198 -0
- package/src/page-type-add-field-image.ts +66 -13
- package/src/page-type-add-field-key-text.ts +66 -13
- package/src/page-type-add-field-link.ts +66 -13
- package/src/page-type-add-field-number.ts +66 -13
- package/src/page-type-add-field-rich-text.ts +66 -13
- package/src/page-type-add-field-select.ts +67 -22
- package/src/page-type-add-field-timestamp.ts +66 -13
- package/src/page-type-add-field-uid.ts +37 -1
- package/src/page-type-add-field.ts +5 -0
- package/src/page-type-create.ts +25 -0
- package/src/repo-create.ts +59 -0
- package/src/skill-install.ts +177 -0
- package/src/skill-uninstall.ts +85 -0
- package/src/skill.ts +50 -0
- package/src/slice-add-field-boolean.ts +90 -16
- package/src/slice-add-field-color.ts +90 -16
- package/src/slice-add-field-date.ts +90 -16
- package/src/slice-add-field-embed.ts +90 -16
- package/src/slice-add-field-geo-point.ts +90 -16
- package/src/slice-add-field-group.ts +191 -0
- package/src/slice-add-field-image.ts +90 -16
- package/src/slice-add-field-key-text.ts +90 -16
- package/src/slice-add-field-link.ts +90 -16
- package/src/slice-add-field-number.ts +90 -16
- package/src/slice-add-field-rich-text.ts +90 -16
- package/src/slice-add-field-select.ts +91 -25
- package/src/slice-add-field-timestamp.ts +90 -16
- package/src/slice-add-field.ts +5 -0
- package/src/slice-create.ts +66 -5
- package/src/slice-set-screenshot.ts +235 -0
- package/src/slice-view.ts +3 -0
- package/src/slice.ts +5 -0
- package/src/status.ts +164 -124
package/src/repo-create.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { parseArgs } from "node:util";
|
|
|
2
2
|
|
|
3
3
|
import { isAuthenticated, readHost } from "./lib/auth";
|
|
4
4
|
import { createConfig, readConfig, updateConfig } from "./lib/config";
|
|
5
|
+
import { type Framework, detectFrameworkInfo, getClientFilePath } from "./lib/framework";
|
|
5
6
|
import { stringify } from "./lib/json";
|
|
6
7
|
import { ForbiddenRequestError, request } from "./lib/request";
|
|
7
8
|
import { getRepoUrl } from "./lib/url";
|
|
@@ -31,6 +32,26 @@ LEARN MORE
|
|
|
31
32
|
|
|
32
33
|
const DOMAIN_REGEX = /^[a-zA-Z0-9][-a-zA-Z0-9]{2,}[a-zA-Z0-9]$/;
|
|
33
34
|
|
|
35
|
+
function getDocsPath(framework: Framework): string {
|
|
36
|
+
switch (framework) {
|
|
37
|
+
case "next":
|
|
38
|
+
return "nextjs/with-cli";
|
|
39
|
+
case "nuxt":
|
|
40
|
+
return "nuxt/with-cli";
|
|
41
|
+
case "sveltekit":
|
|
42
|
+
return "sveltekit/with-cli";
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function getClientSetupAnchor(framework: Framework): string {
|
|
47
|
+
switch (framework) {
|
|
48
|
+
case "nuxt":
|
|
49
|
+
return "#configure-the-modules-prismic-client";
|
|
50
|
+
default:
|
|
51
|
+
return "#set-up-a-prismic-client";
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
34
55
|
export async function repoCreate(): Promise<void> {
|
|
35
56
|
const {
|
|
36
57
|
values: { help, name, "no-config": noConfig, replace },
|
|
@@ -81,6 +102,23 @@ export async function repoCreate(): Promise<void> {
|
|
|
81
102
|
return;
|
|
82
103
|
}
|
|
83
104
|
|
|
105
|
+
// Check if domain is available
|
|
106
|
+
const available = await checkDomainAvailable(domain);
|
|
107
|
+
if (!available.ok) {
|
|
108
|
+
if (available.error instanceof ForbiddenRequestError) {
|
|
109
|
+
handleUnauthenticated();
|
|
110
|
+
} else {
|
|
111
|
+
console.error(`Failed to check domain availability: ${stringify(available.error)}`);
|
|
112
|
+
process.exitCode = 1;
|
|
113
|
+
}
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (!available.value) {
|
|
117
|
+
console.error(`Repository name "${domain}" is already taken.`);
|
|
118
|
+
process.exitCode = 1;
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
|
|
84
122
|
const response = await createRepository(domain, name);
|
|
85
123
|
if (!response.ok) {
|
|
86
124
|
if (response.error instanceof ForbiddenRequestError) {
|
|
@@ -113,6 +151,27 @@ export async function repoCreate(): Promise<void> {
|
|
|
113
151
|
|
|
114
152
|
console.info(`Repository created: ${domain}`);
|
|
115
153
|
console.info(`URL: ${await getRepoUrl(domain)}`);
|
|
154
|
+
|
|
155
|
+
// Print framework-specific next steps
|
|
156
|
+
const frameworkInfo = await detectFrameworkInfo();
|
|
157
|
+
if (frameworkInfo?.framework) {
|
|
158
|
+
const docsPath = getDocsPath(frameworkInfo.framework);
|
|
159
|
+
const anchor = getClientSetupAnchor(frameworkInfo.framework);
|
|
160
|
+
const clientFile = getClientFilePath(frameworkInfo);
|
|
161
|
+
const fileDesc = clientFile ? `creating ${clientFile}` : "configuring Prismic";
|
|
162
|
+
console.info();
|
|
163
|
+
console.info(`Next: Run \`prismic docs ${docsPath}${anchor}\` for instructions on ${fileDesc}`);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function checkDomainAvailable(domain: string) {
|
|
168
|
+
const url = new URL(`/app/dashboard/repositories/${domain}/exists`, await readHost());
|
|
169
|
+
const response = await request<string>(url);
|
|
170
|
+
if (!response.ok) {
|
|
171
|
+
return response;
|
|
172
|
+
}
|
|
173
|
+
// Endpoint returns "false" when repository exists, "true" when available
|
|
174
|
+
return { ok: true as const, value: response.value === "true" };
|
|
116
175
|
}
|
|
117
176
|
|
|
118
177
|
async function createRepository(domain: string, name = domain) {
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
|
|
6
|
+
import { exists } from "./lib/file";
|
|
7
|
+
import { appendTrailingSlash } from "./lib/url";
|
|
8
|
+
|
|
9
|
+
const HELP = `
|
|
10
|
+
Install the Prismic skill into supported global AI tool skill directories.
|
|
11
|
+
|
|
12
|
+
USAGE
|
|
13
|
+
prismic skill install [flags]
|
|
14
|
+
|
|
15
|
+
FLAGS
|
|
16
|
+
-h, --help Show help for command
|
|
17
|
+
|
|
18
|
+
LEARN MORE
|
|
19
|
+
This command currently installs to global/user tool directories only.
|
|
20
|
+
`.trim();
|
|
21
|
+
|
|
22
|
+
const SKILL_ID = "prismic";
|
|
23
|
+
const SKILL_FILENAME = "SKILL.md";
|
|
24
|
+
|
|
25
|
+
export const PRISMIC_SKILL_TEMPLATE = `
|
|
26
|
+
---
|
|
27
|
+
name: prismic
|
|
28
|
+
description: Use when a task involves Prismic setup, repository configuration, content modeling, content type or slice changes, localization, previews, API tokens, webhooks, syncing local models, or reading Prismic documentation.
|
|
29
|
+
allowed-tools: Bash(prismic *)
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
# Prismic Workflows
|
|
33
|
+
|
|
34
|
+
For Prismic-related tasks, use the \`prismic\` CLI first.
|
|
35
|
+
|
|
36
|
+
1. Discover capabilities with \`prismic --help\`.
|
|
37
|
+
2. Inspect details with \`prismic <command> --help\`.
|
|
38
|
+
3. Use \`prismic docs <path>\` when documentation is needed.
|
|
39
|
+
4. Prefer CLI workflows over direct API/manual changes.
|
|
40
|
+
5. If the CLI does not support a required operation, state that explicitly, then use the next-best fallback.
|
|
41
|
+
`.trim();
|
|
42
|
+
|
|
43
|
+
export type SkillInstallTarget = {
|
|
44
|
+
tool: string;
|
|
45
|
+
baseDir: URL;
|
|
46
|
+
skillsDir: URL;
|
|
47
|
+
skillDir: URL;
|
|
48
|
+
skillFile: URL;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export async function findGlobalSkillInstallTargets(config?: {
|
|
52
|
+
homeDir?: string;
|
|
53
|
+
}): Promise<SkillInstallTarget[]> {
|
|
54
|
+
const homeURL = appendTrailingSlash(pathToFileURL(config?.homeDir ?? homedir()));
|
|
55
|
+
const codexBaseURL = new URL(".codex/", homeURL);
|
|
56
|
+
|
|
57
|
+
const candidates: SkillInstallTarget[] = [
|
|
58
|
+
createTarget("Claude", new URL(".claude/", homeURL), new URL(".claude/skills/", homeURL)),
|
|
59
|
+
createTarget("Cursor", new URL(".cursor/", homeURL), new URL(".cursor/skills/", homeURL)),
|
|
60
|
+
createTarget("Gemini", new URL(".gemini/", homeURL), new URL(".gemini/skills/", homeURL)),
|
|
61
|
+
createTarget(
|
|
62
|
+
"OpenCode",
|
|
63
|
+
new URL(".config/opencode/", homeURL),
|
|
64
|
+
new URL(".config/opencode/skill/", homeURL),
|
|
65
|
+
),
|
|
66
|
+
createTarget("Codex", codexBaseURL, new URL("skills/", codexBaseURL)),
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const targets: SkillInstallTarget[] = [];
|
|
70
|
+
for (const target of candidates) {
|
|
71
|
+
if (await exists(target.baseDir)) {
|
|
72
|
+
targets.push(target);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return targets;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function skillInstall(): Promise<void> {
|
|
80
|
+
const {
|
|
81
|
+
values: { help },
|
|
82
|
+
} = parseArgs({
|
|
83
|
+
args: process.argv.slice(4), // skip: node, script, "skill", "install"
|
|
84
|
+
options: {
|
|
85
|
+
help: { type: "boolean", short: "h" },
|
|
86
|
+
},
|
|
87
|
+
allowPositionals: true,
|
|
88
|
+
strict: false,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (help) {
|
|
92
|
+
console.info(HELP);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const targets = await findGlobalSkillInstallTargets();
|
|
97
|
+
|
|
98
|
+
if (targets.length === 0) {
|
|
99
|
+
console.error(
|
|
100
|
+
"No supported global AI tool directories were detected (Claude, Cursor, Gemini, OpenCode, Codex).",
|
|
101
|
+
);
|
|
102
|
+
process.exitCode = 1;
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const conflicts: SkillInstallTarget[] = [];
|
|
107
|
+
conflicts.push(...(await findExistingSkillConflicts(targets)));
|
|
108
|
+
|
|
109
|
+
if (conflicts.length > 0) {
|
|
110
|
+
console.error("Skill already exists in one or more targets:");
|
|
111
|
+
for (const conflict of conflicts) {
|
|
112
|
+
console.error(`- ${fileURLToPath(conflict.skillFile)}`);
|
|
113
|
+
}
|
|
114
|
+
console.error("Nothing was installed. Remove existing files and retry.");
|
|
115
|
+
process.exitCode = 1;
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const installResult = await installSkillTemplateToTargets(PRISMIC_SKILL_TEMPLATE, targets);
|
|
120
|
+
if (!installResult.ok) {
|
|
121
|
+
console.error(`Failed to install skill for ${installResult.target.tool}: ${installResult.error}`);
|
|
122
|
+
console.error(`Path: ${fileURLToPath(installResult.target.skillFile)}`);
|
|
123
|
+
process.exitCode = 1;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const installedPaths = installResult.installedPaths;
|
|
127
|
+
|
|
128
|
+
for (const installedPath of installedPaths) {
|
|
129
|
+
console.info(`Installed: ${installedPath}`);
|
|
130
|
+
}
|
|
131
|
+
console.info(`Installed ${installedPaths.length} Prismic skill file(s).`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function findExistingSkillConflicts(
|
|
135
|
+
targets: SkillInstallTarget[],
|
|
136
|
+
): Promise<SkillInstallTarget[]> {
|
|
137
|
+
const conflicts: SkillInstallTarget[] = [];
|
|
138
|
+
for (const target of targets) {
|
|
139
|
+
if (await exists(target.skillFile)) {
|
|
140
|
+
conflicts.push(target);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return conflicts;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export async function installSkillTemplateToTargets(
|
|
147
|
+
template: string,
|
|
148
|
+
targets: SkillInstallTarget[],
|
|
149
|
+
): Promise<
|
|
150
|
+
| { ok: true; installedPaths: string[] }
|
|
151
|
+
| { ok: false; target: SkillInstallTarget; error: string }
|
|
152
|
+
> {
|
|
153
|
+
const installedPaths: string[] = [];
|
|
154
|
+
for (const target of targets) {
|
|
155
|
+
try {
|
|
156
|
+
await mkdir(target.skillDir, { recursive: true });
|
|
157
|
+
await writeFile(target.skillFile, template);
|
|
158
|
+
installedPaths.push(fileURLToPath(target.skillFile));
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
161
|
+
return { ok: false, target, error: message };
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return { ok: true, installedPaths };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createTarget(tool: string, baseDir: URL, skillsDir: URL): SkillInstallTarget {
|
|
169
|
+
const skillDir = new URL(`${SKILL_ID}/`, skillsDir);
|
|
170
|
+
return {
|
|
171
|
+
tool,
|
|
172
|
+
baseDir,
|
|
173
|
+
skillsDir,
|
|
174
|
+
skillDir,
|
|
175
|
+
skillFile: new URL(SKILL_FILENAME, skillDir),
|
|
176
|
+
};
|
|
177
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { rm, rmdir } from "node:fs/promises";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
|
|
5
|
+
import { exists } from "./lib/file";
|
|
6
|
+
import { findGlobalSkillInstallTargets } from "./skill-install";
|
|
7
|
+
|
|
8
|
+
const HELP = `
|
|
9
|
+
Uninstall the Prismic skill from supported global AI tool skill directories.
|
|
10
|
+
|
|
11
|
+
USAGE
|
|
12
|
+
prismic skill uninstall [flags]
|
|
13
|
+
|
|
14
|
+
FLAGS
|
|
15
|
+
-h, --help Show help for command
|
|
16
|
+
|
|
17
|
+
LEARN MORE
|
|
18
|
+
This command currently uninstalls from global/user tool directories only.
|
|
19
|
+
`.trim();
|
|
20
|
+
|
|
21
|
+
export async function skillUninstall(): Promise<void> {
|
|
22
|
+
const {
|
|
23
|
+
values: { help },
|
|
24
|
+
} = parseArgs({
|
|
25
|
+
args: process.argv.slice(4), // skip: node, script, "skill", "uninstall"
|
|
26
|
+
options: {
|
|
27
|
+
help: { type: "boolean", short: "h" },
|
|
28
|
+
},
|
|
29
|
+
allowPositionals: true,
|
|
30
|
+
strict: false,
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
if (help) {
|
|
34
|
+
console.info(HELP);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const targets = await findGlobalSkillInstallTargets();
|
|
39
|
+
|
|
40
|
+
const removedSkillFiles: string[] = [];
|
|
41
|
+
|
|
42
|
+
for (const target of targets) {
|
|
43
|
+
if (!(await exists(target.skillFile))) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
await rm(target.skillFile);
|
|
49
|
+
removedSkillFiles.push(fileURLToPath(target.skillFile));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
52
|
+
console.error(`Failed to remove skill for ${target.tool}: ${message}`);
|
|
53
|
+
console.error(`Path: ${fileURLToPath(target.skillFile)}`);
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
await rmdir(target.skillDir);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (error && typeof error === "object" && "code" in error) {
|
|
62
|
+
const code = String(error.code);
|
|
63
|
+
if (code === "ENOTEMPTY" || code === "ENOENT") {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
console.error(`Failed to clean up skill directory for ${target.tool}: ${message}`);
|
|
70
|
+
console.error(`Path: ${fileURLToPath(target.skillDir)}`);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (removedSkillFiles.length === 0) {
|
|
77
|
+
console.info("No Prismic skill installation found. Nothing to uninstall.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const removedSkillFile of removedSkillFiles) {
|
|
82
|
+
console.info(`Removed: ${removedSkillFile}`);
|
|
83
|
+
}
|
|
84
|
+
console.info(`Uninstalled ${removedSkillFiles.length} Prismic skill file(s).`);
|
|
85
|
+
}
|
package/src/skill.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
|
|
3
|
+
import { skillInstall } from "./skill-install";
|
|
4
|
+
import { skillUninstall } from "./skill-uninstall";
|
|
5
|
+
|
|
6
|
+
const HELP = `
|
|
7
|
+
Manage Prismic skills in supported AI tool directories.
|
|
8
|
+
|
|
9
|
+
USAGE
|
|
10
|
+
prismic skill <command> [flags]
|
|
11
|
+
|
|
12
|
+
COMMANDS
|
|
13
|
+
install Install the Prismic skill into detected global skill directories
|
|
14
|
+
uninstall Uninstall the Prismic skill from detected global skill directories
|
|
15
|
+
|
|
16
|
+
FLAGS
|
|
17
|
+
-h, --help Show help for command
|
|
18
|
+
|
|
19
|
+
LEARN MORE
|
|
20
|
+
Use \`prismic skill <command> --help\` for more information about a command.
|
|
21
|
+
`.trim();
|
|
22
|
+
|
|
23
|
+
export async function skill(): Promise<void> {
|
|
24
|
+
const {
|
|
25
|
+
positionals: [subcommand],
|
|
26
|
+
} = parseArgs({
|
|
27
|
+
args: process.argv.slice(3), // skip: node, script, "skill"
|
|
28
|
+
options: {
|
|
29
|
+
help: { type: "boolean", short: "h" },
|
|
30
|
+
},
|
|
31
|
+
allowPositionals: true,
|
|
32
|
+
strict: false,
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
switch (subcommand) {
|
|
36
|
+
case "install":
|
|
37
|
+
await skillInstall();
|
|
38
|
+
break;
|
|
39
|
+
case "uninstall":
|
|
40
|
+
await skillUninstall();
|
|
41
|
+
break;
|
|
42
|
+
default: {
|
|
43
|
+
if (subcommand) {
|
|
44
|
+
console.error(`Unknown skill subcommand: ${subcommand}\n`);
|
|
45
|
+
process.exitCode = 1;
|
|
46
|
+
}
|
|
47
|
+
console.info(HELP);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -4,6 +4,8 @@ import { writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
|
|
6
6
|
import { buildTypes } from "./codegen-types";
|
|
7
|
+
import { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
|
|
8
|
+
import { type Framework, detectFrameworkInfo } from "./lib/framework";
|
|
7
9
|
import { stringify } from "./lib/json";
|
|
8
10
|
import { findSliceModel } from "./lib/slice";
|
|
9
11
|
import { humanReadable } from "./lib/string";
|
|
@@ -33,6 +35,28 @@ EXAMPLES
|
|
|
33
35
|
prismic slice add-field boolean product available --true-label "In Stock" --false-label "Out of Stock"
|
|
34
36
|
`.trim();
|
|
35
37
|
|
|
38
|
+
function getDocsPath(framework: Framework): string {
|
|
39
|
+
switch (framework) {
|
|
40
|
+
case "next":
|
|
41
|
+
return "nextjs/with-cli";
|
|
42
|
+
case "nuxt":
|
|
43
|
+
return "nuxt/with-cli";
|
|
44
|
+
case "sveltekit":
|
|
45
|
+
return "sveltekit/with-cli";
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getWriteComponentsAnchor(framework: Framework): string {
|
|
50
|
+
switch (framework) {
|
|
51
|
+
case "nuxt":
|
|
52
|
+
return "#write-vue-components";
|
|
53
|
+
case "sveltekit":
|
|
54
|
+
return "#write-svelte-components";
|
|
55
|
+
default:
|
|
56
|
+
return "#write-react-components";
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
36
60
|
export async function sliceAddFieldBoolean(): Promise<void> {
|
|
37
61
|
const {
|
|
38
62
|
values: {
|
|
@@ -78,6 +102,15 @@ export async function sliceAddFieldBoolean(): Promise<void> {
|
|
|
78
102
|
return;
|
|
79
103
|
}
|
|
80
104
|
|
|
105
|
+
// Parse and validate field path
|
|
106
|
+
const fieldPath = parseFieldPath(fieldId);
|
|
107
|
+
const pathValidation = validateNestedFieldPath(fieldPath);
|
|
108
|
+
if (!pathValidation.ok) {
|
|
109
|
+
console.error(pathValidation.error);
|
|
110
|
+
process.exitCode = 1;
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
81
114
|
// Find the slice model
|
|
82
115
|
const result = await findSliceModel(sliceId);
|
|
83
116
|
if (!result.ok) {
|
|
@@ -113,28 +146,55 @@ export async function sliceAddFieldBoolean(): Promise<void> {
|
|
|
113
146
|
targetVariation.primary = {};
|
|
114
147
|
}
|
|
115
148
|
|
|
116
|
-
// Check if field already exists in any variation
|
|
117
|
-
for (const v of model.variations) {
|
|
118
|
-
if (v.primary?.[fieldId]) {
|
|
119
|
-
console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
|
|
120
|
-
process.exitCode = 1;
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
149
|
// Build field definition
|
|
126
150
|
const fieldDefinition: BooleanField = {
|
|
127
151
|
type: "Boolean",
|
|
128
152
|
config: {
|
|
129
|
-
label: label ?? humanReadable(fieldId),
|
|
153
|
+
label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
|
|
130
154
|
...(defaultValue && { default_value: true }),
|
|
131
155
|
...(trueLabel && { placeholder_true: trueLabel }),
|
|
132
156
|
...(falseLabel && { placeholder_false: falseLabel }),
|
|
133
157
|
},
|
|
134
158
|
};
|
|
135
159
|
|
|
136
|
-
// Add field to variation
|
|
137
|
-
|
|
160
|
+
// Add field to variation (with nested field support)
|
|
161
|
+
if (fieldPath.type === "nested") {
|
|
162
|
+
const groupResult = findGroupInVariation(targetVariation.primary, fieldPath.groupId, targetVariation.id);
|
|
163
|
+
if (!groupResult.ok) {
|
|
164
|
+
console.error(groupResult.error);
|
|
165
|
+
process.exitCode = 1;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Check if nested field already exists
|
|
169
|
+
if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
|
|
170
|
+
console.error(
|
|
171
|
+
`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`,
|
|
172
|
+
);
|
|
173
|
+
process.exitCode = 1;
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
|
|
177
|
+
} else {
|
|
178
|
+
// Check if field already exists in any variation (at top level or in groups)
|
|
179
|
+
for (const v of model.variations) {
|
|
180
|
+
if (v.primary?.[fieldId]) {
|
|
181
|
+
console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
|
|
182
|
+
process.exitCode = 1;
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
// Also check inside groups
|
|
186
|
+
for (const [groupFieldId, groupField] of Object.entries(v.primary ?? {})) {
|
|
187
|
+
if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
|
|
188
|
+
console.error(
|
|
189
|
+
`Field "${fieldId}" already exists in group "${groupFieldId}" in variation "${v.id}"`,
|
|
190
|
+
);
|
|
191
|
+
process.exitCode = 1;
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
targetVariation.primary[fieldId] = fieldDefinition;
|
|
197
|
+
}
|
|
138
198
|
|
|
139
199
|
// Write updated model
|
|
140
200
|
try {
|
|
@@ -149,9 +209,15 @@ export async function sliceAddFieldBoolean(): Promise<void> {
|
|
|
149
209
|
return;
|
|
150
210
|
}
|
|
151
211
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
212
|
+
if (fieldPath.type === "nested") {
|
|
213
|
+
console.info(
|
|
214
|
+
`Added field "${fieldPath.nestedFieldId}" (Boolean) to group "${fieldPath.groupId}" in ${sliceId}`,
|
|
215
|
+
);
|
|
216
|
+
} else {
|
|
217
|
+
console.info(
|
|
218
|
+
`Added field "${fieldId}" (Boolean) to "${targetVariation.id}" variation in ${sliceId}`,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
155
221
|
|
|
156
222
|
try {
|
|
157
223
|
await buildTypes({ output: types });
|
|
@@ -162,5 +228,13 @@ export async function sliceAddFieldBoolean(): Promise<void> {
|
|
|
162
228
|
|
|
163
229
|
console.info();
|
|
164
230
|
console.info("Next: Add more fields with `prismic slice add-field`");
|
|
165
|
-
|
|
231
|
+
|
|
232
|
+
const frameworkInfo = await detectFrameworkInfo();
|
|
233
|
+
if (frameworkInfo?.framework) {
|
|
234
|
+
const docsPath = getDocsPath(frameworkInfo.framework);
|
|
235
|
+
const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
|
|
236
|
+
console.info(
|
|
237
|
+
` Run \`prismic docs ${docsPath}${anchor}\` to learn how to implement the slice's component`,
|
|
238
|
+
);
|
|
239
|
+
}
|
|
166
240
|
}
|
|
@@ -4,6 +4,8 @@ import { writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { parseArgs } from "node:util";
|
|
5
5
|
|
|
6
6
|
import { buildTypes } from "./codegen-types";
|
|
7
|
+
import { findGroupInVariation, isGroupField, parseFieldPath, validateNestedFieldPath } from "./lib/field-path";
|
|
8
|
+
import { type Framework, detectFrameworkInfo } from "./lib/framework";
|
|
7
9
|
import { stringify } from "./lib/json";
|
|
8
10
|
import { findSliceModel } from "./lib/slice";
|
|
9
11
|
import { humanReadable } from "./lib/string";
|
|
@@ -31,6 +33,28 @@ EXAMPLES
|
|
|
31
33
|
prismic slice add-field color banner theme_color --variation "dark"
|
|
32
34
|
`.trim();
|
|
33
35
|
|
|
36
|
+
function getDocsPath(framework: Framework): string {
|
|
37
|
+
switch (framework) {
|
|
38
|
+
case "next":
|
|
39
|
+
return "nextjs/with-cli";
|
|
40
|
+
case "nuxt":
|
|
41
|
+
return "nuxt/with-cli";
|
|
42
|
+
case "sveltekit":
|
|
43
|
+
return "sveltekit/with-cli";
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function getWriteComponentsAnchor(framework: Framework): string {
|
|
48
|
+
switch (framework) {
|
|
49
|
+
case "nuxt":
|
|
50
|
+
return "#write-vue-components";
|
|
51
|
+
case "sveltekit":
|
|
52
|
+
return "#write-svelte-components";
|
|
53
|
+
default:
|
|
54
|
+
return "#write-react-components";
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
34
58
|
export async function sliceAddFieldColor(): Promise<void> {
|
|
35
59
|
const {
|
|
36
60
|
values: { help, variation, label, placeholder, types },
|
|
@@ -66,6 +90,15 @@ export async function sliceAddFieldColor(): Promise<void> {
|
|
|
66
90
|
return;
|
|
67
91
|
}
|
|
68
92
|
|
|
93
|
+
// Parse and validate field path
|
|
94
|
+
const fieldPath = parseFieldPath(fieldId);
|
|
95
|
+
const pathValidation = validateNestedFieldPath(fieldPath);
|
|
96
|
+
if (!pathValidation.ok) {
|
|
97
|
+
console.error(pathValidation.error);
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
69
102
|
// Find the slice model
|
|
70
103
|
const result = await findSliceModel(sliceId);
|
|
71
104
|
if (!result.ok) {
|
|
@@ -101,26 +134,53 @@ export async function sliceAddFieldColor(): Promise<void> {
|
|
|
101
134
|
targetVariation.primary = {};
|
|
102
135
|
}
|
|
103
136
|
|
|
104
|
-
// Check if field already exists in any variation
|
|
105
|
-
for (const v of model.variations) {
|
|
106
|
-
if (v.primary?.[fieldId]) {
|
|
107
|
-
console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
|
|
108
|
-
process.exitCode = 1;
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
137
|
// Build field definition
|
|
114
138
|
const fieldDefinition: Color = {
|
|
115
139
|
type: "Color",
|
|
116
140
|
config: {
|
|
117
|
-
label: label ?? humanReadable(fieldId),
|
|
141
|
+
label: label ?? humanReadable(fieldPath.type === "nested" ? fieldPath.nestedFieldId : fieldId),
|
|
118
142
|
...(placeholder && { placeholder }),
|
|
119
143
|
},
|
|
120
144
|
};
|
|
121
145
|
|
|
122
|
-
// Add field to variation
|
|
123
|
-
|
|
146
|
+
// Add field to variation (with nested field support)
|
|
147
|
+
if (fieldPath.type === "nested") {
|
|
148
|
+
const groupResult = findGroupInVariation(targetVariation.primary, fieldPath.groupId, targetVariation.id);
|
|
149
|
+
if (!groupResult.ok) {
|
|
150
|
+
console.error(groupResult.error);
|
|
151
|
+
process.exitCode = 1;
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
// Check if nested field already exists
|
|
155
|
+
if (groupResult.group.config.fields[fieldPath.nestedFieldId]) {
|
|
156
|
+
console.error(
|
|
157
|
+
`Field "${fieldPath.nestedFieldId}" already exists in group "${fieldPath.groupId}"`,
|
|
158
|
+
);
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
groupResult.group.config.fields[fieldPath.nestedFieldId] = fieldDefinition;
|
|
163
|
+
} else {
|
|
164
|
+
// Check if field already exists in any variation (at top level or in groups)
|
|
165
|
+
for (const v of model.variations) {
|
|
166
|
+
if (v.primary?.[fieldId]) {
|
|
167
|
+
console.error(`Field "${fieldId}" already exists in variation "${v.id}"`);
|
|
168
|
+
process.exitCode = 1;
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
// Also check inside groups
|
|
172
|
+
for (const [groupFieldId, groupField] of Object.entries(v.primary ?? {})) {
|
|
173
|
+
if (isGroupField(groupField) && groupField.config.fields[fieldId]) {
|
|
174
|
+
console.error(
|
|
175
|
+
`Field "${fieldId}" already exists in group "${groupFieldId}" in variation "${v.id}"`,
|
|
176
|
+
);
|
|
177
|
+
process.exitCode = 1;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
targetVariation.primary[fieldId] = fieldDefinition;
|
|
183
|
+
}
|
|
124
184
|
|
|
125
185
|
// Write updated model
|
|
126
186
|
try {
|
|
@@ -135,9 +195,15 @@ export async function sliceAddFieldColor(): Promise<void> {
|
|
|
135
195
|
return;
|
|
136
196
|
}
|
|
137
197
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
198
|
+
if (fieldPath.type === "nested") {
|
|
199
|
+
console.info(
|
|
200
|
+
`Added field "${fieldPath.nestedFieldId}" (Color) to group "${fieldPath.groupId}" in ${sliceId}`,
|
|
201
|
+
);
|
|
202
|
+
} else {
|
|
203
|
+
console.info(
|
|
204
|
+
`Added field "${fieldId}" (Color) to "${targetVariation.id}" variation in ${sliceId}`,
|
|
205
|
+
);
|
|
206
|
+
}
|
|
141
207
|
|
|
142
208
|
try {
|
|
143
209
|
await buildTypes({ output: types });
|
|
@@ -148,5 +214,13 @@ export async function sliceAddFieldColor(): Promise<void> {
|
|
|
148
214
|
|
|
149
215
|
console.info();
|
|
150
216
|
console.info("Next: Add more fields with `prismic slice add-field`");
|
|
151
|
-
|
|
217
|
+
|
|
218
|
+
const frameworkInfo = await detectFrameworkInfo();
|
|
219
|
+
if (frameworkInfo?.framework) {
|
|
220
|
+
const docsPath = getDocsPath(frameworkInfo.framework);
|
|
221
|
+
const anchor = getWriteComponentsAnchor(frameworkInfo.framework);
|
|
222
|
+
console.info(
|
|
223
|
+
` Run \`prismic docs ${docsPath}${anchor}\` to learn how to implement the slice's component`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
152
226
|
}
|