canopycms 0.0.61 → 0.0.63

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 (237) hide show
  1. package/dist/ai/index.d.ts +9 -0
  2. package/dist/ai/index.d.ts.map +1 -1
  3. package/dist/ai/index.js +9 -0
  4. package/dist/ai/index.js.map +1 -1
  5. package/dist/ai/json-to-markdown.js +1 -2
  6. package/dist/ai/json-to-markdown.js.map +1 -1
  7. package/dist/ai/to-plain-text.d.ts +57 -0
  8. package/dist/ai/to-plain-text.d.ts.map +1 -0
  9. package/dist/ai/to-plain-text.js +255 -0
  10. package/dist/ai/to-plain-text.js.map +1 -0
  11. package/dist/ai/transform-components.d.ts.map +1 -1
  12. package/dist/ai/transform-components.js +70 -5
  13. package/dist/ai/transform-components.js.map +1 -1
  14. package/dist/api/__test__/mock-client.d.ts +5 -1
  15. package/dist/api/__test__/mock-client.d.ts.map +1 -1
  16. package/dist/api/__test__/mock-client.js +9 -2
  17. package/dist/api/__test__/mock-client.js.map +1 -1
  18. package/dist/api/admin-branch-health.d.ts +36 -1
  19. package/dist/api/admin-branch-health.d.ts.map +1 -1
  20. package/dist/api/admin-branch-health.js +176 -2
  21. package/dist/api/admin-branch-health.js.map +1 -1
  22. package/dist/api/admin.d.ts +8 -1
  23. package/dist/api/admin.d.ts.map +1 -1
  24. package/dist/api/admin.js.map +1 -1
  25. package/dist/api/branch.d.ts.map +1 -1
  26. package/dist/api/branch.js +16 -2
  27. package/dist/api/branch.js.map +1 -1
  28. package/dist/api/client.d.ts +5 -1
  29. package/dist/api/client.d.ts.map +1 -1
  30. package/dist/api/client.js +6 -0
  31. package/dist/api/client.js.map +1 -1
  32. package/dist/api/content.d.ts +11 -5
  33. package/dist/api/content.d.ts.map +1 -1
  34. package/dist/api/content.js +79 -6
  35. package/dist/api/content.js.map +1 -1
  36. package/dist/api/entries.d.ts +10 -0
  37. package/dist/api/entries.d.ts.map +1 -1
  38. package/dist/api/entries.js +40 -15
  39. package/dist/api/entries.js.map +1 -1
  40. package/dist/api/schema.d.ts +4 -4
  41. package/dist/api/schema.d.ts.map +1 -1
  42. package/dist/api/schema.js +13 -13
  43. package/dist/api/schema.js.map +1 -1
  44. package/dist/assets/transform.d.ts.map +1 -1
  45. package/dist/assets/transform.js +19 -6
  46. package/dist/assets/transform.js.map +1 -1
  47. package/dist/authorization/branch.d.ts +55 -3
  48. package/dist/authorization/branch.d.ts.map +1 -1
  49. package/dist/authorization/branch.js +55 -9
  50. package/dist/authorization/branch.js.map +1 -1
  51. package/dist/authorization/types.d.ts +1 -1
  52. package/dist/authorization/types.d.ts.map +1 -1
  53. package/dist/branch-health.d.ts +13 -0
  54. package/dist/branch-health.d.ts.map +1 -1
  55. package/dist/branch-health.js +23 -0
  56. package/dist/branch-health.js.map +1 -1
  57. package/dist/build-canopy.d.ts +49 -0
  58. package/dist/build-canopy.d.ts.map +1 -0
  59. package/dist/build-canopy.js +51 -0
  60. package/dist/build-canopy.js.map +1 -0
  61. package/dist/cli/cli.js +935 -562
  62. package/dist/cli/generate-ai-content.js +837 -480
  63. package/dist/cli/init.js +25 -7
  64. package/dist/cli/template-files/Dockerfile.cms.template +19 -1
  65. package/dist/cli/template-files/canopycms.config.ts.template +25 -1
  66. package/dist/cli/template-files/cms-stack.ts.template +7 -1
  67. package/dist/client.d.ts +1 -0
  68. package/dist/client.d.ts.map +1 -1
  69. package/dist/client.js.map +1 -1
  70. package/dist/config/schemas/field.d.ts +15 -15
  71. package/dist/config/types.d.ts +2 -2
  72. package/dist/config/types.d.ts.map +1 -1
  73. package/dist/config/types.js +0 -1
  74. package/dist/config/types.js.map +1 -1
  75. package/dist/config/validation.d.ts +9 -0
  76. package/dist/config/validation.d.ts.map +1 -1
  77. package/dist/config/validation.js +14 -0
  78. package/dist/config/validation.js.map +1 -1
  79. package/dist/content-id-index.d.ts +97 -5
  80. package/dist/content-id-index.d.ts.map +1 -1
  81. package/dist/content-id-index.js +170 -18
  82. package/dist/content-id-index.js.map +1 -1
  83. package/dist/content-listing.d.ts +110 -5
  84. package/dist/content-listing.d.ts.map +1 -1
  85. package/dist/content-listing.js +164 -23
  86. package/dist/content-listing.js.map +1 -1
  87. package/dist/content-reader.d.ts +41 -4
  88. package/dist/content-reader.d.ts.map +1 -1
  89. package/dist/content-reader.js +17 -4
  90. package/dist/content-reader.js.map +1 -1
  91. package/dist/content-store.d.ts +91 -8
  92. package/dist/content-store.d.ts.map +1 -1
  93. package/dist/content-store.js +443 -258
  94. package/dist/content-store.js.map +1 -1
  95. package/dist/content-tree.d.ts +31 -2
  96. package/dist/content-tree.d.ts.map +1 -1
  97. package/dist/content-tree.js +22 -5
  98. package/dist/content-tree.js.map +1 -1
  99. package/dist/context.d.ts +30 -10
  100. package/dist/context.d.ts.map +1 -1
  101. package/dist/context.js +55 -4
  102. package/dist/context.js.map +1 -1
  103. package/dist/editor/BranchManager.d.ts.map +1 -1
  104. package/dist/editor/BranchManager.js +18 -6
  105. package/dist/editor/BranchManager.js.map +1 -1
  106. package/dist/editor/CanopyEditor.d.ts.map +1 -1
  107. package/dist/editor/CanopyEditor.js +2 -2
  108. package/dist/editor/CanopyEditor.js.map +1 -1
  109. package/dist/editor/CanopyEditorPage.d.ts +9 -1
  110. package/dist/editor/CanopyEditorPage.d.ts.map +1 -1
  111. package/dist/editor/CanopyEditorPage.js +9 -2
  112. package/dist/editor/CanopyEditorPage.js.map +1 -1
  113. package/dist/editor/Editor.d.ts +9 -1
  114. package/dist/editor/Editor.d.ts.map +1 -1
  115. package/dist/editor/Editor.js +58 -24
  116. package/dist/editor/Editor.js.map +1 -1
  117. package/dist/editor/FormRenderer.d.ts.map +1 -1
  118. package/dist/editor/FormRenderer.js +12 -0
  119. package/dist/editor/FormRenderer.js.map +1 -1
  120. package/dist/editor/admin/SystemHealthPanel.js +6 -2
  121. package/dist/editor/admin/SystemHealthPanel.js.map +1 -1
  122. package/dist/editor/components/EntryCreateModal.d.ts +9 -1
  123. package/dist/editor/components/EntryCreateModal.d.ts.map +1 -1
  124. package/dist/editor/components/EntryCreateModal.js +4 -1
  125. package/dist/editor/components/EntryCreateModal.js.map +1 -1
  126. package/dist/editor/fields/DateTimeField.d.ts +46 -0
  127. package/dist/editor/fields/DateTimeField.d.ts.map +1 -0
  128. package/dist/editor/fields/DateTimeField.js +66 -0
  129. package/dist/editor/fields/DateTimeField.js.map +1 -0
  130. package/dist/editor/fields/NumberField.d.ts +34 -0
  131. package/dist/editor/fields/NumberField.d.ts.map +1 -0
  132. package/dist/editor/fields/NumberField.js +53 -0
  133. package/dist/editor/fields/NumberField.js.map +1 -0
  134. package/dist/editor/fields/NumberListField.d.ts +25 -0
  135. package/dist/editor/fields/NumberListField.d.ts.map +1 -0
  136. package/dist/editor/fields/NumberListField.js +53 -0
  137. package/dist/editor/fields/NumberListField.js.map +1 -0
  138. package/dist/editor/hooks/useBranchManager.d.ts.map +1 -1
  139. package/dist/editor/hooks/useBranchManager.js +115 -26
  140. package/dist/editor/hooks/useBranchManager.js.map +1 -1
  141. package/dist/editor/hooks/useDraftManager.d.ts +8 -0
  142. package/dist/editor/hooks/useDraftManager.d.ts.map +1 -1
  143. package/dist/editor/hooks/useDraftManager.js +261 -21
  144. package/dist/editor/hooks/useDraftManager.js.map +1 -1
  145. package/dist/editor/hooks/useEntryManager.d.ts +21 -0
  146. package/dist/editor/hooks/useEntryManager.d.ts.map +1 -1
  147. package/dist/editor/hooks/useEntryManager.js +25 -2
  148. package/dist/editor/hooks/useEntryManager.js.map +1 -1
  149. package/dist/editor/theme.d.ts +8 -8
  150. package/dist/entry-schema.d.ts +228 -8
  151. package/dist/entry-schema.d.ts.map +1 -1
  152. package/dist/entry-schema.js +124 -0
  153. package/dist/entry-schema.js.map +1 -1
  154. package/dist/git-manager.d.ts +51 -7
  155. package/dist/git-manager.d.ts.map +1 -1
  156. package/dist/git-manager.js +105 -14
  157. package/dist/git-manager.js.map +1 -1
  158. package/dist/http/handler.d.ts.map +1 -1
  159. package/dist/http/handler.js +70 -27
  160. package/dist/http/handler.js.map +1 -1
  161. package/dist/http/router.d.ts.map +1 -1
  162. package/dist/http/router.js +40 -3
  163. package/dist/http/router.js.map +1 -1
  164. package/dist/index.d.ts +1 -0
  165. package/dist/index.d.ts.map +1 -1
  166. package/dist/index.js +7 -0
  167. package/dist/index.js.map +1 -1
  168. package/dist/operating-mode/deployment-name-fixtures.d.ts +29 -0
  169. package/dist/operating-mode/deployment-name-fixtures.d.ts.map +1 -0
  170. package/dist/operating-mode/deployment-name-fixtures.js +55 -0
  171. package/dist/operating-mode/deployment-name-fixtures.js.map +1 -0
  172. package/dist/operating-mode/deployment-name.d.ts +9 -0
  173. package/dist/operating-mode/deployment-name.d.ts.map +1 -1
  174. package/dist/operating-mode/deployment-name.js +9 -1
  175. package/dist/operating-mode/deployment-name.js.map +1 -1
  176. package/dist/operating-mode/index.d.ts +2 -1
  177. package/dist/operating-mode/index.d.ts.map +1 -1
  178. package/dist/operating-mode/index.js +4 -1
  179. package/dist/operating-mode/index.js.map +1 -1
  180. package/dist/operating-mode/mode-env.d.ts +58 -0
  181. package/dist/operating-mode/mode-env.d.ts.map +1 -0
  182. package/dist/operating-mode/mode-env.js +97 -0
  183. package/dist/operating-mode/mode-env.js.map +1 -0
  184. package/dist/resolve-canopy-user.d.ts +34 -0
  185. package/dist/resolve-canopy-user.d.ts.map +1 -0
  186. package/dist/resolve-canopy-user.js +55 -0
  187. package/dist/resolve-canopy-user.js.map +1 -0
  188. package/dist/schema/schema-store.d.ts +53 -2
  189. package/dist/schema/schema-store.d.ts.map +1 -1
  190. package/dist/schema/schema-store.js +92 -21
  191. package/dist/schema/schema-store.js.map +1 -1
  192. package/dist/server.d.ts +60 -0
  193. package/dist/server.d.ts.map +1 -1
  194. package/dist/server.js +58 -0
  195. package/dist/server.js.map +1 -1
  196. package/dist/services.d.ts +1 -2
  197. package/dist/services.d.ts.map +1 -1
  198. package/dist/services.js +42 -12
  199. package/dist/services.js.map +1 -1
  200. package/dist/settings-workspace.d.ts +40 -0
  201. package/dist/settings-workspace.d.ts.map +1 -1
  202. package/dist/settings-workspace.js +139 -151
  203. package/dist/settings-workspace.js.map +1 -1
  204. package/dist/static/index.d.ts +65 -5
  205. package/dist/static/index.d.ts.map +1 -1
  206. package/dist/static/index.js +65 -12
  207. package/dist/static/index.js.map +1 -1
  208. package/dist/static/seo.d.ts +150 -0
  209. package/dist/static/seo.d.ts.map +1 -0
  210. package/dist/static/seo.js +189 -0
  211. package/dist/static/seo.js.map +1 -0
  212. package/dist/types.d.ts +10 -0
  213. package/dist/types.d.ts.map +1 -1
  214. package/dist/utils/content-write-lock.d.ts +127 -0
  215. package/dist/utils/content-write-lock.d.ts.map +1 -0
  216. package/dist/utils/content-write-lock.js +170 -0
  217. package/dist/utils/content-write-lock.js.map +1 -0
  218. package/dist/utils/error.d.ts +1 -1
  219. package/dist/utils/error.js +1 -1
  220. package/dist/utils/git.d.ts +16 -0
  221. package/dist/utils/git.d.ts.map +1 -1
  222. package/dist/utils/git.js +22 -0
  223. package/dist/utils/git.js.map +1 -1
  224. package/dist/utils/sanitize-href.d.ts +74 -3
  225. package/dist/utils/sanitize-href.d.ts.map +1 -1
  226. package/dist/utils/sanitize-href.js +125 -6
  227. package/dist/utils/sanitize-href.js.map +1 -1
  228. package/dist/validation/entry-link-validator.d.ts +1 -1
  229. package/dist/validation/entry-link-validator.js +3 -3
  230. package/dist/validation/entry-link-validator.js.map +1 -1
  231. package/dist/validation/entry-validator.js +1 -1
  232. package/dist/validation/entry-validator.js.map +1 -1
  233. package/dist/worker/cms-worker.d.ts +13 -0
  234. package/dist/worker/cms-worker.d.ts.map +1 -1
  235. package/dist/worker/cms-worker.js +326 -264
  236. package/dist/worker/cms-worker.js.map +1 -1
  237. package/package.json +2 -2
@@ -2,83 +2,41 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import { GitManager } from './git-manager.js';
4
4
  import { createDebugLogger } from './utils/debug.js';
5
- import { getErrorMessage, isFileExistsError } from './utils/error.js';
5
+ import { getErrorMessage } from './utils/error.js';
6
+ import { acquireProvisioningLock } from './utils/provisioning-lock.js';
6
7
  import { RESERVED_SETTINGS_BRANCH_PREFIX } from './paths/index.js';
7
8
  const log = createDebugLogger({ prefix: 'SettingsWorkspace' });
8
9
  // In-memory lock to prevent concurrent workspace initialization within the same process.
9
10
  // Settings only need one lock (not per-branch like content branches).
10
11
  let settingsInitLock = null;
11
- // Stale lock threshold — init should complete well within this window
12
- const LOCK_STALE_MS = 30_000;
12
+ const SETTINGS_INIT_LOCK_DIR = '.settings-init';
13
+ const SETTINGS_INIT_LOCK_NAME = 'lock';
13
14
  /**
14
- * Acquire a file-based lock for cross-process synchronization.
15
- * Uses O_CREAT|O_EXCL (wx flag) for atomic file creation.
16
- * Stale locks (older than LOCK_STALE_MS) are cleaned up automatically.
15
+ * Directory the cross-process init lock is anchored on — a dedicated sibling of
16
+ * the settings root, and NOT the settings root itself.
17
17
  *
18
- * Returns true if lock was acquired, false if another process holds it.
19
- */
20
- async function acquireFileLock(lockPath) {
21
- const lockContent = JSON.stringify({
22
- pid: process.pid,
23
- timestamp: new Date().toISOString(),
24
- });
25
- await fs.mkdir(path.dirname(lockPath), { recursive: true });
26
- try {
27
- const handle = await fs.open(lockPath, 'wx');
28
- await handle.writeFile(lockContent, 'utf-8');
29
- await handle.close();
30
- return true;
31
- }
32
- catch (err) {
33
- if (!isFileExistsError(err))
34
- throw err;
35
- }
36
- // Lock file exists — check if stale
37
- try {
38
- const stat = await fs.stat(lockPath);
39
- const ageMs = Date.now() - stat.mtimeMs;
40
- if (ageMs < LOCK_STALE_MS) {
41
- // Lock is fresh — another process is initializing
42
- return false;
43
- }
44
- // Stale lock — another process likely crashed during init
45
- log.debug('workspace', 'Removing stale settings init lock', { ageMs });
46
- await fs.unlink(lockPath).catch(() => { });
47
- }
48
- catch {
49
- // Lock file vanished between check and stat — try again
50
- }
51
- // Retry lock acquisition after stale cleanup
52
- try {
53
- const handle = await fs.open(lockPath, 'wx');
54
- await handle.writeFile(lockContent, 'utf-8');
55
- await handle.close();
56
- return true;
57
- }
58
- catch (retryErr) {
59
- if (isFileExistsError(retryErr))
60
- return false;
61
- throw retryErr;
62
- }
63
- }
64
- async function releaseFileLock(lockPath) {
65
- await fs.unlink(lockPath).catch(() => { });
66
- }
67
- /**
68
- * Manages settings filesystem workspace and git operations.
18
+ * Two properties this name has to satisfy:
69
19
  *
70
- * Settings are stored separately from content branches:
71
- * - prod/dev: Orphan git branches (no shared history with content)
72
- *
73
- * Unlike BranchWorkspaceManager, this does not:
74
- * - Create or manage metadata files
75
- * - Interact with the branch registry
76
- * - Check for special cases (settings are always settings)
77
- *
78
- * Uses two layers of locking:
79
- * - In-memory Promise lock for within-process serialization (Lambda request lifecycle)
80
- * - File-based lock for cross-process synchronization (multiple Lambda instances on EFS)
20
+ * 1. **It must not pre-create the settings root.** `acquireProvisioningLock`
21
+ * mkdir's its target, and `GitManager.initializeWorkspace` clones INTO the
22
+ * settings root — `git clone` refuses a destination that already has content
23
+ * in it. So the lock lives beside the settings root, never inside it.
24
+ * 2. **It must not alias any other proper-lockfile target.** proper-lockfile
25
+ * keys its module-level bookkeeping (refresh timer + release function) by the
26
+ * TARGET path passed to `lock()`, not by `lockfilePath`, so two live locks in
27
+ * one process that share a target clobber each other's registry entry (see
28
+ * `.claude/future-tasks/proper-lockfile-hazards.md`). This target is a
29
+ * dedicated dot-directory, so it collides with none of the existing targets:
30
+ * the content-branches root and the bare remote's parent (provisioning locks
31
+ * — note the latter IS `path.dirname(settingsRoot)`, which is precisely why
32
+ * this cannot be anchored there: settings init calls into
33
+ * `ensureLocalSimulatedRemote` while holding this lock), `<branchRoot>/.canopy-meta`
34
+ * (content-write lock), `<workspaceRoot>/.tasks` (worker task lock), and the
35
+ * individual JSON file paths used by `withOccFileLock`.
81
36
  */
37
+ export function settingsInitLockTarget(settingsRoot) {
38
+ return path.join(path.dirname(path.resolve(settingsRoot)), SETTINGS_INIT_LOCK_DIR);
39
+ }
82
40
  /**
83
41
  * Whether a settings workspace actually holds settings data yet. Used by the
84
42
  * rename guard to tell an already-populated workspace (refuse) apart from a
@@ -99,6 +57,87 @@ async function settingsFilesPresent(settingsRoot) {
99
57
  }
100
58
  return false;
101
59
  }
60
+ /**
61
+ * Rename guard (settings-branch protection). Refuses to boot when an
62
+ * ALREADY-POPULATED settings workspace would be re-orphaned under a different
63
+ * branch name.
64
+ *
65
+ * Trace of what happens without it: GitManager.initializeWorkspace sees an
66
+ * existing .git (no clone happens) and goes straight to
67
+ * createOrphanSettingsBranch(branchName). If that name isn't already known, git
68
+ * runs `checkout --orphan <name>` + `rm -rf .` + an empty `--allow-empty`
69
+ * commit — orphan branches share no history, so this isn't a "migration", it
70
+ * PERMANENTLY WIPES permissions.json/groups.json with nothing to recover from.
71
+ * That almost always means deploymentName / settingsBranch /
72
+ * CANOPYCMS_DEPLOYMENT_NAME changed on a live deployment (see
73
+ * resolveDeploymentName in operating-mode/deployment-name.ts).
74
+ *
75
+ * What this deliberately does NOT refuse, and why it matters:
76
+ * initializeWorkspace clones at the BASE branch and only creates the orphan
77
+ * branch several steps later (addConfig, resolveRemoteUrl, ensureRemote come in
78
+ * between). If that first init is interrupted — a Lambda timeout on a slow EFS
79
+ * clone, an OOM, a spot interruption — it leaves a perfectly valid repo sitting
80
+ * on the base branch with NO settings files in it. Refusing on "branch differs"
81
+ * alone would then brick the deployment permanently on every subsequent boot, to
82
+ * protect data that was never written. So the refusal additionally requires
83
+ * evidence that this really is a populated settings workspace: either it is
84
+ * checked out on some OTHER settings branch, or the settings files are actually
85
+ * on disk.
86
+ *
87
+ * This function is NEVER gated on holding the init lock — see its two call
88
+ * sites in ensureGitWorkspace and the comment there.
89
+ */
90
+ async function assertSettingsWorkspaceIdentity(options) {
91
+ const repoExists = await GitManager.repoExistsAt(options.settingsRoot);
92
+ if (!repoExists)
93
+ return;
94
+ let currentBranch;
95
+ let readError;
96
+ try {
97
+ const existing = new GitManager({ repoPath: options.settingsRoot });
98
+ currentBranch = (await existing.status()).current ?? undefined;
99
+ }
100
+ catch (err) {
101
+ currentBranch = undefined;
102
+ readError = getErrorMessage(err);
103
+ }
104
+ const onDifferentBranch = currentBranch !== options.branchName;
105
+ const looksLikeSettingsBranch = currentBranch !== undefined && currentBranch.startsWith(RESERVED_SETTINGS_BRANCH_PREFIX);
106
+ const hasSettingsData = await settingsFilesPresent(options.settingsRoot);
107
+ if (onDifferentBranch && (looksLikeSettingsBranch || hasSettingsData)) {
108
+ throw new Error(`CanopyCMS: refusing to initialize settings workspace at ${options.settingsRoot}. ` +
109
+ (currentBranch
110
+ ? `It is currently on branch '${currentBranch}', but this deployment resolved ` +
111
+ `settings branch '${options.branchName}'.`
112
+ : `Its current branch could not be read (${readError ?? 'unknown error'}), but it ` +
113
+ `holds settings files, and this deployment resolved settings branch ` +
114
+ `'${options.branchName}'.`) +
115
+ ` Proceeding would run \`git checkout --orphan\` + \`rm -rf .\` on this workspace, ` +
116
+ `which PERMANENTLY WIPES permissions.json/groups.json (orphan branches share no ` +
117
+ `history — there is nothing to migrate from). This almost always means ` +
118
+ `deploymentName, settingsBranch, or CANOPYCMS_DEPLOYMENT_NAME changed on a ` +
119
+ `deployment that already has a populated settings workspace. To resolve: restore ` +
120
+ `the previous value so this resolves back to ` +
121
+ `'${currentBranch ?? options.branchName}', or, if starting fresh is genuinely ` +
122
+ `intended, move ${options.settingsRoot} aside manually first.`);
123
+ }
124
+ }
125
+ /**
126
+ * Manages settings filesystem workspace and git operations.
127
+ *
128
+ * Settings are stored separately from content branches:
129
+ * - prod/dev: Orphan git branches (no shared history with content)
130
+ *
131
+ * Unlike BranchWorkspaceManager, this does not:
132
+ * - Create or manage metadata files
133
+ * - Interact with the branch registry
134
+ * - Check for special cases (settings are always settings)
135
+ *
136
+ * Uses two layers of locking, mirroring BranchWorkspaceManager:
137
+ * - In-memory Promise lock for within-process serialization (Lambda request lifecycle)
138
+ * - `acquireProvisioningLock` (proper-lockfile) for cross-process/cross-host
139
+ * synchronization of the init itself (multiple Lambda containers on EFS)
140
+ */
102
141
  export class SettingsWorkspaceManager {
103
142
  constructor(config) {
104
143
  this.config = config;
@@ -117,88 +156,34 @@ export class SettingsWorkspaceManager {
117
156
  branchName: options.branchName,
118
157
  mode: options.mode,
119
158
  });
120
- // Layer 2: File-based lock (prevents concurrent init across processes)
121
- // Lock file is placed OUTSIDE the settings root (as a sibling) so that
122
- // acquireFileLock's mkdir does not pre-create the settings directory,
123
- // which would cause git clone to fail ("already exists and is not empty").
124
- const lockPath = path.join(path.dirname(options.settingsRoot), '.settings-init.lock');
125
- const acquired = await acquireFileLock(lockPath);
159
+ // Rename guard, run LOCK-FREE and unconditionally, before any waiting.
160
+ // A deployment whose settings-branch name no longer matches the
161
+ // workspace on disk is misconfigured, not contended: it must refuse
162
+ // immediately rather than queue behind a live provisioner for what can
163
+ // be minutes. Running it here also keeps the guarantee the guard has
164
+ // always had — no code path reaches initializeWorkspace without it
165
+ // having run in THIS process.
166
+ await assertSettingsWorkspaceIdentity(options);
167
+ // Layer 2: cross-process/cross-host lock around the init itself
168
+ // (proper-lockfile: heartbeat-refreshed while the holder lives, so a
169
+ // slow EFS clone is not mistaken for a crash, and patient retries so a
170
+ // loser WAITS instead of racing into a concurrent clone).
171
+ //
172
+ // This replaced a bespoke O_CREAT|O_EXCL + 30s-mtime scheme whose
173
+ // return value was only ever used to decide whether to release: the
174
+ // loser proceeded into initializeWorkspace anyway, concurrently with
175
+ // the holder, where it could see a half-written .git, classify it
176
+ // corrupt, and `rm -rf` it out from under the in-flight clone.
177
+ const releaseLock = await acquireProvisioningLock(settingsInitLockTarget(options.settingsRoot), SETTINGS_INIT_LOCK_NAME);
126
178
  try {
127
- // Rename guard (settings-branch protection). Refuses to boot when
128
- // an ALREADY-POPULATED settings workspace would be re-orphaned
129
- // under a different branch name.
130
- //
131
- // Trace of what happens without it: GitManager.initializeWorkspace
132
- // sees an existing .git (no clone happens) and goes straight to
133
- // createOrphanSettingsBranch(options.branchName). If that name
134
- // isn't already known, git runs `checkout --orphan <name>` +
135
- // `rm -rf .` + an empty `--allow-empty` commit — orphan branches
136
- // share no history, so this isn't a "migration", it PERMANENTLY
137
- // WIPES permissions.json/groups.json with nothing to recover from.
138
- // That almost always means deploymentName / settingsBranch /
139
- // CANOPYCMS_DEPLOYMENT_NAME changed on a live deployment (see
140
- // resolveDeploymentName in operating-mode/deployment-name.ts).
141
- //
142
- // What this deliberately does NOT refuse, and why it matters:
143
- // initializeWorkspace clones at the BASE branch and only creates
144
- // the orphan branch several steps later (addConfig, resolveRemoteUrl,
145
- // ensureRemote come in between). If that first init is interrupted
146
- // — a Lambda timeout on a slow EFS clone, an OOM, a spot
147
- // interruption — it leaves a perfectly valid repo sitting on the
148
- // base branch with NO settings files in it. Refusing on "branch
149
- // differs" alone would then brick the deployment permanently on
150
- // every subsequent boot, to protect data that was never written.
151
- // So the refusal additionally requires evidence that this really
152
- // is a populated settings workspace: either it is checked out on
153
- // some OTHER settings branch, or the settings files are actually
154
- // on disk.
155
- const repoExists = await GitManager.repoExistsAt(options.settingsRoot);
156
- // Deliberately NOT gated on `acquired`: when another process holds
157
- // the init lock, THIS process still proceeds to initializeWorkspace
158
- // below (pre-existing concurrent-init design), so skipping the
159
- // guard here would let the un-locked process wipe a populated
160
- // workspace — concurrent cold starts are routine right after a
161
- // deploy, which is exactly when a changed deploymentName arrives.
162
- // Running the guard lock-free cannot false-positive on a
163
- // legitimate concurrent init: a first-ever init sits on the base
164
- // branch with no settings files (fires neither arm of the
165
- // condition below), and a same-name re-init has
166
- // currentBranch === options.branchName. The only mid-init state
167
- // that fires is another process initializing a DIFFERENT settings
168
- // branch name — the rename hazard itself, where firing is correct.
169
- if (repoExists) {
170
- let currentBranch;
171
- let readError;
172
- try {
173
- const existing = new GitManager({ repoPath: options.settingsRoot });
174
- currentBranch = (await existing.status()).current ?? undefined;
175
- }
176
- catch (err) {
177
- currentBranch = undefined;
178
- readError = getErrorMessage(err);
179
- }
180
- const onDifferentBranch = currentBranch !== options.branchName;
181
- const looksLikeSettingsBranch = currentBranch !== undefined &&
182
- currentBranch.startsWith(RESERVED_SETTINGS_BRANCH_PREFIX);
183
- const hasSettingsData = await settingsFilesPresent(options.settingsRoot);
184
- if (onDifferentBranch && (looksLikeSettingsBranch || hasSettingsData)) {
185
- throw new Error(`CanopyCMS: refusing to initialize settings workspace at ${options.settingsRoot}. ` +
186
- (currentBranch
187
- ? `It is currently on branch '${currentBranch}', but this deployment resolved ` +
188
- `settings branch '${options.branchName}'.`
189
- : `Its current branch could not be read (${readError ?? 'unknown error'}), but it ` +
190
- `holds settings files, and this deployment resolved settings branch ` +
191
- `'${options.branchName}'.`) +
192
- ` Proceeding would run \`git checkout --orphan\` + \`rm -rf .\` on this workspace, ` +
193
- `which PERMANENTLY WIPES permissions.json/groups.json (orphan branches share no ` +
194
- `history — there is nothing to migrate from). This almost always means ` +
195
- `deploymentName, settingsBranch, or CANOPYCMS_DEPLOYMENT_NAME changed on a ` +
196
- `deployment that already has a populated settings workspace. To resolve: restore ` +
197
- `the previous value so this resolves back to ` +
198
- `'${currentBranch ?? options.branchName}', or, if starting fresh is genuinely ` +
199
- `intended, move ${options.settingsRoot} aside manually first.`);
200
- }
201
- }
179
+ // Re-run the guard on the now-stable state. If we waited above, the
180
+ // previous holder may have created the workspace (or moved it onto
181
+ // its own settings branch) after we sampled it, and acting on that
182
+ // stale sample is exactly the destructive path the guard exists to
183
+ // stop. Still not gated on any "did I win the race" flag — there is
184
+ // no such flag by design; every process either holds the lock here
185
+ // or has already thrown.
186
+ await assertSettingsWorkspaceIdentity(options);
202
187
  // GitManager.initializeWorkspace is idempotent (checks for .git),
203
188
  // so it's safe to call even if another process just finished init.
204
189
  await GitManager.initializeWorkspace({
@@ -217,8 +202,11 @@ export class SettingsWorkspaceManager {
217
202
  });
218
203
  }
219
204
  finally {
220
- if (acquired) {
221
- await releaseFileLock(lockPath);
205
+ try {
206
+ await releaseLock();
207
+ }
208
+ catch (err) {
209
+ log.debug('workspace', 'Failed to release settings-init lock', { err });
222
210
  }
223
211
  }
224
212
  }
@@ -1 +1 @@
1
- {"version":3,"file":"settings-workspace.js","sourceRoot":"","sources":["../src/settings-workspace.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"settings-workspace.js","sourceRoot":"","sources":["../src/settings-workspace.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,kBAAkB,CAAA;AACjC,OAAO,IAAI,MAAM,WAAW,CAAA;AAG5B,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AACjD,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAA;AAC/C,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAA;AACnE,OAAO,EAAE,+BAA+B,EAAE,MAAM,SAAS,CAAA;AAEzD,MAAM,GAAG,GAAG,iBAAiB,CAAC,EAAE,MAAM,EAAE,mBAAmB,EAAE,CAAC,CAAA;AAE9D,yFAAyF;AACzF,sEAAsE;AACtE,IAAI,gBAAgB,GAAyB,IAAI,CAAA;AAEjD,MAAM,sBAAsB,GAAG,gBAAgB,CAAA;AAC/C,MAAM,uBAAuB,GAAG,MAAM,CAAA;AAEtC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,UAAU,sBAAsB,CAAC,YAAoB;IACzD,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC,EAAE,sBAAsB,CAAC,CAAA;AACpF,CAAC;AASD;;;;;;GAMG;AACH,KAAK,UAAU,oBAAoB,CAAC,YAAoB;IACtD,MAAM,KAAK,GAAG,CAAC,kBAAkB,EAAE,aAAa,CAAC,CAAA;IACjD,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC,CAAA;YAC9C,OAAO,IAAI,CAAA;QACb,CAAC;QAAC,MAAM,CAAC;YACP,wCAAwC;QAC1C,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,KAAK,UAAU,+BAA+B,CAC5C,OAAuC;IAEvC,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,YAAY,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;IACtE,IAAI,CAAC,UAAU;QAAE,OAAM;IAEvB,IAAI,aAAiC,CAAA;IACrC,IAAI,SAA6B,CAAA;IACjC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,UAAU,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAA;QACnE,aAAa,GAAG,CAAC,MAAM,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,IAAI,SAAS,CAAA;IAChE,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,aAAa,GAAG,SAAS,CAAA;QACzB,SAAS,GAAG,eAAe,CAAC,GAAG,CAAC,CAAA;IAClC,CAAC;IAED,MAAM,iBAAiB,GAAG,aAAa,KAAK,OAAO,CAAC,UAAU,CAAA;IAC9D,MAAM,uBAAuB,GAC3B,aAAa,KAAK,SAAS,IAAI,aAAa,CAAC,UAAU,CAAC,+BAA+B,CAAC,CAAA;IAC1F,MAAM,eAAe,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,YAAY,CAAC,CAAA;IAExE,IAAI,iBAAiB,IAAI,CAAC,uBAAuB,IAAI,eAAe,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CACb,2DAA2D,OAAO,CAAC,YAAY,IAAI;YACjF,CAAC,aAAa;gBACZ,CAAC,CAAC,8BAA8B,aAAa,kCAAkC;oBAC7E,oBAAoB,OAAO,CAAC,UAAU,IAAI;gBAC5C,CAAC,CAAC,yCAAyC,SAAS,IAAI,eAAe,YAAY;oBACjF,qEAAqE;oBACrE,IAAI,OAAO,CAAC,UAAU,IAAI,CAAC;YAC/B,oFAAoF;YACpF,iFAAiF;YACjF,wEAAwE;YACxE,4EAA4E;YAC5E,kFAAkF;YAClF,8CAA8C;YAC9C,IAAI,aAAa,IAAI,OAAO,CAAC,UAAU,wCAAwC;YAC/E,kBAAkB,OAAO,CAAC,YAAY,wBAAwB,CACjE,CAAA;IACH,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,OAAO,wBAAwB;IAGnC,YAAY,MAAoB;QAC9B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAA;IACtB,CAAC;IAED,KAAK,CAAC,kBAAkB,CAAC,OAAuC;QAC9D,OAAO,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,oBAAoB,EAAE,KAAK,IAAI,EAAE;YAC7D,+EAA+E;YAC/E,IAAI,gBAAgB,EAAE,CAAC;gBACrB,MAAM,gBAAgB,CAAA;gBACtB,OAAM;YACR,CAAC;YAED,oCAAoC;YACpC,gBAAgB,GAAG,CAAC,KAAK,IAAI,EAAE;gBAC7B,IAAI,CAAC;oBACH,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,iCAAiC,EAAE;wBACxD,UAAU,EAAE,OAAO,CAAC,UAAU;wBAC9B,IAAI,EAAE,OAAO,CAAC,IAAI;qBACnB,CAAC,CAAA;oBAEF,uEAAuE;oBACvE,gEAAgE;oBAChE,oEAAoE;oBACpE,uEAAuE;oBACvE,qEAAqE;oBACrE,mEAAmE;oBACnE,8BAA8B;oBAC9B,MAAM,+BAA+B,CAAC,OAAO,CAAC,CAAA;oBAE9C,gEAAgE;oBAChE,qEAAqE;oBACrE,uEAAuE;oBACvE,0DAA0D;oBAC1D,EAAE;oBACF,kEAAkE;oBAClE,oEAAoE;oBACpE,qEAAqE;oBACrE,kEAAkE;oBAClE,+DAA+D;oBAC/D,MAAM,WAAW,GAAG,MAAM,uBAAuB,CAC/C,sBAAsB,CAAC,OAAO,CAAC,YAAY,CAAC,EAC5C,uBAAuB,CACxB,CAAA;oBAED,IAAI,CAAC;wBACH,oEAAoE;wBACpE,mEAAmE;wBACnE,mEAAmE;wBACnE,mEAAmE;wBACnE,oEAAoE;wBACpE,mEAAmE;wBACnE,yBAAyB;wBACzB,MAAM,+BAA+B,CAAC,OAAO,CAAC,CAAA;wBAE9C,kEAAkE;wBAClE,mEAAmE;wBACnE,MAAM,UAAU,CAAC,mBAAmB,CAAC;4BACnC,aAAa,EAAE,OAAO,CAAC,YAAY;4BACnC,UAAU,EAAE,OAAO,CAAC,UAAU;4BAC9B,IAAI,EAAE,OAAO,CAAC,IAAI;4BAClB,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB;4BACzC,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,UAAU;4BAClC,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB;4BAC9C,SAAS,EAAE,OAAO,CAAC,SAAS;4BAC5B,UAAU,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB;4BACzC,wBAAwB,EAAE,IAAI,CAAC,MAAM,CAAC,wBAAwB;4BAC9D,UAAU,EAAE,QAAQ,EAAE,6CAA6C;4BACnE,gBAAgB,EAAE,IAAI,CAAC,MAAM,CAAC,gBAAgB;4BAC9C,iBAAiB,EAAE,IAAI,CAAC,MAAM,CAAC,iBAAiB;yBACjD,CAAC,CAAA;oBACJ,CAAC;4BAAS,CAAC;wBACT,IAAI,CAAC;4BACH,MAAM,WAAW,EAAE,CAAA;wBACrB,CAAC;wBAAC,OAAO,GAAY,EAAE,CAAC;4BACtB,GAAG,CAAC,KAAK,CAAC,WAAW,EAAE,sCAAsC,EAAE,EAAE,GAAG,EAAE,CAAC,CAAA;wBACzE,CAAC;oBACH,CAAC;gBACH,CAAC;wBAAS,CAAC;oBACT,oEAAoE;oBACpE,gBAAgB,GAAG,IAAI,CAAA;gBACzB,CAAC;YACH,CAAC,CAAC,EAAE,CAAA;YAEJ,sCAAsC;YACtC,MAAM,gBAAgB,CAAA;QACxB,CAAC,CAAC,CAAA;IACJ,CAAC;CACF"}
@@ -6,10 +6,12 @@ import { type EntryFieldError } from '../validation/entry-validator';
6
6
  * (no Next.js types) so that any framework adapter — e.g. canopycms-next — can map them to its own
7
7
  * static-generation shapes (generateStaticParams, sitemap, metadata).
8
8
  *
9
- * Sitemap (`collectPublishedEntries`) and SEO metadata (`extractSeoFields`) helpers are tracked as
10
- * follow-up work; see .claude/future-tasks/static-export-sitemap.md and
11
- * .claude/future-tasks/static-export-seo-metadata.md.
9
+ * - `collectStaticPaths` — routable paths only, for `generateStaticParams`.
10
+ * - `collectRoutableEntries` — the same enumeration with each entry's `data` and `updatedAt`
11
+ * carried through, for surfaces that must inspect content (sitemap, feeds, search index).
12
+ * - `./seo` — `extractSeoFields` / `isNoindexEntry` / URL shaping, re-exported below.
12
13
  */
14
+ export { DEFAULT_SEO_FIELD_NAMES, extractSeoFields, isNoindexEntry, isAbsoluteUrl, withTrailingSlash, resolveSeoUrl, stripTrailingSlashes, type SeoFields, type SeoFieldNames, type SeoFieldLocation, type SeoOgType, type SeoTwitterCard, type ExtractSeoFieldsOptions, type ResolveSeoUrlOptions, } from './seo';
13
15
  /** A routable content entry, reduced to what static path generation needs. */
14
16
  export interface StaticPathEntry {
15
17
  /**
@@ -27,6 +29,26 @@ export interface StaticPathEntry {
27
29
  /** Entry type name (e.g. 'post', 'doc'). */
28
30
  entryType: string;
29
31
  }
32
+ /**
33
+ * A routable content entry with its content attached — everything `StaticPathEntry` carries,
34
+ * plus the entry `data` and its `updatedAt`.
35
+ *
36
+ * This is what surfaces that must look INSIDE an entry need (sitemap, feeds, index grids),
37
+ * as opposed to `generateStaticParams`, which only needs the path.
38
+ */
39
+ export interface RoutableEntry<T = Record<string, unknown>> extends StaticPathEntry {
40
+ /** Raw entry data (frontmatter merged with the body field for md/mdx), as `listEntries` returns it. */
41
+ data: T;
42
+ /**
43
+ * Filesystem mtime (ISO 8601) of the entry file.
44
+ *
45
+ * Caveat, and read it before wiring this to a sitemap `<lastmod>`: this is a checkout-time
46
+ * timestamp, not an editorial one. A fresh CI clone (or a fresh branch-clone checkout) resets
47
+ * every file's mtime, so there it says "when this tree was checked out", not "when this content
48
+ * was last edited". Treat it as "changed since the last build" at best.
49
+ */
50
+ updatedAt?: string;
51
+ }
30
52
  export interface CollectStaticPathsOptions {
31
53
  /**
32
54
  * Scope to a collection logical path (e.g. 'content/posts'). Defaults to the whole content root.
@@ -36,6 +58,35 @@ export interface CollectStaticPathsOptions {
36
58
  /** Keep only entries matching this predicate (e.g. drop the root index, or filter by entryType). */
37
59
  filter?: (entry: StaticPathEntry) => boolean;
38
60
  }
61
+ export interface CollectRoutableEntriesOptions<T = Record<string, unknown>> {
62
+ /**
63
+ * Scope to a collection logical path (e.g. 'content/posts'). Defaults to the whole content root.
64
+ * Skips loading entries outside this scope.
65
+ */
66
+ rootPath?: string;
67
+ /** Keep only entries matching this predicate. Runs after `data` is attached, so it can read content. */
68
+ filter?: (entry: RoutableEntry<T>) => boolean;
69
+ }
70
+ /**
71
+ * Enumerate routable content entries WITH their data — the input to any surface that must read
72
+ * inside an entry to decide what to publish (sitemap, RSS, search index, index grids).
73
+ *
74
+ * Identical enumeration to `collectStaticPaths` (same single `listEntries` pass, same build-time
75
+ * schema-validity guard); it simply keeps `data` and `updatedAt` instead of discarding them.
76
+ *
77
+ * **Enumerates every entry type by default.** There is no allow-list of "publishable" types, and
78
+ * that is deliberate: a sitemap built from a hand-maintained list of entry types silently omits
79
+ * whichever type nobody remembered to add, and nothing fails. Narrow the result with `filter`
80
+ * when you actually mean to — omission should be a decision, not an oversight.
81
+ *
82
+ * `noindex` is NOT applied here: an entry that must not be advertised must still be built. Apply
83
+ * `isNoindexEntry` at the advertising surface (as `generateContentSitemap` does).
84
+ *
85
+ * @example
86
+ * const entries = await collectRoutableEntries(await getCanopyForBuild())
87
+ * const published = entries.filter((e) => !isNoindexEntry(e.data))
88
+ */
89
+ export declare function collectRoutableEntries<T = Record<string, unknown>>(ctx: Pick<CanopyBuildContext, 'listEntries'>, options?: CollectRoutableEntriesOptions<T>): Promise<RoutableEntry<T>[]>;
39
90
  /**
40
91
  * Enumerate routable content entries as neutral path descriptors for static generation.
41
92
  *
@@ -57,6 +108,15 @@ export interface InvalidBuildEntry {
57
108
  entryPath: string;
58
109
  errors: EntryFieldError[];
59
110
  }
111
+ /**
112
+ * What the build-time validity scan needs off a listing item. `data` is `unknown` rather than
113
+ * `ListEntriesItem['data']` so a caller that listed with an entry-shape generic (e.g.
114
+ * `collectRoutableEntries<PostContent>`) can still be scanned — the scan re-guards the value
115
+ * anyway, since on-disk data is never trusted.
116
+ */
117
+ type BuildScanItem = Pick<ListEntriesItem, 'entryPath' | 'schema'> & {
118
+ data: unknown;
119
+ };
60
120
  /**
61
121
  * Scan listEntries-shaped items for schema-invalid entries.
62
122
  *
@@ -71,7 +131,7 @@ export interface InvalidBuildEntry {
71
131
  * Items whose `schema` couldn't be resolved (unknown entry type) are skipped here — that's a
72
132
  * different failure class, handled elsewhere.
73
133
  */
74
- export declare function findInvalidEntries(items: readonly Pick<ListEntriesItem, 'entryPath' | 'schema' | 'data'>[]): InvalidBuildEntry[];
134
+ export declare function findInvalidEntries(items: readonly BuildScanItem[]): InvalidBuildEntry[];
75
135
  /**
76
136
  * Throw a single, descriptive Error if any item is schema-invalid.
77
137
  *
@@ -79,5 +139,5 @@ export declare function findInvalidEntries(items: readonly Pick<ListEntriesItem,
79
139
  * disappears from a static build is a worse failure mode than a red build. Lists every
80
140
  * offending entry so one build catches every abandoned scaffold, not just the first.
81
141
  */
82
- export declare function assertBuildEntriesValid(items: readonly Pick<ListEntriesItem, 'entryPath' | 'schema' | 'data'>[], phaseLabel: string): void;
142
+ export declare function assertBuildEntriesValid(items: readonly BuildScanItem[], phaseLabel: string): void;
83
143
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/static/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAEvF;;;;;;;;GAQG;AAEH,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,yGAAyG;IACzG,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oGAAoG;IACpG,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAA;CAC7C;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,EAC5C,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,eAAe,EAAE,CAAC,CAc5B;AAMD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,eAAe,EAAE,CAAA;CAC1B;AAkCD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,SAAS,IAAI,CAAC,eAAe,EAAE,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC,EAAE,GACvE,iBAAiB,EAAE,CAWrB;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CACrC,KAAK,EAAE,SAAS,IAAI,CAAC,eAAe,EAAE,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC,EAAE,EACxE,UAAU,EAAE,MAAM,GACjB,IAAI,CAkBN"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/static/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAEpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAA;AACzD,OAAO,EAAqB,KAAK,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAEvF;;;;;;;;;GASG;AAEH,OAAO,EACL,uBAAuB,EACvB,gBAAgB,EAChB,cAAc,EACd,aAAa,EACb,iBAAiB,EACjB,aAAa,EACb,oBAAoB,EACpB,KAAK,SAAS,EACd,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,uBAAuB,EAC5B,KAAK,oBAAoB,GAC1B,MAAM,OAAO,CAAA;AAEd,8EAA8E;AAC9E,MAAM,WAAW,eAAe;IAC9B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAA;IACf;;;OAGG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,yGAAyG;IACzG,IAAI,EAAE,MAAM,CAAA;IACZ,4CAA4C;IAC5C,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAE,SAAQ,eAAe;IACjF,uGAAuG;IACvG,IAAI,EAAE,CAAC,CAAA;IACP;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,oGAAoG;IACpG,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,OAAO,CAAA;CAC7C;AAED,MAAM,WAAW,6BAA6B,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IACxE;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,wGAAwG;IACxG,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,KAAK,OAAO,CAAA;CAC9C;AA6BD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACtE,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,EAC5C,OAAO,GAAE,6BAA6B,CAAC,CAAC,CAAM,GAC7C,OAAO,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAO7B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,kBAAkB,CACtC,GAAG,EAAE,IAAI,CAAC,kBAAkB,EAAE,aAAa,CAAC,EAC5C,OAAO,GAAE,yBAA8B,GACtC,OAAO,CAAC,eAAe,EAAE,CAAC,CAW5B;AAMD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,eAAe,EAAE,CAAA;CAC1B;AAED;;;;;GAKG;AACH,KAAK,aAAa,GAAG,IAAI,CAAC,eAAe,EAAE,WAAW,GAAG,QAAQ,CAAC,GAAG;IAAE,IAAI,EAAE,OAAO,CAAA;CAAE,CAAA;AAkCtF;;;;;;;;;;;;;GAaG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,GAAG,iBAAiB,EAAE,CAWvF;AAED;;;;;;GAMG;AACH,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,SAAS,aAAa,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,IAAI,CAkBjG"}
@@ -1,5 +1,62 @@
1
1
  import { isBuildMode } from '../build-mode.js';
2
2
  import { validateEntryData } from '../validation/entry-validator.js';
3
+ /**
4
+ * Framework-agnostic helpers for static-site generation. These produce neutral data structures
5
+ * (no Next.js types) so that any framework adapter — e.g. canopycms-next — can map them to its own
6
+ * static-generation shapes (generateStaticParams, sitemap, metadata).
7
+ *
8
+ * - `collectStaticPaths` — routable paths only, for `generateStaticParams`.
9
+ * - `collectRoutableEntries` — the same enumeration with each entry's `data` and `updatedAt`
10
+ * carried through, for surfaces that must inspect content (sitemap, feeds, search index).
11
+ * - `./seo` — `extractSeoFields` / `isNoindexEntry` / URL shaping, re-exported below.
12
+ */
13
+ export { DEFAULT_SEO_FIELD_NAMES, extractSeoFields, isNoindexEntry, isAbsoluteUrl, withTrailingSlash, resolveSeoUrl, stripTrailingSlashes, } from './seo.js';
14
+ /**
15
+ * Shared enumeration behind `collectStaticPaths` and `collectRoutableEntries`.
16
+ *
17
+ * `phaseLabel` only names the phase in the build-guard error, but it is threaded through rather
18
+ * than fixed so the message points at the surface that actually failed.
19
+ */
20
+ async function enumerateRoutableEntries(ctx, rootPath, phaseLabel) {
21
+ const entries = await ctx.listEntries({ rootPath });
22
+ // Build-time only: `next dev` runs generateStaticParams against the live working tree, where
23
+ // fresh create-scaffolds legitimately exist mid-edit. Only fail the actual production build —
24
+ // an abandoned schema-invalid scaffold shipping into a static build silently drops that page's
25
+ // route (or worse, renders broken), which is worse than a red build.
26
+ if (isBuildMode())
27
+ assertBuildEntriesValid(entries, phaseLabel);
28
+ return entries.map((entry) => ({
29
+ urlPath: entry.urlPath,
30
+ segments: entry.urlPath === '/' ? [] : entry.urlPath.replace(/^\//, '').split('/'),
31
+ slug: entry.slug,
32
+ entryType: entry.entryType,
33
+ data: entry.data,
34
+ ...(entry.updatedAt ? { updatedAt: entry.updatedAt } : {}),
35
+ }));
36
+ }
37
+ /**
38
+ * Enumerate routable content entries WITH their data — the input to any surface that must read
39
+ * inside an entry to decide what to publish (sitemap, RSS, search index, index grids).
40
+ *
41
+ * Identical enumeration to `collectStaticPaths` (same single `listEntries` pass, same build-time
42
+ * schema-validity guard); it simply keeps `data` and `updatedAt` instead of discarding them.
43
+ *
44
+ * **Enumerates every entry type by default.** There is no allow-list of "publishable" types, and
45
+ * that is deliberate: a sitemap built from a hand-maintained list of entry types silently omits
46
+ * whichever type nobody remembered to add, and nothing fails. Narrow the result with `filter`
47
+ * when you actually mean to — omission should be a decision, not an oversight.
48
+ *
49
+ * `noindex` is NOT applied here: an entry that must not be advertised must still be built. Apply
50
+ * `isNoindexEntry` at the advertising surface (as `generateContentSitemap` does).
51
+ *
52
+ * @example
53
+ * const entries = await collectRoutableEntries(await getCanopyForBuild())
54
+ * const published = entries.filter((e) => !isNoindexEntry(e.data))
55
+ */
56
+ export async function collectRoutableEntries(ctx, options = {}) {
57
+ const entries = await enumerateRoutableEntries(ctx, options.rootPath, 'routable entry enumeration');
58
+ return options.filter ? entries.filter(options.filter) : entries;
59
+ }
3
60
  /**
4
61
  * Enumerate routable content entries as neutral path descriptors for static generation.
5
62
  *
@@ -14,18 +71,14 @@ import { validateEntryData } from '../validation/entry-validator.js';
14
71
  * const posts = await collectStaticPaths(await getCanopyForBuild(), { rootPath: 'content/posts' })
15
72
  */
16
73
  export async function collectStaticPaths(ctx, options = {}) {
17
- const entries = await ctx.listEntries({ rootPath: options.rootPath });
18
- // Build-time only: `next dev` runs generateStaticParams against the live working tree, where
19
- // fresh create-scaffolds legitimately exist mid-edit. Only fail the actual production build —
20
- // an abandoned schema-invalid scaffold shipping into a static build silently drops that page's
21
- // route (or worse, renders broken), which is worse than a red build.
22
- if (isBuildMode())
23
- assertBuildEntriesValid(entries, 'static path enumeration');
24
- const mapped = entries.map((entry) => ({
25
- urlPath: entry.urlPath,
26
- segments: entry.urlPath === '/' ? [] : entry.urlPath.replace(/^\//, '').split('/'),
27
- slug: entry.slug,
28
- entryType: entry.entryType,
74
+ const entries = await enumerateRoutableEntries(ctx, options.rootPath, 'static path enumeration');
75
+ // Drop data/updatedAt: generateStaticParams needs paths only, and returning entry content from
76
+ // a path-enumeration helper invites page code to read content from the admin build context.
77
+ const mapped = entries.map(({ urlPath, segments, slug, entryType }) => ({
78
+ urlPath,
79
+ segments,
80
+ slug,
81
+ entryType,
29
82
  }));
30
83
  return options.filter ? mapped.filter(options.filter) : mapped;
31
84
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/static/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAE3C,OAAO,EAAE,iBAAiB,EAAwB,MAAM,+BAA+B,CAAA;AAwCvF;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,GAA4C,EAC5C,UAAqC,EAAE;IAEvC,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,WAAW,CAAC,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,CAAA;IACrE,6FAA6F;IAC7F,8FAA8F;IAC9F,+FAA+F;IAC/F,qEAAqE;IACrE,IAAI,WAAW,EAAE;QAAE,uBAAuB,CAAC,OAAO,EAAE,yBAAyB,CAAC,CAAA;IAC9E,MAAM,MAAM,GAAsB,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACxD,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,QAAQ,EAAE,KAAK,CAAC,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC;QAClF,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,SAAS,EAAE,KAAK,CAAC,SAAS;KAC3B,CAAC,CAAC,CAAA;IACH,OAAO,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAA;AAChE,CAAC;AAcD;;;;;;;;;;;;;;;;;;GAkBG;AACH,SAAS,kBAAkB,CAAC,KAAc;IACxC,IAAI,KAAK,YAAY,IAAI;QAAE,OAAO,KAAK,CAAC,WAAW,EAAE,CAAA;IACrD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAA;IAC9D,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,MAAM,GAA4B,EAAE,CAAA;QAC1C,KAAK,MAAM,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC,EAAE,CAAC;YACxE,MAAM,CAAC,GAAG,CAAC,GAAG,kBAAkB,CAAC,CAAC,CAAC,CAAA;QACrC,CAAC;QACD,OAAO,MAAM,CAAA;IACf,CAAC;IACD,OAAO,KAAK,CAAA;AACd,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAChC,KAAwE;IAExE,MAAM,OAAO,GAAwB,EAAE,CAAA;IACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,CAAC,IAAI,CAAC,MAAM;YAAE,SAAQ;QAC1B,MAAM,IAAI,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAA4B,CAAA;QACrE,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;QACnD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,OAAO,CAAC,IAAI,CAAC,EAAE,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC,CAAA;QACrD,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,KAAwE,EACxE,UAAkB;IAElB,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAA;IACzC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IAEhC,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE;QAClD,MAAM,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,GAAG,MAAM,CAAA;QAC/B,MAAM,OAAO,GACX,IAAI,CAAC,MAAM,GAAG,CAAC;YACb,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,OAAO,MAAM,IAAI,CAAC,MAAM,QAAQ;YAC/D,CAAC,CAAC,GAAG,KAAK,CAAC,SAAS,KAAK,KAAK,CAAC,OAAO,EAAE,CAAA;QAC5C,OAAO,OAAO,SAAS,MAAM,OAAO,EAAE,CAAA;IACxC,CAAC,CAAC,CAAA;IAEF,MAAM,IAAI,KAAK,CACb,iCAAiC,OAAO,CAAC,MAAM,mBAAmB,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,WAAW,UAAU,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QACzJ,wHAAwH;QACxH,qGAAqG,CACxG,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/static/index.ts"],"names":[],"mappings":"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"}