Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions src/clients/core.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import * as z from "zod/mini";

import { request } from "../lib/request";

const PreviewSchema = z.object({
id: z.string(),
label: z.string(),
url: z.string(),
});

const GetPreviewsResponseSchema = z.object({
results: z.array(PreviewSchema),
});

export type Preview = z.infer<typeof PreviewSchema>;

export async function getPreviews(config: {
repo: string;
token: string | undefined;
host: string;
}): Promise<Preview[]> {
const { repo, token, host } = config;
const url = new URL(
"core/repository/preview_configs",
getCoreBaseUrl(repo, host),
);
const response = await request(url, {
credentials: { "prismic-auth": token },
schema: GetPreviewsResponseSchema,
});
return response.results;
}

export async function addPreview(
previewConfig: {
name: string;
websiteURL: string;
resolverPath: string | undefined;
},
config: { repo: string; token: string | undefined; host: string },
): Promise<void> {
const { repo, token, host } = config;
const url = new URL("previews/new", getCoreBaseUrl(repo, host));
await request(url, {
method: "POST",
body: {
name: previewConfig.name,
websiteURL: previewConfig.websiteURL,
resolverPath: previewConfig.resolverPath,
},
credentials: { "prismic-auth": token },
});
}

export async function removePreview(
id: string,
config: { repo: string; token: string | undefined; host: string },
): Promise<void> {
const { repo, token, host } = config;
const url = new URL(
`previews/delete/${id}`,
getCoreBaseUrl(repo, host),
);
await request(url, {
method: "POST",
body: {},
credentials: { "prismic-auth": token },
});
}

const RepositoryResponseSchema = z.object({
simulator_url: z.optional(z.string()),
});

export async function getSimulatorUrl(config: {
repo: string;
token: string | undefined;
host: string;
}): Promise<string | undefined> {
const { repo, token, host } = config;
const url = new URL("core/repository", getCoreBaseUrl(repo, host));
const response = await request(url, {
credentials: { "prismic-auth": token },
schema: RepositoryResponseSchema,
});
return response.simulator_url;
}

export async function setSimulatorUrl(
simulatorUrl: string,
config: { repo: string; token: string | undefined; host: string },
): Promise<void> {
const { repo, token, host } = config;
const url = new URL("core/repository", getCoreBaseUrl(repo, host));
await request(url, {
method: "PATCH",
body: { simulator_url: simulatorUrl },
credentials: { "prismic-auth": token },
});
}

function getCoreBaseUrl(repo: string, host: string): URL {
return new URL(`https://${repo}.${host}/`);
}
93 changes: 93 additions & 0 deletions src/commands/preview-add.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { parseArgs } from "node:util";

import { getHost, getToken } from "../auth";
import { addPreview } from "../clients/core";
import { UnknownRequestError } from "../lib/request";
import { safeGetRepositoryName } from "../project";

const HELP = `
Add a preview configuration to a Prismic repository.

By default, this command reads the repository from prismic.config.json at the
project root.

USAGE
prismic preview add <url> [flags]

ARGUMENTS
<url> Preview URL (e.g. https://example.com/api/preview)

FLAGS
-n, --name string Display name (defaults to hostname)
-r, --repo string Repository domain
-h, --help Show help for command

LEARN MORE
Use \`prismic <command> <subcommand> --help\` for more information about a command.
`.trim();

export async function previewAdd(): Promise<void> {
const {
values: { help, name, repo = await safeGetRepositoryName() },
positionals: [previewUrl],
} = parseArgs({
args: process.argv.slice(4), // skip: node, script, "preview", "add"
options: {
name: { type: "string", short: "n" },
repo: { type: "string", short: "r" },
help: { type: "boolean", short: "h" },
},
allowPositionals: true,
});

if (help) {
console.info(HELP);
return;
}

if (!previewUrl) {
console.error("Missing required argument: <url>");
process.exitCode = 1;
return;
}

if (!repo) {
console.error("Missing prismic.config.json or --repo option");
process.exitCode = 1;
return;
}

let parsed: URL;
try {
parsed = new URL(previewUrl);
} catch {
console.error(`Invalid URL: ${previewUrl}`);
process.exitCode = 1;
return;
}

const displayName = name || parsed.hostname;
const websiteURL = `${parsed.protocol}//${parsed.host}`;
const resolverPath =
parsed.pathname === "/" ? undefined : parsed.pathname;

const token = await getToken();
const host = await getHost();

try {
await addPreview(
{ name: displayName, websiteURL, resolverPath },
{ repo, token, host },
);
} catch (error) {
if (error instanceof UnknownRequestError) {
const message = await error.text();
console.error(`Failed to add preview: ${message}`);
process.exitCode = 1;
return;
}
throw error;
}

console.info(`Preview added: ${previewUrl}`);
}
80 changes: 80 additions & 0 deletions src/commands/preview-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { parseArgs } from "node:util";

import { getHost, getToken } from "../auth";
import { getPreviews, getSimulatorUrl } from "../clients/core";
import { stringify } from "../lib/json";
import { safeGetRepositoryName } from "../project";

const HELP = `
List all preview configurations in a Prismic repository.

By default, this command reads the repository from prismic.config.json at the
project root.

USAGE
prismic preview list [flags]

FLAGS
--json Output as JSON
-r, --repo string Repository domain
-h, --help Show help for command

LEARN MORE
Use \`prismic <command> <subcommand> --help\` for more information about a command.
`.trim();

export async function previewList(): Promise<void> {
const {
values: { help, repo = await safeGetRepositoryName(), json },
} = parseArgs({
args: process.argv.slice(4), // skip: node, script, "preview", "list"
options: {
json: { type: "boolean" },
repo: { type: "string", short: "r" },
help: { type: "boolean", short: "h" },
},
allowPositionals: false,
});

if (help) {
console.info(HELP);
return;
}

if (!repo) {
console.error("Missing prismic.config.json or --repo option");
process.exitCode = 1;
return;
}

const token = await getToken();
const host = await getHost();

const [previews, simulatorUrl] = await Promise.all([
getPreviews({ repo, token, host }),
getSimulatorUrl({ repo, token, host }),
]);

if (json) {
console.info(
stringify({
previews,
simulatorUrl: simulatorUrl ?? null,
}),
);
return;
}

if (previews.length === 0 && !simulatorUrl) {
console.info("No preview configurations found.");
return;
}

for (const preview of previews) {
console.info(`${preview.url} ${preview.label}`);
}

if (simulatorUrl) {
console.info(`\nSimulator: ${simulatorUrl}`);
}
}
82 changes: 82 additions & 0 deletions src/commands/preview-remove.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { parseArgs } from "node:util";

import { getHost, getToken } from "../auth";
import { getPreviews, removePreview } from "../clients/core";
import { UnknownRequestError } from "../lib/request";
import { safeGetRepositoryName } from "../project";

const HELP = `
Remove a preview configuration from a Prismic repository.

By default, this command reads the repository from prismic.config.json at the
project root.

USAGE
prismic preview remove <url> [flags]

ARGUMENTS
<url> Preview URL to remove

FLAGS
-r, --repo string Repository domain
-h, --help Show help for command

LEARN MORE
Use \`prismic <command> <subcommand> --help\` for more information about a command.
`.trim();

export async function previewRemove(): Promise<void> {
const {
values: { help, repo = await safeGetRepositoryName() },
positionals: [previewUrl],
} = parseArgs({
args: process.argv.slice(4), // skip: node, script, "preview", "remove"
options: {
repo: { type: "string", short: "r" },
help: { type: "boolean", short: "h" },
},
allowPositionals: true,
});

if (help) {
console.info(HELP);
return;
}

if (!previewUrl) {
console.error("Missing required argument: <url>");
process.exitCode = 1;
return;
}

if (!repo) {
console.error("Missing prismic.config.json or --repo option");
process.exitCode = 1;
return;
}

const token = await getToken();
const host = await getHost();

const previews = await getPreviews({ repo, token, host });
const preview = previews.find((p) => p.url === previewUrl);
if (!preview) {
console.error(`Preview not found: ${previewUrl}`);
process.exitCode = 1;
return;
}

try {
await removePreview(preview.id, { repo, token, host });
} catch (error) {
if (error instanceof UnknownRequestError) {
const message = await error.text();
console.error(`Failed to remove preview: ${message}`);
process.exitCode = 1;
return;
}
throw error;
}

console.info(`Preview removed: ${previewUrl}`);
}
Loading