From c4dd7da8afe40a9b35a87a79d694be2f8c05ff90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Mon, 18 May 2026 16:22:07 +0100 Subject: [PATCH 01/10] feat: delete documents on with ct --- src/clients/core.ts | 46 +++++++++++++++++++ src/clients/wroom.ts | 10 +++++ src/commands/push.ts | 105 +++++++++++++++++++++++++++++++++++++++++-- src/lib/request.ts | 10 +++++ 4 files changed, 168 insertions(+), 3 deletions(-) diff --git a/src/clients/core.ts b/src/clients/core.ts index 3002314..a954392 100644 --- a/src/clients/core.ts +++ b/src/clients/core.ts @@ -157,6 +157,52 @@ 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 { + 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; + } +} + +export async function deleteDocumentsByCustomType( + customTypeId: string, + config: { repo: string; token: string | undefined; host: string }, +): Promise { + const { repo, token, host } = config; + const url = new URL("core/documents", getCoreBaseUrl(repo, host)); + try { + await request(url, { + method: "DELETE", + body: { customtype_ids: [customTypeId] }, + credentials: { "prismic-auth": token }, + }); + } 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}/`); } diff --git a/src/clients/wroom.ts b/src/clients/wroom.ts index 408b5f0..7bf0ffc 100644 --- a/src/clients/wroom.ts +++ b/src/clients/wroom.ts @@ -421,3 +421,13 @@ 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 +} diff --git a/src/commands/push.ts b/src/commands/push.ts index 485b84a..2518730 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -2,6 +2,10 @@ import { pascalCase } from "change-case"; import { getAdapter } from "../adapters"; import { getHost, getToken } from "../auth"; +import { + deleteDocumentsByCustomType, + getDocumentTotalByCustomTypes, +} from "../clients/core"; import { getCustomTypes, getSlices, @@ -16,10 +20,12 @@ import { completeOnboardingStepsSilently, type OnboardingStep, } from "../clients/repository"; +import { getWorkingDocumentsUrlForCustomType } 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"; @@ -31,16 +37,30 @@ const config = { Local models are the source of truth. Remote models are created, updated, or deleted to match. + + Use --delete-pages when you need push to skip the interactive confirmation + before deleting documents belonging to removed custom types (--force alone + does not skip that confirmation). `, options: { force: { type: "boolean", short: "f", description: "Skip safety checks" }, + "delete-pages": { + type: "boolean", + description: + "Skip confirmation before bulk-deleting documents when removing a custom type that has content", + }, repo: { type: "string", short: "r", description: "Repository domain" }, env: { type: "string", short: "e", description: "Environment domain" }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { force = false, repo: parentRepo = await getRepositoryName(), env } = values; + const { + force = false, + "delete-pages": deletePages = false, + repo: parentRepo = await getRepositoryName(), + env, + } = values; const token = await getToken(); const host = await getHost(); @@ -134,8 +154,13 @@ 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.id, { + repo, + token, + host, + deletePages, + }); } for (const model of sliceOps.insert) { await insertSlice(model, { repo, token, host }); @@ -173,3 +198,77 @@ export default createCommand(config, async ({ values }) => { if (totalDeletes > 0) console.info(`Deleted ${totalDeletes} model(s).`); } }); + +const DELETE_PAGES_LIMIT = 200; // same hard limit from type builder and sm-api + +async function removeCustomTypeWithDocumentHandling( + id: string, + config: { + repo: string; + token: string | undefined; + host: string; + deletePages: boolean; + }, +): Promise { + const { repo, token, host, deletePages: forceDeletePages } = config; + try { + await removeCustomType(id, { repo, token, host }); + } catch (error) { + if (!isDocumentsInUseError(error)) throw error; + + let documentCount: number; + try { + documentCount = await getDocumentTotalByCustomTypes(id, { repo, token, host }); + } catch { + throw new CommandError( + `Failed to check whether type "${id}" has associated pages. ` + + "Please try pushing again, or manually delete any associated pages in Prismic: " + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), + ); + } + + if (documentCount === 0) { + try { + await removeCustomType(id, { repo, token, host }); + return; + } catch (retryError) { + if (!isDocumentsInUseError(retryError)) throw retryError; + throw new CommandError( + `Unable to delete type "${id}". It may have associated pages. ` + + `Please try pushing again, or manually delete any associated pages in Prismic: ` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), + ); + } + } + + if (documentCount > DELETE_PAGES_LIMIT) { + const plural = documentCount === 1 ? "" : "s"; + throw new CommandError( + `Cannot delete type "${id}": it has ${documentCount} associated page${plural}, ` + + `which exceeds the limit of ${DELETE_PAGES_LIMIT} that can be bulk-deleted. ` + + `Delete pages manually before pushing: ` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), + ); + } + + if (!forceDeletePages) { + const plural = documentCount === 1 ? "" : "s"; + throw new CommandError( + `Type "${id}" has ${documentCount} associated page${plural}. ` + + `Deleting it type will also permanently delete all associated pages: \n` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }) + "\n\n" + + `Pass --delete-pages to confirm this cascading deletion.`, + ); + + } + + console.info(`Deleting pages associated with type "${id}"...`); + await deleteDocumentsByCustomType(id, { repo, token, host }); + await removeCustomType(id, { repo, token, host }); + } +} + +function isDocumentsInUseError(error: unknown): error is BadRequestError { + if (!(error instanceof BadRequestError)) return false; + const { body } = error; + return ( + typeof body === "string" && + (body.includes("associated documents") || body.includes("Delete all documents belonging")) + ); +} diff --git a/src/lib/request.ts b/src/lib/request.ts index f9c63ad..b645916 100644 --- a/src/lib/request.ts +++ b/src/lib/request.ts @@ -53,6 +53,7 @@ export async function request( return value; } else { + if (response.status === 400) throw new BadRequestError(response, value); if (response.status === 401) throw new UnauthorizedRequestError(response); if (response.status === 403) throw new ForbiddenRequestError(response); if (response.status === 404) throw new NotFoundRequestError(response); @@ -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; + } +} export class NotFoundRequestError extends RequestError { name = "NotFoundRequestError"; From 682efc8563965b740d75c45edebf5ee821138741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Mon, 18 May 2026 16:46:50 +0100 Subject: [PATCH 02/10] feat: rename and message --- src/commands/push.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commands/push.ts b/src/commands/push.ts index 2518730..8a4d0ba 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -43,11 +43,11 @@ const config = { does not skip that confirmation). `, options: { - force: { type: "boolean", short: "f", description: "Skip safety checks" }, + force: { type: "boolean", short: "f", description: "Skip overwrite safety checks" }, "delete-pages": { type: "boolean", description: - "Skip confirmation before bulk-deleting documents when removing a custom type that has content", + "Confirm the bulk-deletion of associated pages when removing a type", }, repo: { type: "string", short: "r", description: "Repository domain" }, env: { type: "string", short: "e", description: "Environment domain" }, From fb1b2d51fa445251d6d6d9e2fa7d384786bbf12a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Mon, 18 May 2026 16:53:09 +0100 Subject: [PATCH 03/10] feat: no desc --- src/commands/push.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/commands/push.ts b/src/commands/push.ts index 8a4d0ba..cc50c7b 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -37,10 +37,6 @@ const config = { Local models are the source of truth. Remote models are created, updated, or deleted to match. - - Use --delete-pages when you need push to skip the interactive confirmation - before deleting documents belonging to removed custom types (--force alone - does not skip that confirmation). `, options: { force: { type: "boolean", short: "f", description: "Skip overwrite safety checks" }, From 27b315c9b6c5e4a78dd75430cac61578a6d4a27b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Mon, 18 May 2026 16:54:31 +0100 Subject: [PATCH 04/10] feat: lowercase --- src/commands/push.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/commands/push.ts b/src/commands/push.ts index cc50c7b..dc969cd 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -261,10 +261,7 @@ async function removeCustomTypeWithDocumentHandling( } function isDocumentsInUseError(error: unknown): error is BadRequestError { - if (!(error instanceof BadRequestError)) return false; - const { body } = error; - return ( - typeof body === "string" && - (body.includes("associated documents") || body.includes("Delete all documents belonging")) - ); + if (!(error instanceof BadRequestError) || typeof error.body !== "string") return false; + const body = error.body.toString(); + return body.includes("associated documents") || body.includes("Delete all documents belonging"); } From d43bea3c77ad40e2fc454978cb59b6630b521978 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Mon, 18 May 2026 17:02:38 +0100 Subject: [PATCH 05/10] fix: typo --- src/clients/wroom.ts | 2 +- src/commands/push.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/clients/wroom.ts b/src/clients/wroom.ts index 7bf0ffc..c2a1d2f 100644 --- a/src/clients/wroom.ts +++ b/src/clients/wroom.ts @@ -429,5 +429,5 @@ export function getWorkingDocumentsUrlForCustomType( const { repo, host, customTypeId } = args; const url = new URL("builder/working", getWroomUrl(repo, host)); url.searchParams.set("customTypes", customTypeId); - return url.href + return url.href; } diff --git a/src/commands/push.ts b/src/commands/push.ts index dc969cd..a8d919c 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -248,7 +248,7 @@ async function removeCustomTypeWithDocumentHandling( const plural = documentCount === 1 ? "" : "s"; throw new CommandError( `Type "${id}" has ${documentCount} associated page${plural}. ` + - `Deleting it type will also permanently delete all associated pages: \n` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }) + "\n\n" + + `Deleting it will also permanently delete all associated pages: \n` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }) + "\n\n" + `Pass --delete-pages to confirm this cascading deletion.`, ); From 86370a74ce5d41f331622c8327295de42dc6f036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Mon, 18 May 2026 17:12:13 +0100 Subject: [PATCH 06/10] fix: body --- src/commands/push.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commands/push.ts b/src/commands/push.ts index a8d919c..9304912 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -210,7 +210,7 @@ async function removeCustomTypeWithDocumentHandling( try { await removeCustomType(id, { repo, token, host }); } catch (error) { - if (!isDocumentsInUseError(error)) throw error; + if (!(await isDocumentsInUseError(error))) throw error; let documentCount: number; try { @@ -227,7 +227,7 @@ async function removeCustomTypeWithDocumentHandling( await removeCustomType(id, { repo, token, host }); return; } catch (retryError) { - if (!isDocumentsInUseError(retryError)) throw retryError; + if (!(await isDocumentsInUseError(retryError))) throw retryError; throw new CommandError( `Unable to delete type "${id}". It may have associated pages. ` + `Please try pushing again, or manually delete any associated pages in Prismic: ` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), @@ -260,8 +260,8 @@ async function removeCustomTypeWithDocumentHandling( } } -function isDocumentsInUseError(error: unknown): error is BadRequestError { - if (!(error instanceof BadRequestError) || typeof error.body !== "string") return false; - const body = error.body.toString(); +async function isDocumentsInUseError(error: unknown): Promise { + if (!(error instanceof BadRequestError)) return false; + const body = await error.text(); return body.includes("associated documents") || body.includes("Delete all documents belonging"); } From 711c26e1a0045ec79316ea8ce49a45606b9ebeef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Thu, 21 May 2026 11:21:28 +0100 Subject: [PATCH 07/10] feat: no document deletion --- src/clients/core.ts | 20 ------------------ src/commands/push.ts | 48 +++++++------------------------------------- 2 files changed, 7 insertions(+), 61 deletions(-) diff --git a/src/clients/core.ts b/src/clients/core.ts index a954392..92122ea 100644 --- a/src/clients/core.ts +++ b/src/clients/core.ts @@ -183,26 +183,6 @@ export async function getDocumentTotalByCustomTypes( } } -export async function deleteDocumentsByCustomType( - customTypeId: string, - config: { repo: string; token: string | undefined; host: string }, -): Promise { - const { repo, token, host } = config; - const url = new URL("core/documents", getCoreBaseUrl(repo, host)); - try { - await request(url, { - method: "DELETE", - body: { customtype_ids: [customTypeId] }, - credentials: { "prismic-auth": token }, - }); - } 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}/`); } diff --git a/src/commands/push.ts b/src/commands/push.ts index 9304912..b75c152 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -2,10 +2,7 @@ import { pascalCase } from "change-case"; import { getAdapter } from "../adapters"; import { getHost, getToken } from "../auth"; -import { - deleteDocumentsByCustomType, - getDocumentTotalByCustomTypes, -} from "../clients/core"; +import { getDocumentTotalByCustomTypes } from "../clients/core"; import { getCustomTypes, getSlices, @@ -40,11 +37,6 @@ const config = { `, options: { force: { type: "boolean", short: "f", description: "Skip overwrite safety checks" }, - "delete-pages": { - type: "boolean", - description: - "Confirm the bulk-deletion of associated pages when removing a type", - }, repo: { type: "string", short: "r", description: "Repository domain" }, env: { type: "string", short: "e", description: "Environment domain" }, }, @@ -53,7 +45,6 @@ const config = { export default createCommand(config, async ({ values }) => { const { force = false, - "delete-pages": deletePages = false, repo: parentRepo = await getRepositoryName(), env, } = values; @@ -155,7 +146,6 @@ export default createCommand(config, async ({ values }) => { repo, token, host, - deletePages, }); } for (const model of sliceOps.insert) { @@ -195,18 +185,15 @@ export default createCommand(config, async ({ values }) => { } }); -const DELETE_PAGES_LIMIT = 200; // same hard limit from type builder and sm-api - async function removeCustomTypeWithDocumentHandling( id: string, config: { repo: string; token: string | undefined; host: string; - deletePages: boolean; }, ): Promise { - const { repo, token, host, deletePages: forceDeletePages } = config; + const { repo, token, host } = config; try { await removeCustomType(id, { repo, token, host }); } catch (error) { @@ -228,35 +215,14 @@ async function removeCustomTypeWithDocumentHandling( return; } catch (retryError) { if (!(await isDocumentsInUseError(retryError))) throw retryError; - throw new CommandError( - `Unable to delete type "${id}". It may have associated pages. ` + - `Please try pushing again, or manually delete any associated pages in Prismic: ` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), - ); } } - if (documentCount > DELETE_PAGES_LIMIT) { - const plural = documentCount === 1 ? "" : "s"; - throw new CommandError( - `Cannot delete type "${id}": it has ${documentCount} associated page${plural}, ` + - `which exceeds the limit of ${DELETE_PAGES_LIMIT} that can be bulk-deleted. ` + - `Delete pages manually before pushing: ` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), - ); - } - - if (!forceDeletePages) { - const plural = documentCount === 1 ? "" : "s"; - throw new CommandError( - `Type "${id}" has ${documentCount} associated page${plural}. ` + - `Deleting it will also permanently delete all associated pages: \n` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }) + "\n\n" + - `Pass --delete-pages to confirm this cascading deletion.`, - ); - - } - - console.info(`Deleting pages associated with type "${id}"...`); - await deleteDocumentsByCustomType(id, { repo, token, host }); - await removeCustomType(id, { repo, token, host }); + const plural = documentCount === 1 ? "" : "s"; + throw new CommandError( + `Cannot delete type "${id}": it has ${documentCount} associated page${plural}. ` + + `Delete pages manually before pushing: ` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), + ); } } From 28c44531258dc104580c051ca6d5ae1faf651976 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Thu, 21 May 2026 11:53:51 +0100 Subject: [PATCH 08/10] refactor: remove --delete-pages flag and block deletion when pages exist Remove the ability to cascade-delete documents when removing a custom type. The CLI now always blocks and directs users to delete pages manually, eliminating the risk of irrecoverable data loss from AI agents or accidental usage in production. Co-authored-by: Cursor --- src/clients/wroom.ts | 13 +++++++++++++ src/commands/push.ts | 40 ++++++++++++++++++++++------------------ 2 files changed, 35 insertions(+), 18 deletions(-) diff --git a/src/clients/wroom.ts b/src/clients/wroom.ts index c2a1d2f..de1ce41 100644 --- a/src/clients/wroom.ts +++ b/src/clients/wroom.ts @@ -431,3 +431,16 @@ export function getWorkingDocumentsUrlForCustomType( 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; +} \ No newline at end of file diff --git a/src/commands/push.ts b/src/commands/push.ts index b75c152..8939a02 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -1,5 +1,7 @@ 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"; @@ -17,7 +19,7 @@ import { completeOnboardingStepsSilently, type OnboardingStep, } from "../clients/repository"; -import { getWorkingDocumentsUrlForCustomType } from "../clients/wroom"; +import { getWorkingDocumentsUrlForCustomType, getCustomTypeListUrl } from "../clients/wroom"; import { resolveEnvironment } from "../environments"; import { CommandError, createCommand, type CommandConfig } from "../lib/command"; import { diffArrays } from "../lib/diff"; @@ -142,7 +144,7 @@ export default createCommand(config, async ({ values }) => { await updateCustomType(model, { repo, token, host }); } for (const model of customTypeOps.delete) { - await removeCustomTypeWithDocumentHandling(model.id, { + await removeCustomTypeWithDocumentHandling(model, { repo, token, host, @@ -186,7 +188,7 @@ export default createCommand(config, async ({ values }) => { }); async function removeCustomTypeWithDocumentHandling( - id: string, + model: CustomType, config: { repo: string; token: string | undefined; @@ -194,34 +196,36 @@ async function removeCustomTypeWithDocumentHandling( }, ): Promise { const { repo, token, host } = config; + const { id, format } = model; + try { await removeCustomType(id, { repo, token, host }); } catch (error) { - if (!(await isDocumentsInUseError(error))) throw error; + if (!(await isDocumentsInUseError(error))) { + const errorMessage = error instanceof Error ? error.message : String(error); + throw new CommandError( + `Could not delete type "${id}: ${errorMessage}"` + + "\n\nPlease try again, or manually delete the types at:" + + getCustomTypeListUrl({ repo, host, format: format ?? "custom" }) + ); + } let documentCount: number; try { documentCount = await getDocumentTotalByCustomTypes(id, { repo, token, host }); } catch { throw new CommandError( - `Failed to check whether type "${id}" has associated pages. ` + - "Please try pushing again, or manually delete any associated pages in Prismic: " + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), + `Could not check whether type "${id}" has associated pages. ` + + "\nPlease try again, or manually delete any associated pages at: " + + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), ); } - if (documentCount === 0) { - try { - await removeCustomType(id, { repo, token, host }); - return; - } catch (retryError) { - if (!(await isDocumentsInUseError(retryError))) throw retryError; - } - } - - const plural = documentCount === 1 ? "" : "s"; + const countLabel = documentCount > 0 ? ` ${documentCount}` : ""; + const pluralPages = documentCount === 1 ? "page" : "pages"; throw new CommandError( - `Cannot delete type "${id}": it has ${documentCount} associated page${plural}. ` + - `Delete pages manually before pushing: ` + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), + `Could not delete type "${id}" because it has${countLabel} associated ${pluralPages}. ` + + `\nDelete any associated pages manually before pushing at: ${getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id })}`, ); } } From dd3ea2f855efb8f3e9ad6608df776077ada3e348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Thu, 21 May 2026 12:25:55 +0100 Subject: [PATCH 09/10] fix: error message formatting in removeCustomTypeWithDocumentHandling Co-authored-by: Cursor --- src/commands/push.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/commands/push.ts b/src/commands/push.ts index 8939a02..7b5acd2 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -204,8 +204,8 @@ async function removeCustomTypeWithDocumentHandling( if (!(await isDocumentsInUseError(error))) { const errorMessage = error instanceof Error ? error.message : String(error); throw new CommandError( - `Could not delete type "${id}: ${errorMessage}"` + - "\n\nPlease try again, or manually delete the types at:" + + `Could not delete type "${id}": ${errorMessage}"` + + "\nPlease try again, or manually deleting the type at: " + getCustomTypeListUrl({ repo, host, format: format ?? "custom" }) ); } @@ -225,7 +225,8 @@ async function removeCustomTypeWithDocumentHandling( 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 })}`, + `\nDelete any associated pages manually before pushing at: ` + + getWorkingDocumentsUrlForCustomType({ repo, host, customTypeId: id }), ); } } From 32ac7871d05ceac0800c29002fff038e75b899dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pereira?= <7235666+jomifepe@users.noreply.github.com> Date: Thu, 21 May 2026 15:33:34 +0100 Subject: [PATCH 10/10] feat: unnecessary changes --- src/commands/push.ts | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/src/commands/push.ts b/src/commands/push.ts index 7b5acd2..7fb3871 100644 --- a/src/commands/push.ts +++ b/src/commands/push.ts @@ -38,18 +38,14 @@ const config = { updated, or deleted to match. `, options: { - force: { type: "boolean", short: "f", description: "Skip overwrite safety checks" }, + force: { type: "boolean", short: "f", description: "Skip safety checks" }, repo: { type: "string", short: "r", description: "Repository domain" }, env: { type: "string", short: "e", description: "Environment domain" }, }, } satisfies CommandConfig; export default createCommand(config, async ({ values }) => { - const { - force = false, - repo: parentRepo = await getRepositoryName(), - env, - } = values; + const { force = false, repo: parentRepo = await getRepositoryName(), env } = values; const token = await getToken(); const host = await getHost(); @@ -144,11 +140,7 @@ export default createCommand(config, async ({ values }) => { await updateCustomType(model, { repo, token, host }); } for (const model of customTypeOps.delete) { - await removeCustomTypeWithDocumentHandling(model, { - repo, - token, - host, - }); + await removeCustomTypeWithDocumentHandling(model, { repo, token, host }); } for (const model of sliceOps.insert) { await insertSlice(model, { repo, token, host });