Summary
emdash schema delete --force cannot delete a collection that has content. The flag only skips the CLI's own confirmation prompt — it is never sent to the API — so the request is rejected every time:
$ npx emdash schema delete rules --force
ERROR Collection "rules" has content. Use force: true to delete.
There is no other CLI path to it. --force is the only force-shaped option the command has, so a collection with rows cannot be deleted from the CLI at all.
Affected version: emdash@0.32.0, verified unchanged in 0.36.0 (current latest).
Mechanism
The command consumes --force locally and then calls the client without it:
// packages/core/src/cli/commands/schema.ts (0.36.0)
if (!args.force) {
const confirmed = await consola.prompt(`Delete collection "${args.collection}"?`, { type: "confirm" });
if (!confirmed) { consola.info("Cancelled"); return; }
}
const client = createClientFromArgs(args);
await client.deleteCollection(args.collection); // ← force dropped here
The client has no parameter to carry it:
// packages/core/src/client/index.ts (0.36.0:548)
async deleteCollection(slug: string): Promise<void> {
await this.request<unknown>("DELETE", `/schema/collections/${encodeURIComponent(slug)}`);
}
The route does support it, as a query parameter:
// packages/core/src/astro/routes/api/schema/collections/[slug]/index.ts (0.36.0:63)
const force = url.searchParams.get("force") === "true";
So the capability exists end-to-end except for the two layers in between. The MCP server passes it correctly, which is why the same operation works there:
// packages/core/src/mcp/server.ts
await registry.deleteCollection(args.slug, { force: args.force });
Reproduction
- Create a collection and add one entry.
npx emdash schema delete <slug> --force
- Fails with
Collection "<slug>" has content. Use force: true to delete.
DELETE /_emdash/api/schema/collections/<slug>?force=true succeeds against the same instance.
Impact
Any scripted cleanup has to bypass the CLI and call the REST endpoint directly. It bit us migrating a site away from four collections whose content had moved into a pages entry — the collections were unreferenced and safe to drop, but --force refused, so the migration script had to hand-roll the HTTP call and its own auth.
The failure is also confusing rather than merely unhelpful: the error names the exact option the user just passed.
Suggested fix
Thread the flag through both layers:
// client
async deleteCollection(slug: string, options?: { force?: boolean }): Promise<void> {
const query = options?.force ? "?force=true" : "";
await this.request<unknown>("DELETE", `/schema/collections/${encodeURIComponent(slug)}${query}`);
}
// command
await client.deleteCollection(args.collection, { force: args.force });
That does conflate two meanings of --force — "don't ask me" and "delete even though it has content" — which may be intended, since the prompt already exists to guard the destructive case. If they should stay separate, a distinct flag (--force-content, say) would keep the confirmation prompt meaningful for empty collections while giving scripts a way through. Happy to open a PR for whichever shape you prefer.
Summary
emdash schema delete --forcecannot delete a collection that has content. The flag only skips the CLI's own confirmation prompt — it is never sent to the API — so the request is rejected every time:There is no other CLI path to it.
--forceis the only force-shaped option the command has, so a collection with rows cannot be deleted from the CLI at all.Affected version:
emdash@0.32.0, verified unchanged in0.36.0(currentlatest).Mechanism
The command consumes
--forcelocally and then calls the client without it:The client has no parameter to carry it:
The route does support it, as a query parameter:
So the capability exists end-to-end except for the two layers in between. The MCP server passes it correctly, which is why the same operation works there:
Reproduction
npx emdash schema delete <slug> --forceCollection "<slug>" has content. Use force: true to delete.DELETE /_emdash/api/schema/collections/<slug>?force=truesucceeds against the same instance.Impact
Any scripted cleanup has to bypass the CLI and call the REST endpoint directly. It bit us migrating a site away from four collections whose content had moved into a
pagesentry — the collections were unreferenced and safe to drop, but--forcerefused, so the migration script had to hand-roll the HTTP call and its own auth.The failure is also confusing rather than merely unhelpful: the error names the exact option the user just passed.
Suggested fix
Thread the flag through both layers:
That does conflate two meanings of
--force— "don't ask me" and "delete even though it has content" — which may be intended, since the prompt already exists to guard the destructive case. If they should stay separate, a distinct flag (--force-content, say) would keep the confirmation prompt meaningful for empty collections while giving scripts a way through. Happy to open a PR for whichever shape you prefer.