canopycms 0.0.67 → 0.0.68-int.93

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/dist/ai/handler.js +8 -0
  2. package/dist/api/admin-branch-health.js +12 -7
  3. package/dist/api/admin.d.ts +8 -8
  4. package/dist/api/branch-status.js +6 -2
  5. package/dist/api/client.d.ts +12 -0
  6. package/dist/api/client.js +35 -11
  7. package/dist/api/content.d.ts +6 -5
  8. package/dist/api/content.js +38 -39
  9. package/dist/api/entries.js +6 -8
  10. package/dist/api/github-sync.d.ts +10 -1
  11. package/dist/api/github-sync.js +20 -10
  12. package/dist/api/reference-options.js +1 -1
  13. package/dist/api/request-url.d.ts +8 -0
  14. package/dist/api/request-url.js +15 -0
  15. package/dist/api/resolve-references.js +2 -2
  16. package/dist/api/settings-helpers.d.ts +1 -1
  17. package/dist/api/settings-helpers.js +1 -4
  18. package/dist/authorization/content.d.ts +5 -4
  19. package/dist/authorization/content.js +5 -5
  20. package/dist/authorization/path.d.ts +11 -6
  21. package/dist/authorization/path.js +11 -6
  22. package/dist/authorization/types.d.ts +2 -2
  23. package/dist/branch-health.js +3 -3
  24. package/dist/branch-schema-cache.d.ts +5 -3
  25. package/dist/branch-schema-cache.js +29 -11
  26. package/dist/branch-workspace.js +2 -2
  27. package/dist/cli/cli.js +285 -168
  28. package/dist/cli/generate-ai-content.js +190 -109
  29. package/dist/cli/github-app-manifest.d.ts +4 -3
  30. package/dist/cli/github-app-manifest.js +4 -3
  31. package/dist/cli/init.js +15 -10
  32. package/dist/cli/sync.js +15 -13
  33. package/dist/config/schemas/config.d.ts +6 -3
  34. package/dist/config/schemas/config.js +3 -1
  35. package/dist/config/schemas/field.js +1 -0
  36. package/dist/config/types.d.ts +11 -2
  37. package/dist/config/validation.d.ts +6 -0
  38. package/dist/config/validation.js +37 -0
  39. package/dist/content-listing.d.ts +2 -2
  40. package/dist/content-listing.js +3 -3
  41. package/dist/content-reader.js +4 -4
  42. package/dist/content-store.d.ts +2 -0
  43. package/dist/content-store.js +2 -1
  44. package/dist/content-tree.js +1 -1
  45. package/dist/context.d.ts +16 -7
  46. package/dist/context.js +116 -67
  47. package/dist/editor/Editor.js +4 -4
  48. package/dist/editor/FormRenderer.js +20 -5
  49. package/dist/editor/admin/SystemHealthPanel.js +37 -6
  50. package/dist/editor/components/NoEditPermissionNotice.d.ts +10 -0
  51. package/dist/editor/components/NoEditPermissionNotice.js +17 -0
  52. package/dist/editor/hooks/useBranchesData.js +3 -1
  53. package/dist/editor/hooks/useDraftManager.d.ts +1 -1
  54. package/dist/editor/hooks/useDraftManager.js +2 -1
  55. package/dist/editor/hooks/useEntriesData.js +2 -2
  56. package/dist/editor/hooks/useEntryManager.js +17 -11
  57. package/dist/entry-schema-registry.js +2 -1
  58. package/dist/git-manager.d.ts +20 -0
  59. package/dist/git-manager.js +50 -4
  60. package/dist/github-service.d.ts +8 -0
  61. package/dist/github-service.js +5 -1
  62. package/dist/http/handler.js +24 -9
  63. package/dist/http/index.d.ts +2 -0
  64. package/dist/http/index.js +2 -0
  65. package/dist/http/router.d.ts +2 -0
  66. package/dist/http/router.js +2 -1
  67. package/dist/http/worker-not-ready.d.ts +9 -0
  68. package/dist/http/worker-not-ready.js +17 -0
  69. package/dist/operating-mode/client-unsafe-strategy.js +0 -6
  70. package/dist/operating-mode/types.d.ts +0 -3
  71. package/dist/paths/index.d.ts +1 -1
  72. package/dist/paths/index.js +1 -1
  73. package/dist/paths/normalize.d.ts +7 -0
  74. package/dist/paths/normalize.js +9 -0
  75. package/dist/reference-resolver.d.ts +3 -3
  76. package/dist/reference-resolver.js +3 -2
  77. package/dist/resolve-canopy-user.js +2 -1
  78. package/dist/services.d.ts +12 -5
  79. package/dist/services.js +56 -56
  80. package/dist/settings-workspace.js +36 -0
  81. package/dist/static/seo.d.ts +2 -14
  82. package/dist/static/seo.js +2 -25
  83. package/dist/submission-attribution.d.ts +73 -0
  84. package/dist/submission-attribution.js +221 -0
  85. package/dist/sync-core.js +7 -8
  86. package/dist/types.d.ts +23 -2
  87. package/dist/utils/content-write-lock.d.ts +3 -7
  88. package/dist/utils/content-write-lock.js +6 -4
  89. package/dist/utils/debug.d.ts +8 -0
  90. package/dist/utils/debug.js +10 -2
  91. package/dist/utils/git.d.ts +28 -0
  92. package/dist/utils/git.js +38 -0
  93. package/dist/utils/provisioning-lock.d.ts +15 -5
  94. package/dist/utils/provisioning-lock.js +31 -11
  95. package/dist/utils/request-timing.d.ts +25 -0
  96. package/dist/utils/request-timing.js +101 -0
  97. package/dist/utils/url-prefix.d.ts +15 -0
  98. package/dist/utils/url-prefix.js +26 -0
  99. package/dist/worker/canopy-state.d.ts +45 -0
  100. package/dist/worker/canopy-state.js +75 -0
  101. package/dist/worker/git-sync.d.ts +4 -2
  102. package/dist/worker/git-sync.js +111 -24
  103. package/dist/worker/provisioned-workspace.d.ts +35 -0
  104. package/dist/worker/provisioned-workspace.js +50 -0
  105. package/dist/worker/rebase.js +75 -12
  106. package/dist/worker/task-runner.js +39 -4
  107. package/package.json +1 -1
package/dist/cli/cli.js CHANGED
@@ -1285,7 +1285,8 @@ var init_init = __esm({
1285
1285
  });
1286
1286
  objectFieldSchema = fieldBaseSchema.extend({
1287
1287
  type: z.literal("object"),
1288
- fields: z.array(z.lazy(() => fieldHolder[0])).min(1)
1288
+ fields: z.array(z.lazy(() => fieldHolder[0])).min(1),
1289
+ itemTitleField: z.string().min(1).optional()
1289
1290
  });
1290
1291
  inlineGroupFieldSchema = z.object({
1291
1292
  type: z.literal("group"),
@@ -1466,6 +1467,9 @@ var init_init = __esm({
1466
1467
  defaultRemoteUrl: defaultRemoteUrlSchema.optional(),
1467
1468
  gitBotAuthorName: gitBotAuthorNameSchema,
1468
1469
  gitBotAuthorEmail: gitBotAuthorEmailSchema,
1470
+ // Unset reads as on for Edited-by and off for Co-authored-by (services.ts submitBranch).
1471
+ gitEditedByTrailers: z5.boolean().optional(),
1472
+ gitCoAuthoredByTrailers: z5.boolean().optional(),
1469
1473
  githubTokenEnvVar: githubTokenEnvVarSchema.optional(),
1470
1474
  // Required by design (follow-up to SEC-C1): a prod deploy that omits `mode` must fail
1471
1475
  // validation loudly rather than silently running header-trusting dev auth semantics.
@@ -1477,7 +1481,6 @@ var init_init = __esm({
1477
1481
  // Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
1478
1482
  allowNetworkRemoteInProd: z5.boolean().optional(),
1479
1483
  settingsBranch: z5.string().optional(),
1480
- autoCreateSettingsPR: z5.boolean().optional(),
1481
1484
  // `.optional()`, NOT bare `deploymentNameSchema`: its own `.default('prod')` would make
1482
1485
  // `parse(undefined)` resolve to 'prod' instead of staying `undefined`, collapsing the
1483
1486
  // env > config > modeDefault precedence chain `resolveDeploymentName`
@@ -1734,6 +1737,10 @@ var init_init = __esm({
1734
1737
  throw new Error(errorMsg);
1735
1738
  }
1736
1739
  }
1740
+ /**
1741
+ * Keyed by label alone, so two overlapping timers with one label overwrite each other;
1742
+ * anything that can run concurrently uses {@link timed}.
1743
+ */
1737
1744
  time(label) {
1738
1745
  this.timers.set(label, Date.now());
1739
1746
  }
@@ -1748,12 +1755,16 @@ var init_init = __esm({
1748
1755
  this.debug(category, `${label} completed`, { durationMs: duration });
1749
1756
  return duration;
1750
1757
  }
1758
+ /**
1759
+ * Log `<label> completed {durationMs}` once `fn` settles. The start time is local to the
1760
+ * call, so concurrent spans with the same label on one logger never overwrite each other.
1761
+ */
1751
1762
  async timed(category, label, fn) {
1752
- this.time(label);
1763
+ const start = Date.now();
1753
1764
  try {
1754
1765
  return await fn();
1755
1766
  } finally {
1756
- this.timeEnd(category, label);
1767
+ this.debug(category, `${label} completed`, { durationMs: Date.now() - start });
1757
1768
  }
1758
1769
  }
1759
1770
  };
@@ -1919,9 +1930,6 @@ var init_init = __esm({
1919
1930
  throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
1920
1931
  }
1921
1932
  }
1922
- shouldCreateSettingsPR(config) {
1923
- return config.autoCreateSettingsPR ?? true;
1924
- }
1925
1933
  };
1926
1934
  DevStrategy = class extends DevClientSafeStrategy {
1927
1935
  getWorkspaceRoot(sourceRoot) {
@@ -1968,9 +1976,6 @@ var init_init = __esm({
1968
1976
  }
1969
1977
  validateConfig(_config) {
1970
1978
  }
1971
- shouldCreateSettingsPR(_config) {
1972
- return false;
1973
- }
1974
1979
  };
1975
1980
  strategyCache = /* @__PURE__ */ new Map();
1976
1981
  init_mode_env();
@@ -3058,6 +3063,8 @@ import { promises as fs62 } from "fs";
3058
3063
  import { join as join3 } from "pathe";
3059
3064
  import { z as z8 } from "zod";
3060
3065
  import chokidar from "chokidar";
3066
+ import { AsyncLocalStorage } from "node:async_hooks";
3067
+ import { simpleGit as simpleGit3 } from "simple-git";
3061
3068
  import fs82 from "node:fs/promises";
3062
3069
  import { sep, resolve, relative } from "node:path";
3063
3070
  import fs9 from "node:fs/promises";
@@ -3079,7 +3086,6 @@ import lockfile2 from "proper-lockfile";
3079
3086
  import fs16 from "node:fs/promises";
3080
3087
  import path17 from "node:path";
3081
3088
  import { simpleGit as simpleGit22 } from "simple-git";
3082
- import { simpleGit as simpleGit3 } from "simple-git";
3083
3089
  import fs17 from "node:fs/promises";
3084
3090
  import path19 from "node:path";
3085
3091
  import matter3 from "gray-matter";
@@ -3145,6 +3151,9 @@ function createLogicalPath(...segments) {
3145
3151
  }
3146
3152
  return normalized;
3147
3153
  }
3154
+ function entryLogicalPath(collectionLogicalPath, slug) {
3155
+ return collectionLogicalPath ? `${collectionLogicalPath}/${slug}` : slug;
3156
+ }
3148
3157
  function trimSlashes(path222) {
3149
3158
  let start = 0;
3150
3159
  let end = path222.length;
@@ -3473,12 +3482,14 @@ async function withLock(key, fn) {
3473
3482
  locks.delete(key);
3474
3483
  }
3475
3484
  }
3476
- function provisioningLockOptions(lockPath, retries, onCompromised) {
3485
+ function provisioningLockOptions(lockPath, retries, onCompromised, staleMs) {
3477
3486
  return {
3478
3487
  lockfilePath: lockPath,
3479
3488
  realpath: false,
3480
3489
  retries,
3481
- stale: 3e4,
3490
+ stale: staleMs,
3491
+ // Fixed, not stale/2: every holder refreshes at 15s whatever threshold it judges others by.
3492
+ update: 15e3,
3482
3493
  // proper-lockfile invokes this from inside its refresh timer, so ANY throw escaping here is
3483
3494
  // an uncaught exception that kills the process. Call sites are told not to throw (see
3484
3495
  // OnLockCompromised); this makes it structural rather than a convention, and also covers the
@@ -3514,23 +3525,26 @@ function releaseIgnoringAlreadyReleased(release, lockPath) {
3514
3525
  }
3515
3526
  };
3516
3527
  }
3528
+ function branchProvisioningLockName(dirName) {
3529
+ return `.${dirName}.init.lock`;
3530
+ }
3517
3531
  async function acquireProvisioningLock(lockTargetDir2, lockName, onCompromised) {
3518
3532
  await fs23.mkdir(lockTargetDir2, { recursive: true });
3519
3533
  const lockPath = path22.join(lockTargetDir2, lockName);
3520
- const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, { retries: 600, factor: 1, minTimeout: 300, maxTimeout: 800, randomize: true }, onCompromised));
3534
+ const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, { retries: 600, factor: 1, minTimeout: 300, maxTimeout: 800, randomize: true }, onCompromised, PROVISIONING_LOCK_STALE_MS));
3521
3535
  return releaseIgnoringAlreadyReleased(release, lockPath);
3522
3536
  }
3523
- async function tryAcquireProvisioningLock(lockTargetDir2, lockName, onCompromised) {
3537
+ async function tryAcquireProvisioningLock(lockTargetDir2, lockName, onCompromised, staleMs = PROVISIONING_LOCK_STALE_MS) {
3524
3538
  await fs23.mkdir(lockTargetDir2, { recursive: true });
3525
3539
  const lockPath = path22.join(lockTargetDir2, lockName);
3526
- const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, 0, onCompromised));
3540
+ const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, 0, onCompromised, staleMs));
3527
3541
  return releaseIgnoringAlreadyReleased(release, lockPath);
3528
3542
  }
3529
3543
  function lockTargetDir(branchRoot) {
3530
3544
  return path32.join(path32.resolve(branchRoot), META_DIR);
3531
3545
  }
3532
3546
  function tryAcquireContentWriteLock(branchRoot, onCompromised) {
3533
- return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME, onCompromised);
3547
+ return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME, onCompromised, CONTENT_WRITE_LOCK_STALE_MS);
3534
3548
  }
3535
3549
  async function acquireContentWriteLock(branchRoot, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, onCompromised) {
3536
3550
  const startedAt = Date.now();
@@ -4006,6 +4020,61 @@ function isValidSchema(schema) {
4006
4020
  const hasCollections = !!(schema.collections && schema.collections.length > 0);
4007
4021
  return hasEntries || hasCollections;
4008
4022
  }
4023
+ async function timeRequestPhase(phase, fn) {
4024
+ const parent = storage.getStore();
4025
+ if (!parent)
4026
+ return fn();
4027
+ const key = parent.path ? `${parent.path}>${phase}` : phase;
4028
+ let total = parent.phases.get(key);
4029
+ if (!total) {
4030
+ total = { count: 0, ms: 0 };
4031
+ parent.phases.set(key, total);
4032
+ }
4033
+ const start = performance.now();
4034
+ try {
4035
+ return await storage.run({ ...parent, path: key }, fn);
4036
+ } finally {
4037
+ total.count += 1;
4038
+ total.ms += performance.now() - start;
4039
+ }
4040
+ }
4041
+ function isNetworkRemoteUrl(url) {
4042
+ if (/^file:\/\//i.test(url))
4043
+ return false;
4044
+ if (NETWORK_SCHEME_PATTERN.test(url))
4045
+ return true;
4046
+ if (TRANSPORT_HELPER_PATTERN.test(url))
4047
+ return true;
4048
+ if (LEADING_OPTION_PATTERN.test(url))
4049
+ return true;
4050
+ if (WINDOWS_DRIVE_PATTERN.test(url))
4051
+ return false;
4052
+ return COLON_BEFORE_SLASH_PATTERN.test(url);
4053
+ }
4054
+ function isMissingRemoteRefFailure(message) {
4055
+ return MISSING_REMOTE_REF_REASONS.some((reason) => message.includes(reason));
4056
+ }
4057
+ async function stageAllExceptCanopyState(git) {
4058
+ await git.add(["-A"]);
4059
+ await git.raw(["reset", "-q", "--", CANOPY_META_DIR]);
4060
+ }
4061
+ async function detectHeadBranch(repoRoot, fallback = "main") {
4062
+ try {
4063
+ const git = simpleGit3({ baseDir: repoRoot });
4064
+ const head = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
4065
+ return head && head !== "HEAD" ? head : fallback;
4066
+ } catch {
4067
+ return fallback;
4068
+ }
4069
+ }
4070
+ async function resolveBaseBranch(options) {
4071
+ if (options.defaultBaseBranch)
4072
+ return options.defaultBaseBranch;
4073
+ if (options.mode === "dev") {
4074
+ return detectHeadBranch(options.detectFrom ?? process.cwd());
4075
+ }
4076
+ return "main";
4077
+ }
4009
4078
  async function isStaleByMtime(dir, cachedAt) {
4010
4079
  let entries;
4011
4080
  try {
@@ -4027,6 +4096,11 @@ async function isStaleByMtime(dir, cachedAt) {
4027
4096
  }
4028
4097
  return false;
4029
4098
  }
4099
+ async function schemaCacheDir(branchRoot) {
4100
+ const gitDir = path82.join(branchRoot, ".git");
4101
+ const isClone = await fs72.stat(gitDir).then((stat) => stat.isDirectory(), () => false);
4102
+ return isClone ? path82.join(gitDir, "canopycms") : path82.join(branchRoot, CANOPY_META_DIR);
4103
+ }
4030
4104
  async function bumpContentIndexGeneration(root) {
4031
4105
  return bumpResourceGeneration(root, RESOURCE);
4032
4106
  }
@@ -4663,7 +4737,7 @@ function resolveEntryLinksInText(text3, idIndex, contentRoot, customResolver) {
4663
4737
  return part.text.replace(ENTRY_LINK_PATTERN, (_match, id, anchor) => {
4664
4738
  const location = idIndex.findById(id);
4665
4739
  if (!location || location.type !== "entry" || !location.collection || !location.slug) {
4666
- log22.warn("resolve", `Entry link target not found: entry:${id}`);
4740
+ log3.warn("resolve", `Entry link target not found: entry:${id}`);
4667
4741
  return anchor ?? "#";
4668
4742
  }
4669
4743
  let url;
@@ -5118,7 +5192,7 @@ async function withOccFileLock(filePath, fn) {
5118
5192
  // Our critical sections are short and idempotent-on-conflict (OCC
5119
5193
  // verify inside); log and let the section finish.
5120
5194
  onCompromised: (err) => {
5121
- log4.warn("lock", `Lock compromised mid-hold for ${filePath}`, {
5195
+ log5.warn("lock", `Lock compromised mid-hold for ${filePath}`, {
5122
5196
  error: getErrorMessage3(err)
5123
5197
  });
5124
5198
  }
@@ -5140,43 +5214,10 @@ async function withOccFileLock(filePath, fn) {
5140
5214
  await release().catch((err) => {
5141
5215
  if (isNodeError3(err) && err.code === "ENOENT")
5142
5216
  return;
5143
- log4.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage3(err) });
5217
+ log5.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage3(err) });
5144
5218
  });
5145
5219
  }
5146
5220
  }
5147
- function isNetworkRemoteUrl(url) {
5148
- if (/^file:\/\//i.test(url))
5149
- return false;
5150
- if (NETWORK_SCHEME_PATTERN.test(url))
5151
- return true;
5152
- if (TRANSPORT_HELPER_PATTERN.test(url))
5153
- return true;
5154
- if (LEADING_OPTION_PATTERN.test(url))
5155
- return true;
5156
- if (WINDOWS_DRIVE_PATTERN.test(url))
5157
- return false;
5158
- return COLON_BEFORE_SLASH_PATTERN.test(url);
5159
- }
5160
- function isMissingRemoteRefFailure(message) {
5161
- return MISSING_REMOTE_REF_REASONS.some((reason) => message.includes(reason));
5162
- }
5163
- async function detectHeadBranch(repoRoot, fallback = "main") {
5164
- try {
5165
- const git = simpleGit3({ baseDir: repoRoot });
5166
- const head = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
5167
- return head && head !== "HEAD" ? head : fallback;
5168
- } catch {
5169
- return fallback;
5170
- }
5171
- }
5172
- async function resolveBaseBranch(options) {
5173
- if (options.defaultBaseBranch)
5174
- return options.defaultBaseBranch;
5175
- if (options.mode === "dev") {
5176
- return detectHeadBranch(options.detectFrom ?? process.cwd());
5177
- }
5178
- return "main";
5179
- }
5180
5221
  function gitChildEnv(overrides) {
5181
5222
  const env = {};
5182
5223
  for (const [key, value] of Object.entries(process.env)) {
@@ -5197,13 +5238,13 @@ async function ensureGitExcludePattern(repoPath, pattern) {
5197
5238
  }
5198
5239
  const lines = content.split("\n");
5199
5240
  if (lines.some((line) => line.trim() === pattern)) {
5200
- log5.debug("git", "Pattern already in .git/info/exclude", { pattern });
5241
+ log6.debug("git", "Pattern already in .git/info/exclude", { pattern });
5201
5242
  return;
5202
5243
  }
5203
5244
  const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
5204
5245
  const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
5205
5246
  await fs16.writeFile(excludePath, newContent, "utf-8");
5206
- log5.debug("git", "Added pattern to .git/info/exclude", { pattern });
5247
+ log6.debug("git", "Added pattern to .git/info/exclude", { pattern });
5207
5248
  }
5208
5249
  async function loadOrCreateBranchContext(options) {
5209
5250
  if (readsFromCheckout(options.config)) {
@@ -5269,7 +5310,7 @@ async function listEntries(branchRoot, flatSchema, contentRootName, options, vis
5269
5310
  const resolver = options?.resolveReferences ? createReferenceResolver(branchRoot, flatSchema, contentRootName) : null;
5270
5311
  const collectionResults = await Promise.all(collections.map(async (collection) => {
5271
5312
  const entries = await listCollectionEntries(branchRoot, collection, (file) => skippedFiles.push(file));
5272
- const visible = shouldInclude ? entries.filter((e) => shouldInclude(e.physicalPath)) : entries;
5313
+ const visible = shouldInclude ? entries.filter((e) => shouldInclude(e.logicalPath)) : entries;
5273
5314
  const resolved = resolver ? await resolveCollectionItemReferences(visible, collection, resolver) : visible;
5274
5315
  return resolved.map((entry) => ({ entry, collection }));
5275
5316
  }));
@@ -5751,7 +5792,7 @@ async function generateAIContentCLI(options) {
5751
5792
  console.log(` Output: ${result.outputDir}
5752
5793
  `);
5753
5794
  }
5754
- var __defProp3, __getOwnPropNames3, __esm3, __export3, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize2, ROOT_COLLECTION_ID, init_types3, normalizePathValue, flattenSchema, init_flatten2, primitiveFieldTypes2, fieldTypes2, init_types22, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, assetMountUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env2, forEachReferenceField, init_validation2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned22, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, permissionTargetSchema2, pathPermissionSchema2, init_permissions2, init_helpers2, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, EVIDENCE_MAX_DEPTH, locks, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep, DEFAULT_SEO_FIELD_NAMES, SEO_GROUP_FIELDS, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, LOG_LEVELS2, DebugLogger2, testLogger2, log2, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_GENERATION_RESOURCE, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, FLAT_CELL_TYPES, log22, BASE58_CHAR, ENTRY_LINK_PATTERN, BRANCH_META_DIR, BRANCH_META_FILE, BranchMetadataCorruptError, branchMetadataFilePath, log3, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, BranchRegistry, log4, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, getBranchMetadataFileManager, loadBranchContext, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, log5, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, GitManager, log6, workspaceInitLocks, BranchWorkspaceManager, log7, normalizePath, readEntryData, createReferenceResolver, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
5795
+ var __defProp3, __getOwnPropNames3, __esm3, __export3, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize2, ROOT_COLLECTION_ID, init_types3, normalizePathValue, flattenSchema, init_flatten2, primitiveFieldTypes2, fieldTypes2, init_types22, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, assetMountUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env2, forEachReferenceField, init_validation2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned22, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, permissionTargetSchema2, pathPermissionSchema2, init_permissions2, init_helpers2, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, EVIDENCE_MAX_DEPTH, locks, PROVISIONING_LOCK_STALE_MS, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep, CONTENT_WRITE_LOCK_STALE_MS, DEFAULT_SEO_FIELD_NAMES, SEO_GROUP_FIELDS, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, LOG_LEVELS2, DebugLogger2, testLogger2, log2, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log22, 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, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, FLAT_CELL_TYPES, log3, BASE58_CHAR, ENTRY_LINK_PATTERN, BRANCH_META_DIR, BRANCH_META_FILE, BranchMetadataCorruptError, branchMetadataFilePath, log4, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, BranchRegistry, log5, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, getBranchMetadataFileManager, loadBranchContext, log6, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, GitManager, log7, workspaceInitLocks, BranchWorkspaceManager, log8, normalizePath, readEntryData, createReferenceResolver, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
5755
5796
  var init_generate_ai_content = __esm({
5756
5797
  "dist/cli/generate-ai-content.js"() {
5757
5798
  "use strict";
@@ -5975,7 +6016,8 @@ var init_generate_ai_content = __esm({
5975
6016
  });
5976
6017
  objectFieldSchema2 = fieldBaseSchema2.extend({
5977
6018
  type: z22.literal("object"),
5978
- fields: z22.array(z22.lazy(() => fieldHolder2[0])).min(1)
6019
+ fields: z22.array(z22.lazy(() => fieldHolder2[0])).min(1),
6020
+ itemTitleField: z22.string().min(1).optional()
5979
6021
  });
5980
6022
  inlineGroupFieldSchema2 = z22.object({
5981
6023
  type: z22.literal("group"),
@@ -6149,6 +6191,9 @@ var init_generate_ai_content = __esm({
6149
6191
  defaultRemoteUrl: defaultRemoteUrlSchema2.optional(),
6150
6192
  gitBotAuthorName: gitBotAuthorNameSchema2,
6151
6193
  gitBotAuthorEmail: gitBotAuthorEmailSchema2,
6194
+ // Unset reads as on for Edited-by and off for Co-authored-by (services.ts submitBranch).
6195
+ gitEditedByTrailers: z62.boolean().optional(),
6196
+ gitCoAuthoredByTrailers: z62.boolean().optional(),
6152
6197
  githubTokenEnvVar: githubTokenEnvVarSchema2.optional(),
6153
6198
  // Required by design (follow-up to SEC-C1): a prod deploy that omits `mode` must fail
6154
6199
  // validation loudly rather than silently running header-trusting dev auth semantics.
@@ -6160,7 +6205,6 @@ var init_generate_ai_content = __esm({
6160
6205
  // Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
6161
6206
  allowNetworkRemoteInProd: z62.boolean().optional(),
6162
6207
  settingsBranch: z62.string().optional(),
6163
- autoCreateSettingsPR: z62.boolean().optional(),
6164
6208
  // `.optional()`, NOT bare `deploymentNameSchema`: its own `.default('prod')` would make
6165
6209
  // `parse(undefined)` resolve to 'prod' instead of staying `undefined`, collapsing the
6166
6210
  // env > config > modeDefault precedence chain `resolveDeploymentName`
@@ -6377,9 +6421,6 @@ var init_generate_ai_content = __esm({
6377
6421
  throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
6378
6422
  }
6379
6423
  }
6380
- shouldCreateSettingsPR(config) {
6381
- return config.autoCreateSettingsPR ?? true;
6382
- }
6383
6424
  };
6384
6425
  DevStrategy2 = class extends DevClientSafeStrategy2 {
6385
6426
  getWorkspaceRoot(sourceRoot) {
@@ -6426,9 +6467,6 @@ var init_generate_ai_content = __esm({
6426
6467
  }
6427
6468
  validateConfig(_config) {
6428
6469
  }
6429
- shouldCreateSettingsPR(_config) {
6430
- return false;
6431
- }
6432
6470
  };
6433
6471
  strategyCache2 = /* @__PURE__ */ new Map();
6434
6472
  }
@@ -6458,6 +6496,7 @@ var init_generate_ai_content = __esm({
6458
6496
  locks = /* @__PURE__ */ new Map();
6459
6497
  init_logger2();
6460
6498
  init_logger2();
6499
+ PROVISIONING_LOCK_STALE_MS = 9e4;
6461
6500
  META_DIR = ".canopy-meta";
6462
6501
  CONTENT_WRITE_LOCK_NAME = "content-write.lock";
6463
6502
  DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2e3;
@@ -6470,6 +6509,7 @@ var init_generate_ai_content = __esm({
6470
6509
  sleep = (ms) => new Promise((resolve2) => {
6471
6510
  setTimeout(resolve2, ms);
6472
6511
  });
6512
+ CONTENT_WRITE_LOCK_STALE_MS = 3e4;
6473
6513
  init_sanitize_href2();
6474
6514
  DEFAULT_SEO_FIELD_NAMES = {
6475
6515
  title: "metaTitle",
@@ -6915,6 +6955,10 @@ var init_generate_ai_content = __esm({
6915
6955
  throw new Error(errorMsg);
6916
6956
  }
6917
6957
  }
6958
+ /**
6959
+ * Keyed by label alone, so two overlapping timers with one label overwrite each other;
6960
+ * anything that can run concurrently uses {@link timed}.
6961
+ */
6918
6962
  time(label) {
6919
6963
  this.timers.set(label, Date.now());
6920
6964
  }
@@ -6929,12 +6973,16 @@ var init_generate_ai_content = __esm({
6929
6973
  this.debug(category, `${label} completed`, { durationMs: duration });
6930
6974
  return duration;
6931
6975
  }
6976
+ /**
6977
+ * Log `<label> completed {durationMs}` once `fn` settles. The start time is local to the
6978
+ * call, so concurrent spans with the same label on one logger never overwrite each other.
6979
+ */
6932
6980
  async timed(category, label, fn) {
6933
- this.time(label);
6981
+ const start = Date.now();
6934
6982
  try {
6935
6983
  return await fn();
6936
6984
  } finally {
6937
- this.timeEnd(category, label);
6985
+ this.debug(category, `${label} completed`, { durationMs: Date.now() - start });
6938
6986
  }
6939
6987
  }
6940
6988
  };
@@ -7059,8 +7107,18 @@ var init_generate_ai_content = __esm({
7059
7107
  email: "static-deploy@canopycms",
7060
7108
  name: "Static Deploy"
7061
7109
  });
7110
+ storage = new AsyncLocalStorage();
7111
+ log22 = createDebugLogger2({ prefix: "CanopyCMS" });
7112
+ NETWORK_SCHEME_PATTERN = /^(https?|ssh|git):\/\//i;
7113
+ TRANSPORT_HELPER_PATTERN = /^[a-z][a-z0-9+.-]*::/i;
7114
+ LEADING_OPTION_PATTERN = /^-/;
7115
+ WINDOWS_DRIVE_PATTERN = /^[a-zA-Z]:[\\/]/;
7116
+ COLON_BEFORE_SLASH_PATTERN = /^[^/\\\s]+:/;
7117
+ MISSING_REMOTE_REF_REASONS = ["couldn't find remote ref", "Couldn't find remote ref"];
7118
+ CANOPY_META_DIR = ".canopy-meta";
7062
7119
  SCHEMA_CACHE_VERSION = 3;
7063
7120
  MTIME_CHECK_DEBOUNCE_MS = 1e3;
7121
+ SCHEMA_CACHE_FILE = "schema-cache.json";
7064
7122
  SCHEMA_GENERATION_RESOURCE = "schema";
7065
7123
  BranchSchemaCache = class {
7066
7124
  constructor(mode = "prod") {
@@ -7070,7 +7128,7 @@ var init_generate_ai_content = __esm({
7070
7128
  /**
7071
7129
  * Whether to skip the on-disk cache for this branchRoot.
7072
7130
  *
7073
- * Never write `.canopy-meta/` at the project root, whichever entrypoint
7131
+ * Never write a schema cache at the project root, whichever entrypoint
7074
7132
  * produced the cwd branchRoot. branchRoot equals process.cwd() only in the
7075
7133
  * synthetic contexts static deployments and build phases use; a real branch
7076
7134
  * root is always nested under the workspace.
@@ -7080,7 +7138,7 @@ var init_generate_ai_content = __esm({
7080
7138
  }
7081
7139
  /** Get schema for a branch, from the cache when fresh, else resolved fresh. */
7082
7140
  async getSchema(branchRoot, entrySchemaRegistry, contentRootName = "content") {
7083
- return this.loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName);
7141
+ return timeRequestPhase("schema", () => this.loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName));
7084
7142
  }
7085
7143
  /**
7086
7144
  * Resolve the schema from disk. Protected rather than a direct resolveSchema
@@ -7094,8 +7152,7 @@ var init_generate_ai_content = __esm({
7094
7152
  const contentRoot = path82.join(branchRoot, contentRootName);
7095
7153
  const skipDiskCache = this.skipDiskCache(branchRoot);
7096
7154
  if (!skipDiskCache) {
7097
- const cacheDir = path82.join(branchRoot, ".canopy-meta");
7098
- const cachePath = path82.join(cacheDir, "schema-cache.json");
7155
+ const cachePath = path82.join(await schemaCacheDir(branchRoot), SCHEMA_CACHE_FILE);
7099
7156
  let cacheData = null;
7100
7157
  try {
7101
7158
  const cacheContent = await fs72.readFile(cachePath, "utf-8");
@@ -7137,7 +7194,7 @@ var init_generate_ai_content = __esm({
7137
7194
  const { skipDiskCache } = options;
7138
7195
  const contentRoot = path82.join(branchRoot, contentRootName);
7139
7196
  const read = skipDiskCache ? null : await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
7140
- const result = await this.resolveFresh(contentRoot, entrySchemaRegistry);
7197
+ const result = await timeRequestPhase("resolve", () => this.resolveFresh(contentRoot, entrySchemaRegistry));
7141
7198
  if (!isValidSchema(result.schema)) {
7142
7199
  throw new Error(`No schema found in ${contentRoot}. Create .collection.json files with references to field schemas defined in your entry schema registry.`);
7143
7200
  }
@@ -7148,9 +7205,9 @@ var init_generate_ai_content = __esm({
7148
7205
  }
7149
7206
  const flatSchema = flattenSchema(result.schema, contentRootName);
7150
7207
  if (!skipDiskCache) {
7151
- const cacheDir = path82.join(branchRoot, ".canopy-meta");
7152
- const cachePath = path82.join(cacheDir, "schema-cache.json");
7153
- await fs72.unlink(path82.join(cacheDir, "schema-cache.stale")).catch(() => {
7208
+ const cacheDir = await schemaCacheDir(branchRoot);
7209
+ const cachePath = path82.join(cacheDir, SCHEMA_CACHE_FILE);
7210
+ await fs72.unlink(path82.join(branchRoot, CANOPY_META_DIR, "schema-cache.stale")).catch(() => {
7154
7211
  });
7155
7212
  if (read && read.ok) {
7156
7213
  const newCache = {
@@ -7643,6 +7700,7 @@ var init_generate_ai_content = __esm({
7643
7700
  return {
7644
7701
  absolutePath: resolved,
7645
7702
  relativePath: path11.relative(this.root, resolved),
7703
+ logicalPath: entryLogicalPath(schemaItem.logicalPath, safeSlug),
7646
7704
  id,
7647
7705
  entryTypeName: finalEntryTypeName,
7648
7706
  existed
@@ -8337,7 +8395,7 @@ var init_generate_ai_content = __esm({
8337
8395
  "reference",
8338
8396
  "image"
8339
8397
  ]);
8340
- log22 = createDebugLogger2({ prefix: "EntryLinks" });
8398
+ log3 = createDebugLogger2({ prefix: "EntryLinks" });
8341
8399
  BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
8342
8400
  ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
8343
8401
  BRANCH_META_DIR = ".canopy-meta";
@@ -8352,7 +8410,7 @@ var init_generate_ai_content = __esm({
8352
8410
  };
8353
8411
  branchMetadataFilePath = (branchRoot) => path13.join(path13.resolve(branchRoot), BRANCH_META_DIR, BRANCH_META_FILE);
8354
8412
  init_logger2();
8355
- log3 = createDebugLogger2({ prefix: "BranchRegistry" });
8413
+ log4 = createDebugLogger2({ prefix: "BranchRegistry" });
8356
8414
  REGISTRY_FILE = "branches.json";
8357
8415
  REGISTRY_STALE_FILE = "branches.stale.json";
8358
8416
  REGISTRY_TEMP_FILE = "branches.tmp.json";
@@ -8432,7 +8490,7 @@ var init_generate_ai_content = __esm({
8432
8490
  });
8433
8491
  await this.regenerate();
8434
8492
  } catch (err) {
8435
- log3.warn("invalidate", "Eager regeneration after invalidate() failed", {
8493
+ log4.warn("invalidate", "Eager regeneration after invalidate() failed", {
8436
8494
  error: getErrorMessage3(err)
8437
8495
  });
8438
8496
  }
@@ -8517,7 +8575,7 @@ var init_generate_ai_content = __esm({
8517
8575
  return branches;
8518
8576
  }
8519
8577
  };
8520
- log4 = createDebugLogger2({ prefix: "OccJsonWrite" });
8578
+ log5 = createDebugLogger2({ prefix: "OccJsonWrite" });
8521
8579
  OccWriteConflictError = class extends Error {
8522
8580
  constructor(message = "Concurrent modification detected") {
8523
8581
  super(message);
@@ -8687,13 +8745,7 @@ var init_generate_ai_content = __esm({
8687
8745
  };
8688
8746
  };
8689
8747
  init_operating_mode();
8690
- NETWORK_SCHEME_PATTERN = /^(https?|ssh|git):\/\//i;
8691
- TRANSPORT_HELPER_PATTERN = /^[a-z][a-z0-9+.-]*::/i;
8692
- LEADING_OPTION_PATTERN = /^-/;
8693
- WINDOWS_DRIVE_PATTERN = /^[a-zA-Z]:[\\/]/;
8694
- COLON_BEFORE_SLASH_PATTERN = /^[^/\\\s]+:/;
8695
- MISSING_REMOTE_REF_REASONS = ["couldn't find remote ref", "Couldn't find remote ref"];
8696
- log5 = createDebugLogger2({ prefix: "GitManager" });
8748
+ log6 = createDebugLogger2({ prefix: "GitManager" });
8697
8749
  GIT_ENV_PASSTHROUGH = /^(PATH|HOME|USER|LANG|LC_[A-Z]+|TZ|TMPDIR|GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL|DATE)|GIT_TERMINAL_PROMPT|GIT_TRACE[0-9A-Z_]*)$/;
8698
8750
  FORCE_C_LOCALE = { LC_ALL: "C", LANG: "C" };
8699
8751
  remoteInitLocks = /* @__PURE__ */ new Map();
@@ -8714,6 +8766,13 @@ var init_generate_ai_content = __esm({
8714
8766
  this.name = "GitRemoteRefMissingError";
8715
8767
  }
8716
8768
  };
8769
+ RemoteNotReadyError = class extends Error {
8770
+ constructor(expectedRemotePath) {
8771
+ super(`CanopyCMS: no git remote is available yet. defaultRemoteUrl (or CANOPYCMS_REMOTE_URL) is not set and the CMS worker has not created ${expectedRemotePath}; the worker may still be starting.`);
8772
+ this.expectedRemotePath = expectedRemotePath;
8773
+ this.name = "RemoteNotReadyError";
8774
+ }
8775
+ };
8717
8776
  GitManager = class _GitManager {
8718
8777
  constructor(options, gitOptions) {
8719
8778
  this.repoPath = path17.resolve(options.repoPath);
@@ -8724,14 +8783,14 @@ var init_generate_ai_content = __esm({
8724
8783
  this.git.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path17.dirname(this.repoPath) }));
8725
8784
  }
8726
8785
  static async cloneRepo(remoteUrl, targetPath, baseBranch = "main") {
8727
- log5.debug("git", "Cloning repository", {
8786
+ log6.debug("git", "Cloning repository", {
8728
8787
  remoteUrl,
8729
8788
  targetPath,
8730
8789
  baseBranch
8731
8790
  });
8732
8791
  const git = simpleGit22();
8733
8792
  await git.clone(remoteUrl, targetPath, ["--branch", baseBranch, "--single-branch"]);
8734
- log5.debug("git", "Clone complete");
8793
+ log6.debug("git", "Clone complete");
8735
8794
  }
8736
8795
  /**
8737
8796
  * Initializes a local bare git repository to simulate a remote for dev mode,
@@ -8745,15 +8804,15 @@ var init_generate_ai_content = __esm({
8745
8804
  static async ensureLocalSimulatedRemote(options) {
8746
8805
  const existingLock = remoteInitLocks.get(options.remotePath);
8747
8806
  if (existingLock) {
8748
- log5.debug("git", "Waiting for existing remote initialization", {
8807
+ log6.debug("git", "Waiting for existing remote initialization", {
8749
8808
  remotePath: options.remotePath
8750
8809
  });
8751
8810
  await existingLock;
8752
8811
  }
8753
- const lockPromise = log5.timed("git", "ensureLocalSimulatedRemote", async () => {
8812
+ const lockPromise = log6.timed("git", "ensureLocalSimulatedRemote", async () => {
8754
8813
  let releaseLock;
8755
8814
  try {
8756
- log5.debug("git", "Initializing local simulated remote", {
8815
+ log6.debug("git", "Initializing local simulated remote", {
8757
8816
  remotePath: options.remotePath,
8758
8817
  baseBranch: options.baseBranch
8759
8818
  });
@@ -8768,7 +8827,7 @@ var init_generate_ai_content = __esm({
8768
8827
  throw err;
8769
8828
  }
8770
8829
  if (remoteExists && await _GitManager.bareRemoteHasBranch(options.remotePath, options.baseBranch)) {
8771
- log5.debug("git", "Remote already has base branch, skipping");
8830
+ log6.debug("git", "Remote already has base branch, skipping");
8772
8831
  return;
8773
8832
  }
8774
8833
  let gitRoot = options.sourcePath;
@@ -8787,8 +8846,8 @@ var init_generate_ai_content = __esm({
8787
8846
  }
8788
8847
  let hasCommits = false;
8789
8848
  try {
8790
- const log82 = await sourceGit.log(["-1"]);
8791
- hasCommits = log82.total > 0;
8849
+ const log92 = await sourceGit.log(["-1"]);
8850
+ hasCommits = log92.total > 0;
8792
8851
  } catch {
8793
8852
  hasCommits = false;
8794
8853
  }
@@ -8800,12 +8859,12 @@ var init_generate_ai_content = __esm({
8800
8859
  throw new Error(`Cannot initialize local simulated remote: base branch '${options.baseBranch}' does not exist locally. Please checkout '${options.baseBranch}' first or provide an explicit remoteUrl.`);
8801
8860
  }
8802
8861
  if (remoteExists) {
8803
- log5.debug("git", "Existing remote is missing base branch \u2014 pushing it from source", {
8862
+ log6.debug("git", "Existing remote is missing base branch \u2014 pushing it from source", {
8804
8863
  remotePath: options.remotePath,
8805
8864
  baseBranch: options.baseBranch
8806
8865
  });
8807
8866
  } else {
8808
- log5.debug("git", "Creating bare remote repository");
8867
+ log6.debug("git", "Creating bare remote repository");
8809
8868
  await simpleGit22().raw([
8810
8869
  "init",
8811
8870
  "--bare",
@@ -8819,13 +8878,13 @@ var init_generate_ai_content = __esm({
8819
8878
  baseBranch: options.baseBranch,
8820
8879
  subdirectory: options.subdirectory
8821
8880
  });
8822
- log5.debug("git", "Remote initialization complete");
8881
+ log6.debug("git", "Remote initialization complete");
8823
8882
  } finally {
8824
8883
  if (releaseLock) {
8825
8884
  try {
8826
8885
  await releaseLock();
8827
8886
  } catch (err) {
8828
- log5.debug("git", "Failed to release remote-init lock", { err });
8887
+ log6.debug("git", "Failed to release remote-init lock", { err });
8829
8888
  }
8830
8889
  }
8831
8890
  remoteInitLocks.delete(options.remotePath);
@@ -9060,7 +9119,7 @@ var init_generate_ai_content = __esm({
9060
9119
  try {
9061
9120
  const stat = await fs16.stat(config.autoDetectRemotePath);
9062
9121
  if (stat.isDirectory()) {
9063
- log5.debug("git", "Auto-detected local remote", {
9122
+ log6.debug("git", "Auto-detected local remote", {
9064
9123
  path: config.autoDetectRemotePath
9065
9124
  });
9066
9125
  return config.autoDetectRemotePath;
@@ -9124,7 +9183,7 @@ var init_generate_ai_content = __esm({
9124
9183
  try {
9125
9184
  const stat = await fs16.stat(gitPath);
9126
9185
  if (stat.isDirectory()) {
9127
- log5.debug("git", "Removing corrupt .git directory", {
9186
+ log6.debug("git", "Removing corrupt .git directory", {
9128
9187
  workspacePath: options.workspacePath
9129
9188
  });
9130
9189
  await fs16.rm(gitPath, { recursive: true });
@@ -9145,6 +9204,10 @@ var init_generate_ai_content = __esm({
9145
9204
  allowNetworkRemoteInProd: options.allowNetworkRemoteInProd
9146
9205
  });
9147
9206
  if (!remoteUrl) {
9207
+ const { operatingStrategy: operatingStrategy22 } = await Promise.resolve().then(() => (init_operating_mode(), operating_mode_exports));
9208
+ const { autoDetectRemotePath } = operatingStrategy22(options.mode).getRemoteUrlConfig();
9209
+ if (autoDetectRemotePath)
9210
+ throw new RemoteNotReadyError(autoDetectRemotePath);
9148
9211
  throw new Error("CanopyCMS: defaultRemoteUrl (or CANOPYCMS_REMOTE_URL) is required to initialize workspace");
9149
9212
  }
9150
9213
  try {
@@ -9168,7 +9231,7 @@ var init_generate_ai_content = __esm({
9168
9231
  await git.git.addConfig("canopycms.managed", "true");
9169
9232
  await git.git.addConfig("user.name", options.gitBotAuthorName);
9170
9233
  await git.git.addConfig("user.email", options.gitBotAuthorEmail);
9171
- log5.debug("git", "Marked workspace as CanopyCMS-managed", {
9234
+ log6.debug("git", "Marked workspace as CanopyCMS-managed", {
9172
9235
  workspacePath: options.workspacePath
9173
9236
  });
9174
9237
  if (!justCloned) {
@@ -9351,6 +9414,10 @@ var init_generate_ai_content = __esm({
9351
9414
  const fileArray = Array.isArray(files) ? files : [files];
9352
9415
  await this.git.add(fileArray);
9353
9416
  }
9417
+ /** Stage every working-tree change except canopycms's own state. See {@link stageAllExceptCanopyState}. */
9418
+ async addAllExceptCanopyState() {
9419
+ await stageAllExceptCanopyState(this.git);
9420
+ }
9354
9421
  async commit(message) {
9355
9422
  await this.git.commit(message);
9356
9423
  }
@@ -9382,12 +9449,12 @@ var init_generate_ai_content = __esm({
9382
9449
  * ("couldn't find remote ref"); that counts as "ahead", not an error.
9383
9450
  */
9384
9451
  async hasUnpushedCommits(branch) {
9385
- const target = branch ?? await this.git.revparse(["--abbrev-ref", "--end-of-options", "HEAD"]);
9386
- const localSha = (await this.git.revparse(["--end-of-options", target])).trim();
9452
+ const target = branch ?? await this.git.revparse(["--verify", "--abbrev-ref", "--end-of-options", "HEAD"]);
9453
+ const localSha = (await this.git.revparse(["--verify", "--end-of-options", target])).trim();
9387
9454
  let fetchedTip;
9388
9455
  try {
9389
9456
  await this.git.raw(["fetch", "--end-of-options", this.remote, target]);
9390
- fetchedTip = (await this.git.revparse(["--end-of-options", "FETCH_HEAD"])).trim();
9457
+ fetchedTip = (await this.git.revparse(["--verify", "--end-of-options", "FETCH_HEAD"])).trim();
9391
9458
  } catch {
9392
9459
  return true;
9393
9460
  }
@@ -9396,6 +9463,25 @@ var init_generate_ai_content = __esm({
9396
9463
  const aheadCount = (await this.git.raw(["rev-list", "--count", `${fetchedTip}..${localSha}`])).trim();
9397
9464
  return aheadCount !== "0";
9398
9465
  }
9466
+ /**
9467
+ * Repo-relative paths that differ between HEAD and its merge base with the
9468
+ * base branch: everything the branch changes, across all of its commits.
9469
+ * Diffs against the just-fetched base tip pinned to a SHA — the pullBaseInner
9470
+ * constraint.
9471
+ */
9472
+ async listChangedPathsSinceBase() {
9473
+ await this.git.raw(["fetch", "--end-of-options", this.remote, this.baseBranch]);
9474
+ const baseTip = (await this.git.revparse(["--verify", "--end-of-options", "FETCH_HEAD"])).trim();
9475
+ const output = await this.git.raw([
9476
+ "diff",
9477
+ "--name-only",
9478
+ "-z",
9479
+ "--no-renames",
9480
+ `${baseTip}...HEAD`,
9481
+ "--"
9482
+ ]);
9483
+ return output.split("\0").filter((p5) => p5.length > 0);
9484
+ }
9399
9485
  async ensureAuthor(author) {
9400
9486
  const config = await this.git.listConfig();
9401
9487
  const isManaged = config.all["canopycms.managed"] === "true";
@@ -9462,10 +9548,10 @@ var init_generate_ai_content = __esm({
9462
9548
  }
9463
9549
  }
9464
9550
  async createOrphanSettingsBranchInner(branchName, initialFiles) {
9465
- log5.debug("git", "Creating orphan settings branch", { branchName });
9551
+ log6.debug("git", "Creating orphan settings branch", { branchName });
9466
9552
  const branches = await this.git.branch();
9467
9553
  if (branches.all.includes(branchName)) {
9468
- log5.debug("git", "Orphan branch already exists", { branchName });
9554
+ log6.debug("git", "Orphan branch already exists", { branchName });
9469
9555
  await this.git.checkout(branchName);
9470
9556
  return;
9471
9557
  }
@@ -9481,17 +9567,17 @@ var init_generate_ai_content = __esm({
9481
9567
  await this.git.add(filePath);
9482
9568
  }
9483
9569
  await this.git.commit("Initialize settings branch", ["--allow-empty"]);
9484
- log5.debug("git", "Orphan settings branch created", { branchName });
9570
+ log6.debug("git", "Orphan settings branch created", { branchName });
9485
9571
  }
9486
9572
  };
9487
- log6 = createDebugLogger2({ prefix: "BranchWorkspace" });
9573
+ log7 = createDebugLogger2({ prefix: "BranchWorkspace" });
9488
9574
  workspaceInitLocks = /* @__PURE__ */ new Map();
9489
9575
  BranchWorkspaceManager = class {
9490
9576
  constructor(config) {
9491
9577
  this.config = config;
9492
9578
  }
9493
9579
  async ensureGitWorkspace(options) {
9494
- return log6.timed("workspace", "ensureGitWorkspace", async () => {
9580
+ return log7.timed("workspace", "ensureGitWorkspace", async () => {
9495
9581
  const existingLock = workspaceInitLocks.get(options.branchRoot);
9496
9582
  if (existingLock) {
9497
9583
  await existingLock;
@@ -9500,11 +9586,11 @@ var init_generate_ai_content = __esm({
9500
9586
  const lockPromise = (async () => {
9501
9587
  let releaseLock;
9502
9588
  try {
9503
- log6.debug("workspace", "Ensuring git workspace", {
9589
+ log7.debug("workspace", "Ensuring git workspace", {
9504
9590
  branchName: options.branchName,
9505
9591
  mode: options.mode
9506
9592
  });
9507
- releaseLock = await acquireProvisioningLock(path18.dirname(options.branchRoot), `.${path18.basename(options.branchRoot)}.init.lock`);
9593
+ releaseLock = await acquireProvisioningLock(path18.dirname(options.branchRoot), branchProvisioningLockName(path18.basename(options.branchRoot)));
9508
9594
  await GitManager.initializeWorkspace({
9509
9595
  workspacePath: options.branchRoot,
9510
9596
  branchName: options.branchName,
@@ -9525,7 +9611,7 @@ var init_generate_ai_content = __esm({
9525
9611
  try {
9526
9612
  await releaseLock();
9527
9613
  } catch (err) {
9528
- log6.debug("workspace", "Failed to release workspace-init lock", { err });
9614
+ log7.debug("workspace", "Failed to release workspace-init lock", { err });
9529
9615
  }
9530
9616
  }
9531
9617
  workspaceInitLocks.delete(options.branchRoot);
@@ -9572,7 +9658,7 @@ var init_generate_ai_content = __esm({
9572
9658
  };
9573
9659
  }
9574
9660
  };
9575
- log7 = createDebugLogger2({ prefix: "ContentListing" });
9661
+ log8 = createDebugLogger2({ prefix: "ContentListing" });
9576
9662
  normalizePath = (root, target) => {
9577
9663
  const result = validateAndNormalizePath(root, target);
9578
9664
  if (!result.valid) {
@@ -9598,7 +9684,7 @@ var init_generate_ai_content = __esm({
9598
9684
  } catch (err) {
9599
9685
  if (isNotFoundError3(err))
9600
9686
  return {};
9601
- log7.warn("readEntryData", `Failed to read entry data from ${filePath}: ${getErrorMessage3(err)}`);
9687
+ log8.warn("readEntryData", `Failed to read entry data from ${filePath}: ${getErrorMessage3(err)}`);
9602
9688
  return {};
9603
9689
  }
9604
9690
  };
@@ -9655,7 +9741,7 @@ var init_generate_ai_content = __esm({
9655
9741
  const relativePath = normalizePath(root, absolutePath);
9656
9742
  const parsed = parseTypedFilename(file.name, entryTypes);
9657
9743
  if (!parsed) {
9658
- log7.warn("listCollectionEntries", `Skipping file with unrecognized filename format: ${file.name} (expected {type}.{slug}.{id}.{ext} with a known entry type and valid 12-char Base58 ID)`);
9744
+ log8.warn("listCollectionEntries", `Skipping file with unrecognized filename format: ${file.name} (expected {type}.{slug}.{id}.{ext} with a known entry type and valid 12-char Base58 ID)`);
9659
9745
  if (looksLikeMalformedEntry(file.name, entryTypes)) {
9660
9746
  onSkip?.({ filename: file.name, collectionPath: collection.logicalPath });
9661
9747
  }
@@ -9670,7 +9756,7 @@ var init_generate_ai_content = __esm({
9670
9756
  readEntryData(absolutePath, format, bodyField)
9671
9757
  ]);
9672
9758
  const item = {
9673
- logicalPath: `${collection.logicalPath}/${slug}`,
9759
+ logicalPath: entryLogicalPath(collection.logicalPath, slug),
9674
9760
  contentId,
9675
9761
  slug,
9676
9762
  collectionPath: collection.logicalPath,
@@ -9697,6 +9783,23 @@ var init_generate_ai_content = __esm({
9697
9783
  }
9698
9784
  });
9699
9785
 
9786
+ // dist/utils/git.js
9787
+ import { simpleGit as simpleGit4 } from "simple-git";
9788
+ function isCanopyInternalPath(repoRelativePath) {
9789
+ return repoRelativePath === CANOPY_META_DIR2 || repoRelativePath.startsWith(`${CANOPY_META_DIR2}/`);
9790
+ }
9791
+ async function stageAllExceptCanopyState2(git) {
9792
+ await git.add(["-A"]);
9793
+ await git.raw(["reset", "-q", "--", CANOPY_META_DIR2]);
9794
+ }
9795
+ var CANOPY_META_DIR2;
9796
+ var init_git = __esm({
9797
+ "dist/utils/git.js"() {
9798
+ "use strict";
9799
+ CANOPY_META_DIR2 = ".canopy-meta";
9800
+ }
9801
+ });
9802
+
9700
9803
  // dist/utils/atomic-write.js
9701
9804
  import fs19 from "node:fs/promises";
9702
9805
  import path23 from "node:path";
@@ -9783,6 +9886,10 @@ var init_debug2 = __esm({
9783
9886
  throw new Error(errorMsg);
9784
9887
  }
9785
9888
  }
9889
+ /**
9890
+ * Keyed by label alone, so two overlapping timers with one label overwrite each other;
9891
+ * anything that can run concurrently uses {@link timed}.
9892
+ */
9786
9893
  time(label) {
9787
9894
  this.timers.set(label, Date.now());
9788
9895
  }
@@ -9797,12 +9904,16 @@ var init_debug2 = __esm({
9797
9904
  this.debug(category, `${label} completed`, { durationMs: duration });
9798
9905
  return duration;
9799
9906
  }
9907
+ /**
9908
+ * Log `<label> completed {durationMs}` once `fn` settles. The start time is local to the
9909
+ * call, so concurrent spans with the same label on one logger never overwrite each other.
9910
+ */
9800
9911
  async timed(category, label, fn) {
9801
- this.time(label);
9912
+ const start = Date.now();
9802
9913
  try {
9803
9914
  return await fn();
9804
9915
  } finally {
9805
- this.timeEnd(category, label);
9916
+ this.debug(category, `${label} completed`, { durationMs: Date.now() - start });
9806
9917
  }
9807
9918
  }
9808
9919
  };
@@ -9827,7 +9938,7 @@ async function bumpResourceGeneration2(root, resource, options) {
9827
9938
  await atomicWriteFile3(resourceGenerationPath2(root, resource), token);
9828
9939
  return token;
9829
9940
  } catch (err) {
9830
- log8.warn("generation", `Failed to bump ${resource} generation for ${root}`, {
9941
+ log9.warn("generation", `Failed to bump ${resource} generation for ${root}`, {
9831
9942
  error: getErrorMessage(err)
9832
9943
  });
9833
9944
  if (options?.mustSucceed)
@@ -9842,7 +9953,7 @@ async function readResourceGeneration2(root, resource) {
9842
9953
  } catch (err) {
9843
9954
  if (isNodeError(err) && err.code === "ENOENT")
9844
9955
  return { ok: true, token: null };
9845
- log8.warn("generation", `Failed to read ${resource} generation for ${root}`, {
9956
+ log9.warn("generation", `Failed to read ${resource} generation for ${root}`, {
9846
9957
  error: getErrorMessage(err)
9847
9958
  });
9848
9959
  return { ok: false };
@@ -9851,14 +9962,14 @@ async function readResourceGeneration2(root, resource) {
9851
9962
  function isGenerationCurrent2(snapshotToken, read) {
9852
9963
  return read.ok && read.token === snapshotToken;
9853
9964
  }
9854
- var log8, META_DIR3;
9965
+ var log9, META_DIR3;
9855
9966
  var init_resource_generation = __esm({
9856
9967
  "dist/resource-generation.js"() {
9857
9968
  "use strict";
9858
9969
  init_atomic_write2();
9859
9970
  init_error();
9860
9971
  init_debug2();
9861
- log8 = createDebugLogger3({ prefix: "ResourceGeneration" });
9972
+ log9 = createDebugLogger3({ prefix: "ResourceGeneration" });
9862
9973
  META_DIR3 = ".canopy-meta";
9863
9974
  }
9864
9975
  });
@@ -10263,7 +10374,8 @@ var init_field3 = __esm({
10263
10374
  });
10264
10375
  objectFieldSchema3 = fieldBaseSchema3.extend({
10265
10376
  type: z10.literal("object"),
10266
- fields: z10.array(z10.lazy(() => fieldHolder3[0])).min(1)
10377
+ fields: z10.array(z10.lazy(() => fieldHolder3[0])).min(1),
10378
+ itemTitleField: z10.string().min(1).optional()
10267
10379
  });
10268
10380
  inlineGroupFieldSchema3 = z10.object({
10269
10381
  type: z10.literal("group"),
@@ -10505,6 +10617,9 @@ var init_config4 = __esm({
10505
10617
  defaultRemoteUrl: defaultRemoteUrlSchema3.optional(),
10506
10618
  gitBotAuthorName: gitBotAuthorNameSchema3,
10507
10619
  gitBotAuthorEmail: gitBotAuthorEmailSchema3,
10620
+ // Unset reads as on for Edited-by and off for Co-authored-by (services.ts submitBranch).
10621
+ gitEditedByTrailers: z14.boolean().optional(),
10622
+ gitCoAuthoredByTrailers: z14.boolean().optional(),
10508
10623
  githubTokenEnvVar: githubTokenEnvVarSchema3.optional(),
10509
10624
  // Required by design (follow-up to SEC-C1): a prod deploy that omits `mode` must fail
10510
10625
  // validation loudly rather than silently running header-trusting dev auth semantics.
@@ -10516,7 +10631,6 @@ var init_config4 = __esm({
10516
10631
  // Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
10517
10632
  allowNetworkRemoteInProd: z14.boolean().optional(),
10518
10633
  settingsBranch: z14.string().optional(),
10519
- autoCreateSettingsPR: z14.boolean().optional(),
10520
10634
  // `.optional()`, NOT bare `deploymentNameSchema`: its own `.default('prod')` would make
10521
10635
  // `parse(undefined)` resolve to 'prod' instead of staying `undefined`, collapsing the
10522
10636
  // env > config > modeDefault precedence chain `resolveDeploymentName`
@@ -10577,6 +10691,18 @@ var init_build_mode = __esm({
10577
10691
  }
10578
10692
  });
10579
10693
 
10694
+ // dist/utils/request-timing.js
10695
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
10696
+ var storage2, log10;
10697
+ var init_request_timing = __esm({
10698
+ "dist/utils/request-timing.js"() {
10699
+ "use strict";
10700
+ init_debug2();
10701
+ storage2 = new AsyncLocalStorage2();
10702
+ log10 = createDebugLogger3({ prefix: "CanopyCMS" });
10703
+ }
10704
+ });
10705
+
10580
10706
  // dist/branch-schema-cache.js
10581
10707
  var SCHEMA_GENERATION_RESOURCE2;
10582
10708
  var init_branch_schema_cache = __esm({
@@ -10587,6 +10713,8 @@ var init_branch_schema_cache = __esm({
10587
10713
  init_entry_type_reference_validator();
10588
10714
  init_build_mode();
10589
10715
  init_resource_generation();
10716
+ init_request_timing();
10717
+ init_git();
10590
10718
  SCHEMA_GENERATION_RESOURCE2 = "schema";
10591
10719
  }
10592
10720
  });
@@ -10614,7 +10742,7 @@ var init_content_index_generation = __esm({
10614
10742
  // dist/sync-core.js
10615
10743
  import fs24 from "node:fs/promises";
10616
10744
  import path26 from "node:path";
10617
- import { simpleGit as simpleGit4 } from "simple-git";
10745
+ import { simpleGit as simpleGit5 } from "simple-git";
10618
10746
  function assertWithinDir(resolved, parent, label) {
10619
10747
  const normalizedResolved = path26.resolve(resolved);
10620
10748
  const normalizedParent = path26.resolve(parent);
@@ -10698,10 +10826,10 @@ async function pushContentToWorkspace(options) {
10698
10826
  throw err;
10699
10827
  }
10700
10828
  try {
10701
- const wsGit = simpleGit4({ baseDir: branchPath });
10702
- await wsGit.add("-A");
10703
- const postStatus = await wsGit.status();
10704
- if (postStatus.files.length === 0) {
10829
+ const wsGit = simpleGit5({ baseDir: branchPath });
10830
+ await stageAllExceptCanopyState2(wsGit);
10831
+ const staged = (await wsGit.status()).files.filter((f) => !isCanopyInternalPath(f.path));
10832
+ if (staged.length === 0) {
10705
10833
  if (baseTag)
10706
10834
  await wsGit.tag(["-f", baseTag]);
10707
10835
  return { fileCount: 0 };
@@ -10709,7 +10837,7 @@ async function pushContentToWorkspace(options) {
10709
10837
  await wsGit.commit(commitMessage ?? "sync: update content from working tree");
10710
10838
  if (baseTag)
10711
10839
  await wsGit.tag(["-f", baseTag]);
10712
- return { fileCount: postStatus.files.length };
10840
+ return { fileCount: staged.length };
10713
10841
  } finally {
10714
10842
  await invalidateBranchContentCaches2(branchPath);
10715
10843
  }
@@ -10720,18 +10848,11 @@ var init_sync_core = __esm({
10720
10848
  "use strict";
10721
10849
  init_content_index_generation();
10722
10850
  init_fs();
10851
+ init_git();
10723
10852
  SYNC_BASE_TAG = "canopycms-sync-base";
10724
10853
  }
10725
10854
  });
10726
10855
 
10727
- // dist/utils/git.js
10728
- import { simpleGit as simpleGit5 } from "simple-git";
10729
- var init_git = __esm({
10730
- "dist/utils/git.js"() {
10731
- "use strict";
10732
- }
10733
- });
10734
-
10735
10856
  // dist/utils/provisioning-lock.js
10736
10857
  import lockfile3 from "proper-lockfile";
10737
10858
  var init_provisioning_lock = __esm({
@@ -10758,15 +10879,15 @@ async function ensureGitExcludePattern2(repoPath, pattern) {
10758
10879
  }
10759
10880
  const lines = content.split("\n");
10760
10881
  if (lines.some((line) => line.trim() === pattern)) {
10761
- log9.debug("git", "Pattern already in .git/info/exclude", { pattern });
10882
+ log11.debug("git", "Pattern already in .git/info/exclude", { pattern });
10762
10883
  return;
10763
10884
  }
10764
10885
  const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
10765
10886
  const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
10766
10887
  await fs25.writeFile(excludePath, newContent, "utf-8");
10767
- log9.debug("git", "Added pattern to .git/info/exclude", { pattern });
10888
+ log11.debug("git", "Added pattern to .git/info/exclude", { pattern });
10768
10889
  }
10769
- var log9;
10890
+ var log11;
10770
10891
  var init_git_manager = __esm({
10771
10892
  "dist/git-manager.js"() {
10772
10893
  "use strict";
@@ -10776,7 +10897,7 @@ var init_git_manager = __esm({
10776
10897
  init_error();
10777
10898
  init_git();
10778
10899
  init_provisioning_lock();
10779
- log9 = createDebugLogger3({ prefix: "GitManager" });
10900
+ log11 = createDebugLogger3({ prefix: "GitManager" });
10780
10901
  }
10781
10902
  });
10782
10903
 
@@ -10993,9 +11114,6 @@ var init_client_unsafe_strategy2 = __esm({
10993
11114
  throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
10994
11115
  }
10995
11116
  }
10996
- shouldCreateSettingsPR(config) {
10997
- return config.autoCreateSettingsPR ?? true;
10998
- }
10999
11117
  };
11000
11118
  DevStrategy3 = class extends DevClientSafeStrategy3 {
11001
11119
  getWorkspaceRoot(sourceRoot) {
@@ -11042,9 +11160,6 @@ var init_client_unsafe_strategy2 = __esm({
11042
11160
  }
11043
11161
  validateConfig(_config) {
11044
11162
  }
11045
- shouldCreateSettingsPR(_config) {
11046
- return false;
11047
- }
11048
11163
  };
11049
11164
  strategyCache3 = /* @__PURE__ */ new Map();
11050
11165
  }
@@ -11157,13 +11272,14 @@ async function syncPush(options) {
11157
11272
  }
11158
11273
  return { fileCount: 0 };
11159
11274
  }
11160
- if (status.files.length > 0 && !options.force) {
11161
- p2.log.warn(`Branch workspace has ${status.files.length} uncommitted change(s) that will be committed to history then overwritten:`);
11162
- for (const file of status.files.slice(0, 10)) {
11275
+ const editorChanges = status.files.filter((f) => !isCanopyInternalPath(f.path));
11276
+ if (editorChanges.length > 0 && !options.force) {
11277
+ p2.log.warn(`Branch workspace has ${editorChanges.length} uncommitted change(s) that will be committed to history then overwritten:`);
11278
+ for (const file of editorChanges.slice(0, 10)) {
11163
11279
  p2.log.warn(` ${file.path}`);
11164
11280
  }
11165
- if (status.files.length > 10) {
11166
- p2.log.warn(` ... and ${status.files.length - 10} more`);
11281
+ if (editorChanges.length > 10) {
11282
+ p2.log.warn(` ... and ${editorChanges.length - 10} more`);
11167
11283
  }
11168
11284
  const confirm5 = await p2.confirm({
11169
11285
  message: "Continue? Editor changes will be preserved in git history.",
@@ -11174,8 +11290,8 @@ async function syncPush(options) {
11174
11290
  return { fileCount: 0 };
11175
11291
  }
11176
11292
  }
11177
- if (status.files.length > 0) {
11178
- await wsGit.add("-A");
11293
+ if (editorChanges.length > 0) {
11294
+ await stageAllExceptCanopyState2(wsGit);
11179
11295
  await wsGit.commit("sync: save editor state before push");
11180
11296
  p2.log.info("Committed editor changes to history before push");
11181
11297
  }
@@ -11312,8 +11428,8 @@ async function syncBoth(options) {
11312
11428
  }
11313
11429
  return { pushed: 0, pulled: 0 };
11314
11430
  }
11315
- if (status.files.length > 0) {
11316
- await wsGit.add("-A");
11431
+ if (status.files.some((f) => !isCanopyInternalPath(f.path))) {
11432
+ await stageAllExceptCanopyState2(wsGit);
11317
11433
  await wsGit.commit("sync: save editor state before merge");
11318
11434
  p2.log.info("Committed editor changes before merge");
11319
11435
  }
@@ -11342,9 +11458,9 @@ async function syncBoth(options) {
11342
11458
  });
11343
11459
  throw err;
11344
11460
  }
11345
- await wsGit.add("-A");
11346
- const incomingStatus = await wsGit.status();
11347
- if (incomingStatus.files.length === 0) {
11461
+ await stageAllExceptCanopyState2(wsGit);
11462
+ const incomingFiles = (await wsGit.status()).files.filter((f) => !isCanopyInternalPath(f.path));
11463
+ if (incomingFiles.length === 0) {
11348
11464
  await wsGit.checkout(currentBranch);
11349
11465
  await wsGit.raw(["branch", "-D", incomingBranch]);
11350
11466
  p2.log.info("No working-tree changes to merge \u2014 pulling editor changes only");
@@ -11385,7 +11501,7 @@ async function syncBoth(options) {
11385
11501
  await wsGit.tag(["-f", SYNC_BASE_TAG]);
11386
11502
  p2.log.success("Merged working-tree changes with editor changes");
11387
11503
  const pullResult = await syncPull({ ...options, branch: branchName, force: true });
11388
- return { pushed: incomingStatus.files.length, pulled: pullResult.fileCount };
11504
+ return { pushed: incomingFiles.length, pulled: pullResult.fileCount };
11389
11505
  } finally {
11390
11506
  await invalidateBranchContentCaches2(branchPath);
11391
11507
  }
@@ -11433,6 +11549,7 @@ var init_sync = __esm({
11433
11549
  "dist/cli/sync.js"() {
11434
11550
  "use strict";
11435
11551
  init_fs();
11552
+ init_git();
11436
11553
  init_sync_core();
11437
11554
  init_content_index_generation();
11438
11555
  init_git_manager();
@@ -11616,7 +11733,7 @@ async function withOccFileLock2(filePath, fn) {
11616
11733
  // Our critical sections are short and idempotent-on-conflict (OCC
11617
11734
  // verify inside); log and let the section finish.
11618
11735
  onCompromised: (err) => {
11619
- log11.warn("lock", `Lock compromised mid-hold for ${filePath}`, {
11736
+ log13.warn("lock", `Lock compromised mid-hold for ${filePath}`, {
11620
11737
  error: getErrorMessage(err)
11621
11738
  });
11622
11739
  }
@@ -11638,17 +11755,17 @@ async function withOccFileLock2(filePath, fn) {
11638
11755
  await release().catch((err) => {
11639
11756
  if (isNodeError(err) && err.code === "ENOENT")
11640
11757
  return;
11641
- log11.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage(err) });
11758
+ log13.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage(err) });
11642
11759
  });
11643
11760
  }
11644
11761
  }
11645
- var log11, OccWriteConflictError2;
11762
+ var log13, OccWriteConflictError2;
11646
11763
  var init_occ_json_write = __esm({
11647
11764
  "dist/utils/occ-json-write.js"() {
11648
11765
  "use strict";
11649
11766
  init_error();
11650
11767
  init_debug2();
11651
- log11 = createDebugLogger3({ prefix: "OccJsonWrite" });
11768
+ log13 = createDebugLogger3({ prefix: "OccJsonWrite" });
11652
11769
  OccWriteConflictError2 = class extends Error {
11653
11770
  constructor(message = "Concurrent modification detected") {
11654
11771
  super(message);
@@ -11698,7 +11815,7 @@ var init_branch_metadata_file = __esm({
11698
11815
  // dist/branch-registry.js
11699
11816
  import fs29 from "node:fs/promises";
11700
11817
  import path33 from "node:path";
11701
- var log12, REGISTRY_FILE2, REGISTRY_STALE_FILE2, REGISTRY_TEMP_FILE2, REGISTRY_VERSION2, RESOURCE4, GET_MISS_REFRESH_MIN_INTERVAL_MS2, BranchRegistry2;
11818
+ var log14, REGISTRY_FILE2, REGISTRY_STALE_FILE2, REGISTRY_TEMP_FILE2, REGISTRY_VERSION2, RESOURCE4, GET_MISS_REFRESH_MIN_INTERVAL_MS2, BranchRegistry2;
11702
11819
  var init_branch_registry = __esm({
11703
11820
  "dist/branch-registry.js"() {
11704
11821
  "use strict";
@@ -11707,7 +11824,7 @@ var init_branch_registry = __esm({
11707
11824
  init_debug2();
11708
11825
  init_logger3();
11709
11826
  init_resource_generation();
11710
- log12 = createDebugLogger3({ prefix: "BranchRegistry" });
11827
+ log14 = createDebugLogger3({ prefix: "BranchRegistry" });
11711
11828
  REGISTRY_FILE2 = "branches.json";
11712
11829
  REGISTRY_STALE_FILE2 = "branches.stale.json";
11713
11830
  REGISTRY_TEMP_FILE2 = "branches.tmp.json";
@@ -11787,7 +11904,7 @@ var init_branch_registry = __esm({
11787
11904
  });
11788
11905
  await this.regenerate();
11789
11906
  } catch (err) {
11790
- log12.warn("invalidate", "Eager regeneration after invalidate() failed", {
11907
+ log14.warn("invalidate", "Eager regeneration after invalidate() failed", {
11791
11908
  error: getErrorMessage(err)
11792
11909
  });
11793
11910
  }