canopycms 0.5.0 → 0.7.0

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.
Files changed (107) hide show
  1. package/CHANGELOG.md +185 -46
  2. package/dist/api/branch-review.d.ts +11 -1
  3. package/dist/api/branch-review.js +96 -18
  4. package/dist/api/branch-status.d.ts +26 -2
  5. package/dist/api/branch-status.js +38 -2
  6. package/dist/api/branch-withdraw.js +4 -7
  7. package/dist/api/branch.d.ts +2 -0
  8. package/dist/api/client.d.ts +3 -3
  9. package/dist/api/client.js +4 -4
  10. package/dist/api/comments.d.ts +2 -2
  11. package/dist/api/comments.js +4 -0
  12. package/dist/api/content.d.ts +2 -2
  13. package/dist/api/github-sync.d.ts +4 -0
  14. package/dist/api/github-sync.js +38 -6
  15. package/dist/api/pr-comment-sync.d.ts +18 -0
  16. package/dist/api/pr-comment-sync.js +77 -0
  17. package/dist/api/request-deadline.d.ts +15 -7
  18. package/dist/api/request-deadline.js +15 -7
  19. package/dist/api/schema.d.ts +16 -16
  20. package/dist/authorization/review-role.d.ts +33 -0
  21. package/dist/authorization/review-role.js +17 -0
  22. package/dist/branch-metadata-file.d.ts +3 -0
  23. package/dist/branch-metadata-file.js +1 -0
  24. package/dist/cli/cli.js +704 -131
  25. package/dist/cli/generate-ai-content.js +524 -59
  26. package/dist/cli/github-app-output.d.ts +34 -0
  27. package/dist/cli/github-app-output.js +85 -0
  28. package/dist/cli/init-github-app.d.ts +18 -0
  29. package/dist/cli/init-github-app.js +66 -58
  30. package/dist/cli/init.js +15 -2
  31. package/dist/cli/template-files/cms-stack.ts.template +3 -0
  32. package/dist/cli/templates.js +5 -1
  33. package/dist/comment-store.d.ts +5 -0
  34. package/dist/comment-store.js +24 -0
  35. package/dist/config/helpers.js +1 -3
  36. package/dist/config/schemas/collection.d.ts +2 -2
  37. package/dist/config/schemas/config.d.ts +3 -0
  38. package/dist/config/schemas/config.js +2 -1
  39. package/dist/config/schemas/url.d.ts +6 -0
  40. package/dist/config/schemas/url.js +16 -0
  41. package/dist/config/types.d.ts +12 -1
  42. package/dist/content-id-index.d.ts +9 -0
  43. package/dist/content-id-index.js +31 -0
  44. package/dist/editor/BranchManager.d.ts +3 -0
  45. package/dist/editor/BranchManager.js +75 -44
  46. package/dist/editor/CanopyEditor.js +2 -2
  47. package/dist/editor/CanopyEditorPage.js +3 -4
  48. package/dist/editor/Editor.js +47 -8
  49. package/dist/editor/branch-lock.d.ts +7 -0
  50. package/dist/editor/branch-lock.js +9 -2
  51. package/dist/editor/client-reference-resolver.d.ts +5 -3
  52. package/dist/editor/client-reference-resolver.js +10 -5
  53. package/dist/editor/components/BranchesLoadFailedNotice.d.ts +13 -0
  54. package/dist/editor/components/BranchesLoadFailedNotice.js +12 -0
  55. package/dist/editor/components/EditorHeader.d.ts +10 -4
  56. package/dist/editor/components/EditorHeader.js +108 -28
  57. package/dist/editor/components/EntryHeader.d.ts +5 -1
  58. package/dist/editor/components/EntryHeader.js +3 -1
  59. package/dist/editor/components/ReasonedMenuItem.d.ts +8 -0
  60. package/dist/editor/components/ReasonedMenuItem.js +2 -2
  61. package/dist/editor/components/ReviewDialogs.d.ts +19 -0
  62. package/dist/editor/components/ReviewDialogs.js +41 -0
  63. package/dist/editor/copy.d.ts +4 -0
  64. package/dist/editor/copy.js +4 -0
  65. package/dist/editor/fields/MarkdownField.js +1 -1
  66. package/dist/editor/hooks/create-branch-request.d.ts +6 -2
  67. package/dist/editor/hooks/create-branch-request.js +7 -2
  68. package/dist/editor/hooks/useBranchManager.d.ts +7 -2
  69. package/dist/editor/hooks/useBranchManager.js +252 -37
  70. package/dist/editor-url.d.ts +20 -0
  71. package/dist/editor-url.js +71 -0
  72. package/dist/git-manager.js +10 -6
  73. package/dist/github-service.d.ts +2 -2
  74. package/dist/github-service.js +4 -2
  75. package/dist/pr-comments.d.ts +33 -0
  76. package/dist/pr-comments.js +41 -0
  77. package/dist/schema/schema-store.d.ts +8 -8
  78. package/dist/services.d.ts +2 -17
  79. package/dist/services.js +38 -1
  80. package/dist/settings-workspace.d.ts +21 -7
  81. package/dist/settings-workspace.js +91 -16
  82. package/dist/submission-attribution.d.ts +38 -1
  83. package/dist/submission-attribution.js +197 -18
  84. package/dist/task-queue/cms-task-queue.d.ts +1 -1
  85. package/dist/types.d.ts +6 -0
  86. package/dist/utils/content-serialize.js +7 -6
  87. package/dist/utils/markdown-body-splice.d.ts +15 -10
  88. package/dist/utils/markdown-body-splice.js +71 -28
  89. package/dist/utils/markdown-export-options.d.ts +13 -0
  90. package/dist/utils/markdown-export-options.js +13 -0
  91. package/dist/utils/markdown-restyle.d.ts +58 -0
  92. package/dist/utils/markdown-restyle.js +482 -0
  93. package/dist/version.d.ts +1 -1
  94. package/dist/version.js +1 -1
  95. package/dist/worker/bundle-header.d.ts +13 -9
  96. package/dist/worker/bundle-header.js +17 -11
  97. package/dist/worker/git-process.d.ts +2 -2
  98. package/dist/worker/github-gateway-http.js +5 -4
  99. package/dist/worker/github-gateway-wire.d.ts +3 -0
  100. package/dist/worker/github-gateway-wire.js +1 -0
  101. package/dist/worker/github-gateway.d.ts +1 -1
  102. package/dist/worker/github-gateway.js +2 -2
  103. package/dist/worker/github-transfer.js +72 -2
  104. package/dist/worker/task-runner.js +53 -3
  105. package/package.json +2 -2
  106. package/dist/editor/fields/markdown-export-options.d.ts +0 -2
  107. package/dist/editor/fields/markdown-export-options.js +0 -8
package/dist/cli/cli.js CHANGED
@@ -233,9 +233,13 @@ async function canopyContext(options) {
233
233
  "// authenticated request instead of silently falling back to unauthenticated dev auth.",
234
234
  "// CLERK_SECRET_KEY is read only where Clerk's backend API is called: the auth-cache",
235
235
  "// refresh (the worker in prod, lazily on the dev server in dev).",
236
+ "// useOrganizationsAsGroups needs Organizations enabled on the Clerk instance and offers",
237
+ "// every organization in the instance as a group. In an AWS deployment the worker reads",
238
+ "// the setting from `clerkOrganizationsAsGroups` in the CDK stack, which governs there;",
239
+ "// keep the two equal.",
236
240
  "const authPlugin =",
237
241
  " config.server.mode === 'prod' || process.env.CANOPY_AUTH_MODE === 'clerk'",
238
- " ? createClerkAuthPlugin({ useOrganizationsAsGroups: true })",
242
+ " ? createClerkAuthPlugin({ useOrganizationsAsGroups: false })",
239
243
  " : createDevAuthPlugin()"
240
244
  ].join("\n") : "const authPlugin = createDevAuthPlugin()";
241
245
  return template.replace("{{AUTH_IMPORTS}}", authImports).replace("{{AUTH_PLUGIN_SETUP}}", authPluginSetup).replace("{{CONFIG_IMPORT}}", options.configImport);
@@ -1223,7 +1227,7 @@ CanopyCMS worker run-once (mode: ${mode}, auth: ${authMode})
1223
1227
  }
1224
1228
  console.log("\nDone");
1225
1229
  }
1226
- var __defProp2, __getOwnPropNames2, __esm2, __export2, active, init_logger, scalarFieldTypes, markdownFieldTypes, primitiveFieldTypes, fieldTypes, init_types, SAFE_HTML_TAGS, COMPONENT_NAME, MDX_BINDINGS, FORBIDDEN_ATTRIBUTES, init_mdx_allowlist, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, mdxPropAllowSchema, mdxAllowlistSchema, markdownFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceSortFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, SENTINEL_BASE, DOT_SEGMENT, init_sanitize_href, uploadTargetUrlSchema, previewPrefixSchema, init_url, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, basePathSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, seoFieldNameSchema, seoSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, init_config2, init_error2, init_fs2, templates_exports, __dirname, TEMPLATES_DIR, SCAFFOLD_STACK_NAME, init_templates, project_detect_exports, COMMANDS, MAX_WORKSPACE_DEPTH, PLACEHOLDER_GITHUB_REPO, CDK_DEPENDENCIES, init_project_detect, task_queue_config_exports, init_task_queue_config, envFlag, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, cms_task_queue_exports, debugLogger, cmsTaskQueueLogger, init_cms_task_queue, ProdClientSafeStrategy, DevClientSafeStrategy, warned, VALID_DEPLOYMENT_NAME, isValidDeploymentName, ProdStrategy, DevStrategy, strategyCache;
1230
+ var __defProp2, __getOwnPropNames2, __esm2, __export2, active, init_logger, scalarFieldTypes, markdownFieldTypes, primitiveFieldTypes, fieldTypes, init_types, SAFE_HTML_TAGS, COMPONENT_NAME, MDX_BINDINGS, FORBIDDEN_ATTRIBUTES, init_mdx_allowlist, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, mdxPropAllowSchema, mdxAllowlistSchema, markdownFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceSortFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, SENTINEL_BASE, DOT_SEGMENT, init_sanitize_href, uploadTargetUrlSchema, previewPrefixSchema, editorUrlSchema, init_url, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, basePathSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, seoFieldNameSchema, seoSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, init_config2, init_error2, init_fs2, templates_exports, __dirname, TEMPLATES_DIR, SCAFFOLD_STACK_NAME, init_templates, project_detect_exports, COMMANDS, MAX_WORKSPACE_DEPTH, PLACEHOLDER_GITHUB_REPO, CDK_DEPENDENCIES, init_project_detect, task_queue_config_exports, init_task_queue_config, envFlag, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, cms_task_queue_exports, debugLogger, cmsTaskQueueLogger, init_cms_task_queue, ProdClientSafeStrategy, DevClientSafeStrategy, warned, VALID_DEPLOYMENT_NAME, isValidDeploymentName, ProdStrategy, DevStrategy, strategyCache;
1227
1231
  var init_init = __esm({
1228
1232
  "dist/cli/init.js"() {
1229
1233
  "use strict";
@@ -1538,6 +1542,14 @@ var init_init = __esm({
1538
1542
  previewPrefixSchema = z3.string().trim().refine((value) => isHttpUrlOrSameOriginPath(value), {
1539
1543
  message: 'editor.previewPrefix must be an absolute http(s) URL or a site-relative path beginning with a single "/", with no query or fragment'
1540
1544
  });
1545
+ editorUrlSchema = z3.string().trim().refine((value) => {
1546
+ if (!isHttpUrlOrSameOriginPath(value) || !/^https?:\/\//i.test(value))
1547
+ return false;
1548
+ const url = new URL(value);
1549
+ return !url.username && !url.password;
1550
+ }, {
1551
+ message: "editorUrl must be an absolute http(s) URL with no credentials, query or fragment"
1552
+ });
1541
1553
  }
1542
1554
  });
1543
1555
  init_media = __esm2({
@@ -1680,6 +1692,7 @@ var init_init = __esm({
1680
1692
  contentRoot: contentRootSchema.default("content"),
1681
1693
  sourceRoot: sourceRootSchema.optional(),
1682
1694
  basePath: basePathSchema,
1695
+ editorUrl: editorUrlSchema.optional(),
1683
1696
  unauthenticatedStatus: z5.union([z5.literal(401), z5.literal(419)]).optional(),
1684
1697
  danglingReferences: z5.enum(["error", "warn"]).optional(),
1685
1698
  editor: editorConfigSchema.optional(),
@@ -2233,6 +2246,84 @@ var init_github_app_manifest = __esm({
2233
2246
  }
2234
2247
  });
2235
2248
 
2249
+ // dist/cli/github-app-output.js
2250
+ import pc from "picocolors";
2251
+ function colorsFor(stream) {
2252
+ const { NO_COLOR, FORCE_COLOR, TERM } = process.env;
2253
+ if (NO_COLOR)
2254
+ return pc.createColors(false);
2255
+ if (FORCE_COLOR)
2256
+ return pc.createColors(FORCE_COLOR !== "0" && FORCE_COLOR !== "false");
2257
+ return pc.createColors(Boolean(stream.isTTY) && TERM !== "dumb");
2258
+ }
2259
+ function recoveryPreamble() {
2260
+ return [
2261
+ outColors.bold(outColors.yellow("If this goes wrong after you click Create")),
2262
+ " The App will exist, but this command will not have its private key. To recover, either:",
2263
+ " \u2022 delete the App in its settings and re-run this command, or",
2264
+ ' \u2022 open the App, scroll to "Private keys" and click "Generate a private key". The',
2265
+ " .pem it downloads is your only chance to save it.",
2266
+ outColors.dim(' (Not the "client secret" a few sections above: that is for OAuth user flows, is unused\n by this App, and fails confusingly when used to sign.)'),
2267
+ ""
2268
+ ].join("\n");
2269
+ }
2270
+ function registrationMessage(input) {
2271
+ const width = Math.max(...Object.keys(CANOPY_APP_PERMISSIONS).map((name) => name.length));
2272
+ const permissions = Object.entries(CANOPY_APP_PERMISSIONS).map(([name, level]) => ` ${name.padEnd(width)} ${outColors.bold(level)}`);
2273
+ return [
2274
+ outColors.bold(`Registering "${input.name}" for ${input.owner}/${input.repo}`),
2275
+ "",
2276
+ `${outColors.bold("Permissions")} ${outColors.dim("(the part that matters)")}`,
2277
+ ...permissions,
2278
+ "",
2279
+ `${outColors.bold("Private key")} goes to ${input.destination}`,
2280
+ outColors.dim(" If that fails you are asked for somewhere else before the key is discarded."),
2281
+ "",
2282
+ step(1, "Open this file in a browser:"),
2283
+ "",
2284
+ ` ${link(input.formFile)}`,
2285
+ "",
2286
+ " The browser matters, twice over. It must be:",
2287
+ ` \u2022 ${outColors.bold("logged in to GitHub")} as an owner of ${input.owner}`,
2288
+ ` \u2022 running ${outColors.bold("on this machine")}: the redirect returns to 127.0.0.1:${input.callbackPort}, which only a local browser can reach`,
2289
+ outColors.dim(" An embedded or app-internal browser bounces off the GitHub login wall and appears\n to do nothing when you click Create."),
2290
+ "",
2291
+ " The page posts the manifest to GitHub, which shows the App pre-filled with exactly",
2292
+ " those permissions. Review them and click Create.",
2293
+ "",
2294
+ outColors.dim(`Waiting for GitHub to redirect back (up to ${input.timeoutMinutes} minutes)...`)
2295
+ ].join("\n");
2296
+ }
2297
+ function deploymentSettingsMessage(appId, installationId) {
2298
+ return [
2299
+ "",
2300
+ outColors.bold("Set these on your deployment"),
2301
+ "",
2302
+ ` GITHUB_APP_ID=${appId}`,
2303
+ ` GITHUB_APP_INSTALLATION_ID=${installationId ?? "<run `verify` to read it>"}`,
2304
+ ` GITHUB_APP_PRIVATE_KEY_SECRET_ARN=<wherever you just put the key>`,
2305
+ "",
2306
+ outColors.dim(' Both ids are numeric. The App ID is NOT the "Client ID" (`Iv1.\u2026`) shown beside it,\n and the installation id is NOT the App ID: an App installed twice has one App ID\n and two installation ids.'),
2307
+ "",
2308
+ outColors.bold("Re-check this any time, without creating anything"),
2309
+ ` canopycms init-github-app verify --app-id ${appId} --key-file <path>`
2310
+ ].join("\n");
2311
+ }
2312
+ var outColors, errColors, tick, failMark, cross, link, step;
2313
+ var init_github_app_output = __esm({
2314
+ "dist/cli/github-app-output.js"() {
2315
+ "use strict";
2316
+ init_github_app_manifest();
2317
+ outColors = colorsFor(process.stdout);
2318
+ errColors = colorsFor(process.stderr);
2319
+ tick = (text3) => `${outColors.green("\u2713")} ${text3}`;
2320
+ failMark = errColors.red("\u2717");
2321
+ cross = (text3) => `${failMark} ${text3}`;
2322
+ link = (url, palette = outColors) => palette.cyan(palette.underline(url));
2323
+ step = (n, text3) => outColors.bold(outColors.cyan(`${n}.`) + ` ${text3}`);
2324
+ }
2325
+ });
2326
+
2236
2327
  // dist/cli/prompt.js
2237
2328
  import { createInterface } from "node:readline";
2238
2329
  function readLineOnce(prompt) {
@@ -2460,10 +2551,13 @@ var init_github_app_exports = {};
2460
2551
  __export(init_github_app_exports, {
2461
2552
  appJwt: () => appJwt,
2462
2553
  appManifest: () => appManifest,
2554
+ appSettingsUrl: () => appSettingsUrl,
2463
2555
  creationForm: () => creationForm,
2464
2556
  handOffKey: () => handOffKey,
2465
2557
  handOffWithRetry: () => handOffWithRetry,
2466
2558
  initGitHubApp: () => initGitHubApp,
2559
+ installStepMessage: () => installStepMessage,
2560
+ keyNotStoredMessage: () => keyNotStoredMessage,
2467
2561
  manifestPostUrl: () => manifestPostUrl,
2468
2562
  parseKeyRetryAnswer: () => parseKeyRetryAnswer,
2469
2563
  startCallbackServer: () => startCallbackServer
@@ -2475,8 +2569,33 @@ import { mkdtemp, rm, writeFile as writeFile2 } from "node:fs/promises";
2475
2569
  import { tmpdir } from "node:os";
2476
2570
  import path10 from "node:path";
2477
2571
  import { Readable } from "node:stream";
2572
+ function appsBaseUrl(target) {
2573
+ return target.isOrganization ? `https://github.com/organizations/${encodeURIComponent(target.owner)}/settings/apps` : "https://github.com/settings/apps";
2574
+ }
2575
+ function appSettingsUrl(target, slug) {
2576
+ return `${appsBaseUrl(target)}/${encodeURIComponent(slug)}`;
2577
+ }
2578
+ function keyNotStoredMessage(target, created) {
2579
+ return `
2580
+ ${errColors.bold(errColors.red("Stopping here:"))} the App exists but its key was not stored, so installing it now
2581
+ would achieve nothing. To recover, open the App and generate a private key from
2582
+ its "Private keys" section, then run \`verify\`; or delete the App and start again.
2583
+
2584
+ GITHUB_APP_ID=${created.id}
2585
+ App settings: ${link(appSettingsUrl(target, created.slug), errColors)}`;
2586
+ }
2587
+ function installStepMessage(target, slug) {
2588
+ return `
2589
+ ${step(2, `Install it on ${target.owner}/${target.repo}:`)}
2590
+
2591
+ ${link(`${appSettingsUrl(target, slug)}/installations`)}
2592
+
2593
+ That page shows an Install button first \u2014 the repository picker only appears after
2594
+ you click it. Choose "Only select repositories" and pick that one repository.
2595
+ `;
2596
+ }
2478
2597
  function manifestPostUrl(target, state) {
2479
- const base = target.isOrganization ? `https://github.com/organizations/${encodeURIComponent(target.owner)}/settings/apps/new` : "https://github.com/settings/apps/new";
2598
+ const base = `${appsBaseUrl(target)}/new`;
2480
2599
  return `${base}?state=${encodeURIComponent(state)}`;
2481
2600
  }
2482
2601
  function appManifest(target, redirectUrl, name = appName(target.repo)) {
@@ -2780,7 +2899,7 @@ async function handOffWithRetry(pem, destination) {
2780
2899
  const result = await handOffKey(pem, attempt);
2781
2900
  if (result.stored) {
2782
2901
  console.log(`
2783
- private key: ${result.detail}`);
2902
+ ${tick(`Private key: ${result.detail}`)}`);
2784
2903
  return true;
2785
2904
  }
2786
2905
  console.error(`
@@ -2880,41 +2999,41 @@ No installation of this App found on ${target.owner}/${target.repo} (HTTP ${foun
2880
2999
  }
2881
3000
  const installation = found.body;
2882
3001
  console.log(`
2883
- installation ${installation.id} on ${target.owner}/${target.repo}`);
3002
+ ${outColors.bold(`Installation ${installation.id}`)} on ${target.owner}/${target.repo}`);
2884
3003
  const findings = readbackVerdict(installation);
2885
3004
  if (findings.length === 0) {
2886
- console.log(" grant matches exactly what this package needs:");
3005
+ console.log(" The grant matches exactly what this package needs:");
2887
3006
  for (const [name, level] of Object.entries(CANOPY_APP_PERMISSIONS)) {
2888
- console.log(` \u2713 ${name}: ${level}`);
3007
+ console.log(` ${tick(`${name}: ${level}`)}`);
2889
3008
  }
2890
3009
  } else {
2891
3010
  for (const finding of findings) {
2892
- console.error(` \u2717 ${finding.message}`);
3011
+ console.error(` ${cross(finding.message)}`);
2893
3012
  }
2894
3013
  }
2895
3014
  let installationsOk = false;
2896
3015
  const installations = await installationCount(jwt);
2897
3016
  if (installations === null) {
2898
- console.error(` \u2717 could not list this App's installations, so "installed once" is UNCHECKED.
3017
+ console.error(` ${failMark} could not list this App's installations, so "installed once" is UNCHECKED.
2899
3018
  Reported as a failure rather than passed over: this is the check that observes
2900
3019
  the one-App-per-site rule, and an unread check is not a satisfied one.`);
2901
3020
  } else if (installations === 1) {
2902
3021
  installationsOk = true;
2903
- console.log(" \u2713 installed once, so this key reaches no other account");
3022
+ console.log(` ${tick("installed once, so this key reaches no other account")}`);
2904
3023
  } else if (installations > 1) {
2905
- console.error(` \u2717 this App is installed ${installations} times, and should be installed ONCE.
3024
+ console.error(` ${failMark} this App is installed ${installations} times, and should be installed ONCE.
2906
3025
  Its private key is App-level: whoever holds it can mint a token for any of
2907
3026
  those installations. Register a separate App per site instead, and remove
2908
3027
  the installations that do not belong to this one.`);
2909
3028
  } else {
2910
- console.error(` \u2717 GitHub reports ${installations} installations of this App, yet one was just read
3029
+ console.error(` ${failMark} GitHub reports ${installations} installations of this App, yet one was just read
2911
3030
  back for this repository. Something is inconsistent; re-run before trusting it.`);
2912
3031
  }
2913
3032
  const mint = await proveTokenMint(jwt, installation.id, target.repo);
2914
3033
  if (mint.ok) {
2915
- console.log(` \u2713 installation token ${mint.detail}`);
3034
+ console.log(` ${tick(`installation token ${mint.detail}`)}`);
2916
3035
  } else {
2917
- console.error(` \u2717 could not mint an installation token \u2014 ${mint.detail}
3036
+ console.error(` ${failMark} could not mint an installation token \u2014 ${mint.detail}
2918
3037
  If that message says the requested permissions are not granted to this
2919
3038
  installation, that is a GitHub behaviour rather than a bug here: adding a
2920
3039
  permission to an App does NOT apply to existing installations until an account
@@ -2990,7 +3109,7 @@ async function createCommand(options) {
2990
3109
  console.log(`! Could not check whether the name "${name}" is already taken, so this is unverified.
2991
3110
  `);
2992
3111
  }
2993
- console.log('IF THIS GOES WRONG AFTER YOU CLICK CREATE, read this first:\n The App will exist and this command will not have its private key. That is\n recoverable. Either:\n - delete the App in its settings and re-run this command, or\n - open the App, scroll to "Private keys", and click "Generate a private key".\n It downloads a .pem and that is the only chance to save it. This is NOT the\n "client secret" listed a few sections above: that is for OAuth user flows, is\n unused by this App, and fails confusingly when used to sign.\n');
3112
+ console.log(recoveryPreamble());
2994
3113
  const state = randomUUID();
2995
3114
  let formDir;
2996
3115
  let callback;
@@ -3013,29 +3132,15 @@ async function createCommand(options) {
3013
3132
  formDir = await mkdtemp(path10.join(tmpdir(), "canopycms-app-"));
3014
3133
  const formFile = path10.join(formDir, `create-${slug}.html`);
3015
3134
  await writeFile2(formFile, creationForm(postUrl, manifest), { mode: 384, flag: "wx" });
3016
- console.log(`Registering "${name}" for ${target.owner}/${target.repo}.
3017
-
3018
- Its permissions, which is the part that matters:
3019
-
3020
- ${JSON.stringify(CANOPY_APP_PERMISSIONS, null, 2)}
3021
-
3022
- The key will go to ${describeDestination(destination)} \u2014 and if that fails you will
3023
- be asked for somewhere else before it is discarded.
3024
-
3025
- 1. Open this file in a browser:
3026
-
3027
- ${formFile}
3028
-
3029
- The browser matters, twice over. It must be LOGGED IN TO GITHUB as an owner of
3030
- ${target.owner}, and it must be running on THIS machine \u2014 the redirect comes back to
3031
- 127.0.0.1:${callback.port}, which only a local browser can reach. An embedded or
3032
- app-internal browser bounces off the GitHub login wall and simply appears to do
3033
- nothing when you click Create.
3034
-
3035
- The page posts the manifest to GitHub, which shows you the App pre-filled with
3036
- exactly those permissions. Review them and click Create.
3037
-
3038
- Waiting for GitHub to redirect back (up to ${CALLBACK_TIMEOUT_MS / 6e4} minutes)...`);
3135
+ console.log(registrationMessage({
3136
+ name,
3137
+ owner: target.owner,
3138
+ repo: target.repo,
3139
+ destination: describeDestination(destination),
3140
+ formFile,
3141
+ callbackPort: callback.port,
3142
+ timeoutMinutes: CALLBACK_TIMEOUT_MS / 6e4
3143
+ }));
3039
3144
  let code;
3040
3145
  try {
3041
3146
  code = await callback.code;
@@ -3050,32 +3155,20 @@ ${getErrorMessage(err2)}.
3050
3155
  See the recovery note printed above if the App was in fact created.`);
3051
3156
  return 1;
3052
3157
  }
3053
- console.log(" got the manifest code; exchanging it for the App credentials");
3158
+ console.log(`
3159
+ ${tick("Got the manifest code; exchanging it for the App credentials")}`);
3054
3160
  const created = await convertManifest(code);
3055
3161
  if (!created) {
3056
3162
  console.error(' the exchange failed, so this command never received the private key.\n\n Do NOT simply re-run `create`: it submits a fresh manifest and would register a\n SECOND App. The App you just created almost certainly exists \u2014 open it, generate\n a key from its "Private keys" section (see the recovery note above), and then run\n `canopycms init-github-app verify`. Delete the App instead if you would rather\n start over.');
3057
3163
  return 1;
3058
3164
  }
3059
- console.log(` created App ${created.slug} (id ${created.id}) \u2014 private key captured; sending it to ${describeDestination(destination)}`);
3165
+ console.log(tick(`Created App ${outColors.bold(created.slug)} (id ${created.id}); private key captured, sending it to ${describeDestination(destination)}`));
3060
3166
  const stored = await handOffWithRetry(created.pem, destination);
3061
3167
  if (!stored) {
3062
- console.error(`
3063
- Stopping here: the App exists but its key was not stored, so installing it now
3064
- would achieve nothing. To recover, open the App and generate a private key from
3065
- its "Private keys" section, then run \`verify\`; or delete the App and start again.
3066
-
3067
- GITHUB_APP_ID=${created.id}
3068
- App settings: https://github.com/settings/apps/${created.slug}`);
3168
+ console.error(keyNotStoredMessage(target, created));
3069
3169
  return 1;
3070
3170
  }
3071
- console.log(`
3072
- 2. Install it on ${target.owner}/${target.repo}:
3073
-
3074
- https://github.com/settings/apps/${created.slug}/installations
3075
-
3076
- That page shows an Install button first \u2014 the repository picker only appears after
3077
- you click it. Choose "Only select repositories" and pick that one repository.
3078
- `);
3171
+ console.log(installStepMessage(target, created.slug));
3079
3172
  await pressEnter(" Press Enter once it is installed.");
3080
3173
  let readback;
3081
3174
  try {
@@ -3086,19 +3179,7 @@ Stopping here: the App exists but its key was not stored, so installing it now
3086
3179
  This says nothing about the App or the key \u2014 re-run \`canopycms init-github-app verify\`.`);
3087
3180
  readback = { ok: false, installationId: null };
3088
3181
  }
3089
- console.log(`
3090
- Set these on your deployment:
3091
-
3092
- GITHUB_APP_ID=${created.id}
3093
- GITHUB_APP_INSTALLATION_ID=${readback.installationId ?? "<run `verify` to read it>"}
3094
- GITHUB_APP_PRIVATE_KEY_SECRET_ARN=<wherever you just put the key>
3095
-
3096
- Both ids are numeric. The App ID is NOT the "Client ID" (\`Iv1.\u2026\`) shown beside it,
3097
- and the installation id is NOT the App ID \u2014 an App installed twice has one App ID
3098
- and two installation ids.
3099
-
3100
- Re-check this any time, without creating anything:
3101
- canopycms init-github-app verify --app-id ${created.id} --key-file <path>`);
3182
+ console.log(deploymentSettingsMessage(created.id, readback.installationId));
3102
3183
  return stored && readback.ok ? 0 : 1;
3103
3184
  } finally {
3104
3185
  callback.close();
@@ -3143,6 +3224,7 @@ var init_init_github_app = __esm({
3143
3224
  init_error();
3144
3225
  init_github_auth();
3145
3226
  init_github_app_manifest();
3227
+ init_github_app_output();
3146
3228
  init_prompt();
3147
3229
  CALLBACK_TIMEOUT_MS = 10 * 60 * 1e3;
3148
3230
  REQUEST_TIMEOUT_MS = 3e4;
@@ -3706,6 +3788,70 @@ function isBlockStructuralKey(key) {
3706
3788
  function createDebugLogger2(options) {
3707
3789
  return new DebugLogger2(options);
3708
3790
  }
3791
+ function startOf(node) {
3792
+ return node?.position?.start.offset;
3793
+ }
3794
+ function endOf(node) {
3795
+ return node?.position?.end.offset;
3796
+ }
3797
+ function apply(source, edits, from = 0, to = source.length) {
3798
+ let out = "";
3799
+ let at = from;
3800
+ for (const edit of [...edits].sort((a, b) => a.start - b.start)) {
3801
+ if (edit.start < at || edit.end > to)
3802
+ continue;
3803
+ out += source.slice(at, edit.start) + edit.text;
3804
+ at = edit.end;
3805
+ }
3806
+ return out + source.slice(at, to);
3807
+ }
3808
+ function underscoreStarts(node) {
3809
+ const bare = startOf(node) === startOf(node.children?.[0]);
3810
+ if (node.type === "link" && !bare || node.type === "linkReference")
3811
+ return [];
3812
+ if (node.type === "text" || node.type === "mdxTextExpression") {
3813
+ const start = startOf(node);
3814
+ return start !== void 0 && (node.value ?? "").includes("_") ? [start] : [];
3815
+ }
3816
+ return (node.children ?? []).flatMap(underscoreStarts);
3817
+ }
3818
+ function orderedNumber(numbers, index) {
3819
+ const [first = 1, second, third] = numbers;
3820
+ if (index === 0)
3821
+ return first;
3822
+ const ones = numbers.length > 1 && second === 1 && (first !== 0 || numbers.length < 3 || third === 1);
3823
+ return ones ? 1 : first + index;
3824
+ }
3825
+ function restyleMarkdown(source, tree, fixed, readsBack) {
3826
+ if (source.startsWith("\uFEFF"))
3827
+ return void 0;
3828
+ const blocks = tree.children ?? [];
3829
+ const groups = blocks.map(() => []);
3830
+ new Restyler(source).children(tree, [], [], fixed, (index) => groups[index] ?? []);
3831
+ const changed = groups.filter((group) => group.length > 0);
3832
+ if (changed.length === 0)
3833
+ return void 0;
3834
+ let kept = [];
3835
+ let best;
3836
+ let budget = 8 * Math.ceil(Math.log2(changed.length + 1)) + 8;
3837
+ const attempt = (tried) => {
3838
+ if (budget-- <= 0)
3839
+ return;
3840
+ const trial = [...kept, ...tried.flat()];
3841
+ const text3 = apply(source, trial);
3842
+ const parsed = readsBack(text3);
3843
+ if (parsed !== void 0) {
3844
+ kept = trial;
3845
+ best = { text: text3, tree: parsed };
3846
+ } else if (tried.length > 1) {
3847
+ const half = Math.ceil(tried.length / 2);
3848
+ attempt(tried.slice(0, half));
3849
+ attempt(tried.slice(half));
3850
+ }
3851
+ };
3852
+ attempt(changed);
3853
+ return best;
3854
+ }
3709
3855
  function snapshotDocument(doc) {
3710
3856
  const items = /* @__PURE__ */ new Map();
3711
3857
  const visitNode = (node) => {
@@ -3819,15 +3965,15 @@ function canonical(node) {
3819
3965
  function kindOf(node) {
3820
3966
  return `${node.type}:${String(node.ordered ?? "")}:${String(node.checked ?? "")}`;
3821
3967
  }
3822
- function startOf(node) {
3968
+ function startOf2(node) {
3823
3969
  return node?.position?.start.offset;
3824
3970
  }
3825
- function endOf(node) {
3971
+ function endOf2(node) {
3826
3972
  return node?.position?.end.offset;
3827
3973
  }
3828
3974
  function sliceOf(text3, node) {
3829
- const start = startOf(node);
3830
- const end = endOf(node);
3975
+ const start = startOf2(node);
3976
+ const end = endOf2(node);
3831
3977
  return start === void 0 || end === void 0 ? void 0 : text3.slice(start, end);
3832
3978
  }
3833
3979
  function reindent(text3, from, to) {
@@ -3891,12 +4037,12 @@ function leadWidth(text3, offset) {
3891
4037
  return /^(?: |[-*+](?= )|\d{1,9}[.)](?= )|\[[ xX]\](?= ))*$/.test(lead) ? lead.length : void 0;
3892
4038
  }
3893
4039
  function gapBetween(text3, a, b) {
3894
- const end = endOf(a);
3895
- const start = startOf(b);
4040
+ const end = endOf2(a);
4041
+ const start = startOf2(b);
3896
4042
  return end === void 0 || start === void 0 ? void 0 : text3.slice(end, start);
3897
4043
  }
3898
4044
  function listMarkers(text3, items, ordered) {
3899
- const firstStart = startOf(items[0]);
4045
+ const firstStart = startOf2(items[0]);
3900
4046
  if (firstStart === void 0)
3901
4047
  return void 0;
3902
4048
  if (!ordered) {
@@ -3909,16 +4055,14 @@ function listMarkers(text3, items, ordered) {
3909
4055
  const first = numberAt(firstStart);
3910
4056
  if (first === null)
3911
4057
  return void 0;
3912
- const start = Number(first[1]);
3913
4058
  const delimiter = first[2] ?? ".";
3914
4059
  const padTo = first[1]?.startsWith("0") ? first[1].length : 0;
3915
- const second = items.length > 1 ? numberAt(startOf(items[1])) : null;
3916
- const step = second !== null && Number(second[1]) === start ? 0 : 1;
4060
+ const typed = items.map((item) => Number(numberAt(startOf2(item))?.[1]));
3917
4061
  return (itemText, index) => {
3918
4062
  const current = /^\d{1,9}[.)]/.exec(itemText);
3919
4063
  if (current === null)
3920
4064
  return void 0;
3921
- const marker = `${String(start + step * index).padStart(padTo, "0")}${delimiter}`;
4065
+ const marker = `${String(orderedNumber(typed, index)).padStart(padTo, "0")}${delimiter}`;
3922
4066
  if (marker.length !== current[0].length && itemText.includes("\n"))
3923
4067
  return void 0;
3924
4068
  return marker + itemText.slice(current[0].length);
@@ -3934,22 +4078,56 @@ function preserveMarkdownSource(original, updated, format) {
3934
4078
  return updated;
3935
4079
  }
3936
4080
  }
4081
+ function diskBlocks(oNodes, nNodes) {
4082
+ const oKeys = oNodes.map(canonical);
4083
+ const nKeys = nNodes.map(canonical);
4084
+ const pairs = matchSequences(oKeys, nKeys);
4085
+ const fromDisk = new Set(pairs.map(([, ni]) => ni));
4086
+ const matched = new Set(pairs.map(([oi]) => oi));
4087
+ const unmatched = /* @__PURE__ */ new Map();
4088
+ oKeys.forEach((key, index) => {
4089
+ if (!matched.has(index))
4090
+ unmatched.set(key, (unmatched.get(key) ?? 0) + 1);
4091
+ });
4092
+ nKeys.forEach((key, index) => {
4093
+ const left = unmatched.get(key) ?? 0;
4094
+ if (fromDisk.has(index) || left === 0)
4095
+ return;
4096
+ unmatched.set(key, left - 1);
4097
+ fromDisk.add(index);
4098
+ });
4099
+ return fromDisk;
4100
+ }
3937
4101
  function splice(original, updated, format) {
3938
- const before = parse(original, format);
4102
+ const before = original === void 0 ? void 0 : parse(original, format);
3939
4103
  const after = parse(updated, format);
3940
- if (before === void 0 || after === void 0)
4104
+ if (after === void 0)
3941
4105
  return void 0;
3942
4106
  const target = canonical(after);
3943
- for (const deep of [true, false]) {
3944
- const candidate = new Splicer(original, updated, deep).body(before, after);
3945
- if (candidate === void 0)
3946
- continue;
3947
- const reparsed = parse(candidate, format);
3948
- if (reparsed !== void 0 && canonical(reparsed) === target)
3949
- return candidate;
3950
- log2.debug("markdown-body-splice", "splice does not read back as the edit", { deep });
4107
+ const fromDisk = diskBlocks(before?.children ?? [], after.children);
4108
+ const restyled = restyleMarkdown(updated, after, (index) => fromDisk.has(index), (text3) => {
4109
+ const tree = parse(text3, format);
4110
+ return tree !== void 0 && canonical(tree) === target ? tree : void 0;
4111
+ });
4112
+ if (original === void 0 || before === void 0)
4113
+ return restyled?.text;
4114
+ const asSent = { text: updated, tree: after };
4115
+ const edits = restyled === void 0 || restyled.text === updated ? [asSent] : [restyled, asSent];
4116
+ for (const edit of edits) {
4117
+ for (const deep of [true, false]) {
4118
+ const candidate = new Splicer(original, edit.text, deep).body(before, edit.tree);
4119
+ if (candidate === void 0)
4120
+ continue;
4121
+ const reparsed = parse(candidate, format);
4122
+ if (reparsed !== void 0 && canonical(reparsed) === target)
4123
+ return candidate;
4124
+ log2.debug("markdown-body-splice", "splice does not read back as the edit", {
4125
+ deep,
4126
+ restyled: edit !== asSent
4127
+ });
4128
+ }
3951
4129
  }
3952
- return void 0;
4130
+ return restyled?.text;
3953
4131
  }
3954
4132
  function createNode(ctx, value, asKey = false) {
3955
4133
  const node = ctx.doc.createNode(value);
@@ -4234,7 +4412,7 @@ function writesFrontmatter(data) {
4234
4412
  }
4235
4413
  function bodyToWrite(body, data, format, existingRaw) {
4236
4414
  const priorBody = existingRaw === void 0 ? void 0 : extractBody(existingRaw);
4237
- const spliced = priorBody === void 0 ? body : preserveMarkdownSource(priorBody, body, format);
4415
+ const spliced = preserveMarkdownSource(priorBody, body, format);
4238
4416
  if (!writesFrontmatter(data)) {
4239
4417
  const flush = spliced.replace(/^(?:\r?\n)+/, "");
4240
4418
  return /^\uFEFF?---/.test(flush) ? `
@@ -7828,7 +8006,7 @@ async function generateAIContentCLI(options) {
7828
8006
  console.log(` Output: ${result.outputDir}
7829
8007
  `);
7830
8008
  }
7831
- var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceSortFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, seoFieldNameSchema2, seoSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env, forEachReferenceField, init_validation, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, envFlag2, LOG_LEVELS2, DebugLogger2, testLogger2, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, heldRoots, getFormatExtension, isDataOnlyFormat, asRecord, DEFAULT_SEO_FIELD_NAMES, asRecord2, asText, asFlag, asImageUrl, asOgType, asTwitterCard, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned4, SchemaUnavailableError, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, RESERVED_SETTINGS_BRANCH_PREFIX, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, INLINE_LINK_TARGET, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
8009
+ var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceSortFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, editorUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, seoFieldNameSchema2, seoSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env, forEachReferenceField, init_validation, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, envFlag2, LOG_LEVELS2, DebugLogger2, testLogger2, MARKDOWN_EXPORT_OPTIONS, BULLETS, DELIMITERS, EMPHASIS, STRONG, RULE, WORD_CHAR, GAP, Restyler, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, heldRoots, getFormatExtension, isDataOnlyFormat, asRecord, DEFAULT_SEO_FIELD_NAMES, asRecord2, asText, asFlag, asImageUrl, asOgType, asTwitterCard, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned4, SchemaUnavailableError, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, parseTypedFilename, EMPTY_LOGICAL_PATH, ENTRY_EXTENSIONS, ContentIdIndex, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, RESERVED_SETTINGS_BRANCH_PREFIX, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, INLINE_LINK_TARGET, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
7832
8010
  var init_generate_ai_content = __esm({
7833
8011
  "dist/cli/generate-ai-content.js"() {
7834
8012
  "use strict";
@@ -8281,6 +8459,14 @@ var init_generate_ai_content = __esm({
8281
8459
  previewPrefixSchema2 = z42.string().trim().refine((value) => isHttpUrlOrSameOriginPath2(value), {
8282
8460
  message: 'editor.previewPrefix must be an absolute http(s) URL or a site-relative path beginning with a single "/", with no query or fragment'
8283
8461
  });
8462
+ editorUrlSchema2 = z42.string().trim().refine((value) => {
8463
+ if (!isHttpUrlOrSameOriginPath2(value) || !/^https?:\/\//i.test(value))
8464
+ return false;
8465
+ const url = new URL(value);
8466
+ return !url.username && !url.password;
8467
+ }, {
8468
+ message: "editorUrl must be an absolute http(s) URL with no credentials, query or fragment"
8469
+ });
8284
8470
  }
8285
8471
  });
8286
8472
  init_media2 = __esm3({
@@ -8423,6 +8609,7 @@ var init_generate_ai_content = __esm({
8423
8609
  contentRoot: contentRootSchema2.default("content"),
8424
8610
  sourceRoot: sourceRootSchema2.optional(),
8425
8611
  basePath: basePathSchema2,
8612
+ editorUrl: editorUrlSchema2.optional(),
8426
8613
  unauthenticatedStatus: z6.union([z6.literal(401), z6.literal(419)]).optional(),
8427
8614
  danglingReferences: z6.enum(["error", "warn"]).optional(),
8428
8615
  editor: editorConfigSchema2.optional(),
@@ -8792,6 +8979,360 @@ var init_generate_ai_content = __esm({
8792
8979
  });
8793
8980
  init_error3();
8794
8981
  init_error3();
8982
+ MARKDOWN_EXPORT_OPTIONS = {
8983
+ listItemIndent: "one",
8984
+ bullet: "-",
8985
+ emphasis: "_",
8986
+ strong: "*",
8987
+ rule: "-"
8988
+ };
8989
+ BULLETS = [MARKDOWN_EXPORT_OPTIONS.bullet, "*"];
8990
+ DELIMITERS = [".", ")"];
8991
+ EMPHASIS = MARKDOWN_EXPORT_OPTIONS.emphasis;
8992
+ STRONG = MARKDOWN_EXPORT_OPTIONS.strong.repeat(2);
8993
+ RULE = MARKDOWN_EXPORT_OPTIONS.rule.repeat(3);
8994
+ WORD_CHAR = /[^\s\p{P}\p{S}]/u;
8995
+ GAP = /^[ \t>]*(?:\r?\n[ \t>]*)+$/;
8996
+ Restyler = class {
8997
+ constructor(source) {
8998
+ this.source = source;
8999
+ this.listMarker = /* @__PURE__ */ new Map();
9000
+ this.underscores = /* @__PURE__ */ new Map();
9001
+ }
9002
+ /** Edits for `node` and everything under it, given its ancestors (nearest last). */
9003
+ collect(node, ancestors, out) {
9004
+ const inside = [...ancestors, node];
9005
+ switch (node.type) {
9006
+ case "emphasis":
9007
+ this.emphasis(node, ancestors, out);
9008
+ break;
9009
+ case "strong":
9010
+ this.strong(node, out);
9011
+ break;
9012
+ case "text":
9013
+ this.text(node, ancestors, out);
9014
+ return;
9015
+ case "thematicBreak":
9016
+ this.replaceWhole(node, this.ruleIn(ancestors), out);
9017
+ return;
9018
+ case "code":
9019
+ this.fence(node, out);
9020
+ return;
9021
+ case "heading":
9022
+ this.heading(node, inside, out);
9023
+ return;
9024
+ case "table":
9025
+ this.table(node, inside, out);
9026
+ return;
9027
+ default:
9028
+ break;
9029
+ }
9030
+ this.children(node, inside, out);
9031
+ }
9032
+ /**
9033
+ * Edits for `parent`'s children and the gaps between them. `fixed` names children whose text
9034
+ * must stay as it is (untouched top-level blocks); their lists still count for alternation.
9035
+ */
9036
+ children(parent, inside, out, fixed = () => false, outFor = () => out) {
9037
+ const children = parent.children ?? [];
9038
+ const markers = this.listMarkers(children, fixed);
9039
+ children.forEach((child, index) => {
9040
+ if (fixed(index))
9041
+ return;
9042
+ const edits = outFor(index);
9043
+ const marker = markers.get(child);
9044
+ if (marker !== void 0)
9045
+ this.list(child, marker, edits);
9046
+ this.collect(child, inside, edits);
9047
+ });
9048
+ const always = parent.type === "root" || parent.type === "blockquote";
9049
+ const spaced = always || parent.type === "list" || parent.type === "listItem";
9050
+ if (!spaced)
9051
+ return;
9052
+ for (let index = 1; index < children.length; index++) {
9053
+ if (fixed(index) && fixed(index - 1))
9054
+ continue;
9055
+ this.gap(children[index - 1], children[index], always, outFor(fixed(index) ? index - 1 : index));
9056
+ }
9057
+ }
9058
+ /** The marker each list among `children` takes: its own, then alternating with the one before. */
9059
+ listMarkers(children, fixed) {
9060
+ const markers = /* @__PURE__ */ new Map();
9061
+ let previous;
9062
+ children.forEach((child, index) => {
9063
+ if (child.type !== "list") {
9064
+ previous = void 0;
9065
+ return;
9066
+ }
9067
+ const ordered = Boolean(child.ordered);
9068
+ const current = this.markerOf(child);
9069
+ if (current === void 0) {
9070
+ previous = void 0;
9071
+ return;
9072
+ }
9073
+ let marker = current;
9074
+ if (!fixed(index)) {
9075
+ const choices = ordered ? DELIMITERS : BULLETS;
9076
+ const next = children[index + 1];
9077
+ const nextFixed = next?.type === "list" && Boolean(next.ordered) === ordered && fixed(index + 1) ? this.markerOf(next) : void 0;
9078
+ const before = previous?.ordered === ordered ? previous.marker : void 0;
9079
+ marker = choices.find((choice) => choice !== before && choice !== nextFixed) ?? current;
9080
+ markers.set(child, marker);
9081
+ }
9082
+ this.listMarker.set(child, marker);
9083
+ previous = { ordered, marker };
9084
+ });
9085
+ return markers;
9086
+ }
9087
+ /**
9088
+ * A rule's text: `---`, but inside a list Prettier writes `***`, or `---` when the list takes
9089
+ * the alternate marker.
9090
+ */
9091
+ ruleIn(ancestors) {
9092
+ const list = [...ancestors].reverse().find((a) => a.type === "list");
9093
+ if (list === void 0)
9094
+ return RULE;
9095
+ const marker = this.listMarker.get(list);
9096
+ return marker === BULLETS[0] || marker === DELIMITERS[0] ? "***" : RULE;
9097
+ }
9098
+ /** A list's bullet, or its ordered delimiter, read from its first item. */
9099
+ markerOf(list) {
9100
+ const at = startOf(list.children?.[0]);
9101
+ if (at === void 0)
9102
+ return void 0;
9103
+ if (!list.ordered)
9104
+ return this.source[at];
9105
+ return /^\d{1,9}([.)])/.exec(this.source.slice(at, at + 10))?.[1];
9106
+ }
9107
+ /** Item markers: the list's bullet or delimiter, and Prettier's numbering where widths allow. */
9108
+ list(list, marker, out) {
9109
+ const items = list.children ?? [];
9110
+ const starts = items.map(startOf);
9111
+ if (!list.ordered) {
9112
+ for (const at of starts) {
9113
+ if (at !== void 0 && this.source[at] !== marker) {
9114
+ out.push({ start: at, end: at + 1, text: marker });
9115
+ }
9116
+ }
9117
+ return;
9118
+ }
9119
+ const numbers = starts.map((at) => at === void 0 ? null : /^(\d{1,9})[.)]/.exec(this.source.slice(at, at + 11)));
9120
+ const typed = numbers.map((match) => Number(match?.[1]));
9121
+ const renumber = numbers.every((match, i) => match !== null && String(orderedNumber(typed, i)).length === match[1]?.length);
9122
+ numbers.forEach((match, i) => {
9123
+ const at = starts[i];
9124
+ if (match === null || at === void 0)
9125
+ return;
9126
+ const digits = match[1] ?? "";
9127
+ const wanted = renumber ? String(orderedNumber(typed, i)) : digits;
9128
+ if (wanted !== digits || match[0].slice(-1) !== marker) {
9129
+ out.push({ start: at, end: at + match[0].length, text: wanted + marker });
9130
+ }
9131
+ });
9132
+ }
9133
+ /**
9134
+ * The gap between two siblings with exactly one blank line, kept in the container's own
9135
+ * prefix and line ending; where `always` is false, only a gap that had a blank line.
9136
+ */
9137
+ gap(a, b, always, out) {
9138
+ const end = this.contentEnd(a);
9139
+ const start = startOf(b);
9140
+ if (end === void 0 || start === void 0)
9141
+ return;
9142
+ const gap = this.source.slice(end, start);
9143
+ if (!GAP.test(gap))
9144
+ return;
9145
+ const lines = gap.split(/\r?\n/);
9146
+ if (!always && lines.length < 3)
9147
+ return;
9148
+ const eol = gap.includes("\r\n") ? "\r\n" : "\n";
9149
+ const prefix = lines[lines.length - 1] ?? "";
9150
+ const together = a?.type === "definition" && b?.type === "definition";
9151
+ const text3 = together ? eol + prefix : eol + prefix.trimEnd() + eol + prefix;
9152
+ if (text3 !== gap)
9153
+ out.push({ start: end, end: start, text: text3 });
9154
+ }
9155
+ /**
9156
+ * Where `node`'s last line of content ends: a list's position runs on over the blank lines
9157
+ * after it, which in a block quote are lines of `>`.
9158
+ */
9159
+ contentEnd(node) {
9160
+ const start = startOf(node);
9161
+ let end = endOf(node);
9162
+ if (start === void 0 || end === void 0)
9163
+ return end;
9164
+ for (; ; ) {
9165
+ const lineStart = this.source.lastIndexOf("\n", end - 1);
9166
+ if (lineStart <= start || !/^[ \t>]*$/.test(this.source.slice(lineStart + 1, end)))
9167
+ break;
9168
+ end = this.source[lineStart - 1] === "\r" ? lineStart - 1 : lineStart;
9169
+ }
9170
+ return end;
9171
+ }
9172
+ emphasis(node, ancestors, out) {
9173
+ const start = startOf(node);
9174
+ const end = endOf(node);
9175
+ if (start === void 0 || end === void 0)
9176
+ return;
9177
+ const open = this.source[start];
9178
+ if (open !== this.source[end - 1] || open !== "*" && open !== "_")
9179
+ return;
9180
+ const marker = ancestors.some((a) => a.type === "emphasis") || this.besideWord(start, end) || this.afterUnderscore(start, ancestors) ? "*" : EMPHASIS;
9181
+ if (open === marker)
9182
+ return;
9183
+ out.push({ start, end: start + 1, text: marker }, { start: end - 1, end, text: marker });
9184
+ }
9185
+ strong(node, out) {
9186
+ const start = startOf(node);
9187
+ const end = endOf(node);
9188
+ if (start === void 0 || end === void 0)
9189
+ return;
9190
+ if (this.source.slice(start, start + 2) !== "__" || this.source.slice(end - 2, end) !== "__") {
9191
+ return;
9192
+ }
9193
+ out.push({ start, end: start + 2, text: STRONG }, { start: end - 2, end, text: STRONG });
9194
+ }
9195
+ /** Whether the characters past the markers around `[start, end)` include a word character. */
9196
+ besideWord(start, end) {
9197
+ const markers = "*_";
9198
+ let before = start - 1;
9199
+ while (before >= 0 && markers.includes(this.source[before] ?? ""))
9200
+ before--;
9201
+ let after = end;
9202
+ while (after < this.source.length && markers.includes(this.source[after] ?? ""))
9203
+ after++;
9204
+ return WORD_CHAR.test(this.source[before] ?? " ") || WORD_CHAR.test(this.source[after] ?? " ");
9205
+ }
9206
+ afterUnderscore(start, ancestors) {
9207
+ const block = [...ancestors].reverse().find((a) => a.type === "paragraph" || a.type === "heading" || a.type === "tableCell");
9208
+ if (block === void 0)
9209
+ return false;
9210
+ let starts = this.underscores.get(block);
9211
+ if (starts === void 0) {
9212
+ starts = underscoreStarts(block);
9213
+ this.underscores.set(block, starts);
9214
+ }
9215
+ const link2 = [...ancestors].reverse().find((a) => a.type === "link" || a.type === "linkReference");
9216
+ const inLink = (link2?.children ?? []).flatMap(underscoreStarts);
9217
+ return [...starts, ...inLink].some((at) => at < start);
9218
+ }
9219
+ text(node, ancestors, out) {
9220
+ const start = startOf(node);
9221
+ const end = endOf(node);
9222
+ if (start === void 0 || end === void 0)
9223
+ return;
9224
+ const raw = this.source.slice(start, end);
9225
+ for (const match of raw.matchAll(/[ \t]+(?=\r?\n)/g)) {
9226
+ out.push({ start: start + match.index, end: start + match.index + match[0].length, text: "" });
9227
+ }
9228
+ if (ancestors.some((a) => a.type === "link" || a.type === "linkReference"))
9229
+ return;
9230
+ const block = [...ancestors].reverse().find((a) => a.type === "paragraph" || a.type === "heading" || a.type === "tableCell");
9231
+ const blockStart = startOf(block);
9232
+ const blockEnd = endOf(block);
9233
+ if (blockStart === void 0 || blockEnd === void 0)
9234
+ return;
9235
+ if (/\$|\[\[/.test(this.source.slice(blockStart, blockEnd)))
9236
+ return;
9237
+ for (const match of raw.matchAll(/(?<=[\p{L}\p{N}])\\_(?=[\p{L}\p{N}])/gu)) {
9238
+ out.push({ start: start + match.index, end: start + match.index + 1, text: "" });
9239
+ }
9240
+ }
9241
+ replaceWhole(node, text3, out) {
9242
+ const start = startOf(node);
9243
+ const end = endOf(node);
9244
+ if (start === void 0 || end === void 0)
9245
+ return;
9246
+ if (this.source.slice(start, end) !== text3)
9247
+ out.push({ start, end, text: text3 });
9248
+ }
9249
+ /** A `~~~` fence as backticks, when nothing inside it would end a backtick fence. */
9250
+ fence(node, out) {
9251
+ const start = startOf(node);
9252
+ const end = endOf(node);
9253
+ if (start === void 0 || end === void 0)
9254
+ return;
9255
+ const raw = this.source.slice(start, end);
9256
+ const close = /(?:\r?\n)[ \t>]*(~{3,})[ \t]*$/.exec(raw);
9257
+ if (!/^~~~(?!~)[^`\n]*\n/.test(raw) || close?.[1] === void 0 || raw.includes("`"))
9258
+ return;
9259
+ const closeAt = start + close.index + close[0].indexOf("~");
9260
+ out.push({ start, end: start + 3, text: "```" }, { start: closeAt, end: closeAt + close[1].length, text: "```" });
9261
+ }
9262
+ /** A one-line heading as `# x`, its content with its own edits. */
9263
+ heading(node, inside, out) {
9264
+ const inner = [];
9265
+ this.children(node, inside, inner);
9266
+ const children = node.children ?? [];
9267
+ const start = startOf(node);
9268
+ const end = endOf(node);
9269
+ const contentStart = startOf(children[0]);
9270
+ const contentEnd = endOf(children[children.length - 1]);
9271
+ if (start === void 0 || end === void 0 || contentStart === void 0 || contentEnd === void 0 || /[\r\n]/.test(this.source.slice(contentStart, contentEnd))) {
9272
+ out.push(...inner);
9273
+ return;
9274
+ }
9275
+ const content = apply(this.source, inner, contentStart, contentEnd);
9276
+ const text3 = `${"#".repeat(node.depth ?? 1)} ${content}`;
9277
+ if (text3 !== this.source.slice(start, end))
9278
+ out.push({ start, end, text: text3 });
9279
+ else
9280
+ out.push(...inner);
9281
+ }
9282
+ /** A table padded to its columns, as Prettier pads it; its cells keep their own text. */
9283
+ table(node, inside, out) {
9284
+ const start = startOf(node);
9285
+ const end = endOf(node);
9286
+ if (start === void 0 || end === void 0)
9287
+ return;
9288
+ const prefix = this.source.slice(this.source.lastIndexOf("\n", start - 1) + 1, start);
9289
+ const inner = [];
9290
+ const rows = (node.children ?? []).map((row) => (row.children ?? []).map((cell) => {
9291
+ const edits = [];
9292
+ this.children(cell, [...inside, row, cell], edits);
9293
+ inner.push(...edits);
9294
+ const content = cell.children ?? [];
9295
+ const from = startOf(content[0]);
9296
+ const to = endOf(content[content.length - 1]);
9297
+ return from === void 0 || to === void 0 ? "" : apply(this.source, edits, from, to);
9298
+ }));
9299
+ const plain = rows.every((row) => row.every((cell) => /^[\x20-\x7e]*$/.test(cell)));
9300
+ if (!plain || !/^[ \t>]*$/.test(prefix)) {
9301
+ out.push(...inner);
9302
+ return;
9303
+ }
9304
+ const align = node.align ?? [];
9305
+ const columns = Math.max(...rows.map((row) => row.length), align.length);
9306
+ const widths = Array.from({ length: columns }, (_, c) => Math.max(3, ...rows.map((row) => row[c]?.length ?? 0)));
9307
+ const pad = (text22, c) => {
9308
+ const room = (widths[c] ?? 3) - text22.length;
9309
+ if (align[c] === "right")
9310
+ return " ".repeat(room) + text22;
9311
+ if (align[c] === "center") {
9312
+ const left = Math.floor(room / 2);
9313
+ return " ".repeat(left) + text22 + " ".repeat(room - left);
9314
+ }
9315
+ return text22 + " ".repeat(room);
9316
+ };
9317
+ const delimiter = widths.map((width, c) => {
9318
+ const a = align[c];
9319
+ if (a === "left")
9320
+ return `:${"-".repeat(width - 1)}`;
9321
+ if (a === "right")
9322
+ return `${"-".repeat(width - 1)}:`;
9323
+ if (a === "center")
9324
+ return `:${"-".repeat(width - 2)}:`;
9325
+ return "-".repeat(width);
9326
+ });
9327
+ const line = (cells) => `| ${cells.join(" | ")} |`;
9328
+ const lines = rows.map((row) => line(row.map(pad)));
9329
+ lines.splice(1, 0, line(delimiter));
9330
+ const eol = this.source.slice(start, end).includes("\r\n") ? "\r\n" : "\n";
9331
+ const text3 = lines.join(eol + prefix);
9332
+ if (text3 !== this.source.slice(start, end))
9333
+ out.push({ start, end, text: text3 });
9334
+ }
9335
+ };
8795
9336
  END = Symbol("end");
8796
9337
  FRESH = Symbol("fresh");
8797
9338
  LINE_WIDTH = 80;
@@ -9123,8 +9664,8 @@ var init_generate_ai_content = __esm({
9123
9664
  body(before, after) {
9124
9665
  const oNodes = before.children ?? [];
9125
9666
  const nNodes = after.children ?? [];
9126
- const first = startOf(oNodes[0]);
9127
- const last = endOf(oNodes[oNodes.length - 1]);
9667
+ const first = startOf2(oNodes[0]);
9668
+ const last = endOf2(oNodes[oNodes.length - 1]);
9128
9669
  if (first === void 0 || last === void 0 || nNodes.length === 0)
9129
9670
  return void 0;
9130
9671
  const o = { text: this.original, indent: "" };
@@ -9232,9 +9773,9 @@ var init_generate_ai_content = __esm({
9232
9773
  item(o, oItem, n, nItem) {
9233
9774
  const oChildren = oItem.children ?? [];
9234
9775
  const nChildren = nItem.children ?? [];
9235
- const oStart = startOf(oItem);
9236
- const oContent = startOf(oChildren[0]);
9237
- const nContent = startOf(nChildren[0]);
9776
+ const oStart = startOf2(oItem);
9777
+ const oContent = startOf2(oChildren[0]);
9778
+ const nContent = startOf2(nChildren[0]);
9238
9779
  if (oStart === void 0 || oContent === void 0 || nContent === void 0)
9239
9780
  return void 0;
9240
9781
  const prefix = o.text.slice(oStart, oContent);
@@ -9413,7 +9954,32 @@ var init_generate_ai_content = __esm({
9413
9954
  isValidId = isValidContentId;
9414
9955
  init_error3();
9415
9956
  init_logger2();
9957
+ parseTypedFilename = (filename, entryTypes) => {
9958
+ if (filename.startsWith("."))
9959
+ return null;
9960
+ const lastDot = filename.lastIndexOf(".");
9961
+ if (lastDot === -1)
9962
+ return null;
9963
+ const nameWithoutExt = filename.slice(0, lastDot);
9964
+ const parts = nameWithoutExt.split(".");
9965
+ if (parts.length < 3)
9966
+ return null;
9967
+ const potentialType = parts[0];
9968
+ if (entryTypes && !entryTypes.some((e) => e.name === potentialType)) {
9969
+ return null;
9970
+ }
9971
+ const id = parts[parts.length - 1];
9972
+ if (!isValidId(id))
9973
+ return null;
9974
+ const slug = parts.slice(1, -1).join(".").toLowerCase();
9975
+ return {
9976
+ type: potentialType,
9977
+ slug,
9978
+ id
9979
+ };
9980
+ };
9416
9981
  EMPTY_LOGICAL_PATH = "";
9982
+ ENTRY_EXTENSIONS = new Set(Object.keys({ md: true, mdx: true, json: true, yaml: true }).map((format) => getFormatExtension(format)));
9417
9983
  ContentIdIndex = class {
9418
9984
  constructor(root) {
9419
9985
  this.idToLocation = /* @__PURE__ */ new Map();
@@ -9701,30 +10267,6 @@ var init_generate_ai_content = __esm({
9701
10267
  }
9702
10268
  };
9703
10269
  init_error3();
9704
- parseTypedFilename = (filename, entryTypes) => {
9705
- if (filename.startsWith("."))
9706
- return null;
9707
- const lastDot = filename.lastIndexOf(".");
9708
- if (lastDot === -1)
9709
- return null;
9710
- const nameWithoutExt = filename.slice(0, lastDot);
9711
- const parts = nameWithoutExt.split(".");
9712
- if (parts.length < 3)
9713
- return null;
9714
- const potentialType = parts[0];
9715
- if (entryTypes && !entryTypes.some((e) => e.name === potentialType)) {
9716
- return null;
9717
- }
9718
- const id = parts[parts.length - 1];
9719
- if (!isValidId(id))
9720
- return null;
9721
- const slug = parts.slice(1, -1).join(".").toLowerCase();
9722
- return {
9723
- type: potentialType,
9724
- slug,
9725
- id
9726
- };
9727
- };
9728
10270
  CONTENT_EXTENSIONS = [".md", ".mdx", ".json", ".yaml"];
9729
10271
  registry = /* @__PURE__ */ new Map();
9730
10272
  finalization = new FinalizationRegistry(({ rootKey, ref }) => {
@@ -11318,6 +11860,7 @@ var init_generate_ai_content = __esm({
11318
11860
  pullRequestNumber: z72.number().int().optional(),
11319
11861
  submittedAt: z72.string().optional(),
11320
11862
  pushedToGitHubAt: z72.string().optional(),
11863
+ editorUrl: z72.string().optional(),
11321
11864
  syncStatus: enumOf({
11322
11865
  synced: "synced",
11323
11866
  "pending-sync": "pending-sync",
@@ -12371,7 +12914,7 @@ var init_generate_ai_content = __esm({
12371
12914
  throw cleanupErr;
12372
12915
  }
12373
12916
  }
12374
- const step = (name, run) => options.provisionLog ? options.provisionLog.step(name, run) : run();
12917
+ const step2 = (name, run) => options.provisionLog ? options.provisionLog.step(name, run) : run();
12375
12918
  const identity = managedWorkspaceConfig(options.gitBotAuthorName, options.gitBotAuthorEmail);
12376
12919
  let justCloned = false;
12377
12920
  let resolvedRemoteUrl;
@@ -12384,9 +12927,9 @@ var init_generate_ai_content = __esm({
12384
12927
  sourceRoot: options.sourceRoot,
12385
12928
  allowNetworkRemoteInProd: options.allowNetworkRemoteInProd
12386
12929
  });
12387
- await step("clone", () => _GitManager.cloneWorkspace(remoteUrl, options.workspacePath, baseBranch, {
12930
+ await step2("clone", () => _GitManager.cloneWorkspace(remoteUrl, options.workspacePath, baseBranch, {
12388
12931
  remoteName,
12389
- noCheckout: options.branchType === "content",
12932
+ noCheckout: true,
12390
12933
  config: identity
12391
12934
  }));
12392
12935
  justCloned = true;
@@ -12399,7 +12942,7 @@ var init_generate_ai_content = __esm({
12399
12942
  skipIndexMarker: options.branchType === "orphan"
12400
12943
  });
12401
12944
  if (!justCloned) {
12402
- await step("config", async () => {
12945
+ await step2("config", async () => {
12403
12946
  await git.ensureLocalConfig(identity);
12404
12947
  const remoteUrl = await _GitManager.resolveRemoteUrl({
12405
12948
  mode: options.mode,
@@ -12417,19 +12960,19 @@ var init_generate_ai_content = __esm({
12417
12960
  }
12418
12961
  if (options.branchType === "orphan") {
12419
12962
  try {
12420
- await step("checkout", () => git.createOrphanSettingsBranch(options.branchName, {}));
12963
+ await step2("checkout", () => git.createOrphanSettingsBranch(options.branchName, {}));
12421
12964
  } catch (err2) {
12422
12965
  if (err2 instanceof SettingsRemoteUnreadableError && !resolvedRemoteUrl) {
12423
12966
  throw await _GitManager.remoteNotReadyError(options.mode) ?? err2;
12424
12967
  }
12425
12968
  throw err2;
12426
12969
  }
12427
- await step("exclude", () => git.ensureGitExclude("*.lock"));
12970
+ await step2("exclude", () => git.ensureGitExclude("*.lock"));
12428
12971
  } else {
12429
- await step("checkout", () => justCloned ? git.checkoutFreshClone(options.branchName) : git.checkoutBranch(options.branchName));
12972
+ await step2("checkout", () => justCloned ? git.checkoutFreshClone(options.branchName) : git.checkoutBranch(options.branchName));
12430
12973
  const pattern = options.gitExcludePattern;
12431
12974
  if (pattern) {
12432
- await step("exclude", () => git.ensureGitExclude(pattern));
12975
+ await step2("exclude", () => git.ensureGitExclude(pattern));
12433
12976
  }
12434
12977
  }
12435
12978
  return git;
@@ -12854,7 +13397,7 @@ var init_generate_ai_content = __esm({
12854
13397
  log9.debug("git", "Checked out the remote settings branch", { branchName });
12855
13398
  return;
12856
13399
  }
12857
- await this.git.raw(["checkout", "--orphan", branchName]);
13400
+ await this.git.raw(["switch", "--orphan", branchName]);
12858
13401
  await this.git.raw(["rm", "-r", "-f", "-q", "--sparse", "--ignore-unmatch", "--", "."]);
12859
13402
  const leftover = (await this.git.raw(["ls-files"])).trim();
12860
13403
  if (leftover) {
@@ -15571,7 +16114,7 @@ var init_collection3 = __esm({
15571
16114
 
15572
16115
  // dist/config/schemas/url.js
15573
16116
  import { z as z12 } from "zod";
15574
- var uploadTargetUrlSchema3, previewPrefixSchema3;
16117
+ var uploadTargetUrlSchema3, previewPrefixSchema3, editorUrlSchema3;
15575
16118
  var init_url3 = __esm({
15576
16119
  "dist/config/schemas/url.js"() {
15577
16120
  "use strict";
@@ -15582,6 +16125,14 @@ var init_url3 = __esm({
15582
16125
  previewPrefixSchema3 = z12.string().trim().refine((value) => isHttpUrlOrSameOriginPath3(value), {
15583
16126
  message: 'editor.previewPrefix must be an absolute http(s) URL or a site-relative path beginning with a single "/", with no query or fragment'
15584
16127
  });
16128
+ editorUrlSchema3 = z12.string().trim().refine((value) => {
16129
+ if (!isHttpUrlOrSameOriginPath3(value) || !/^https?:\/\//i.test(value))
16130
+ return false;
16131
+ const url = new URL(value);
16132
+ return !url.username && !url.password;
16133
+ }, {
16134
+ message: "editorUrl must be an absolute http(s) URL with no credentials, query or fragment"
16135
+ });
15585
16136
  }
15586
16137
  });
15587
16138
 
@@ -15818,6 +16369,7 @@ var init_config4 = __esm({
15818
16369
  contentRoot: contentRootSchema3.default("content"),
15819
16370
  sourceRoot: sourceRootSchema3.optional(),
15820
16371
  basePath: basePathSchema3,
16372
+ editorUrl: editorUrlSchema3.optional(),
15821
16373
  unauthenticatedStatus: z14.union([z14.literal(401), z14.literal(419)]).optional(),
15822
16374
  danglingReferences: z14.enum(["error", "warn"]).optional(),
15823
16375
  editor: editorConfigSchema3.optional(),
@@ -16459,6 +17011,23 @@ var init_id = __esm({
16459
17011
  }
16460
17012
  });
16461
17013
 
17014
+ // dist/utils/format.js
17015
+ var getFormatExtension2;
17016
+ var init_format = __esm({
17017
+ "dist/utils/format.js"() {
17018
+ "use strict";
17019
+ getFormatExtension2 = (format) => {
17020
+ if (format === "md")
17021
+ return ".md";
17022
+ if (format === "mdx")
17023
+ return ".mdx";
17024
+ if (format === "yaml")
17025
+ return ".yaml";
17026
+ return ".json";
17027
+ };
17028
+ }
17029
+ });
17030
+
16462
17031
  // dist/content-id-index.js
16463
17032
  function extractIdFromFilename2(filename) {
16464
17033
  if (filename.startsWith(".")) {
@@ -16502,10 +17071,13 @@ function extractSlugFromFilename2(filename, entryTypeName) {
16502
17071
  }
16503
17072
  return filename.toLowerCase();
16504
17073
  }
17074
+ var ENTRY_EXTENSIONS2;
16505
17075
  var init_content_id_index = __esm({
16506
17076
  "dist/content-id-index.js"() {
16507
17077
  "use strict";
16508
17078
  init_id();
17079
+ init_format();
17080
+ ENTRY_EXTENSIONS2 = new Set(Object.keys({ md: true, mdx: true, json: true, yaml: true }).map((format) => getFormatExtension2(format)));
16509
17081
  }
16510
17082
  });
16511
17083
 
@@ -17689,6 +18261,7 @@ var init_branch_metadata_file = __esm({
17689
18261
  pullRequestNumber: z18.number().int().optional(),
17690
18262
  submittedAt: z18.string().optional(),
17691
18263
  pushedToGitHubAt: z18.string().optional(),
18264
+ editorUrl: z18.string().optional(),
17692
18265
  syncStatus: enumOf2({
17693
18266
  synced: "synced",
17694
18267
  "pending-sync": "pending-sync",