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
26 changes: 26 additions & 0 deletions src/clients/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,32 @@ export async function setSimulatorUrl(
}
}

const DocumentSearchTotalSchema = z.object({
total: z.number(),
});

export async function getDocumentTotalByCustomTypes(
customTypeId: string,
config: { repo: string; token: string | undefined; host: string },
): Promise<number> {
const { repo, token, host } = config;
const url = new URL("core/documents/search", getCoreBaseUrl(repo, host));
try {
const response = await request(url, {
method: "POST",
body: { customTypes: [customTypeId], limit: 0 },
credentials: { "prismic-auth": token },
schema: DocumentSearchTotalSchema,
});
return response.total;
} catch (error) {
if (error instanceof NotFoundRequestError) {
error.message = `Repository not found: ${repo}`;
}
throw error;
}
}

function getCoreBaseUrl(repo: string, host: string): URL {
return new URL(`https://${repo}.${host}/`);
}
23 changes: 23 additions & 0 deletions src/clients/wroom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,26 @@ function getDashboardUrl(host: string): URL {
function getWroomUrl(repo: string, host: string): URL {
return new URL(`https://${repo}.${host}/`);
}

/** Editor parity: document list filtered by custom type (sidebar / working view). */
export function getWorkingDocumentsUrlForCustomType(
args: { repo: string; host: string; customTypeId: string },
): string {
const { repo, host, customTypeId } = args;
const url = new URL("builder/working", getWroomUrl(repo, host));
url.searchParams.set("customTypes", customTypeId);
return url.href;
}

type GetCustomTypePagesUrlArgs = {
repo: string;
host: string;
format: "custom" | "page";
};

export function getCustomTypeListUrl(args: GetCustomTypePagesUrlArgs): string {
const { repo, host, format } = args;
const path = ["builder", "types", format === "custom" ? "custom-types" : "page-types"].join("/");
const url = new URL(path, getWroomUrl(repo, host));
return url.href;
}
59 changes: 57 additions & 2 deletions src/commands/push.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { pascalCase } from "change-case";

import type { CustomType } from "@prismicio/types-internal/lib/customtypes";

import { getAdapter } from "../adapters";
import { getHost, getToken } from "../auth";
import { getDocumentTotalByCustomTypes } from "../clients/core";
import {
getCustomTypes,
getSlices,
Expand All @@ -16,10 +19,12 @@ import {
completeOnboardingStepsSilently,
type OnboardingStep,
} from "../clients/repository";
import { getWorkingDocumentsUrlForCustomType, getCustomTypeListUrl } from "../clients/wroom";
import { resolveEnvironment } from "../environments";
import { CommandError, createCommand, type CommandConfig } from "../lib/command";
import { diffArrays } from "../lib/diff";
import { getDirtyPaths, getGitRoot } from "../lib/git";
import { BadRequestError } from "../lib/request";
import { appendTrailingSlash, isDescendant, relativePathname } from "../lib/url";
import { canonicalizeModel } from "../models";
import { findProjectRoot, getRepositoryName } from "../project";
Expand Down Expand Up @@ -134,8 +139,8 @@ export default createCommand(config, async ({ values }) => {
for (const model of customTypeOps.update) {
await updateCustomType(model, { repo, token, host });
}
for (const id of customTypeOps.delete.map((m) => m.id)) {
await removeCustomType(id, { repo, token, host });
for (const model of customTypeOps.delete) {
await removeCustomTypeWithDocumentHandling(model, { repo, token, host });
}
for (const model of sliceOps.insert) {
await insertSlice(model, { repo, token, host });
Expand Down Expand Up @@ -173,3 +178,53 @@ export default createCommand(config, async ({ values }) => {
if (totalDeletes > 0) console.info(`Deleted ${totalDeletes} model(s).`);
}
});

async function removeCustomTypeWithDocumentHandling(
model: CustomType,
config: {
repo: string;
token: string | undefined;
host: string;
},
): Promise<void> {
const { repo, token, host } = config;
const { id, format } = model;

try {
await removeCustomType(id, { repo, token, host });
} catch (error) {
if (!(await isDocumentsInUseError(error))) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new CommandError(
`Could not delete type "${id}": ${errorMessage}"` +
"\nPlease try again, or manually deleting the type at: " +
getCustomTypeListUrl({ repo, host, format: format ?? "custom" })
Comment thread
jomifepe marked this conversation as resolved.
);
}

let documentCount: number;
try {
documentCount = await getDocumentTotalByCustomTypes(id, { repo, token, host });
} catch {
throw new CommandError(
`Could not check whether type "${id}" has associated pages. ` +
"\nPlease try again, or manually delete any associated pages at: " +
getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }),
);
}

const countLabel = documentCount > 0 ? ` ${documentCount}` : "";
const pluralPages = documentCount === 1 ? "page" : "pages";
throw new CommandError(
`Could not delete type "${id}" because it has${countLabel} associated ${pluralPages}. ` +
`\nDelete any associated pages manually before pushing at: ` +
getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }),
);
}
}

async function isDocumentsInUseError(error: unknown): Promise<boolean> {
if (!(error instanceof BadRequestError)) return false;
const body = await error.text();
return body.includes("associated documents") || body.includes("Delete all documents belonging");
}
Comment thread
jomifepe marked this conversation as resolved.
10 changes: 10 additions & 0 deletions src/lib/request.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export async function request<T>(

return value;
} else {
if (response.status === 400) throw new BadRequestError(response, value);
Comment thread
jomifepe marked this conversation as resolved.
if (response.status === 401) throw new UnauthorizedRequestError(response);
if (response.status === 403) throw new ForbiddenRequestError(response);
if (response.status === 404) throw new NotFoundRequestError(response);
Expand Down Expand Up @@ -89,6 +90,15 @@ export class RequestError extends Error {
export class UnknownRequestError extends RequestError {
name = "UnknownRequestError";
}
export class BadRequestError extends RequestError {
name = "BadRequestError";
body: unknown;

constructor(response: Response, body: unknown) {
super(response);
this.body = body;
Comment thread
jomifepe marked this conversation as resolved.
}
}
export class NotFoundRequestError extends RequestError {
name = "NotFoundRequestError";

Expand Down
Loading