prismic 0.0.0-canary.2cfb4a8
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/LICENSE +202 -0
- package/README.md +69 -0
- package/dist/builders-hKD4IrLX-CSAFppyU.mjs +97 -0
- package/dist/framework-AxGiKSX9.mjs +17 -0
- package/dist/index.mjs +87 -0
- package/dist/nextjs-D4viImqE.mjs +312 -0
- package/dist/nuxt-CbBJIJw1.mjs +59 -0
- package/dist/sveltekit-BuNy6sKm.mjs +226 -0
- package/package.json +58 -0
- package/src/env.d.ts +12 -0
- package/src/framework/index.ts +399 -0
- package/src/framework/nextjs.templates.ts +426 -0
- package/src/framework/nextjs.ts +216 -0
- package/src/framework/nuxt.templates.ts +74 -0
- package/src/framework/nuxt.ts +250 -0
- package/src/framework/sveltekit.templates.ts +278 -0
- package/src/framework/sveltekit.ts +241 -0
- package/src/index.ts +90 -0
- package/src/init.ts +173 -0
- package/src/lib/auth.ts +200 -0
- package/src/lib/browser.ts +11 -0
- package/src/lib/config.ts +111 -0
- package/src/lib/custom-types-api.ts +385 -0
- package/src/lib/field-path.ts +81 -0
- package/src/lib/file.ts +49 -0
- package/src/lib/json.ts +3 -0
- package/src/lib/packageJson.ts +35 -0
- package/src/lib/profile.ts +39 -0
- package/src/lib/request.ts +116 -0
- package/src/lib/segment.ts +145 -0
- package/src/lib/sentry.ts +63 -0
- package/src/lib/string.ts +10 -0
- package/src/lib/url.ts +31 -0
- package/src/login.ts +45 -0
- package/src/logout.ts +36 -0
- package/src/sync.ts +259 -0
- package/src/whoami.ts +62 -0
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import type { SharedSlice } from "@prismicio/types-internal/lib/customtypes";
|
|
2
|
+
|
|
3
|
+
import { loadFile } from "magicast";
|
|
4
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
5
|
+
import { createRequire } from "node:module";
|
|
6
|
+
|
|
7
|
+
import type { Framework } from ".";
|
|
8
|
+
|
|
9
|
+
import { FrameworkAdapter } from ".";
|
|
10
|
+
import { exists } from "../lib/file";
|
|
11
|
+
import { getNpmPackageVersion } from "../lib/packageJson";
|
|
12
|
+
import { dedent } from "../lib/string";
|
|
13
|
+
import {
|
|
14
|
+
previewAPIRouteTemplate,
|
|
15
|
+
prismicIOFileTemplate,
|
|
16
|
+
rootLayoutTemplate,
|
|
17
|
+
sliceSimulatorPageTemplate,
|
|
18
|
+
sliceTemplate,
|
|
19
|
+
} from "./sveltekit.templates";
|
|
20
|
+
|
|
21
|
+
export class SvelteKitFramework extends FrameworkAdapter {
|
|
22
|
+
readonly id: Framework = "sveltekit";
|
|
23
|
+
|
|
24
|
+
async getDependencies(): Promise<Record<string, string>> {
|
|
25
|
+
return {
|
|
26
|
+
"@prismicio/client": `^${await getNpmPackageVersion("@prismicio/client")}`,
|
|
27
|
+
"@prismicio/svelte": `^${await getNpmPackageVersion("@prismicio/svelte")}`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async initProject(): Promise<void> {
|
|
32
|
+
await super.initProject();
|
|
33
|
+
|
|
34
|
+
await this.#createPrismicIOFile();
|
|
35
|
+
await this.#createSliceSimulatorPage();
|
|
36
|
+
await this.#createPreviewRouteMatcher();
|
|
37
|
+
await this.#createPreviewAPIRoute();
|
|
38
|
+
await this.#createPreviewRouteDirectory();
|
|
39
|
+
await this.#createRootLayoutServerFile();
|
|
40
|
+
await this.#createRootLayoutFile();
|
|
41
|
+
await this.#modifyViteConfig();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async createSliceComponent(
|
|
45
|
+
model: SharedSlice,
|
|
46
|
+
sliceDirectory: URL,
|
|
47
|
+
): Promise<{ componentPath: URL }> {
|
|
48
|
+
const componentPath = new URL("index.svelte", sliceDirectory);
|
|
49
|
+
const contents = sliceTemplate({
|
|
50
|
+
name: model.name,
|
|
51
|
+
typescript: await this.checkIsTypeScriptProject(),
|
|
52
|
+
version: await this.#getSvelteMajor(),
|
|
53
|
+
});
|
|
54
|
+
await writeFile(componentPath, contents);
|
|
55
|
+
return { componentPath };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getSliceImportPath(relativeDirectory: string): string {
|
|
59
|
+
return `./${relativeDirectory}/index.svelte`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async getDefaultSliceLibraryPath(projectRoot: URL): Promise<URL> {
|
|
63
|
+
return new URL("src/lib/slices/", projectRoot);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async getClientFilePath(): Promise<string | null> {
|
|
67
|
+
return "src/lib/prismicio.ts";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async getSlicesDirectoryPath(): Promise<string> {
|
|
71
|
+
return "src/lib/slices/";
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getSliceComponentExtensions(): string[] {
|
|
75
|
+
return [".svelte"];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async getRoutePath(route: string): Promise<{ path: string; extensions: string[] } | null> {
|
|
79
|
+
switch (route) {
|
|
80
|
+
case "/slice-simulator":
|
|
81
|
+
return { path: "src/routes/slice-simulator/+page", extensions: [".svelte"] };
|
|
82
|
+
case "/api/preview":
|
|
83
|
+
return { path: "src/routes/api/preview/+server", extensions: [".ts", ".js"] };
|
|
84
|
+
default:
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async #createPrismicIOFile(): Promise<void> {
|
|
90
|
+
const extension = await this.getJsFileExtension();
|
|
91
|
+
const projectRoot = await this.getProjectRoot();
|
|
92
|
+
const filePath = new URL(`src/lib/prismicio.${extension}`, projectRoot);
|
|
93
|
+
|
|
94
|
+
if (await exists(filePath)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const typescript = await this.checkIsTypeScriptProject();
|
|
99
|
+
const contents = prismicIOFileTemplate({ typescript });
|
|
100
|
+
await mkdir(new URL(".", filePath), { recursive: true });
|
|
101
|
+
await writeFile(filePath, contents);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async #createSliceSimulatorPage(): Promise<void> {
|
|
105
|
+
const projectRoot = await this.getProjectRoot();
|
|
106
|
+
const filePath = new URL("src/routes/slice-simulator/+page.svelte", projectRoot);
|
|
107
|
+
|
|
108
|
+
if (await exists(filePath)) {
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const contents = sliceSimulatorPageTemplate({
|
|
113
|
+
version: await this.#getSvelteMajor(),
|
|
114
|
+
});
|
|
115
|
+
await mkdir(new URL(".", filePath), { recursive: true });
|
|
116
|
+
await writeFile(filePath, contents);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async #createPreviewRouteMatcher(): Promise<void> {
|
|
120
|
+
const extension = await this.getJsFileExtension();
|
|
121
|
+
const projectRoot = await this.getProjectRoot();
|
|
122
|
+
const filePath = new URL(`src/params/preview.${extension}`, projectRoot);
|
|
123
|
+
|
|
124
|
+
if (await exists(filePath)) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const contents = dedent`
|
|
129
|
+
export function match(param) {
|
|
130
|
+
return param === 'preview';
|
|
131
|
+
}
|
|
132
|
+
`;
|
|
133
|
+
await mkdir(new URL(".", filePath), { recursive: true });
|
|
134
|
+
await writeFile(filePath, contents);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async #createPreviewAPIRoute(): Promise<void> {
|
|
138
|
+
const extension = await this.getJsFileExtension();
|
|
139
|
+
const projectRoot = await this.getProjectRoot();
|
|
140
|
+
const filePath = new URL(`src/routes/api/preview/+server.${extension}`, projectRoot);
|
|
141
|
+
|
|
142
|
+
if (await exists(filePath)) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const typescript = await this.checkIsTypeScriptProject();
|
|
147
|
+
const contents = previewAPIRouteTemplate({ typescript });
|
|
148
|
+
await mkdir(new URL(".", filePath), { recursive: true });
|
|
149
|
+
await writeFile(filePath, contents);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async #createPreviewRouteDirectory(): Promise<void> {
|
|
153
|
+
const projectRoot = await this.getProjectRoot();
|
|
154
|
+
const filePath = new URL("src/routes/[[preview=preview]]/README.md", projectRoot);
|
|
155
|
+
|
|
156
|
+
if (await exists(filePath)) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const contents = dedent`
|
|
161
|
+
This directory adds support for optional \`/preview\` routes. Do not remove this directory.
|
|
162
|
+
|
|
163
|
+
All routes within this directory will be served using the following URLs:
|
|
164
|
+
|
|
165
|
+
- \`/example-route\` (prerendered)
|
|
166
|
+
- \`/preview/example-route\` (server-rendered)
|
|
167
|
+
|
|
168
|
+
See <https://prismic.io/docs/svelte-preview> for more information.
|
|
169
|
+
`;
|
|
170
|
+
await mkdir(new URL(".", filePath), { recursive: true });
|
|
171
|
+
await writeFile(filePath, contents);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
async #createRootLayoutServerFile(): Promise<void> {
|
|
175
|
+
const extension = await this.getJsFileExtension();
|
|
176
|
+
const projectRoot = await this.getProjectRoot();
|
|
177
|
+
const filePath = new URL(`src/routes/+layout.server.${extension}`, projectRoot);
|
|
178
|
+
|
|
179
|
+
if (await exists(filePath)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const contents = dedent`
|
|
184
|
+
export const prerender = "auto";
|
|
185
|
+
`;
|
|
186
|
+
await mkdir(new URL(".", filePath), { recursive: true });
|
|
187
|
+
await writeFile(filePath, contents);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async #createRootLayoutFile(): Promise<void> {
|
|
191
|
+
const projectRoot = await this.getProjectRoot();
|
|
192
|
+
const filePath = new URL("src/routes/+layout.svelte", projectRoot);
|
|
193
|
+
|
|
194
|
+
if (await exists(filePath)) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const contents = rootLayoutTemplate({
|
|
199
|
+
version: await this.#getSvelteMajor(),
|
|
200
|
+
});
|
|
201
|
+
await mkdir(new URL(".", filePath), { recursive: true });
|
|
202
|
+
await writeFile(filePath, contents);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async #modifyViteConfig(): Promise<void> {
|
|
206
|
+
const projectRoot = await this.getProjectRoot();
|
|
207
|
+
let configUrl = new URL("vite.config.js", projectRoot);
|
|
208
|
+
if (!(await exists(configUrl))) {
|
|
209
|
+
configUrl = new URL("vite.config.ts", projectRoot);
|
|
210
|
+
}
|
|
211
|
+
if (!(await exists(configUrl))) {
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const filepath = configUrl.pathname;
|
|
216
|
+
const mod = await loadFile(filepath);
|
|
217
|
+
if (mod.exports.default.$type !== "function-call") {
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const config = mod.exports.default.$args[0];
|
|
222
|
+
config.server ??= {};
|
|
223
|
+
config.server.fs ??= {};
|
|
224
|
+
config.server.fs.allow ??= [];
|
|
225
|
+
if (!config.server.fs.allow.includes("./prismic.config.json")) {
|
|
226
|
+
config.server.fs.allow.push("./prismic.config.json");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const contents = mod.generate().code.replace(/\n\s*\n(?=\s*server:)/, "\n");
|
|
230
|
+
await writeFile(configUrl, contents);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async #getSvelteMajor(): Promise<number> {
|
|
234
|
+
const projectRoot = await this.getProjectRoot();
|
|
235
|
+
const require = createRequire(new URL("package.json", projectRoot));
|
|
236
|
+
const { version } = require("svelte/package.json");
|
|
237
|
+
const major = Number.parseInt(version.split(".")[0]);
|
|
238
|
+
if (Number.isNaN(major)) return Infinity;
|
|
239
|
+
return major;
|
|
240
|
+
}
|
|
241
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { parseArgs } from "node:util";
|
|
4
|
+
|
|
5
|
+
import packageJson from "../package.json" with { type: "json" };
|
|
6
|
+
import { init } from "./init";
|
|
7
|
+
import { initSegment, trackEnd, trackStart } from "./lib/segment";
|
|
8
|
+
import { captureError, setupSentry } from "./lib/sentry";
|
|
9
|
+
import { login } from "./login";
|
|
10
|
+
import { logout } from "./logout";
|
|
11
|
+
import { sync } from "./sync";
|
|
12
|
+
import { whoami } from "./whoami";
|
|
13
|
+
|
|
14
|
+
const HELP = `
|
|
15
|
+
Prismic CLI for managing repositories and configurations.
|
|
16
|
+
|
|
17
|
+
USAGE
|
|
18
|
+
prismic <command> [flags]
|
|
19
|
+
|
|
20
|
+
COMMANDS
|
|
21
|
+
init Initialize a Prismic project
|
|
22
|
+
sync Sync types and slices from Prismic
|
|
23
|
+
login Log in to Prismic
|
|
24
|
+
logout Log out of Prismic
|
|
25
|
+
whoami Show the currently logged in user
|
|
26
|
+
|
|
27
|
+
FLAGS
|
|
28
|
+
-v, --version Show CLI version
|
|
29
|
+
-h, --help Show help for command
|
|
30
|
+
|
|
31
|
+
LEARN MORE
|
|
32
|
+
Use \`prismic <command> --help\` for more information about a command.
|
|
33
|
+
`.trim();
|
|
34
|
+
|
|
35
|
+
const {
|
|
36
|
+
positionals,
|
|
37
|
+
values: { version },
|
|
38
|
+
} = parseArgs({
|
|
39
|
+
options: {
|
|
40
|
+
help: { type: "boolean", short: "h" },
|
|
41
|
+
version: { type: "boolean", short: "v" },
|
|
42
|
+
},
|
|
43
|
+
allowPositionals: true,
|
|
44
|
+
strict: false,
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
setupSentry();
|
|
48
|
+
await initSegment();
|
|
49
|
+
|
|
50
|
+
if (version) {
|
|
51
|
+
console.info(packageJson.version);
|
|
52
|
+
} else {
|
|
53
|
+
const command = positionals[0];
|
|
54
|
+
|
|
55
|
+
trackStart(command);
|
|
56
|
+
|
|
57
|
+
try {
|
|
58
|
+
switch (command) {
|
|
59
|
+
case "init":
|
|
60
|
+
await init();
|
|
61
|
+
break;
|
|
62
|
+
case "sync":
|
|
63
|
+
await sync();
|
|
64
|
+
break;
|
|
65
|
+
case "login":
|
|
66
|
+
await login();
|
|
67
|
+
break;
|
|
68
|
+
case "logout":
|
|
69
|
+
await logout();
|
|
70
|
+
break;
|
|
71
|
+
case "whoami":
|
|
72
|
+
await whoami();
|
|
73
|
+
break;
|
|
74
|
+
default: {
|
|
75
|
+
if (command) {
|
|
76
|
+
console.error(`Unknown command: ${command}`);
|
|
77
|
+
process.exitCode = 1;
|
|
78
|
+
}
|
|
79
|
+
console.info(HELP);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
trackEnd(command, process.exitCode !== 1);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
await captureError(error);
|
|
86
|
+
trackEnd(command, false, error);
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
}
|
package/src/init.ts
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { readFile, rm } from "node:fs/promises";
|
|
2
|
+
import { parseArgs } from "node:util";
|
|
3
|
+
import * as v from "valibot";
|
|
4
|
+
|
|
5
|
+
import { createLoginSession, isAuthenticated } from "./lib/auth";
|
|
6
|
+
import { openBrowser } from "./lib/browser";
|
|
7
|
+
import { createConfig, readConfig, UnknownProjectRoot } from "./lib/config";
|
|
8
|
+
import { findUpward } from "./lib/file";
|
|
9
|
+
import { getFramework } from "./framework";
|
|
10
|
+
import { request } from "./lib/request";
|
|
11
|
+
import { getUserServiceUrl } from "./lib/url";
|
|
12
|
+
import { syncCustomTypes, syncSlices } from "./sync";
|
|
13
|
+
|
|
14
|
+
const HELP = `
|
|
15
|
+
Initialize a Prismic project by creating a prismic.config.json file.
|
|
16
|
+
|
|
17
|
+
Detects the project framework, installs dependencies, and syncs models
|
|
18
|
+
from Prismic. If a slicemachine.config.json exists, it will be migrated.
|
|
19
|
+
|
|
20
|
+
USAGE
|
|
21
|
+
prismic init [flags]
|
|
22
|
+
|
|
23
|
+
FLAGS
|
|
24
|
+
-r, --repo string Repository name
|
|
25
|
+
-h, --help Show help for command
|
|
26
|
+
|
|
27
|
+
EXAMPLES
|
|
28
|
+
prismic init --repo my-repo
|
|
29
|
+
|
|
30
|
+
LEARN MORE
|
|
31
|
+
Use \`prismic <command> --help\` for more information about a command.
|
|
32
|
+
`.trim();
|
|
33
|
+
|
|
34
|
+
const ProfileSchema = v.object({
|
|
35
|
+
repositories: v.array(
|
|
36
|
+
v.object({
|
|
37
|
+
domain: v.string(),
|
|
38
|
+
name: v.optional(v.string()),
|
|
39
|
+
}),
|
|
40
|
+
),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
export async function init(): Promise<void> {
|
|
44
|
+
const { values } = parseArgs({
|
|
45
|
+
args: process.argv.slice(3),
|
|
46
|
+
options: {
|
|
47
|
+
help: { type: "boolean", short: "h" },
|
|
48
|
+
repo: { type: "string", short: "r" },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (values.help) {
|
|
53
|
+
console.info(HELP);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check for existing prismic.config.json
|
|
58
|
+
const existingConfig = await readConfig();
|
|
59
|
+
if (existingConfig.ok) {
|
|
60
|
+
console.error("A prismic.config.json file already exists.");
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for legacy slicemachine.config.json
|
|
66
|
+
const legacyConfigPath = await findUpward("slicemachine.config.json", {
|
|
67
|
+
stop: "package.json",
|
|
68
|
+
});
|
|
69
|
+
let legacyRepoName: string | undefined;
|
|
70
|
+
let legacyLibraries: string[] | undefined;
|
|
71
|
+
|
|
72
|
+
if (legacyConfigPath) {
|
|
73
|
+
try {
|
|
74
|
+
const contents = await readFile(legacyConfigPath, "utf8");
|
|
75
|
+
const legacyConfig = JSON.parse(contents);
|
|
76
|
+
legacyRepoName = legacyConfig.repositoryName;
|
|
77
|
+
legacyLibraries = legacyConfig.libraries;
|
|
78
|
+
} catch {
|
|
79
|
+
console.warn("Could not read slicemachine.config.json, ignoring.");
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Determine repo name: --repo flag > legacy config > error
|
|
84
|
+
const repo = values.repo ?? legacyRepoName;
|
|
85
|
+
if (!repo) {
|
|
86
|
+
console.error("Missing required flag: --repo");
|
|
87
|
+
process.exitCode = 1;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Login if needed
|
|
92
|
+
if (!(await isAuthenticated())) {
|
|
93
|
+
console.info("Not logged in. Starting login...");
|
|
94
|
+
const { email } = await createLoginSession({
|
|
95
|
+
onReady: (url) => {
|
|
96
|
+
console.info("Opening browser to complete login...");
|
|
97
|
+
console.info(`If the browser doesn't open, visit: ${url}`);
|
|
98
|
+
openBrowser(url);
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
console.info(`Logged in as ${email}`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Validate repo membership
|
|
105
|
+
const profileUrl = new URL("profile", await getUserServiceUrl());
|
|
106
|
+
const profileResponse = await request(profileUrl, {
|
|
107
|
+
schema: ProfileSchema,
|
|
108
|
+
});
|
|
109
|
+
if (!profileResponse.ok) {
|
|
110
|
+
console.error("Failed to fetch user profile.");
|
|
111
|
+
process.exitCode = 1;
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const repoData = profileResponse.value.repositories.find(
|
|
116
|
+
(r) => r.domain === repo,
|
|
117
|
+
);
|
|
118
|
+
if (!repoData) {
|
|
119
|
+
console.error(
|
|
120
|
+
`Repository "${repo}" not found in your account. Check the name or create it with \`prismic repo create\`.`,
|
|
121
|
+
);
|
|
122
|
+
process.exitCode = 1;
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Detect framework
|
|
127
|
+
const framework = await getFramework();
|
|
128
|
+
if (!framework) {
|
|
129
|
+
console.error(
|
|
130
|
+
"Could not detect a supported framework (Next.js, Nuxt, or SvelteKit).",
|
|
131
|
+
);
|
|
132
|
+
process.exitCode = 1;
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Create prismic.config.json
|
|
137
|
+
const configData: { repositoryName: string; libraries?: string[] } = {
|
|
138
|
+
repositoryName: repo,
|
|
139
|
+
};
|
|
140
|
+
if (legacyLibraries?.length) {
|
|
141
|
+
configData.libraries = legacyLibraries;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const configResult = await createConfig(configData);
|
|
145
|
+
if (!configResult.ok) {
|
|
146
|
+
if (configResult.error instanceof UnknownProjectRoot) {
|
|
147
|
+
console.error(
|
|
148
|
+
"Could not find a package.json file. Run this command from a project directory.",
|
|
149
|
+
);
|
|
150
|
+
} else {
|
|
151
|
+
console.error("Failed to create config file.");
|
|
152
|
+
}
|
|
153
|
+
process.exitCode = 1;
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Delete legacy config after new config is created
|
|
158
|
+
if (legacyConfigPath) {
|
|
159
|
+
await rm(legacyConfigPath);
|
|
160
|
+
console.info("Migrated slicemachine.config.json to prismic.config.json");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Install dependencies and create framework files
|
|
164
|
+
await framework.initProject();
|
|
165
|
+
|
|
166
|
+
// Sync models from remote
|
|
167
|
+
await syncSlices(repo, framework);
|
|
168
|
+
await syncCustomTypes(repo, framework);
|
|
169
|
+
|
|
170
|
+
console.info(
|
|
171
|
+
`Initialized Prismic for repository "${repo}". Run \`npm install\` to install new dependencies.`,
|
|
172
|
+
);
|
|
173
|
+
}
|
package/src/lib/auth.ts
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { access, readFile, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
|
|
6
|
+
import { appendTrailingSlash } from "./url";
|
|
7
|
+
|
|
8
|
+
const AUTH_FILE_PATH = new URL(
|
|
9
|
+
".prismic",
|
|
10
|
+
appendTrailingSlash(pathToFileURL(homedir())),
|
|
11
|
+
);
|
|
12
|
+
const DEFAULT_HOST = "https://prismic.io";
|
|
13
|
+
const LOGIN_TIMEOUT_MS = 3 * 60 * 1000; // 3 minutes
|
|
14
|
+
const PREFERRED_PORT = 5555;
|
|
15
|
+
|
|
16
|
+
type AuthContents = {
|
|
17
|
+
token?: string;
|
|
18
|
+
base?: string;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export async function saveToken(
|
|
22
|
+
token: string,
|
|
23
|
+
options?: { base?: string },
|
|
24
|
+
): Promise<void> {
|
|
25
|
+
const contents: AuthContents = { token, base: options?.base };
|
|
26
|
+
await writeFile(AUTH_FILE_PATH, JSON.stringify(contents, null, 2));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function isAuthenticated(): Promise<boolean> {
|
|
30
|
+
const token = await readToken();
|
|
31
|
+
if (!token) return false;
|
|
32
|
+
|
|
33
|
+
// Verify token is still valid by calling the profile endpoint
|
|
34
|
+
try {
|
|
35
|
+
const host = await readHost();
|
|
36
|
+
host.hostname = `user-service.${host.hostname}`;
|
|
37
|
+
const url = new URL("profile", host);
|
|
38
|
+
|
|
39
|
+
const response = await fetch(url, {
|
|
40
|
+
headers: {
|
|
41
|
+
Accept: "application/json",
|
|
42
|
+
Cookie: `SESSION=fake_session; prismic-auth=${token}`,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
if (!response.ok) {
|
|
47
|
+
await removeToken();
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return true;
|
|
52
|
+
} catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function readToken(): Promise<string | undefined> {
|
|
58
|
+
const auth = await readAuthFile();
|
|
59
|
+
return auth?.token;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function readHost(): Promise<URL> {
|
|
63
|
+
try {
|
|
64
|
+
const auth = await readAuthFile();
|
|
65
|
+
if (!auth?.base) return new URL(DEFAULT_HOST);
|
|
66
|
+
return new URL(auth.base);
|
|
67
|
+
} catch {
|
|
68
|
+
return new URL(DEFAULT_HOST);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export async function createLoginSession(options?: {
|
|
73
|
+
onReady?: (url: URL) => void;
|
|
74
|
+
}): Promise<{ email: string }> {
|
|
75
|
+
const host = await readHost();
|
|
76
|
+
const corsOrigin = host.origin;
|
|
77
|
+
|
|
78
|
+
return new Promise((resolve, reject) => {
|
|
79
|
+
const server = createServer((req, res) => {
|
|
80
|
+
if (req.method === "OPTIONS") {
|
|
81
|
+
res.writeHead(204, {
|
|
82
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
83
|
+
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
84
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
85
|
+
});
|
|
86
|
+
res.end();
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (req.method === "POST") {
|
|
91
|
+
let body = "";
|
|
92
|
+
|
|
93
|
+
req.on("data", (chunk) => {
|
|
94
|
+
body += chunk.toString();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
req.on("end", async () => {
|
|
98
|
+
try {
|
|
99
|
+
const { cookies, email } = JSON.parse(body);
|
|
100
|
+
|
|
101
|
+
const cookie: string | undefined = cookies.find((c: string) =>
|
|
102
|
+
c.startsWith("prismic-auth="),
|
|
103
|
+
);
|
|
104
|
+
const token = cookie?.split(";")[0]?.replace(/^prismic-auth=/, "");
|
|
105
|
+
|
|
106
|
+
if (!token) {
|
|
107
|
+
res.writeHead(400, {
|
|
108
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
109
|
+
"Content-Type": "application/json",
|
|
110
|
+
});
|
|
111
|
+
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
await saveToken(token);
|
|
116
|
+
|
|
117
|
+
res.writeHead(200, {
|
|
118
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
119
|
+
"Content-Type": "application/json",
|
|
120
|
+
});
|
|
121
|
+
res.end(JSON.stringify({ success: true }));
|
|
122
|
+
|
|
123
|
+
clearTimeout(timeoutId);
|
|
124
|
+
server.close();
|
|
125
|
+
resolve({ email });
|
|
126
|
+
} catch {
|
|
127
|
+
res.writeHead(400, {
|
|
128
|
+
"Access-Control-Allow-Origin": corsOrigin,
|
|
129
|
+
"Content-Type": "application/json",
|
|
130
|
+
});
|
|
131
|
+
res.end(JSON.stringify({ error: "Invalid request" }));
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
res.writeHead(404);
|
|
139
|
+
res.end();
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const timeoutId = setTimeout(() => {
|
|
143
|
+
server.close();
|
|
144
|
+
reject(new Error("Login timed out. Please try again."));
|
|
145
|
+
}, LOGIN_TIMEOUT_MS);
|
|
146
|
+
|
|
147
|
+
const onListening = (): void => {
|
|
148
|
+
const address = server.address();
|
|
149
|
+
if (!address || typeof address === "string") {
|
|
150
|
+
clearTimeout(timeoutId);
|
|
151
|
+
server.close();
|
|
152
|
+
reject(new Error("Failed to start login server"));
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const url = buildLoginUrl(host, address.port);
|
|
157
|
+
options?.onReady?.(url);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
server.on("error", (error: NodeJS.ErrnoException) => {
|
|
161
|
+
if (error.code === "EADDRINUSE" && server.listening === false) {
|
|
162
|
+
server.listen(0, "0.0.0.0", onListening);
|
|
163
|
+
} else {
|
|
164
|
+
clearTimeout(timeoutId);
|
|
165
|
+
reject(error);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
server.listen(PREFERRED_PORT, "0.0.0.0", onListening);
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildLoginUrl(host: URL, port: number): URL {
|
|
174
|
+
const url = new URL("/dashboard/cli/login", host);
|
|
175
|
+
url.searchParams.set("source", "prismic-cli");
|
|
176
|
+
url.searchParams.set("port", port.toString());
|
|
177
|
+
return url;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function readAuthFile(): Promise<AuthContents | undefined> {
|
|
181
|
+
try {
|
|
182
|
+
const contents = await readFile(AUTH_FILE_PATH, "utf-8");
|
|
183
|
+
return JSON.parse(contents);
|
|
184
|
+
} catch {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export async function removeToken(): Promise<boolean> {
|
|
190
|
+
try {
|
|
191
|
+
await access(AUTH_FILE_PATH);
|
|
192
|
+
} catch {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const auth = await readAuthFile();
|
|
197
|
+
if (!auth) return false;
|
|
198
|
+
await rm(AUTH_FILE_PATH);
|
|
199
|
+
return true;
|
|
200
|
+
}
|