@fluid-app/fluid-cli-pages 0.1.0 → 0.1.1

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/dist/index.d.mts CHANGED
@@ -1,6 +1,27 @@
1
1
  import { CommandContext, FluidPlugin } from "@fluid-app/fluid-cli";
2
2
  import { Command } from "commander";
3
3
 
4
+ //#region src/api.d.ts
5
+ /** The slice of the CLI's API client these calls use. */
6
+ interface PagesClient {
7
+ get<T>(endpoint: string, params?: Record<string, unknown>): Promise<T>;
8
+ post<T>(endpoint: string, body?: unknown, options?: {
9
+ params?: Record<string, unknown>;
10
+ }): Promise<T>;
11
+ put<T>(endpoint: string, body?: unknown): Promise<T>;
12
+ patch<T>(endpoint: string, body?: unknown): Promise<T>;
13
+ delete<T>(endpoint: string): Promise<T>;
14
+ }
15
+ type TemplateCleanup = {
16
+ deleted: true;
17
+ templateId: number;
18
+ } | {
19
+ deleted: false;
20
+ templateId: number;
21
+ reason: string;
22
+ otherPageIds?: number[];
23
+ };
24
+ //#endregion
4
25
  //#region src/index.d.ts
5
26
  /**
6
27
  * Changes to a live storefront need an explicit yes. Scripts and agents
@@ -10,8 +31,14 @@ declare function confirmChange(question: string, yes: boolean, interactive?: boo
10
31
  /** The readable message from a Fluid error body: v2026-04 sends
11
32
  * `error: { message, details }`, the theme API `error_message` and `errors`. */
12
33
  declare function errorPayload(err: unknown): Record<string, unknown>;
34
+ /**
35
+ * Clean up a just-deleted Page's template. The Page is already gone, so a
36
+ * failed cleanup is reported, with the template left to delete, rather than
37
+ * thrown.
38
+ */
39
+ declare function cleanUpTemplateAfterDelete(api: PagesClient, templateId: number, pageId: number): Promise<TemplateCleanup>;
13
40
  declare function registerPagesCommands(parent: Command, ctx: CommandContext): void;
14
41
  declare const plugin: FluidPlugin;
15
42
  //#endregion
16
- export { confirmChange, plugin as default, errorPayload, registerPagesCommands };
43
+ export { cleanUpTemplateAfterDelete, confirmChange, plugin as default, errorPayload, registerPagesCommands };
17
44
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/index.ts"],"mappings":";;;;;;AA+BA;;iBAAsB,aAAA,CACpB,QAAA,UACA,GAAA,WACA,WAAA,aACC,OAAA;;;iBAca,YAAA,CAAa,GAAA,YAAe,MAAA;AAAA,iBAoD5B,qBAAA,CAAsB,MAAA,EAAQ,OAAA,EAAS,GAAA,EAAK,cAAA;AAAA,cAoOtD,MAAA,EAAQ,WAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/api.ts","../src/index.ts"],"mappings":";;;;;UACiB,WAAA;EACf,GAAA,IAAO,QAAA,UAAkB,MAAA,GAAS,MAAA,oBAA0B,OAAA,CAAQ,CAAA;EACpE,IAAA,IACE,QAAA,UACA,IAAA,YACA,OAAA;IAAY,MAAA,GAAS,MAAA;EAAA,IACpB,OAAA,CAAQ,CAAA;EACX,GAAA,IAAO,QAAA,UAAkB,IAAA,aAAiB,OAAA,CAAQ,CAAA;EAClD,KAAA,IAAS,QAAA,UAAkB,IAAA,aAAiB,OAAA,CAAQ,CAAA;EACpD,MAAA,IAAU,QAAA,WAAmB,OAAA,CAAQ,CAAA;AAAA;AAAA,KA8S3B,eAAA;EACN,OAAA;EAAe,UAAA;AAAA;EACf,OAAA;EAAgB,UAAA;EAAoB,MAAA;EAAgB,YAAA;AAAA;;;;AAzT1D;;;iBCgCsB,aAAA,CACpB,QAAA,UACA,GAAA,WACA,WAAA,aACC,OAAA;;;iBAca,YAAA,CAAa,GAAA,YAAe,MAAA;;;;;;iBAyDtB,0BAAA,CACpB,GAAA,EAAK,WAAA,EACL,UAAA,UACA,MAAA,WACC,OAAA,CAAQ,eAAA;AAAA,iBAYK,qBAAA,CAAsB,MAAA,EAAQ,OAAA,EAAS,GAAA,EAAK,cAAA;AAAA,cA+PtD,MAAA,EAAQ,WAAA"}
package/dist/index.mjs CHANGED
@@ -175,6 +175,51 @@ async function findThemeTemplate(api, themeId, ref) {
175
175
  async function getTemplate(api, id) {
176
176
  return unwrap(await api.get(`${TEMPLATES}/${id}`), "application_theme_template", "template");
177
177
  }
178
+ /**
179
+ * Delete a page template only when no Page still uses it. Both sides are
180
+ * checked: the template's own assignment list, and every Page's template in
181
+ * the active theme. Neither is complete alone (a Page assigned in several
182
+ * themes appears on only one side). `ignorePageId` is a Page just deleted.
183
+ */
184
+ async function deleteUnusedTemplate(api, templateId, ignorePageId) {
185
+ let template;
186
+ try {
187
+ template = await getTemplate(api, templateId);
188
+ } catch (err) {
189
+ if (statusOf(err) === 404) return {
190
+ deleted: false,
191
+ templateId,
192
+ reason: "No template with that id."
193
+ };
194
+ throw err;
195
+ }
196
+ if (template.themeable_type !== "page") return {
197
+ deleted: false,
198
+ templateId,
199
+ reason: `Template ${templateId} is a ${template.themeable_type ?? "non-page"} template, not a Page template.`
200
+ };
201
+ const users = new Set((template.assigned_themeable_ids ?? []).filter((id) => id !== ignorePageId));
202
+ let cursor;
203
+ do {
204
+ const batch = await listPages(api, {
205
+ limit: 100,
206
+ cursor
207
+ });
208
+ for (const page of batch.pages) if (page.id !== ignorePageId && page.application_theme_template_id === templateId) users.add(page.id);
209
+ cursor = batch.nextCursor ?? void 0;
210
+ } while (cursor);
211
+ if (users.size > 0) return {
212
+ deleted: false,
213
+ templateId,
214
+ reason: `Template ${templateId} is still used by another Page, so it was kept.`,
215
+ otherPageIds: [...users]
216
+ };
217
+ await api.delete(`${TEMPLATES}/${templateId}`);
218
+ return {
219
+ deleted: true,
220
+ templateId
221
+ };
222
+ }
178
223
  /** The template a Page renders through, checked to be assigned to it. */
179
224
  async function pageTemplate(api, page, templateId) {
180
225
  const id = templateId ?? page.application_theme_template_id ?? null;
@@ -754,6 +799,22 @@ async function readContent(file) {
754
799
  throw new PagesError(`Couldn't read ${file}: ${err instanceof Error ? err.message : String(err)}`);
755
800
  }
756
801
  }
802
+ /**
803
+ * Clean up a just-deleted Page's template. The Page is already gone, so a
804
+ * failed cleanup is reported, with the template left to delete, rather than
805
+ * thrown.
806
+ */
807
+ async function cleanUpTemplateAfterDelete(api, templateId, pageId) {
808
+ try {
809
+ return await deleteUnusedTemplate(api, templateId, pageId);
810
+ } catch (err) {
811
+ return {
812
+ deleted: false,
813
+ templateId,
814
+ reason: `The Page was deleted but its template could not be cleaned up (${String(errorPayload(err)["error"])}). Retry with \`fluid pages delete-template ${templateId} --yes\`.`
815
+ };
816
+ }
817
+ }
757
818
  function registerPagesCommands(parent, ctx) {
758
819
  parent.command("list").description("List the company's Pages; --cursor pages through").option("--status <status>", "Only draft, scheduled, or published Pages").option("--search <text>", "Match title, description, or slug").option("--limit <n>", "Pages per call, up to 100", "25").option("--cursor <cursor>", "The nextCursor from the previous call").action((opts) => run(async () => {
759
820
  ctx.output(await listPages(await client(ctx), {
@@ -828,18 +889,36 @@ function registerPagesCommands(parent, ctx) {
828
889
  await confirmChange(`${verb} Page ${id}?`, opts.yes);
829
890
  ctx.output(await updatePage(api, id, { status }));
830
891
  }));
831
- parent.command("delete <page>").description("Delete a Page (id or slug); it comes off the storefront immediately (changes live config)").option("--yes", "Confirm without a prompt", false).action((ref, opts) => run(async () => {
892
+ parent.command("delete <page>").description("Delete a Page (id or slug); it comes off the storefront immediately (changes live config)").option("--delete-template", "Also delete the Page's template, if no other Page uses it", false).option("--yes", "Confirm without a prompt", false).action((ref, opts) => run(async () => {
832
893
  const api = await client(ctx);
833
894
  const page = await getPage(api, await resolvePageId(api, ref));
834
895
  await confirmChange(`Delete Page ${page.id}? It comes off the storefront immediately.`, opts.yes);
835
896
  const result = await deletePage(api, page.id);
836
897
  const templateId = page.application_theme_template_id ?? null;
898
+ if (opts.deleteTemplate && templateId !== null) {
899
+ const template = await cleanUpTemplateAfterDelete(api, templateId, page.id);
900
+ ctx.output({
901
+ ...result,
902
+ templateId,
903
+ template
904
+ });
905
+ return;
906
+ }
837
907
  ctx.output({
838
908
  ...result,
839
909
  templateId,
840
- note: templateId === null ? "The Page reported no template in the active theme. Any template assigned to it in another theme was kept; find it with `fluid themes templates list --application-theme-id <theme id>`." : `Template ${templateId} was kept. Once no other Page uses it, delete it with \`fluid themes templates delete ${templateId}\`.`
910
+ note: templateId === null ? "The Page reported no template in the active theme. Any template assigned to it in another theme was kept." : `Template ${templateId} was kept. Delete it, if no other Page uses it, with \`fluid pages delete-template ${templateId} --yes\`.`
841
911
  });
842
912
  }));
913
+ parent.command("delete-template <template-id>").description("Delete a Page template that no Page uses any more (changes live config)").option("--yes", "Confirm without a prompt", false).action((rawId, opts) => run(async () => {
914
+ const templateId = Number(rawId);
915
+ if (!Number.isInteger(templateId) || templateId <= 0) throw new PagesError(`"${rawId}" isn't a template id.`);
916
+ const api = await client(ctx);
917
+ await confirmChange(`Delete template ${templateId} if no Page uses it?`, opts.yes);
918
+ const outcome = await deleteUnusedTemplate(api, templateId);
919
+ ctx.output(outcome);
920
+ if (!outcome.deleted) process.exitCode = 1;
921
+ }));
843
922
  parent.command("template <page>").description("Show the Page's template with its Liquid; --content-file replaces the Liquid (changes live config with --publish)").option("--template-id <id>", "The template, when it's assigned in a theme that isn't active").option("--content-file <path>", "Replace the template's Liquid with this file's contents").option("--publish", "Publish the new template version; without it the change stays a draft version", false).option("--yes", "Confirm without a prompt", false).action((ref, opts) => run(async () => {
844
923
  if (opts.publish && !opts.contentFile) throw new PagesError("--publish goes with --content-file.");
845
924
  const api = await client(ctx);
@@ -869,12 +948,12 @@ function registerPagesCommands(parent, ctx) {
869
948
  }
870
949
  const plugin = {
871
950
  name: "@fluid-app/fluid-cli-pages",
872
- version: "0.1.0",
951
+ version: "0.1.1",
873
952
  register(ctx) {
874
953
  ctx.program.addCommand(createDomainCommand("pages", "Storefront Pages: create a Page with its own theme template, then list, update, publish, and delete", registerPagesCommands));
875
954
  }
876
955
  };
877
956
  //#endregion
878
- export { confirmChange, plugin as default, errorPayload, registerPagesCommands };
957
+ export { cleanUpTemplateAfterDelete, confirmChange, plugin as default, errorPayload, registerPagesCommands };
879
958
 
880
959
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["message"],"sources":["../src/api.ts","../src/walk.ts","../src/index.ts"],"sourcesContent":["/** The slice of the CLI's API client these calls use. */\nexport interface PagesClient {\n get<T>(endpoint: string, params?: Record<string, unknown>): Promise<T>;\n post<T>(\n endpoint: string,\n body?: unknown,\n options?: { params?: Record<string, unknown> },\n ): Promise<T>;\n put<T>(endpoint: string, body?: unknown): Promise<T>;\n patch<T>(endpoint: string, body?: unknown): Promise<T>;\n delete<T>(endpoint: string): Promise<T>;\n}\n\n/** A failure the CLI explains itself, as opposed to an HTTP error. */\nexport class PagesError extends Error {\n readonly details?: Record<string, unknown>;\n\n constructor(message: string, details?: Record<string, unknown>) {\n super(message);\n this.name = \"PagesError\";\n if (details) this.details = details;\n }\n}\n\nconst PAGES = \"/api/v202604/company/pages\";\nconst TEMPLATES = \"/api/application_theme_templates\";\nconst THEMES = \"/api/v202604/themes\";\n\n/** The Liquid a new Page template starts with when no content is given.\n * The same first draft Mist's create-page tool writes. */\nexport const DEFAULT_TEMPLATE_CONTENT =\n \"<main><h1>{{ page.title }}</h1>{% if page.description != blank %}<p>{{ page.description }}</p>{% endif %}</main>\";\n\n/** URL-safe slug, the rule Mist uses for both the Page slug and the\n * template name: lowercase, runs of anything else become one hyphen. */\nexport function pageSlug(value: string): string {\n const slug = value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"page\";\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction statusOf(err: unknown): number | undefined {\n const status = (err as { status?: unknown } | null)?.status;\n return typeof status === \"number\" ? status : undefined;\n}\n\nfunction message(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** The record under the first key that holds one, else the body itself. */\nfunction unwrap(body: unknown, ...keys: string[]): Record<string, unknown> {\n if (!isRecord(body)) return {};\n for (const key of keys) {\n const value = body[key];\n if (isRecord(value)) return value;\n }\n return body;\n}\n\nfunction idOf(record: Record<string, unknown>): number | null {\n const id = record[\"id\"];\n return typeof id === \"number\" && Number.isInteger(id) ? id : null;\n}\n\nfunction positiveId(value: string): number | null {\n return /^\\d+$/.test(value) && Number(value) > 0 ? Number(value) : null;\n}\n\n// ---------------------------------------------------------------------------\n// Pages\n// ---------------------------------------------------------------------------\n\nexport interface PageRecord {\n id: number;\n title?: string;\n slug?: string;\n status?: string;\n active?: boolean;\n description?: string | null;\n canonical_url?: string | null;\n application_theme_template_id?: number | null;\n [key: string]: unknown;\n}\n\ninterface PageListWire {\n pages?: PageRecord[];\n meta?: { pagination?: { next_cursor?: string | null } };\n}\n\nexport interface ListOptions {\n status?: string;\n search?: string;\n limit?: number;\n cursor?: string;\n}\n\nexport async function listPages(\n api: PagesClient,\n opts: ListOptions = {},\n): Promise<{ pages: PageRecord[]; nextCursor: string | null }> {\n const params: Record<string, unknown> = {};\n if (opts.search) params[\"q\"] = opts.search;\n if (opts.status) params[\"filter\"] = { status: opts.status };\n const page: Record<string, unknown> = {};\n if (opts.limit !== undefined) page[\"limit\"] = opts.limit;\n if (opts.cursor) page[\"cursor\"] = opts.cursor;\n if (Object.keys(page).length > 0) params[\"page\"] = page;\n\n const data = await api.get<PageListWire>(PAGES, params);\n return {\n pages: data?.pages ?? [],\n nextCursor: data?.meta?.pagination?.next_cursor ?? null,\n };\n}\n\nexport async function getPage(api: PagesClient, id: number): Promise<PageRecord> {\n const page = unwrap(await api.get(`${PAGES}/${id}`), \"page\") as PageRecord;\n if (idOf(page) === null) {\n throw new PagesError(`The API returned no Page for id ${id}.`);\n }\n return page;\n}\n\n/** A Page by id, or by its exact slug. Search finds most slugs in one call;\n * a page created moments ago may not be indexed yet, or may sit outside the\n * first 100 fuzzy matches, so fall back to walking the whole list. */\nexport async function resolvePageId(api: PagesClient, ref: string): Promise<number> {\n const id = positiveId(ref);\n if (id !== null) return id;\n\n const slug = ref.trim().replace(/^\\/+|\\/+$/g, \"\").replace(/^home\\/pages\\//, \"\");\n const searched = await listPages(api, { search: slug, limit: 100 });\n const hit = searched.pages.find((p) => p.slug === slug);\n if (hit) return hit.id;\n\n let cursor: string | undefined;\n const seen = new Set<string>();\n for (;;) {\n const batch = await listPages(api, { limit: 100, cursor });\n const match = batch.pages.find((p) => p.slug === slug);\n if (match) return match.id;\n // A repeated cursor would loop forever; treat it as the end.\n if (!batch.nextCursor || seen.has(batch.nextCursor)) break;\n seen.add(batch.nextCursor);\n cursor = batch.nextCursor;\n }\n throw new PagesError(`No Page with id or slug \"${ref}\". Run \\`fluid pages list\\` to see them.`);\n}\n\nexport interface PageChanges {\n title?: string;\n slug?: string;\n description?: string;\n status?: \"draft\" | \"published\";\n}\n\nexport async function updatePage(\n api: PagesClient,\n id: number,\n changes: PageChanges,\n): Promise<PageRecord> {\n const page: Record<string, unknown> = { ...changes };\n // A slug sent alone lasts only until the title next changes; pin it.\n if (changes.slug !== undefined) page[\"custom_slug\"] = true;\n if (Object.keys(page).length === 0) {\n throw new PagesError(\"Nothing to change. Pass --title, --slug, or --description.\");\n }\n return unwrap(await api.patch(`${PAGES}/${id}`, { page }), \"page\") as PageRecord;\n}\n\nexport async function deletePage(\n api: PagesClient,\n id: number,\n): Promise<{ deleted: true; id: number }> {\n await api.delete(`${PAGES}/${id}`);\n return { deleted: true, id };\n}\n\n// ---------------------------------------------------------------------------\n// Themes and templates\n// ---------------------------------------------------------------------------\n\nexport interface ThemeRef {\n id: number;\n name: string;\n}\n\ninterface ThemeWire {\n id?: number;\n name?: string;\n}\n\nconst THEME_SCAN_PAGES = 10;\n\n/** The company's named themes. The themes API pages by number. */\nexport async function listThemes(api: PagesClient): Promise<ThemeRef[]> {\n const found: ThemeRef[] = [];\n for (let page = 1; page <= THEME_SCAN_PAGES; page += 1) {\n const data = await api.get<{ themes?: ThemeWire[] }>(THEMES, { page, per_page: 100 });\n const themes = data?.themes ?? [];\n for (const theme of themes) {\n if (typeof theme.id === \"number\" && theme.name) found.push({ id: theme.id, name: theme.name });\n }\n if (themes.length < 100) break;\n }\n return found;\n}\n\n/** The theme a new Page template goes in: the one named (by id or exact\n * name), else the company's active theme. */\nexport async function resolveTheme(api: PagesClient, ref?: string): Promise<ThemeRef> {\n if (!ref) {\n const theme = unwrap(await api.get(`${THEMES}/active`), \"theme\") as ThemeWire;\n if (typeof theme.id !== \"number\") {\n throw new PagesError(\"This company has no active theme. Pass --theme <id or name>.\");\n }\n return { id: theme.id, name: theme.name ?? \"\" };\n }\n\n const id = positiveId(ref);\n if (id !== null) {\n const theme = unwrap(await api.get(`${THEMES}/${id}`), \"theme\") as ThemeWire;\n return { id, name: theme.name ?? \"\" };\n }\n\n const wanted = ref.trim().toLocaleLowerCase();\n const themes = await listThemes(api);\n const seen = themes.map((theme) => theme.name);\n const matches = themes.filter((theme) => theme.name.trim().toLocaleLowerCase() === wanted);\n if (matches.length === 1) return matches[0] as ThemeRef;\n if (matches.length > 1) {\n throw new PagesError(\n `More than one theme is named \"${ref}\". Pass its id with --theme instead.`,\n { themes: matches },\n );\n }\n throw new PagesError(`No theme named \"${ref}\".`, { themes: seen });\n}\n\nexport interface TemplateRecord {\n id: number;\n name?: string;\n content?: string | null;\n status?: string;\n themeable_type?: string;\n application_theme_id?: number | null;\n assigned_themeable_ids?: number[];\n [key: string]: unknown;\n}\n\n/** A theme's page templates, without their Liquid. */\nexport async function listPageTemplates(\n api: PagesClient,\n themeId: number,\n): Promise<Array<{ id: number; name: string }>> {\n const data = await api.get<{ templates?: TemplateRecord[] }>(`${THEMES}/${themeId}/templates`, {\n themeable_type: \"page\",\n });\n return (data?.templates ?? [])\n .filter((t) => typeof t.id === \"number\")\n .map((t) => ({ id: t.id, name: t.name ?? String(t.id) }));\n}\n\n/** One template in a theme, by id or exact name, with its Liquid body. */\nexport async function findThemeTemplate(\n api: PagesClient,\n themeId: number,\n ref: string,\n): Promise<TemplateRecord> {\n let id = positiveId(ref);\n if (id === null) {\n const data = await api.get<{ templates?: TemplateRecord[] }>(\n `${THEMES}/${themeId}/templates`,\n { themeable_type: \"page\" },\n );\n const named = (data?.templates ?? []).filter((t) => t.name === ref);\n if (named.length !== 1) {\n throw new PagesError(\n named.length === 0\n ? `Theme ${themeId} has no page template named \"${ref}\".`\n : `Theme ${themeId} has more than one page template named \"${ref}\". Pass its id.`,\n { pageTemplates: (data?.templates ?? []).map((t) => ({ id: t.id, name: t.name })) },\n );\n }\n id = (named[0] as TemplateRecord).id;\n }\n try {\n return unwrap(\n await api.get(`${THEMES}/${themeId}/templates/${id}`),\n \"template\",\n ) as TemplateRecord;\n } catch (err) {\n if (statusOf(err) === 404) {\n throw new PagesError(`Template ${id} isn't in theme ${themeId}. Pass --theme for the theme it belongs to.`);\n }\n throw err;\n }\n}\n\n/** A template with its details, including which records it is assigned to. */\nexport async function getTemplate(api: PagesClient, id: number): Promise<TemplateRecord> {\n return unwrap(await api.get(`${TEMPLATES}/${id}`), \"application_theme_template\", \"template\") as TemplateRecord;\n}\n\n/** The template a Page renders through, checked to be assigned to it. */\nexport async function pageTemplate(\n api: PagesClient,\n page: PageRecord,\n templateId?: number,\n): Promise<TemplateRecord> {\n const id = templateId ?? page.application_theme_template_id ?? null;\n if (id === null || id === undefined) {\n throw new PagesError(\n `Page ${page.id} has no template of its own in the active theme, so it renders with the theme's default page template. If you assigned one in another theme, pass --template-id.`,\n );\n }\n const template = await getTemplate(api, id);\n // Either side proves the link: the Page names the template it renders\n // with in the active theme, and the template lists the Pages whose last\n // assignment was to it. A Page assigned in several themes shows on only\n // one side, so neither alone is complete.\n const assigned = template.assigned_themeable_ids;\n const linked =\n page.application_theme_template_id === id ||\n (Array.isArray(assigned) && assigned.includes(page.id));\n if (template.themeable_type !== \"page\" || !linked) {\n throw new PagesError(`Template ${id} isn't assigned to Page ${page.id}. Nothing was changed.`);\n }\n return template;\n}\n\nexport async function updateTemplateContent(\n api: PagesClient,\n templateId: number,\n content: string,\n publish: boolean,\n): Promise<{ templateId: number; updated: true; published: boolean }> {\n await api.patch(`${TEMPLATES}/${templateId}`, {\n application_theme_template: { content },\n });\n if (publish) await api.post(`${TEMPLATES}/${templateId}/publish`);\n return { templateId, updated: true, published: publish };\n}\n\n// ---------------------------------------------------------------------------\n// Create: the Page, its own template, and the link between them\n// ---------------------------------------------------------------------------\n\nexport interface CreatePageInput {\n title: string;\n slug?: string;\n description?: string;\n templateName?: string;\n templateContent?: string;\n theme: ThemeRef;\n /** Render the Page with the theme's stylesheets (\"brand style\"). */\n brandStyle: boolean;\n publish: boolean;\n}\n\nexport type Assignment = \"direct-page\" | \"theme-scoped\";\n\nexport interface CreatePageResult {\n page: PageRecord;\n template: { id: number; name: string; themeId: number; themeName: string };\n assignment: Assignment;\n status: \"draft\" | \"published\";\n previewPath: string;\n}\n\n/** Remove what an incomplete create left behind, Page first, and say what\n * happened. Never throws: the caller is already reporting a failure. */\nasync function cleanup(\n api: PagesClient,\n pageId: number | null,\n templateId: number | null,\n): Promise<{ removed: string[]; cleanupFailed: string[] }> {\n const removed: string[] = [];\n const cleanupFailed: string[] = [];\n const steps: Array<[string, () => Promise<unknown>]> = [];\n if (pageId !== null) steps.push([`page ${pageId}`, () => api.delete(`${PAGES}/${pageId}`)]);\n if (templateId !== null) {\n steps.push([`template ${templateId}`, () => api.delete(`${TEMPLATES}/${templateId}`)]);\n }\n for (const [label, remove] of steps) {\n try {\n await remove();\n removed.push(label);\n } catch (err) {\n cleanupFailed.push(`${label} (${statusOf(err) ?? message(err)})`);\n }\n }\n return { removed, cleanupFailed };\n}\n\ntype LinkCheck = { mode: Assignment } | { failure: \"missing\" | \"unverified\" };\n\n/** Confirm the Page and template are linked, reading both back. The Page\n * reports its template only for the company's current theme, so a template\n * in another theme shows up on the template's side instead. \"missing\" is\n * returned only when both reads answered; otherwise \"unverified\", so a\n * read outage never triggers a destructive cleanup. */\nexport async function verifyLink(\n api: PagesClient,\n pageId: number,\n templateId: number,\n): Promise<LinkCheck> {\n let pageAnswered = false;\n let templateAnswered = false;\n\n try {\n const page = await getPage(api, pageId);\n pageAnswered = \"application_theme_template_id\" in page;\n if (page.application_theme_template_id === templateId) return { mode: \"direct-page\" };\n } catch (err) {\n if (statusOf(err) === 401) throw err;\n }\n\n try {\n const template = await getTemplate(api, templateId);\n const assigned = template.assigned_themeable_ids;\n templateAnswered = Array.isArray(assigned);\n if (Array.isArray(assigned) && assigned.includes(pageId)) return { mode: \"theme-scoped\" };\n } catch (err) {\n if (statusOf(err) === 401) throw err;\n }\n\n return pageAnswered && templateAnswered ? { failure: \"missing\" } : { failure: \"unverified\" };\n}\n\n/**\n * Create a Page together with its own page template, the way Mist does:\n *\n * 1. create the template in the theme (`themeable_type: page`);\n * 2. create the Page as a draft;\n * 3. assign the template to the Page;\n * 4. read both back to prove the link;\n * 5. publish, when asked.\n *\n * If a step after the template fails, the draft Page and the template are\n * removed and the error says so. Two cases keep them instead, with their\n * ids in the error: an expired session (cleanup would fail the same way),\n * and an assignment that succeeded but couldn't be read back.\n */\nexport async function createPageWithTemplate(\n api: PagesClient,\n input: CreatePageInput,\n): Promise<CreatePageResult> {\n const slug = pageSlug(input.slug ?? input.title);\n const templateName = pageSlug(input.templateName ?? input.title);\n const content = input.templateContent ?? DEFAULT_TEMPLATE_CONTENT;\n let pageId: number | null = null;\n let templateId: number | null = null;\n\n const abort = async (what: string, err?: unknown): Promise<never> => {\n const status = statusOf(err);\n if (status === 401) {\n // Cleanup would fail with the same expired session; keep the ids.\n throw new PagesError(\n pageId === null && templateId === null\n ? `${what}: your session expired. Nothing was created; sign in again and retry.`\n : `${what}: your session expired. Sign in again, then check or remove the draft resources below before retrying.`,\n { status, pageId, templateId, pageStatus: pageId === null ? null : \"draft\" },\n );\n }\n const result = await cleanup(api, pageId, templateId);\n throw new PagesError(\n `${what}${err === undefined ? \"\" : ` (${status ?? message(err)})`}. ${\n result.cleanupFailed.length === 0\n ? result.removed.length === 0\n ? \"Nothing was created.\"\n : `Removed the incomplete ${result.removed.join(\" and \")}.`\n : `Cleanup also failed for ${result.cleanupFailed.join(\", \")}; remove it by hand. The Page was never published.`\n }`,\n {\n ...(status ? { status } : {}),\n ...(err !== undefined && isRecord((err as { body?: unknown }).body)\n ? { apiError: (err as { body: unknown }).body }\n : {}),\n ...result,\n },\n );\n };\n\n // 1. The template, created and published in the chosen theme.\n try {\n const created = unwrap(\n await api.post(\n TEMPLATES,\n {\n application_theme_template: {\n name: templateName,\n content,\n themeable_type: \"page\",\n use_active_application_theme: input.brandStyle,\n },\n },\n { params: { application_theme_id: input.theme.id } },\n ),\n \"application_theme_template\",\n \"template\",\n );\n templateId = idOf(created);\n } catch (err) {\n return abort(\"Creating the page template failed\", err);\n }\n if (templateId === null) {\n throw new PagesError(\n \"The template response had no id, so no Page was created. Check the theme for an unnamed leftover template.\",\n );\n }\n\n // 2. The Page, kept as a draft until its template is linked.\n try {\n const page = unwrap(\n await api.post(PAGES, {\n page: {\n title: input.title.trim(),\n slug,\n custom_slug: true,\n description: input.description ?? \"\",\n status: \"draft\",\n },\n }),\n \"page\",\n );\n pageId = idOf(page);\n } catch (err) {\n return abort(`Creating the Page with template ${templateId} failed`, err);\n }\n if (pageId === null) return abort(\"The Page response had no id\");\n\n // 3. The link, through the template-assignment endpoint.\n try {\n await api.put(`${TEMPLATES}/${templateId}/themeables_update`, {\n added_ids: [pageId],\n removed_ids: [],\n });\n } catch (err) {\n return abort(`Assigning template ${templateId} to Page ${pageId} failed`, err);\n }\n\n // 4. Proof the link holds, never the write's response alone.\n let link: LinkCheck;\n try {\n link = await verifyLink(api, pageId, templateId);\n } catch (err) {\n return abort(\"Checking the template assignment failed\", err);\n }\n if (\"failure\" in link) {\n if (link.failure === \"unverified\") {\n throw new PagesError(\n \"The template was assigned, but neither the Page nor the template could be read back to confirm it. Both are kept; the Page is still a draft. Check them with `fluid pages show` before retrying.\",\n { pageId, templateId, pageStatus: \"draft\" },\n );\n }\n return abort(`Template ${templateId} isn't linked to Page ${pageId} after assignment`);\n }\n\n // 5. Publish, only when asked.\n if (input.publish) {\n try {\n await api.patch(`${PAGES}/${pageId}`, { page: { status: \"published\" } });\n } catch (err) {\n if (statusOf(err) === 401) return abort(`Publishing Page ${pageId} failed`, err);\n // The publish may have landed even though its response didn't. Only\n // a Page confirmed to still be a draft is safe to remove.\n const state = await getPage(api, pageId).then(\n (page) => page.status,\n () => undefined,\n );\n if (state === \"draft\") return abort(`Publishing Page ${pageId} failed`, err);\n if (state === undefined) {\n throw new PagesError(\n `Publishing Page ${pageId} failed and its state couldn't be read back, so the Page and template ${templateId} are kept. Check with \\`fluid pages show ${pageId}\\` and publish or delete it.`,\n { pageId, templateId, ...(statusOf(err) ? { status: statusOf(err) } : {}) },\n );\n }\n // Any other state means the publish took effect.\n }\n }\n\n const page = await getPage(api, pageId).catch(\n (): PageRecord => ({ id: pageId as number, slug, status: input.publish ? \"published\" : \"draft\" }),\n );\n const finalSlug = typeof page.slug === \"string\" ? page.slug : slug;\n const route = `/home/pages/${encodeURIComponent(finalSlug)}`;\n return {\n page,\n template: {\n id: templateId,\n name: templateName,\n themeId: input.theme.id,\n themeName: input.theme.name,\n },\n assignment: link.mode,\n status: input.publish ? \"published\" : \"draft\",\n previewPath: link.mode === \"direct-page\" ? route : `${route}?theme_template_id=${templateId}`,\n };\n}\n","// `fluid pages walk`: `create` as a conversation. It asks for the title,\n// slug, template and theme, lets a person go back, skip, or jump around\n// until they confirm, then creates the Page through the same function as\n// `create`. Afterwards it offers to publish and to replace the template's\n// Liquid, again through the same functions as `publish` and `template`.\n\nimport { readFile } from \"node:fs/promises\";\nimport {\n NAV_HELP,\n type Choice,\n type MenuItem,\n type Prompter,\n type Question,\n} from \"@fluid-app/fluid-cli\";\n\nimport {\n createPageWithTemplate,\n findThemeTemplate,\n getPage,\n listPageTemplates,\n listThemes,\n pageSlug,\n PagesError,\n resolveTheme,\n updatePage,\n updateTemplateContent,\n type CreatePageResult,\n type PagesClient,\n type ThemeRef,\n} from \"./api.js\";\n\nexport type StepId = \"title\" | \"slug\" | \"template\" | \"theme\" | \"source\";\nexport type TemplateChoice = \"brand\" | \"blank\" | \"copy\";\n\nexport interface Answers {\n title?: string;\n slug?: string;\n template?: TemplateChoice;\n /** Unset means the active theme. */\n theme?: ThemeRef;\n source?: { id: number; name: string };\n}\n\nexport interface WalkDeps {\n api: PagesClient;\n prompter: Prompter;\n readLocal?: (path: string) => Promise<string>;\n}\n\nexport interface WalkResult {\n created: boolean;\n /** Set when the person stopped before creating anything. */\n stopped?: boolean;\n pageId?: number;\n slug?: string;\n templateId?: number;\n assignment?: CreatePageResult[\"assignment\"];\n status?: \"draft\" | \"published\";\n url?: string;\n templateUpdated?: { published: boolean };\n /** A step after the create that failed; the Page itself exists. */\n error?: string;\n}\n\nconst STEP_TITLES: Record<StepId, string> = {\n title: \"Title\",\n slug: \"Slug\",\n template: \"Template\",\n theme: \"Theme\",\n source: \"Template to copy\",\n};\n\nconst TEMPLATE_CHOICES: Choice[] = [\n { title: \"New template, with the theme's brand style\", value: \"brand\" },\n { title: \"New blank template\", value: \"blank\" },\n { title: \"Copy an existing page template\", value: \"copy\" },\n];\n\n/** The questions that apply, in order. */\nexport function activeSteps(answers: Answers): StepId[] {\n const steps: StepId[] = [\"title\", \"slug\", \"template\", \"theme\"];\n if (answers.template === \"copy\") steps.push(\"source\");\n return steps;\n}\n\nconst message = (err: unknown) => (err instanceof Error ? err.message : String(err));\n\nexport async function walkCreatePage(deps: WalkDeps): Promise<WalkResult> {\n const { api, prompter } = deps;\n const readLocal = deps.readLocal ?? ((path: string) => readFile(path, \"utf8\"));\n const answers: Answers = {};\n const history: StepId[] = [];\n let step: StepId | \"review\" = \"title\";\n\n // Looked up once, when first needed.\n let active: ThemeRef | undefined;\n const activeTheme = async () => (active ??= await resolveTheme(api));\n let themes: ThemeRef[] | undefined;\n const templatesByTheme = new Map<number, Array<{ id: number; name: string }>>();\n\n const chosenTheme = async () => answers.theme ?? (await activeTheme());\n const describeTheme = async () => {\n const theme = await chosenTheme();\n return `${theme.name || theme.id}${answers.theme ? \"\" : \" (active)\"}`;\n };\n\n const next = (from: StepId): StepId | \"review\" => {\n const steps = activeSteps(answers);\n const at = steps.indexOf(from);\n // A step that no longer applies (the copy source after it fell back to\n // a new template) was the last one, so go on to the review.\n return at < 0 ? \"review\" : (steps[at + 1] ?? \"review\");\n };\n\n prompter.note(`Create a page and its own template. ${NAV_HELP}`);\n\n for (;;) {\n if (step === \"review\" && answers.template === \"copy\" && !answers.source) step = \"source\";\n if (step === \"review\") {\n const summary = [\n `Title: ${answers.title}`,\n `Slug: ${answers.slug ?? pageSlug(answers.title ?? \"\")}`,\n `Template: ${\n answers.template === \"copy\" && answers.source\n ? `copy of ${answers.source.name}`\n : answers.template === \"blank\"\n ? \"new, blank\"\n : \"new, with the theme's brand style\"\n }`,\n `Theme: ${await describeTheme()}`,\n ];\n prompter.note(summary.join(\"\\n\"));\n const choice = await prompter.menu<string>(\"Create this page as a draft?\", [\n { title: \"Create it\", value: \"create\" },\n ...activeSteps(answers).map((id) => ({ title: `Change the ${STEP_TITLES[id].toLowerCase()}`, value: id })),\n { title: \"Stop without creating anything\", value: \"stop\" },\n ]);\n if (choice === null || choice === \"stop\") return { created: false, stopped: true };\n if (choice !== \"create\") {\n step = choice as StepId;\n continue;\n }\n return finish(deps, readLocal, answers, await chosenTheme());\n }\n\n const current: StepId = step;\n const question = await questionFor(current);\n if (question === null) {\n // Nothing to choose from: the copy source fell back to a new template.\n step = next(current);\n continue;\n }\n const reply = await prompter.ask(question);\n\n if (\"nav\" in reply) {\n if (reply.nav === \"quit\") return { created: false, stopped: true };\n if (reply.nav === \"back\") {\n step = history.pop() ?? current;\n continue;\n }\n if (reply.nav === \"menu\") {\n const items: MenuItem<string>[] = [\n ...activeSteps(answers).map((id) => ({ title: STEP_TITLES[id], value: id, disabled: id !== \"title\" && !answers.title })),\n { title: \"Review and create\", value: \"review\", disabled: !answers.title },\n { title: \"Stop without creating anything\", value: \"stop\" },\n ];\n const choice = await prompter.menu(\"Go to\", items);\n if (choice === \"stop\") return { created: false, stopped: true };\n if (choice !== null) {\n history.push(current);\n step = choice as StepId | \"review\";\n }\n continue;\n }\n // Skip: take the default, except for the title, which has none.\n if (current === \"title\" && !answers.title) {\n prompter.note(\"A page needs a title.\");\n continue;\n }\n if (current === \"slug\") delete answers.slug;\n if (current === \"template\") answers.template ??= \"brand\";\n if (current === \"theme\") {\n // Skipping takes the active theme; a source from another theme can't come along.\n if (answers.theme && answers.theme.id !== (await activeTheme()).id) delete answers.source;\n delete answers.theme;\n }\n if (current === \"source\" && !answers.source) {\n answers.template = \"brand\";\n prompter.note(\"No template to copy chosen; the page gets a new template with the theme's brand style.\");\n }\n history.push(current);\n step = next(current);\n continue;\n }\n\n const value = reply.value.trim();\n if (!apply(current, value)) continue;\n history.push(current);\n step = next(current);\n }\n\n async function questionFor(id: StepId): Promise<Question | null> {\n const base = { section: `New page · ${STEP_TITLES[id]}`, canGoBack: history.length > 0 };\n switch (id) {\n case \"title\":\n return { ...base, kind: \"text\", message: \"Page title\", current: answers.title };\n case \"slug\": {\n const suggested = pageSlug(answers.title ?? \"\");\n return {\n ...base,\n kind: \"text\",\n message: \"URL slug\",\n current: answers.slug ?? suggested,\n hint: `the page lives at /home/pages/<slug>; Enter keeps ${answers.slug ?? suggested}`,\n };\n }\n case \"template\":\n return {\n ...base,\n kind: \"select\",\n message: \"Which template should the page use?\",\n choices: TEMPLATE_CHOICES,\n current: answers.template,\n };\n case \"theme\": {\n const current = await activeTheme();\n themes ??= await listThemes(api);\n const choices: Choice[] = [\n { title: `${current.name || current.id} (active)`, value: String(current.id) },\n ...themes.filter((t) => t.id !== current.id).map((t) => ({ title: t.name, value: String(t.id) })),\n ];\n return {\n ...base,\n kind: \"search\",\n message: \"Which theme should the template go in?\",\n choices,\n current: String((answers.theme ?? current).id),\n hint: \"a template in a theme that isn't active applies once that theme is active\",\n };\n }\n case \"source\": {\n const theme = await chosenTheme();\n let templates = templatesByTheme.get(theme.id);\n if (!templates) {\n templates = await listPageTemplates(api, theme.id);\n templatesByTheme.set(theme.id, templates);\n }\n if (templates.length === 0) {\n answers.template = \"brand\";\n prompter.note(`${theme.name || theme.id} has no page templates to copy; the page gets a new template with the theme's brand style.`);\n return null;\n }\n return {\n ...base,\n kind: \"search\",\n message: \"Copy which page template?\",\n choices: templates.map((t) => ({ title: t.name, value: String(t.id) })),\n current: answers.source ? String(answers.source.id) : undefined,\n };\n }\n }\n }\n\n /** Save one answer; false when it has to be asked again. */\n function apply(id: StepId, value: string): boolean {\n switch (id) {\n case \"title\":\n if (!value) {\n prompter.note(\"A page needs a title.\");\n return false;\n }\n answers.title = value;\n return true;\n case \"slug\":\n if (!value || value === pageSlug(answers.title ?? \"\")) delete answers.slug;\n else answers.slug = pageSlug(value);\n return true;\n case \"template\":\n answers.template = value as TemplateChoice;\n if (value !== \"copy\") delete answers.source;\n return true;\n case \"theme\": {\n const picked = Number(value);\n const before = answers.theme?.id ?? active?.id;\n const other = picked === active?.id ? undefined : themes?.find((t) => t.id === picked);\n if (picked !== active?.id && !other) return false;\n if (other) answers.theme = other;\n else delete answers.theme;\n // A template to copy belongs to one theme; a new theme needs a new pick.\n if (picked !== before) delete answers.source;\n return true;\n }\n case \"source\": {\n const theme = answers.theme?.id ?? active?.id;\n const found = templatesByTheme.get(theme ?? -1)?.find((t) => String(t.id) === value);\n if (!found) return false;\n answers.source = found;\n return true;\n }\n }\n }\n}\n\n/** Create the Page, then offer to publish it and to replace its Liquid. */\nasync function finish(\n deps: WalkDeps,\n readLocal: (path: string) => Promise<string>,\n answers: Answers,\n theme: ThemeRef,\n): Promise<WalkResult> {\n const { api, prompter } = deps;\n const templateContent =\n answers.template === \"copy\" && answers.source\n ? await copiedContent(api, theme.id, answers.source.id)\n : undefined;\n\n const result = await createPageWithTemplate(api, {\n title: answers.title ?? \"\",\n slug: answers.slug,\n templateContent,\n theme,\n brandStyle: answers.template !== \"blank\",\n publish: false,\n });\n const slug = typeof result.page.slug === \"string\" ? result.page.slug : (answers.slug ?? pageSlug(answers.title ?? \"\"));\n const out: WalkResult = {\n created: true,\n pageId: result.page.id,\n slug,\n templateId: result.template.id,\n assignment: result.assignment,\n status: \"draft\",\n };\n prompter.note(\n `Created page ${result.page.id} (draft) and its template ${result.template.id} in ${theme.name || theme.id}, linked ${result.assignment === \"direct-page\" ? \"directly\" : \"for that theme\"}.`,\n );\n\n try {\n if ((await yesNo(prompter, \"Publish the page now?\", \"Publish it\", \"Keep it a draft\")) === \"yes\") {\n try {\n await updatePage(api, result.page.id, { status: \"published\" });\n out.status = \"published\";\n } catch (err) {\n // The publish may have landed though its response didn't; ask the Page.\n const state = await getPage(api, result.page.id).then(\n (page) => page.status,\n () => undefined,\n );\n if (state !== \"published\" && state !== \"scheduled\") throw err;\n out.status = \"published\";\n }\n }\n out.url = pageUrl(result);\n prompter.note(`The page is at ${out.url}${out.status === \"draft\" ? \" once it's published\" : \"\"}.`);\n\n if (\n (await yesNo(prompter, \"Replace the template's Liquid with a file now?\", \"Yes, from a file\", \"Not now\")) === \"yes\"\n ) {\n const reply = await prompter.ask({\n section: \"Template\",\n kind: \"text\",\n message: \"Path to the Liquid file\",\n canGoBack: false,\n });\n if (\"value\" in reply && reply.value.trim()) {\n const content = await readLocal(reply.value.trim());\n const publish =\n (await yesNo(prompter, \"Make the new version live?\", \"Publish it\", \"Keep it a draft version\")) === \"yes\";\n await updateTemplateContent(api, result.template.id, content, publish);\n out.templateUpdated = { published: publish };\n }\n }\n if (!out.templateUpdated) {\n prompter.note(`Edit it later with \\`fluid pages template ${slug} --content-file <file> --yes\\`.`);\n }\n } catch (err) {\n out.error = message(err);\n }\n return out;\n}\n\n/** Where to see the page. A template in a theme that isn't active only shows\n * with the preview override, so that case keeps `?theme_template_id=`. */\nexport function pageUrl(result: CreatePageResult): string {\n const canonical = result.page.canonical_url;\n if (typeof canonical !== \"string\" || !canonical) return result.previewPath;\n if (result.assignment === \"direct-page\") return canonical;\n try {\n return new URL(result.previewPath, canonical).toString();\n } catch {\n return result.previewPath;\n }\n}\n\nasync function copiedContent(api: PagesClient, themeId: number, templateId: number): Promise<string> {\n const source = await findThemeTemplate(api, themeId, String(templateId));\n if (typeof source.content !== \"string\" || !source.content.trim()) {\n throw new PagesError(`Template ${templateId} has no Liquid to copy.`);\n }\n return source.content;\n}\n\nasync function yesNo(prompter: Prompter, question: string, yes: string, no: string): Promise<\"yes\" | \"no\"> {\n const reply = await prompter.ask({\n kind: \"select\",\n message: question,\n choices: [\n { title: yes, value: \"yes\" },\n { title: no, value: \"no\" },\n ],\n canGoBack: false,\n });\n return \"value\" in reply && reply.value === \"yes\" ? \"yes\" : \"no\";\n}\n","import { readFile } from \"node:fs/promises\";\nimport type { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport type { CommandContext, FluidPlugin } from \"@fluid-app/fluid-cli\";\nimport { assertInteractive, createDomainCommand, terminalPrompter } from \"@fluid-app/fluid-cli\";\n\nimport {\n createPageWithTemplate,\n deletePage,\n findThemeTemplate,\n getPage,\n listPages,\n pageTemplate,\n PagesError,\n resolvePageId,\n resolveTheme,\n updatePage,\n updateTemplateContent,\n type PageChanges,\n type PagesClient,\n} from \"./api.js\";\nimport { walkCreatePage } from \"./walk.js\";\n\nasync function client(ctx: CommandContext): Promise<PagesClient> {\n return (await ctx.getClient()) as unknown as PagesClient;\n}\n\n/**\n * Changes to a live storefront need an explicit yes. Scripts and agents\n * pass `--yes`; an interactive terminal is asked. With neither, nothing runs.\n */\nexport async function confirmChange(\n question: string,\n yes: boolean,\n interactive: boolean = Boolean(process.stdin.isTTY),\n): Promise<void> {\n if (yes) return;\n if (!interactive) throw new PagesError(`${question} Pass --yes to confirm.`);\n const answer = await prompts({\n type: \"confirm\",\n name: \"ok\",\n message: question,\n initial: false,\n });\n if (!answer.ok) throw new PagesError(\"Cancelled.\");\n}\n\n/** The readable message from a Fluid error body: v2026-04 sends\n * `error: { message, details }`, the theme API `error_message` and `errors`. */\nexport function errorPayload(err: unknown): Record<string, unknown> {\n const status =\n typeof (err as { status?: unknown }).status === \"number\"\n ? (err as { status: number }).status\n : undefined;\n const body = (err as { body?: Record<string, unknown> | null }).body ?? null;\n const nested = body?.[\"error\"];\n const detail =\n typeof nested === \"object\" && nested !== null\n ? (nested as { message?: unknown; details?: unknown })\n : null;\n const text =\n (typeof detail?.message === \"string\" && detail.message) ||\n (typeof body?.[\"error_message\"] === \"string\" && body[\"error_message\"]) ||\n (typeof nested === \"string\" && nested) ||\n (err instanceof Error ? err.message : String(err));\n const details =\n err instanceof PagesError\n ? err.details\n : (detail?.details ?? body?.[\"errors\"] ?? undefined);\n return {\n error: text,\n ...(status ? { status } : {}),\n ...(details && typeof details === \"object\" ? { details } : {}),\n };\n}\n\n/** Print a failure as JSON on stderr and exit non-zero. */\nfunction fail(err: unknown): void {\n process.stderr.write(JSON.stringify(errorPayload(err)) + \"\\n\");\n process.exitCode = 1;\n}\n\nfunction run(action: () => Promise<void>): Promise<void> {\n return action().catch(fail);\n}\n\nfunction positiveInt(value: string, flag: string): number {\n if (!/^\\d+$/.test(value) || Number(value) <= 0) {\n throw new PagesError(`${flag} must be a positive whole number, not \"${value}\".`);\n }\n return Number(value);\n}\n\nasync function readContent(file: string): Promise<string> {\n try {\n return await readFile(file, \"utf8\");\n } catch (err) {\n throw new PagesError(`Couldn't read ${file}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\nexport function registerPagesCommands(parent: Command, ctx: CommandContext): void {\n parent\n .command(\"list\")\n .description(\"List the company's Pages; --cursor pages through\")\n .option(\"--status <status>\", \"Only draft, scheduled, or published Pages\")\n .option(\"--search <text>\", \"Match title, description, or slug\")\n .option(\"--limit <n>\", \"Pages per call, up to 100\", \"25\")\n .option(\"--cursor <cursor>\", \"The nextCursor from the previous call\")\n .action((opts: { status?: string; search?: string; limit: string; cursor?: string }) =>\n run(async () => {\n ctx.output(\n await listPages(await client(ctx), {\n status: opts.status,\n search: opts.search,\n limit: positiveInt(opts.limit, \"--limit\"),\n cursor: opts.cursor,\n }),\n );\n }),\n );\n\n parent\n .command(\"show <page>\")\n .description(\"Show one Page (id or slug), including the template it renders through\")\n .action((ref: string) =>\n run(async () => {\n const api = await client(ctx);\n ctx.output(await getPage(api, await resolvePageId(api, ref)));\n }),\n );\n\n parent\n .command(\"create\")\n .description(\n \"Create a Page with its own page template in a theme, linked together; stays a draft unless --publish (changes live config)\",\n )\n .requiredOption(\"--title <title>\", \"The Page title\")\n .option(\"--slug <slug>\", \"URL slug; defaults to one made from the title\")\n .option(\"--description <text>\", \"Plain-text Page description\")\n .option(\"--template-name <name>\", \"Name of the new template; defaults to one made from the title\")\n .option(\"--template-from <template>\", \"Start from a copy of this page template's Liquid (id or name, in the same theme)\")\n .option(\"--content-file <path>\", \"Start from the Liquid in this file\")\n .option(\"--theme <theme>\", \"Theme for the template (id or name); defaults to the active theme\")\n .option(\"--blank\", \"Don't apply the theme's brand style to the Page\", false)\n .option(\"--publish\", \"Publish the Page once its template is linked\", false)\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action(\n (opts: {\n title: string;\n slug?: string;\n description?: string;\n templateName?: string;\n templateFrom?: string;\n contentFile?: string;\n theme?: string;\n blank: boolean;\n publish: boolean;\n yes: boolean;\n }) =>\n run(async () => {\n if (!opts.title.trim()) throw new PagesError(\"--title can't be blank.\");\n if (opts.templateFrom && opts.contentFile) {\n throw new PagesError(\"Use --template-from or --content-file, not both.\");\n }\n const api = await client(ctx);\n const theme = await resolveTheme(api, opts.theme);\n let templateContent: string | undefined;\n if (opts.contentFile) {\n templateContent = await readContent(opts.contentFile);\n } else if (opts.templateFrom) {\n const source = await findThemeTemplate(api, theme.id, opts.templateFrom);\n if (typeof source.content !== \"string\" || !source.content.trim()) {\n throw new PagesError(`Template ${source.id} has no Liquid to copy.`);\n }\n templateContent = source.content;\n }\n await confirmChange(\n `Create the Page \"${opts.title}\" and its template in the theme \"${theme.name || theme.id}\"${opts.publish ? \", then publish it\" : \" as a draft\"}?`,\n opts.yes,\n );\n ctx.output(\n await createPageWithTemplate(api, {\n title: opts.title,\n slug: opts.slug,\n description: opts.description,\n templateName: opts.templateName,\n templateContent,\n theme,\n brandStyle: !opts.blank,\n publish: opts.publish,\n }),\n );\n }),\n );\n\n parent\n .command(\"walk\")\n .description(\n \"Create a page by answering questions in the terminal: title, slug, template, theme; go back, skip, or jump around before you confirm (changes live config)\",\n )\n .action(() =>\n run(async () => {\n assertInteractive(\"`fluid pages create --title <title> --yes`\");\n const result = await walkCreatePage({ api: await client(ctx), prompter: terminalPrompter() });\n ctx.output(result);\n if (result.error) fail(new PagesError(result.error, { pageId: result.pageId, templateId: result.templateId }));\n }),\n );\n\n parent\n .command(\"update <page>\")\n .description(\"Change a Page's title, slug, or description (changes live config)\")\n .option(\"--title <title>\", \"New title\")\n .option(\"--slug <slug>\", \"New URL slug; old links to the Page stop working\")\n .option(\"--description <text>\", \"New plain-text description\")\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action((ref: string, opts: { title?: string; slug?: string; description?: string; yes: boolean }) =>\n run(async () => {\n const changes: PageChanges = {};\n if (opts.title !== undefined) changes.title = opts.title;\n if (opts.slug !== undefined) changes.slug = opts.slug;\n if (opts.description !== undefined) changes.description = opts.description;\n if (Object.keys(changes).length === 0) {\n throw new PagesError(\"Nothing to change. Pass --title, --slug, or --description.\");\n }\n const api = await client(ctx);\n const id = await resolvePageId(api, ref);\n await confirmChange(`Update Page ${id}?`, opts.yes);\n ctx.output(await updatePage(api, id, changes));\n }),\n );\n\n for (const [name, status, verb] of [\n [\"publish\", \"published\", \"Publish\"],\n [\"unpublish\", \"draft\", \"Unpublish\"],\n ] as const) {\n parent\n .command(`${name} <page>`)\n .description(\n `${verb} a Page (id or slug); ${status === \"published\" ? \"it goes live on the storefront\" : \"it comes off the storefront and becomes a draft\"} (changes live config)`,\n )\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action((ref: string, opts: { yes: boolean }) =>\n run(async () => {\n const api = await client(ctx);\n const id = await resolvePageId(api, ref);\n await confirmChange(`${verb} Page ${id}?`, opts.yes);\n ctx.output(await updatePage(api, id, { status }));\n }),\n );\n }\n\n parent\n .command(\"delete <page>\")\n .description(\"Delete a Page (id or slug); it comes off the storefront immediately (changes live config)\")\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action((ref: string, opts: { yes: boolean }) =>\n run(async () => {\n const api = await client(ctx);\n const page = await getPage(api, await resolvePageId(api, ref));\n await confirmChange(\n `Delete Page ${page.id}? It comes off the storefront immediately.`,\n opts.yes,\n );\n const result = await deletePage(api, page.id);\n // The template is left in place: the API can't show every Page that\n // uses it across themes, so deleting it here could break another.\n const templateId = page.application_theme_template_id ?? null;\n ctx.output({\n ...result,\n templateId,\n note:\n templateId === null\n ? \"The Page reported no template in the active theme. Any template assigned to it in another theme was kept; find it with `fluid themes templates list --application-theme-id <theme id>`.\"\n : `Template ${templateId} was kept. Once no other Page uses it, delete it with \\`fluid themes templates delete ${templateId}\\`.`,\n });\n }),\n );\n\n parent\n .command(\"template <page>\")\n .description(\n \"Show the Page's template with its Liquid; --content-file replaces the Liquid (changes live config with --publish)\",\n )\n .option(\"--template-id <id>\", \"The template, when it's assigned in a theme that isn't active\")\n .option(\"--content-file <path>\", \"Replace the template's Liquid with this file's contents\")\n .option(\"--publish\", \"Publish the new template version; without it the change stays a draft version\", false)\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action(\n (ref: string, opts: { templateId?: string; contentFile?: string; publish: boolean; yes: boolean }) =>\n run(async () => {\n if (opts.publish && !opts.contentFile) {\n throw new PagesError(\"--publish goes with --content-file.\");\n }\n const api = await client(ctx);\n const page = await getPage(api, await resolvePageId(api, ref));\n const template = await pageTemplate(\n api,\n page,\n opts.templateId === undefined ? undefined : positiveInt(opts.templateId, \"--template-id\"),\n );\n if (!opts.contentFile) {\n ctx.output({\n pageId: page.id,\n template: {\n id: template.id,\n name: template.name,\n status: template.status,\n themeId: template.application_theme_id ?? null,\n assignedPageIds: template.assigned_themeable_ids ?? [],\n content: template.content ?? null,\n },\n });\n return;\n }\n const content = await readContent(opts.contentFile);\n await confirmChange(\n `Replace the Liquid of template ${template.id}${opts.publish ? \" and publish it\" : \" (as a draft version)\"}?`,\n opts.yes,\n );\n ctx.output({\n pageId: page.id,\n ...(await updateTemplateContent(api, template.id, content, opts.publish)),\n });\n }),\n );\n}\n\nconst plugin: FluidPlugin = {\n name: \"@fluid-app/fluid-cli-pages\",\n version: \"0.1.0\",\n register(ctx) {\n ctx.program.addCommand(\n createDomainCommand(\n \"pages\",\n \"Storefront Pages: create a Page with its own theme template, then list, update, publish, and delete\",\n registerPagesCommands,\n ),\n );\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["message"],"sources":["../src/api.ts","../src/walk.ts","../src/index.ts"],"sourcesContent":["/** The slice of the CLI's API client these calls use. */\nexport interface PagesClient {\n get<T>(endpoint: string, params?: Record<string, unknown>): Promise<T>;\n post<T>(\n endpoint: string,\n body?: unknown,\n options?: { params?: Record<string, unknown> },\n ): Promise<T>;\n put<T>(endpoint: string, body?: unknown): Promise<T>;\n patch<T>(endpoint: string, body?: unknown): Promise<T>;\n delete<T>(endpoint: string): Promise<T>;\n}\n\n/** A failure the CLI explains itself, as opposed to an HTTP error. */\nexport class PagesError extends Error {\n readonly details?: Record<string, unknown>;\n\n constructor(message: string, details?: Record<string, unknown>) {\n super(message);\n this.name = \"PagesError\";\n if (details) this.details = details;\n }\n}\n\nconst PAGES = \"/api/v202604/company/pages\";\nconst TEMPLATES = \"/api/application_theme_templates\";\nconst THEMES = \"/api/v202604/themes\";\n\n/** The Liquid a new Page template starts with when no content is given.\n * The same first draft Mist's create-page tool writes. */\nexport const DEFAULT_TEMPLATE_CONTENT =\n \"<main><h1>{{ page.title }}</h1>{% if page.description != blank %}<p>{{ page.description }}</p>{% endif %}</main>\";\n\n/** URL-safe slug, the rule Mist uses for both the Page slug and the\n * template name: lowercase, runs of anything else become one hyphen. */\nexport function pageSlug(value: string): string {\n const slug = value\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return slug || \"page\";\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction statusOf(err: unknown): number | undefined {\n const status = (err as { status?: unknown } | null)?.status;\n return typeof status === \"number\" ? status : undefined;\n}\n\nfunction message(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\n/** The record under the first key that holds one, else the body itself. */\nfunction unwrap(body: unknown, ...keys: string[]): Record<string, unknown> {\n if (!isRecord(body)) return {};\n for (const key of keys) {\n const value = body[key];\n if (isRecord(value)) return value;\n }\n return body;\n}\n\nfunction idOf(record: Record<string, unknown>): number | null {\n const id = record[\"id\"];\n return typeof id === \"number\" && Number.isInteger(id) ? id : null;\n}\n\nfunction positiveId(value: string): number | null {\n return /^\\d+$/.test(value) && Number(value) > 0 ? Number(value) : null;\n}\n\n// ---------------------------------------------------------------------------\n// Pages\n// ---------------------------------------------------------------------------\n\nexport interface PageRecord {\n id: number;\n title?: string;\n slug?: string;\n status?: string;\n active?: boolean;\n description?: string | null;\n canonical_url?: string | null;\n application_theme_template_id?: number | null;\n [key: string]: unknown;\n}\n\ninterface PageListWire {\n pages?: PageRecord[];\n meta?: { pagination?: { next_cursor?: string | null } };\n}\n\nexport interface ListOptions {\n status?: string;\n search?: string;\n limit?: number;\n cursor?: string;\n}\n\nexport async function listPages(\n api: PagesClient,\n opts: ListOptions = {},\n): Promise<{ pages: PageRecord[]; nextCursor: string | null }> {\n const params: Record<string, unknown> = {};\n if (opts.search) params[\"q\"] = opts.search;\n if (opts.status) params[\"filter\"] = { status: opts.status };\n const page: Record<string, unknown> = {};\n if (opts.limit !== undefined) page[\"limit\"] = opts.limit;\n if (opts.cursor) page[\"cursor\"] = opts.cursor;\n if (Object.keys(page).length > 0) params[\"page\"] = page;\n\n const data = await api.get<PageListWire>(PAGES, params);\n return {\n pages: data?.pages ?? [],\n nextCursor: data?.meta?.pagination?.next_cursor ?? null,\n };\n}\n\nexport async function getPage(api: PagesClient, id: number): Promise<PageRecord> {\n const page = unwrap(await api.get(`${PAGES}/${id}`), \"page\") as PageRecord;\n if (idOf(page) === null) {\n throw new PagesError(`The API returned no Page for id ${id}.`);\n }\n return page;\n}\n\n/** A Page by id, or by its exact slug. Search finds most slugs in one call;\n * a page created moments ago may not be indexed yet, or may sit outside the\n * first 100 fuzzy matches, so fall back to walking the whole list. */\nexport async function resolvePageId(api: PagesClient, ref: string): Promise<number> {\n const id = positiveId(ref);\n if (id !== null) return id;\n\n const slug = ref.trim().replace(/^\\/+|\\/+$/g, \"\").replace(/^home\\/pages\\//, \"\");\n const searched = await listPages(api, { search: slug, limit: 100 });\n const hit = searched.pages.find((p) => p.slug === slug);\n if (hit) return hit.id;\n\n let cursor: string | undefined;\n const seen = new Set<string>();\n for (;;) {\n const batch = await listPages(api, { limit: 100, cursor });\n const match = batch.pages.find((p) => p.slug === slug);\n if (match) return match.id;\n // A repeated cursor would loop forever; treat it as the end.\n if (!batch.nextCursor || seen.has(batch.nextCursor)) break;\n seen.add(batch.nextCursor);\n cursor = batch.nextCursor;\n }\n throw new PagesError(`No Page with id or slug \"${ref}\". Run \\`fluid pages list\\` to see them.`);\n}\n\nexport interface PageChanges {\n title?: string;\n slug?: string;\n description?: string;\n status?: \"draft\" | \"published\";\n}\n\nexport async function updatePage(\n api: PagesClient,\n id: number,\n changes: PageChanges,\n): Promise<PageRecord> {\n const page: Record<string, unknown> = { ...changes };\n // A slug sent alone lasts only until the title next changes; pin it.\n if (changes.slug !== undefined) page[\"custom_slug\"] = true;\n if (Object.keys(page).length === 0) {\n throw new PagesError(\"Nothing to change. Pass --title, --slug, or --description.\");\n }\n return unwrap(await api.patch(`${PAGES}/${id}`, { page }), \"page\") as PageRecord;\n}\n\nexport async function deletePage(\n api: PagesClient,\n id: number,\n): Promise<{ deleted: true; id: number }> {\n await api.delete(`${PAGES}/${id}`);\n return { deleted: true, id };\n}\n\n// ---------------------------------------------------------------------------\n// Themes and templates\n// ---------------------------------------------------------------------------\n\nexport interface ThemeRef {\n id: number;\n name: string;\n}\n\ninterface ThemeWire {\n id?: number;\n name?: string;\n}\n\nconst THEME_SCAN_PAGES = 10;\n\n/** The company's named themes. The themes API pages by number. */\nexport async function listThemes(api: PagesClient): Promise<ThemeRef[]> {\n const found: ThemeRef[] = [];\n for (let page = 1; page <= THEME_SCAN_PAGES; page += 1) {\n const data = await api.get<{ themes?: ThemeWire[] }>(THEMES, { page, per_page: 100 });\n const themes = data?.themes ?? [];\n for (const theme of themes) {\n if (typeof theme.id === \"number\" && theme.name) found.push({ id: theme.id, name: theme.name });\n }\n if (themes.length < 100) break;\n }\n return found;\n}\n\n/** The theme a new Page template goes in: the one named (by id or exact\n * name), else the company's active theme. */\nexport async function resolveTheme(api: PagesClient, ref?: string): Promise<ThemeRef> {\n if (!ref) {\n const theme = unwrap(await api.get(`${THEMES}/active`), \"theme\") as ThemeWire;\n if (typeof theme.id !== \"number\") {\n throw new PagesError(\"This company has no active theme. Pass --theme <id or name>.\");\n }\n return { id: theme.id, name: theme.name ?? \"\" };\n }\n\n const id = positiveId(ref);\n if (id !== null) {\n const theme = unwrap(await api.get(`${THEMES}/${id}`), \"theme\") as ThemeWire;\n return { id, name: theme.name ?? \"\" };\n }\n\n const wanted = ref.trim().toLocaleLowerCase();\n const themes = await listThemes(api);\n const seen = themes.map((theme) => theme.name);\n const matches = themes.filter((theme) => theme.name.trim().toLocaleLowerCase() === wanted);\n if (matches.length === 1) return matches[0] as ThemeRef;\n if (matches.length > 1) {\n throw new PagesError(\n `More than one theme is named \"${ref}\". Pass its id with --theme instead.`,\n { themes: matches },\n );\n }\n throw new PagesError(`No theme named \"${ref}\".`, { themes: seen });\n}\n\nexport interface TemplateRecord {\n id: number;\n name?: string;\n content?: string | null;\n status?: string;\n themeable_type?: string;\n application_theme_id?: number | null;\n assigned_themeable_ids?: number[];\n [key: string]: unknown;\n}\n\n/** A theme's page templates, without their Liquid. */\nexport async function listPageTemplates(\n api: PagesClient,\n themeId: number,\n): Promise<Array<{ id: number; name: string }>> {\n const data = await api.get<{ templates?: TemplateRecord[] }>(`${THEMES}/${themeId}/templates`, {\n themeable_type: \"page\",\n });\n return (data?.templates ?? [])\n .filter((t) => typeof t.id === \"number\")\n .map((t) => ({ id: t.id, name: t.name ?? String(t.id) }));\n}\n\n/** One template in a theme, by id or exact name, with its Liquid body. */\nexport async function findThemeTemplate(\n api: PagesClient,\n themeId: number,\n ref: string,\n): Promise<TemplateRecord> {\n let id = positiveId(ref);\n if (id === null) {\n const data = await api.get<{ templates?: TemplateRecord[] }>(\n `${THEMES}/${themeId}/templates`,\n { themeable_type: \"page\" },\n );\n const named = (data?.templates ?? []).filter((t) => t.name === ref);\n if (named.length !== 1) {\n throw new PagesError(\n named.length === 0\n ? `Theme ${themeId} has no page template named \"${ref}\".`\n : `Theme ${themeId} has more than one page template named \"${ref}\". Pass its id.`,\n { pageTemplates: (data?.templates ?? []).map((t) => ({ id: t.id, name: t.name })) },\n );\n }\n id = (named[0] as TemplateRecord).id;\n }\n try {\n return unwrap(\n await api.get(`${THEMES}/${themeId}/templates/${id}`),\n \"template\",\n ) as TemplateRecord;\n } catch (err) {\n if (statusOf(err) === 404) {\n throw new PagesError(`Template ${id} isn't in theme ${themeId}. Pass --theme for the theme it belongs to.`);\n }\n throw err;\n }\n}\n\n/** A template with its details, including which records it is assigned to. */\nexport async function getTemplate(api: PagesClient, id: number): Promise<TemplateRecord> {\n return unwrap(await api.get(`${TEMPLATES}/${id}`), \"application_theme_template\", \"template\") as TemplateRecord;\n}\n\nexport type TemplateCleanup =\n | { deleted: true; templateId: number }\n | { deleted: false; templateId: number; reason: string; otherPageIds?: number[] };\n\n/**\n * Delete a page template only when no Page still uses it. Both sides are\n * checked: the template's own assignment list, and every Page's template in\n * the active theme. Neither is complete alone (a Page assigned in several\n * themes appears on only one side). `ignorePageId` is a Page just deleted.\n */\nexport async function deleteUnusedTemplate(\n api: PagesClient,\n templateId: number,\n ignorePageId?: number,\n): Promise<TemplateCleanup> {\n let template: TemplateRecord;\n try {\n template = await getTemplate(api, templateId);\n } catch (err) {\n if (statusOf(err) === 404) {\n return { deleted: false, templateId, reason: \"No template with that id.\" };\n }\n throw err;\n }\n if (template.themeable_type !== \"page\") {\n return {\n deleted: false,\n templateId,\n reason: `Template ${templateId} is a ${template.themeable_type ?? \"non-page\"} template, not a Page template.`,\n };\n }\n const users = new Set<number>(\n (template.assigned_themeable_ids ?? []).filter((id) => id !== ignorePageId),\n );\n let cursor: string | undefined;\n do {\n const batch = await listPages(api, { limit: 100, cursor });\n for (const page of batch.pages) {\n if (page.id !== ignorePageId && page.application_theme_template_id === templateId) {\n users.add(page.id);\n }\n }\n cursor = batch.nextCursor ?? undefined;\n } while (cursor);\n if (users.size > 0) {\n return {\n deleted: false,\n templateId,\n reason: `Template ${templateId} is still used by another Page, so it was kept.`,\n otherPageIds: [...users],\n };\n }\n await api.delete(`${TEMPLATES}/${templateId}`);\n return { deleted: true, templateId };\n}\n\n/** The template a Page renders through, checked to be assigned to it. */\nexport async function pageTemplate(\n api: PagesClient,\n page: PageRecord,\n templateId?: number,\n): Promise<TemplateRecord> {\n const id = templateId ?? page.application_theme_template_id ?? null;\n if (id === null || id === undefined) {\n throw new PagesError(\n `Page ${page.id} has no template of its own in the active theme, so it renders with the theme's default page template. If you assigned one in another theme, pass --template-id.`,\n );\n }\n const template = await getTemplate(api, id);\n // Either side proves the link: the Page names the template it renders\n // with in the active theme, and the template lists the Pages whose last\n // assignment was to it. A Page assigned in several themes shows on only\n // one side, so neither alone is complete.\n const assigned = template.assigned_themeable_ids;\n const linked =\n page.application_theme_template_id === id ||\n (Array.isArray(assigned) && assigned.includes(page.id));\n if (template.themeable_type !== \"page\" || !linked) {\n throw new PagesError(`Template ${id} isn't assigned to Page ${page.id}. Nothing was changed.`);\n }\n return template;\n}\n\nexport async function updateTemplateContent(\n api: PagesClient,\n templateId: number,\n content: string,\n publish: boolean,\n): Promise<{ templateId: number; updated: true; published: boolean }> {\n await api.patch(`${TEMPLATES}/${templateId}`, {\n application_theme_template: { content },\n });\n if (publish) await api.post(`${TEMPLATES}/${templateId}/publish`);\n return { templateId, updated: true, published: publish };\n}\n\n// ---------------------------------------------------------------------------\n// Create: the Page, its own template, and the link between them\n// ---------------------------------------------------------------------------\n\nexport interface CreatePageInput {\n title: string;\n slug?: string;\n description?: string;\n templateName?: string;\n templateContent?: string;\n theme: ThemeRef;\n /** Render the Page with the theme's stylesheets (\"brand style\"). */\n brandStyle: boolean;\n publish: boolean;\n}\n\nexport type Assignment = \"direct-page\" | \"theme-scoped\";\n\nexport interface CreatePageResult {\n page: PageRecord;\n template: { id: number; name: string; themeId: number; themeName: string };\n assignment: Assignment;\n status: \"draft\" | \"published\";\n previewPath: string;\n}\n\n/** Remove what an incomplete create left behind, Page first, and say what\n * happened. Never throws: the caller is already reporting a failure. */\nasync function cleanup(\n api: PagesClient,\n pageId: number | null,\n templateId: number | null,\n): Promise<{ removed: string[]; cleanupFailed: string[] }> {\n const removed: string[] = [];\n const cleanupFailed: string[] = [];\n const steps: Array<[string, () => Promise<unknown>]> = [];\n if (pageId !== null) steps.push([`page ${pageId}`, () => api.delete(`${PAGES}/${pageId}`)]);\n if (templateId !== null) {\n steps.push([`template ${templateId}`, () => api.delete(`${TEMPLATES}/${templateId}`)]);\n }\n for (const [label, remove] of steps) {\n try {\n await remove();\n removed.push(label);\n } catch (err) {\n cleanupFailed.push(`${label} (${statusOf(err) ?? message(err)})`);\n }\n }\n return { removed, cleanupFailed };\n}\n\ntype LinkCheck = { mode: Assignment } | { failure: \"missing\" | \"unverified\" };\n\n/** Confirm the Page and template are linked, reading both back. The Page\n * reports its template only for the company's current theme, so a template\n * in another theme shows up on the template's side instead. \"missing\" is\n * returned only when both reads answered; otherwise \"unverified\", so a\n * read outage never triggers a destructive cleanup. */\nexport async function verifyLink(\n api: PagesClient,\n pageId: number,\n templateId: number,\n): Promise<LinkCheck> {\n let pageAnswered = false;\n let templateAnswered = false;\n\n try {\n const page = await getPage(api, pageId);\n pageAnswered = \"application_theme_template_id\" in page;\n if (page.application_theme_template_id === templateId) return { mode: \"direct-page\" };\n } catch (err) {\n if (statusOf(err) === 401) throw err;\n }\n\n try {\n const template = await getTemplate(api, templateId);\n const assigned = template.assigned_themeable_ids;\n templateAnswered = Array.isArray(assigned);\n if (Array.isArray(assigned) && assigned.includes(pageId)) return { mode: \"theme-scoped\" };\n } catch (err) {\n if (statusOf(err) === 401) throw err;\n }\n\n return pageAnswered && templateAnswered ? { failure: \"missing\" } : { failure: \"unverified\" };\n}\n\n/**\n * Create a Page together with its own page template, the way Mist does:\n *\n * 1. create the template in the theme (`themeable_type: page`);\n * 2. create the Page as a draft;\n * 3. assign the template to the Page;\n * 4. read both back to prove the link;\n * 5. publish, when asked.\n *\n * If a step after the template fails, the draft Page and the template are\n * removed and the error says so. Two cases keep them instead, with their\n * ids in the error: an expired session (cleanup would fail the same way),\n * and an assignment that succeeded but couldn't be read back.\n */\nexport async function createPageWithTemplate(\n api: PagesClient,\n input: CreatePageInput,\n): Promise<CreatePageResult> {\n const slug = pageSlug(input.slug ?? input.title);\n const templateName = pageSlug(input.templateName ?? input.title);\n const content = input.templateContent ?? DEFAULT_TEMPLATE_CONTENT;\n let pageId: number | null = null;\n let templateId: number | null = null;\n\n const abort = async (what: string, err?: unknown): Promise<never> => {\n const status = statusOf(err);\n if (status === 401) {\n // Cleanup would fail with the same expired session; keep the ids.\n throw new PagesError(\n pageId === null && templateId === null\n ? `${what}: your session expired. Nothing was created; sign in again and retry.`\n : `${what}: your session expired. Sign in again, then check or remove the draft resources below before retrying.`,\n { status, pageId, templateId, pageStatus: pageId === null ? null : \"draft\" },\n );\n }\n const result = await cleanup(api, pageId, templateId);\n throw new PagesError(\n `${what}${err === undefined ? \"\" : ` (${status ?? message(err)})`}. ${\n result.cleanupFailed.length === 0\n ? result.removed.length === 0\n ? \"Nothing was created.\"\n : `Removed the incomplete ${result.removed.join(\" and \")}.`\n : `Cleanup also failed for ${result.cleanupFailed.join(\", \")}; remove it by hand. The Page was never published.`\n }`,\n {\n ...(status ? { status } : {}),\n ...(err !== undefined && isRecord((err as { body?: unknown }).body)\n ? { apiError: (err as { body: unknown }).body }\n : {}),\n ...result,\n },\n );\n };\n\n // 1. The template, created and published in the chosen theme.\n try {\n const created = unwrap(\n await api.post(\n TEMPLATES,\n {\n application_theme_template: {\n name: templateName,\n content,\n themeable_type: \"page\",\n use_active_application_theme: input.brandStyle,\n },\n },\n { params: { application_theme_id: input.theme.id } },\n ),\n \"application_theme_template\",\n \"template\",\n );\n templateId = idOf(created);\n } catch (err) {\n return abort(\"Creating the page template failed\", err);\n }\n if (templateId === null) {\n throw new PagesError(\n \"The template response had no id, so no Page was created. Check the theme for an unnamed leftover template.\",\n );\n }\n\n // 2. The Page, kept as a draft until its template is linked.\n try {\n const page = unwrap(\n await api.post(PAGES, {\n page: {\n title: input.title.trim(),\n slug,\n custom_slug: true,\n description: input.description ?? \"\",\n status: \"draft\",\n },\n }),\n \"page\",\n );\n pageId = idOf(page);\n } catch (err) {\n return abort(`Creating the Page with template ${templateId} failed`, err);\n }\n if (pageId === null) return abort(\"The Page response had no id\");\n\n // 3. The link, through the template-assignment endpoint.\n try {\n await api.put(`${TEMPLATES}/${templateId}/themeables_update`, {\n added_ids: [pageId],\n removed_ids: [],\n });\n } catch (err) {\n return abort(`Assigning template ${templateId} to Page ${pageId} failed`, err);\n }\n\n // 4. Proof the link holds, never the write's response alone.\n let link: LinkCheck;\n try {\n link = await verifyLink(api, pageId, templateId);\n } catch (err) {\n return abort(\"Checking the template assignment failed\", err);\n }\n if (\"failure\" in link) {\n if (link.failure === \"unverified\") {\n throw new PagesError(\n \"The template was assigned, but neither the Page nor the template could be read back to confirm it. Both are kept; the Page is still a draft. Check them with `fluid pages show` before retrying.\",\n { pageId, templateId, pageStatus: \"draft\" },\n );\n }\n return abort(`Template ${templateId} isn't linked to Page ${pageId} after assignment`);\n }\n\n // 5. Publish, only when asked.\n if (input.publish) {\n try {\n await api.patch(`${PAGES}/${pageId}`, { page: { status: \"published\" } });\n } catch (err) {\n if (statusOf(err) === 401) return abort(`Publishing Page ${pageId} failed`, err);\n // The publish may have landed even though its response didn't. Only\n // a Page confirmed to still be a draft is safe to remove.\n const state = await getPage(api, pageId).then(\n (page) => page.status,\n () => undefined,\n );\n if (state === \"draft\") return abort(`Publishing Page ${pageId} failed`, err);\n if (state === undefined) {\n throw new PagesError(\n `Publishing Page ${pageId} failed and its state couldn't be read back, so the Page and template ${templateId} are kept. Check with \\`fluid pages show ${pageId}\\` and publish or delete it.`,\n { pageId, templateId, ...(statusOf(err) ? { status: statusOf(err) } : {}) },\n );\n }\n // Any other state means the publish took effect.\n }\n }\n\n const page = await getPage(api, pageId).catch(\n (): PageRecord => ({ id: pageId as number, slug, status: input.publish ? \"published\" : \"draft\" }),\n );\n const finalSlug = typeof page.slug === \"string\" ? page.slug : slug;\n const route = `/home/pages/${encodeURIComponent(finalSlug)}`;\n return {\n page,\n template: {\n id: templateId,\n name: templateName,\n themeId: input.theme.id,\n themeName: input.theme.name,\n },\n assignment: link.mode,\n status: input.publish ? \"published\" : \"draft\",\n previewPath: link.mode === \"direct-page\" ? route : `${route}?theme_template_id=${templateId}`,\n };\n}\n","// `fluid pages walk`: `create` as a conversation. It asks for the title,\n// slug, template and theme, lets a person go back, skip, or jump around\n// until they confirm, then creates the Page through the same function as\n// `create`. Afterwards it offers to publish and to replace the template's\n// Liquid, again through the same functions as `publish` and `template`.\n\nimport { readFile } from \"node:fs/promises\";\nimport {\n NAV_HELP,\n type Choice,\n type MenuItem,\n type Prompter,\n type Question,\n} from \"@fluid-app/fluid-cli\";\n\nimport {\n createPageWithTemplate,\n findThemeTemplate,\n getPage,\n listPageTemplates,\n listThemes,\n pageSlug,\n PagesError,\n resolveTheme,\n updatePage,\n updateTemplateContent,\n type CreatePageResult,\n type PagesClient,\n type ThemeRef,\n} from \"./api.js\";\n\nexport type StepId = \"title\" | \"slug\" | \"template\" | \"theme\" | \"source\";\nexport type TemplateChoice = \"brand\" | \"blank\" | \"copy\";\n\nexport interface Answers {\n title?: string;\n slug?: string;\n template?: TemplateChoice;\n /** Unset means the active theme. */\n theme?: ThemeRef;\n source?: { id: number; name: string };\n}\n\nexport interface WalkDeps {\n api: PagesClient;\n prompter: Prompter;\n readLocal?: (path: string) => Promise<string>;\n}\n\nexport interface WalkResult {\n created: boolean;\n /** Set when the person stopped before creating anything. */\n stopped?: boolean;\n pageId?: number;\n slug?: string;\n templateId?: number;\n assignment?: CreatePageResult[\"assignment\"];\n status?: \"draft\" | \"published\";\n url?: string;\n templateUpdated?: { published: boolean };\n /** A step after the create that failed; the Page itself exists. */\n error?: string;\n}\n\nconst STEP_TITLES: Record<StepId, string> = {\n title: \"Title\",\n slug: \"Slug\",\n template: \"Template\",\n theme: \"Theme\",\n source: \"Template to copy\",\n};\n\nconst TEMPLATE_CHOICES: Choice[] = [\n { title: \"New template, with the theme's brand style\", value: \"brand\" },\n { title: \"New blank template\", value: \"blank\" },\n { title: \"Copy an existing page template\", value: \"copy\" },\n];\n\n/** The questions that apply, in order. */\nexport function activeSteps(answers: Answers): StepId[] {\n const steps: StepId[] = [\"title\", \"slug\", \"template\", \"theme\"];\n if (answers.template === \"copy\") steps.push(\"source\");\n return steps;\n}\n\nconst message = (err: unknown) => (err instanceof Error ? err.message : String(err));\n\nexport async function walkCreatePage(deps: WalkDeps): Promise<WalkResult> {\n const { api, prompter } = deps;\n const readLocal = deps.readLocal ?? ((path: string) => readFile(path, \"utf8\"));\n const answers: Answers = {};\n const history: StepId[] = [];\n let step: StepId | \"review\" = \"title\";\n\n // Looked up once, when first needed.\n let active: ThemeRef | undefined;\n const activeTheme = async () => (active ??= await resolveTheme(api));\n let themes: ThemeRef[] | undefined;\n const templatesByTheme = new Map<number, Array<{ id: number; name: string }>>();\n\n const chosenTheme = async () => answers.theme ?? (await activeTheme());\n const describeTheme = async () => {\n const theme = await chosenTheme();\n return `${theme.name || theme.id}${answers.theme ? \"\" : \" (active)\"}`;\n };\n\n const next = (from: StepId): StepId | \"review\" => {\n const steps = activeSteps(answers);\n const at = steps.indexOf(from);\n // A step that no longer applies (the copy source after it fell back to\n // a new template) was the last one, so go on to the review.\n return at < 0 ? \"review\" : (steps[at + 1] ?? \"review\");\n };\n\n prompter.note(`Create a page and its own template. ${NAV_HELP}`);\n\n for (;;) {\n if (step === \"review\" && answers.template === \"copy\" && !answers.source) step = \"source\";\n if (step === \"review\") {\n const summary = [\n `Title: ${answers.title}`,\n `Slug: ${answers.slug ?? pageSlug(answers.title ?? \"\")}`,\n `Template: ${\n answers.template === \"copy\" && answers.source\n ? `copy of ${answers.source.name}`\n : answers.template === \"blank\"\n ? \"new, blank\"\n : \"new, with the theme's brand style\"\n }`,\n `Theme: ${await describeTheme()}`,\n ];\n prompter.note(summary.join(\"\\n\"));\n const choice = await prompter.menu<string>(\"Create this page as a draft?\", [\n { title: \"Create it\", value: \"create\" },\n ...activeSteps(answers).map((id) => ({ title: `Change the ${STEP_TITLES[id].toLowerCase()}`, value: id })),\n { title: \"Stop without creating anything\", value: \"stop\" },\n ]);\n if (choice === null || choice === \"stop\") return { created: false, stopped: true };\n if (choice !== \"create\") {\n step = choice as StepId;\n continue;\n }\n return finish(deps, readLocal, answers, await chosenTheme());\n }\n\n const current: StepId = step;\n const question = await questionFor(current);\n if (question === null) {\n // Nothing to choose from: the copy source fell back to a new template.\n step = next(current);\n continue;\n }\n const reply = await prompter.ask(question);\n\n if (\"nav\" in reply) {\n if (reply.nav === \"quit\") return { created: false, stopped: true };\n if (reply.nav === \"back\") {\n step = history.pop() ?? current;\n continue;\n }\n if (reply.nav === \"menu\") {\n const items: MenuItem<string>[] = [\n ...activeSteps(answers).map((id) => ({ title: STEP_TITLES[id], value: id, disabled: id !== \"title\" && !answers.title })),\n { title: \"Review and create\", value: \"review\", disabled: !answers.title },\n { title: \"Stop without creating anything\", value: \"stop\" },\n ];\n const choice = await prompter.menu(\"Go to\", items);\n if (choice === \"stop\") return { created: false, stopped: true };\n if (choice !== null) {\n history.push(current);\n step = choice as StepId | \"review\";\n }\n continue;\n }\n // Skip: take the default, except for the title, which has none.\n if (current === \"title\" && !answers.title) {\n prompter.note(\"A page needs a title.\");\n continue;\n }\n if (current === \"slug\") delete answers.slug;\n if (current === \"template\") answers.template ??= \"brand\";\n if (current === \"theme\") {\n // Skipping takes the active theme; a source from another theme can't come along.\n if (answers.theme && answers.theme.id !== (await activeTheme()).id) delete answers.source;\n delete answers.theme;\n }\n if (current === \"source\" && !answers.source) {\n answers.template = \"brand\";\n prompter.note(\"No template to copy chosen; the page gets a new template with the theme's brand style.\");\n }\n history.push(current);\n step = next(current);\n continue;\n }\n\n const value = reply.value.trim();\n if (!apply(current, value)) continue;\n history.push(current);\n step = next(current);\n }\n\n async function questionFor(id: StepId): Promise<Question | null> {\n const base = { section: `New page · ${STEP_TITLES[id]}`, canGoBack: history.length > 0 };\n switch (id) {\n case \"title\":\n return { ...base, kind: \"text\", message: \"Page title\", current: answers.title };\n case \"slug\": {\n const suggested = pageSlug(answers.title ?? \"\");\n return {\n ...base,\n kind: \"text\",\n message: \"URL slug\",\n current: answers.slug ?? suggested,\n hint: `the page lives at /home/pages/<slug>; Enter keeps ${answers.slug ?? suggested}`,\n };\n }\n case \"template\":\n return {\n ...base,\n kind: \"select\",\n message: \"Which template should the page use?\",\n choices: TEMPLATE_CHOICES,\n current: answers.template,\n };\n case \"theme\": {\n const current = await activeTheme();\n themes ??= await listThemes(api);\n const choices: Choice[] = [\n { title: `${current.name || current.id} (active)`, value: String(current.id) },\n ...themes.filter((t) => t.id !== current.id).map((t) => ({ title: t.name, value: String(t.id) })),\n ];\n return {\n ...base,\n kind: \"search\",\n message: \"Which theme should the template go in?\",\n choices,\n current: String((answers.theme ?? current).id),\n hint: \"a template in a theme that isn't active applies once that theme is active\",\n };\n }\n case \"source\": {\n const theme = await chosenTheme();\n let templates = templatesByTheme.get(theme.id);\n if (!templates) {\n templates = await listPageTemplates(api, theme.id);\n templatesByTheme.set(theme.id, templates);\n }\n if (templates.length === 0) {\n answers.template = \"brand\";\n prompter.note(`${theme.name || theme.id} has no page templates to copy; the page gets a new template with the theme's brand style.`);\n return null;\n }\n return {\n ...base,\n kind: \"search\",\n message: \"Copy which page template?\",\n choices: templates.map((t) => ({ title: t.name, value: String(t.id) })),\n current: answers.source ? String(answers.source.id) : undefined,\n };\n }\n }\n }\n\n /** Save one answer; false when it has to be asked again. */\n function apply(id: StepId, value: string): boolean {\n switch (id) {\n case \"title\":\n if (!value) {\n prompter.note(\"A page needs a title.\");\n return false;\n }\n answers.title = value;\n return true;\n case \"slug\":\n if (!value || value === pageSlug(answers.title ?? \"\")) delete answers.slug;\n else answers.slug = pageSlug(value);\n return true;\n case \"template\":\n answers.template = value as TemplateChoice;\n if (value !== \"copy\") delete answers.source;\n return true;\n case \"theme\": {\n const picked = Number(value);\n const before = answers.theme?.id ?? active?.id;\n const other = picked === active?.id ? undefined : themes?.find((t) => t.id === picked);\n if (picked !== active?.id && !other) return false;\n if (other) answers.theme = other;\n else delete answers.theme;\n // A template to copy belongs to one theme; a new theme needs a new pick.\n if (picked !== before) delete answers.source;\n return true;\n }\n case \"source\": {\n const theme = answers.theme?.id ?? active?.id;\n const found = templatesByTheme.get(theme ?? -1)?.find((t) => String(t.id) === value);\n if (!found) return false;\n answers.source = found;\n return true;\n }\n }\n }\n}\n\n/** Create the Page, then offer to publish it and to replace its Liquid. */\nasync function finish(\n deps: WalkDeps,\n readLocal: (path: string) => Promise<string>,\n answers: Answers,\n theme: ThemeRef,\n): Promise<WalkResult> {\n const { api, prompter } = deps;\n const templateContent =\n answers.template === \"copy\" && answers.source\n ? await copiedContent(api, theme.id, answers.source.id)\n : undefined;\n\n const result = await createPageWithTemplate(api, {\n title: answers.title ?? \"\",\n slug: answers.slug,\n templateContent,\n theme,\n brandStyle: answers.template !== \"blank\",\n publish: false,\n });\n const slug = typeof result.page.slug === \"string\" ? result.page.slug : (answers.slug ?? pageSlug(answers.title ?? \"\"));\n const out: WalkResult = {\n created: true,\n pageId: result.page.id,\n slug,\n templateId: result.template.id,\n assignment: result.assignment,\n status: \"draft\",\n };\n prompter.note(\n `Created page ${result.page.id} (draft) and its template ${result.template.id} in ${theme.name || theme.id}, linked ${result.assignment === \"direct-page\" ? \"directly\" : \"for that theme\"}.`,\n );\n\n try {\n if ((await yesNo(prompter, \"Publish the page now?\", \"Publish it\", \"Keep it a draft\")) === \"yes\") {\n try {\n await updatePage(api, result.page.id, { status: \"published\" });\n out.status = \"published\";\n } catch (err) {\n // The publish may have landed though its response didn't; ask the Page.\n const state = await getPage(api, result.page.id).then(\n (page) => page.status,\n () => undefined,\n );\n if (state !== \"published\" && state !== \"scheduled\") throw err;\n out.status = \"published\";\n }\n }\n out.url = pageUrl(result);\n prompter.note(`The page is at ${out.url}${out.status === \"draft\" ? \" once it's published\" : \"\"}.`);\n\n if (\n (await yesNo(prompter, \"Replace the template's Liquid with a file now?\", \"Yes, from a file\", \"Not now\")) === \"yes\"\n ) {\n const reply = await prompter.ask({\n section: \"Template\",\n kind: \"text\",\n message: \"Path to the Liquid file\",\n canGoBack: false,\n });\n if (\"value\" in reply && reply.value.trim()) {\n const content = await readLocal(reply.value.trim());\n const publish =\n (await yesNo(prompter, \"Make the new version live?\", \"Publish it\", \"Keep it a draft version\")) === \"yes\";\n await updateTemplateContent(api, result.template.id, content, publish);\n out.templateUpdated = { published: publish };\n }\n }\n if (!out.templateUpdated) {\n prompter.note(`Edit it later with \\`fluid pages template ${slug} --content-file <file> --yes\\`.`);\n }\n } catch (err) {\n out.error = message(err);\n }\n return out;\n}\n\n/** Where to see the page. A template in a theme that isn't active only shows\n * with the preview override, so that case keeps `?theme_template_id=`. */\nexport function pageUrl(result: CreatePageResult): string {\n const canonical = result.page.canonical_url;\n if (typeof canonical !== \"string\" || !canonical) return result.previewPath;\n if (result.assignment === \"direct-page\") return canonical;\n try {\n return new URL(result.previewPath, canonical).toString();\n } catch {\n return result.previewPath;\n }\n}\n\nasync function copiedContent(api: PagesClient, themeId: number, templateId: number): Promise<string> {\n const source = await findThemeTemplate(api, themeId, String(templateId));\n if (typeof source.content !== \"string\" || !source.content.trim()) {\n throw new PagesError(`Template ${templateId} has no Liquid to copy.`);\n }\n return source.content;\n}\n\nasync function yesNo(prompter: Prompter, question: string, yes: string, no: string): Promise<\"yes\" | \"no\"> {\n const reply = await prompter.ask({\n kind: \"select\",\n message: question,\n choices: [\n { title: yes, value: \"yes\" },\n { title: no, value: \"no\" },\n ],\n canGoBack: false,\n });\n return \"value\" in reply && reply.value === \"yes\" ? \"yes\" : \"no\";\n}\n","import { readFile } from \"node:fs/promises\";\nimport type { Command } from \"commander\";\nimport prompts from \"prompts\";\nimport type { CommandContext, FluidPlugin } from \"@fluid-app/fluid-cli\";\nimport { assertInteractive, createDomainCommand, terminalPrompter } from \"@fluid-app/fluid-cli\";\n\nimport {\n createPageWithTemplate,\n deletePage,\n deleteUnusedTemplate,\n findThemeTemplate,\n getPage,\n listPages,\n pageTemplate,\n PagesError,\n resolvePageId,\n resolveTheme,\n updatePage,\n updateTemplateContent,\n type PageChanges,\n type PagesClient,\n type TemplateCleanup,\n} from \"./api.js\";\nimport { walkCreatePage } from \"./walk.js\";\n\nasync function client(ctx: CommandContext): Promise<PagesClient> {\n return (await ctx.getClient()) as unknown as PagesClient;\n}\n\n/**\n * Changes to a live storefront need an explicit yes. Scripts and agents\n * pass `--yes`; an interactive terminal is asked. With neither, nothing runs.\n */\nexport async function confirmChange(\n question: string,\n yes: boolean,\n interactive: boolean = Boolean(process.stdin.isTTY),\n): Promise<void> {\n if (yes) return;\n if (!interactive) throw new PagesError(`${question} Pass --yes to confirm.`);\n const answer = await prompts({\n type: \"confirm\",\n name: \"ok\",\n message: question,\n initial: false,\n });\n if (!answer.ok) throw new PagesError(\"Cancelled.\");\n}\n\n/** The readable message from a Fluid error body: v2026-04 sends\n * `error: { message, details }`, the theme API `error_message` and `errors`. */\nexport function errorPayload(err: unknown): Record<string, unknown> {\n const status =\n typeof (err as { status?: unknown }).status === \"number\"\n ? (err as { status: number }).status\n : undefined;\n const body = (err as { body?: Record<string, unknown> | null }).body ?? null;\n const nested = body?.[\"error\"];\n const detail =\n typeof nested === \"object\" && nested !== null\n ? (nested as { message?: unknown; details?: unknown })\n : null;\n const text =\n (typeof detail?.message === \"string\" && detail.message) ||\n (typeof body?.[\"error_message\"] === \"string\" && body[\"error_message\"]) ||\n (typeof nested === \"string\" && nested) ||\n (err instanceof Error ? err.message : String(err));\n const details =\n err instanceof PagesError\n ? err.details\n : (detail?.details ?? body?.[\"errors\"] ?? undefined);\n return {\n error: text,\n ...(status ? { status } : {}),\n ...(details && typeof details === \"object\" ? { details } : {}),\n };\n}\n\n/** Print a failure as JSON on stderr and exit non-zero. */\nfunction fail(err: unknown): void {\n process.stderr.write(JSON.stringify(errorPayload(err)) + \"\\n\");\n process.exitCode = 1;\n}\n\nfunction run(action: () => Promise<void>): Promise<void> {\n return action().catch(fail);\n}\n\nfunction positiveInt(value: string, flag: string): number {\n if (!/^\\d+$/.test(value) || Number(value) <= 0) {\n throw new PagesError(`${flag} must be a positive whole number, not \"${value}\".`);\n }\n return Number(value);\n}\n\nasync function readContent(file: string): Promise<string> {\n try {\n return await readFile(file, \"utf8\");\n } catch (err) {\n throw new PagesError(`Couldn't read ${file}: ${err instanceof Error ? err.message : String(err)}`);\n }\n}\n\n/**\n * Clean up a just-deleted Page's template. The Page is already gone, so a\n * failed cleanup is reported, with the template left to delete, rather than\n * thrown.\n */\nexport async function cleanUpTemplateAfterDelete(\n api: PagesClient,\n templateId: number,\n pageId: number,\n): Promise<TemplateCleanup> {\n try {\n return await deleteUnusedTemplate(api, templateId, pageId);\n } catch (err) {\n return {\n deleted: false,\n templateId,\n reason: `The Page was deleted but its template could not be cleaned up (${String(errorPayload(err)[\"error\"])}). Retry with \\`fluid pages delete-template ${templateId} --yes\\`.`,\n };\n }\n}\n\nexport function registerPagesCommands(parent: Command, ctx: CommandContext): void {\n parent\n .command(\"list\")\n .description(\"List the company's Pages; --cursor pages through\")\n .option(\"--status <status>\", \"Only draft, scheduled, or published Pages\")\n .option(\"--search <text>\", \"Match title, description, or slug\")\n .option(\"--limit <n>\", \"Pages per call, up to 100\", \"25\")\n .option(\"--cursor <cursor>\", \"The nextCursor from the previous call\")\n .action((opts: { status?: string; search?: string; limit: string; cursor?: string }) =>\n run(async () => {\n ctx.output(\n await listPages(await client(ctx), {\n status: opts.status,\n search: opts.search,\n limit: positiveInt(opts.limit, \"--limit\"),\n cursor: opts.cursor,\n }),\n );\n }),\n );\n\n parent\n .command(\"show <page>\")\n .description(\"Show one Page (id or slug), including the template it renders through\")\n .action((ref: string) =>\n run(async () => {\n const api = await client(ctx);\n ctx.output(await getPage(api, await resolvePageId(api, ref)));\n }),\n );\n\n parent\n .command(\"create\")\n .description(\n \"Create a Page with its own page template in a theme, linked together; stays a draft unless --publish (changes live config)\",\n )\n .requiredOption(\"--title <title>\", \"The Page title\")\n .option(\"--slug <slug>\", \"URL slug; defaults to one made from the title\")\n .option(\"--description <text>\", \"Plain-text Page description\")\n .option(\"--template-name <name>\", \"Name of the new template; defaults to one made from the title\")\n .option(\"--template-from <template>\", \"Start from a copy of this page template's Liquid (id or name, in the same theme)\")\n .option(\"--content-file <path>\", \"Start from the Liquid in this file\")\n .option(\"--theme <theme>\", \"Theme for the template (id or name); defaults to the active theme\")\n .option(\"--blank\", \"Don't apply the theme's brand style to the Page\", false)\n .option(\"--publish\", \"Publish the Page once its template is linked\", false)\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action(\n (opts: {\n title: string;\n slug?: string;\n description?: string;\n templateName?: string;\n templateFrom?: string;\n contentFile?: string;\n theme?: string;\n blank: boolean;\n publish: boolean;\n yes: boolean;\n }) =>\n run(async () => {\n if (!opts.title.trim()) throw new PagesError(\"--title can't be blank.\");\n if (opts.templateFrom && opts.contentFile) {\n throw new PagesError(\"Use --template-from or --content-file, not both.\");\n }\n const api = await client(ctx);\n const theme = await resolveTheme(api, opts.theme);\n let templateContent: string | undefined;\n if (opts.contentFile) {\n templateContent = await readContent(opts.contentFile);\n } else if (opts.templateFrom) {\n const source = await findThemeTemplate(api, theme.id, opts.templateFrom);\n if (typeof source.content !== \"string\" || !source.content.trim()) {\n throw new PagesError(`Template ${source.id} has no Liquid to copy.`);\n }\n templateContent = source.content;\n }\n await confirmChange(\n `Create the Page \"${opts.title}\" and its template in the theme \"${theme.name || theme.id}\"${opts.publish ? \", then publish it\" : \" as a draft\"}?`,\n opts.yes,\n );\n ctx.output(\n await createPageWithTemplate(api, {\n title: opts.title,\n slug: opts.slug,\n description: opts.description,\n templateName: opts.templateName,\n templateContent,\n theme,\n brandStyle: !opts.blank,\n publish: opts.publish,\n }),\n );\n }),\n );\n\n parent\n .command(\"walk\")\n .description(\n \"Create a page by answering questions in the terminal: title, slug, template, theme; go back, skip, or jump around before you confirm (changes live config)\",\n )\n .action(() =>\n run(async () => {\n assertInteractive(\"`fluid pages create --title <title> --yes`\");\n const result = await walkCreatePage({ api: await client(ctx), prompter: terminalPrompter() });\n ctx.output(result);\n if (result.error) fail(new PagesError(result.error, { pageId: result.pageId, templateId: result.templateId }));\n }),\n );\n\n parent\n .command(\"update <page>\")\n .description(\"Change a Page's title, slug, or description (changes live config)\")\n .option(\"--title <title>\", \"New title\")\n .option(\"--slug <slug>\", \"New URL slug; old links to the Page stop working\")\n .option(\"--description <text>\", \"New plain-text description\")\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action((ref: string, opts: { title?: string; slug?: string; description?: string; yes: boolean }) =>\n run(async () => {\n const changes: PageChanges = {};\n if (opts.title !== undefined) changes.title = opts.title;\n if (opts.slug !== undefined) changes.slug = opts.slug;\n if (opts.description !== undefined) changes.description = opts.description;\n if (Object.keys(changes).length === 0) {\n throw new PagesError(\"Nothing to change. Pass --title, --slug, or --description.\");\n }\n const api = await client(ctx);\n const id = await resolvePageId(api, ref);\n await confirmChange(`Update Page ${id}?`, opts.yes);\n ctx.output(await updatePage(api, id, changes));\n }),\n );\n\n for (const [name, status, verb] of [\n [\"publish\", \"published\", \"Publish\"],\n [\"unpublish\", \"draft\", \"Unpublish\"],\n ] as const) {\n parent\n .command(`${name} <page>`)\n .description(\n `${verb} a Page (id or slug); ${status === \"published\" ? \"it goes live on the storefront\" : \"it comes off the storefront and becomes a draft\"} (changes live config)`,\n )\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action((ref: string, opts: { yes: boolean }) =>\n run(async () => {\n const api = await client(ctx);\n const id = await resolvePageId(api, ref);\n await confirmChange(`${verb} Page ${id}?`, opts.yes);\n ctx.output(await updatePage(api, id, { status }));\n }),\n );\n }\n\n parent\n .command(\"delete <page>\")\n .description(\"Delete a Page (id or slug); it comes off the storefront immediately (changes live config)\")\n .option(\n \"--delete-template\",\n \"Also delete the Page's template, if no other Page uses it\",\n false,\n )\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action((ref: string, opts: { yes: boolean; deleteTemplate: boolean }) =>\n run(async () => {\n const api = await client(ctx);\n const page = await getPage(api, await resolvePageId(api, ref));\n await confirmChange(\n `Delete Page ${page.id}? It comes off the storefront immediately.`,\n opts.yes,\n );\n const result = await deletePage(api, page.id);\n const templateId = page.application_theme_template_id ?? null;\n if (opts.deleteTemplate && templateId !== null) {\n const template = await cleanUpTemplateAfterDelete(api, templateId, page.id);\n ctx.output({ ...result, templateId, template });\n return;\n }\n // Without --delete-template the template is left in place.\n ctx.output({\n ...result,\n templateId,\n note:\n templateId === null\n ? \"The Page reported no template in the active theme. Any template assigned to it in another theme was kept.\"\n : `Template ${templateId} was kept. Delete it, if no other Page uses it, with \\`fluid pages delete-template ${templateId} --yes\\`.`,\n });\n }),\n );\n\n parent\n .command(\"delete-template <template-id>\")\n .description(\"Delete a Page template that no Page uses any more (changes live config)\")\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action((rawId: string, opts: { yes: boolean }) =>\n run(async () => {\n const templateId = Number(rawId);\n if (!Number.isInteger(templateId) || templateId <= 0) {\n throw new PagesError(`\"${rawId}\" isn't a template id.`);\n }\n const api = await client(ctx);\n await confirmChange(`Delete template ${templateId} if no Page uses it?`, opts.yes);\n const outcome = await deleteUnusedTemplate(api, templateId);\n ctx.output(outcome);\n if (!outcome.deleted) process.exitCode = 1;\n }),\n );\n\n parent\n .command(\"template <page>\")\n .description(\n \"Show the Page's template with its Liquid; --content-file replaces the Liquid (changes live config with --publish)\",\n )\n .option(\"--template-id <id>\", \"The template, when it's assigned in a theme that isn't active\")\n .option(\"--content-file <path>\", \"Replace the template's Liquid with this file's contents\")\n .option(\"--publish\", \"Publish the new template version; without it the change stays a draft version\", false)\n .option(\"--yes\", \"Confirm without a prompt\", false)\n .action(\n (ref: string, opts: { templateId?: string; contentFile?: string; publish: boolean; yes: boolean }) =>\n run(async () => {\n if (opts.publish && !opts.contentFile) {\n throw new PagesError(\"--publish goes with --content-file.\");\n }\n const api = await client(ctx);\n const page = await getPage(api, await resolvePageId(api, ref));\n const template = await pageTemplate(\n api,\n page,\n opts.templateId === undefined ? undefined : positiveInt(opts.templateId, \"--template-id\"),\n );\n if (!opts.contentFile) {\n ctx.output({\n pageId: page.id,\n template: {\n id: template.id,\n name: template.name,\n status: template.status,\n themeId: template.application_theme_id ?? null,\n assignedPageIds: template.assigned_themeable_ids ?? [],\n content: template.content ?? null,\n },\n });\n return;\n }\n const content = await readContent(opts.contentFile);\n await confirmChange(\n `Replace the Liquid of template ${template.id}${opts.publish ? \" and publish it\" : \" (as a draft version)\"}?`,\n opts.yes,\n );\n ctx.output({\n pageId: page.id,\n ...(await updateTemplateContent(api, template.id, content, opts.publish)),\n });\n }),\n );\n}\n\nconst plugin: FluidPlugin = {\n name: \"@fluid-app/fluid-cli-pages\",\n version: \"0.1.1\",\n register(ctx) {\n ctx.program.addCommand(\n createDomainCommand(\n \"pages\",\n \"Storefront Pages: create a Page with its own theme template, then list, update, publish, and delete\",\n registerPagesCommands,\n ),\n );\n },\n};\n\nexport default plugin;\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluid-app/fluid-cli-pages",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Fluid CLI plugin for storefront Pages — create a Page with its own theme template, then list, update, publish, and delete Pages",
5
5
  "files": [
6
6
  "dist",
@@ -21,7 +21,7 @@
21
21
  "access": "public"
22
22
  },
23
23
  "dependencies": {
24
- "@fluid-app/fluid-cli": "0.1.35",
24
+ "@fluid-app/fluid-cli": "0.1.36",
25
25
  "commander": "^12.0.0",
26
26
  "prompts": "^2.4.2"
27
27
  },