canopycms 0.0.61 → 0.0.62
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai/json-to-markdown.js +1 -2
- package/dist/ai/json-to-markdown.js.map +1 -1
- package/dist/api/__test__/mock-client.d.ts +5 -1
- package/dist/api/__test__/mock-client.d.ts.map +1 -1
- package/dist/api/__test__/mock-client.js +9 -2
- package/dist/api/__test__/mock-client.js.map +1 -1
- package/dist/api/admin-branch-health.d.ts +36 -1
- package/dist/api/admin-branch-health.d.ts.map +1 -1
- package/dist/api/admin-branch-health.js +176 -2
- package/dist/api/admin-branch-health.js.map +1 -1
- package/dist/api/admin.d.ts +8 -1
- package/dist/api/admin.d.ts.map +1 -1
- package/dist/api/admin.js.map +1 -1
- package/dist/api/branch.d.ts.map +1 -1
- package/dist/api/branch.js +16 -2
- package/dist/api/branch.js.map +1 -1
- package/dist/api/client.d.ts +5 -1
- package/dist/api/client.d.ts.map +1 -1
- package/dist/api/client.js +6 -0
- package/dist/api/client.js.map +1 -1
- package/dist/api/comments.d.ts +2 -2
- package/dist/api/content.d.ts +11 -5
- package/dist/api/content.d.ts.map +1 -1
- package/dist/api/content.js +79 -6
- package/dist/api/content.js.map +1 -1
- package/dist/api/entries.d.ts +10 -0
- package/dist/api/entries.d.ts.map +1 -1
- package/dist/api/entries.js +40 -15
- package/dist/api/entries.js.map +1 -1
- package/dist/api/schema.d.ts +4 -4
- package/dist/api/schema.d.ts.map +1 -1
- package/dist/api/schema.js +13 -13
- package/dist/api/schema.js.map +1 -1
- package/dist/assets/transform.d.ts.map +1 -1
- package/dist/assets/transform.js +19 -6
- package/dist/assets/transform.js.map +1 -1
- package/dist/branch-health.d.ts +13 -0
- package/dist/branch-health.d.ts.map +1 -1
- package/dist/branch-health.js +23 -0
- package/dist/branch-health.js.map +1 -1
- package/dist/cli/cli.js +858 -537
- package/dist/cli/generate-ai-content.js +760 -455
- package/dist/cli/init.js +25 -7
- package/dist/cli/template-files/Dockerfile.cms.template +19 -1
- package/dist/cli/template-files/canopycms.config.ts.template +7 -0
- package/dist/cli/template-files/cms-stack.ts.template +7 -1
- package/dist/client.d.ts +1 -0
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js.map +1 -1
- package/dist/config/schemas/field.d.ts +15 -15
- package/dist/config/types.d.ts +2 -2
- package/dist/config/types.d.ts.map +1 -1
- package/dist/config/types.js +0 -1
- package/dist/config/types.js.map +1 -1
- package/dist/config/validation.d.ts +9 -0
- package/dist/config/validation.d.ts.map +1 -1
- package/dist/config/validation.js +14 -0
- package/dist/config/validation.js.map +1 -1
- package/dist/content-id-index.d.ts +97 -5
- package/dist/content-id-index.d.ts.map +1 -1
- package/dist/content-id-index.js +170 -18
- package/dist/content-id-index.js.map +1 -1
- package/dist/content-store.d.ts +81 -7
- package/dist/content-store.d.ts.map +1 -1
- package/dist/content-store.js +443 -258
- package/dist/content-store.js.map +1 -1
- package/dist/editor/BranchManager.d.ts.map +1 -1
- package/dist/editor/BranchManager.js +18 -6
- package/dist/editor/BranchManager.js.map +1 -1
- package/dist/editor/CanopyEditor.d.ts.map +1 -1
- package/dist/editor/CanopyEditor.js +2 -2
- package/dist/editor/CanopyEditor.js.map +1 -1
- package/dist/editor/CanopyEditorPage.d.ts +9 -1
- package/dist/editor/CanopyEditorPage.d.ts.map +1 -1
- package/dist/editor/CanopyEditorPage.js +9 -2
- package/dist/editor/CanopyEditorPage.js.map +1 -1
- package/dist/editor/Editor.d.ts +9 -1
- package/dist/editor/Editor.d.ts.map +1 -1
- package/dist/editor/Editor.js +58 -24
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/FormRenderer.d.ts.map +1 -1
- package/dist/editor/FormRenderer.js +12 -0
- package/dist/editor/FormRenderer.js.map +1 -1
- package/dist/editor/admin/SystemHealthPanel.js +6 -2
- package/dist/editor/admin/SystemHealthPanel.js.map +1 -1
- package/dist/editor/components/EntryCreateModal.d.ts +9 -1
- package/dist/editor/components/EntryCreateModal.d.ts.map +1 -1
- package/dist/editor/components/EntryCreateModal.js +4 -1
- package/dist/editor/components/EntryCreateModal.js.map +1 -1
- package/dist/editor/fields/DateTimeField.d.ts +46 -0
- package/dist/editor/fields/DateTimeField.d.ts.map +1 -0
- package/dist/editor/fields/DateTimeField.js +66 -0
- package/dist/editor/fields/DateTimeField.js.map +1 -0
- package/dist/editor/fields/NumberField.d.ts +34 -0
- package/dist/editor/fields/NumberField.d.ts.map +1 -0
- package/dist/editor/fields/NumberField.js +53 -0
- package/dist/editor/fields/NumberField.js.map +1 -0
- package/dist/editor/fields/NumberListField.d.ts +25 -0
- package/dist/editor/fields/NumberListField.d.ts.map +1 -0
- package/dist/editor/fields/NumberListField.js +53 -0
- package/dist/editor/fields/NumberListField.js.map +1 -0
- package/dist/editor/hooks/useBranchManager.d.ts.map +1 -1
- package/dist/editor/hooks/useBranchManager.js +115 -26
- package/dist/editor/hooks/useBranchManager.js.map +1 -1
- package/dist/editor/hooks/useDraftManager.d.ts +8 -0
- package/dist/editor/hooks/useDraftManager.d.ts.map +1 -1
- package/dist/editor/hooks/useDraftManager.js +261 -21
- package/dist/editor/hooks/useDraftManager.js.map +1 -1
- package/dist/editor/hooks/useEntryManager.d.ts +21 -0
- package/dist/editor/hooks/useEntryManager.d.ts.map +1 -1
- package/dist/editor/hooks/useEntryManager.js +25 -2
- package/dist/editor/hooks/useEntryManager.js.map +1 -1
- package/dist/editor/theme.d.ts +8 -8
- package/dist/git-manager.d.ts +51 -7
- package/dist/git-manager.d.ts.map +1 -1
- package/dist/git-manager.js +105 -14
- package/dist/git-manager.js.map +1 -1
- package/dist/http/handler.d.ts.map +1 -1
- package/dist/http/handler.js +70 -27
- package/dist/http/handler.js.map +1 -1
- package/dist/http/router.d.ts.map +1 -1
- package/dist/http/router.js +40 -3
- package/dist/http/router.js.map +1 -1
- package/dist/operating-mode/deployment-name-fixtures.d.ts +29 -0
- package/dist/operating-mode/deployment-name-fixtures.d.ts.map +1 -0
- package/dist/operating-mode/deployment-name-fixtures.js +55 -0
- package/dist/operating-mode/deployment-name-fixtures.js.map +1 -0
- package/dist/operating-mode/deployment-name.d.ts +9 -0
- package/dist/operating-mode/deployment-name.d.ts.map +1 -1
- package/dist/operating-mode/deployment-name.js +9 -1
- package/dist/operating-mode/deployment-name.js.map +1 -1
- package/dist/operating-mode/index.d.ts +2 -1
- package/dist/operating-mode/index.d.ts.map +1 -1
- package/dist/operating-mode/index.js +4 -1
- package/dist/operating-mode/index.js.map +1 -1
- package/dist/operating-mode/mode-env.d.ts +58 -0
- package/dist/operating-mode/mode-env.d.ts.map +1 -0
- package/dist/operating-mode/mode-env.js +97 -0
- package/dist/operating-mode/mode-env.js.map +1 -0
- package/dist/resolve-canopy-user.d.ts +34 -0
- package/dist/resolve-canopy-user.d.ts.map +1 -0
- package/dist/resolve-canopy-user.js +55 -0
- package/dist/resolve-canopy-user.js.map +1 -0
- package/dist/schema/schema-store.d.ts +53 -2
- package/dist/schema/schema-store.d.ts.map +1 -1
- package/dist/schema/schema-store.js +92 -21
- package/dist/schema/schema-store.js.map +1 -1
- package/dist/server.d.ts +9 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +9 -0
- package/dist/server.js.map +1 -1
- package/dist/services.d.ts.map +1 -1
- package/dist/services.js +40 -6
- package/dist/services.js.map +1 -1
- package/dist/settings-workspace.d.ts +40 -0
- package/dist/settings-workspace.d.ts.map +1 -1
- package/dist/settings-workspace.js +139 -151
- package/dist/settings-workspace.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/content-write-lock.d.ts +127 -0
- package/dist/utils/content-write-lock.d.ts.map +1 -0
- package/dist/utils/content-write-lock.js +170 -0
- package/dist/utils/content-write-lock.js.map +1 -0
- package/dist/utils/error.d.ts +1 -1
- package/dist/utils/error.js +1 -1
- package/dist/utils/git.d.ts +16 -0
- package/dist/utils/git.d.ts.map +1 -1
- package/dist/utils/git.js +22 -0
- package/dist/utils/git.js.map +1 -1
- package/dist/utils/sanitize-href.d.ts +28 -3
- package/dist/utils/sanitize-href.d.ts.map +1 -1
- package/dist/utils/sanitize-href.js +64 -6
- package/dist/utils/sanitize-href.js.map +1 -1
- package/dist/validation/entry-link-validator.d.ts +1 -1
- package/dist/validation/entry-link-validator.js +3 -3
- package/dist/validation/entry-link-validator.js.map +1 -1
- package/dist/validation/entry-validator.js +1 -1
- package/dist/validation/entry-validator.js.map +1 -1
- package/dist/worker/cms-worker.d.ts +13 -0
- package/dist/worker/cms-worker.d.ts.map +1 -1
- package/dist/worker/cms-worker.js +326 -264
- package/dist/worker/cms-worker.js.map +1 -1
- package/package.json +2 -2
package/dist/cli/cli.js
CHANGED
|
@@ -112,6 +112,9 @@ import path7 from "node:path";
|
|
|
112
112
|
import * as p from "@clack/prompts";
|
|
113
113
|
import { createJiti } from "jiti";
|
|
114
114
|
import path8 from "node:path";
|
|
115
|
+
function canopyLogWarn(...args) {
|
|
116
|
+
active.warn(...args);
|
|
117
|
+
}
|
|
115
118
|
function getErrorMessage2(err) {
|
|
116
119
|
if (err instanceof Error) {
|
|
117
120
|
return err.message;
|
|
@@ -740,9 +743,6 @@ async function moveToCorrupt(taskDir, sourceDir, fileName, reason, logger) {
|
|
|
740
743
|
});
|
|
741
744
|
}
|
|
742
745
|
}
|
|
743
|
-
function canopyLogWarn(...args) {
|
|
744
|
-
active.warn(...args);
|
|
745
|
-
}
|
|
746
746
|
function resolveDeploymentName(config, modeDefault) {
|
|
747
747
|
const envValue = process.env.CANOPYCMS_DEPLOYMENT_NAME?.trim();
|
|
748
748
|
const configValue = config.deploymentName;
|
|
@@ -1047,7 +1047,7 @@ CanopyCMS worker run-once (mode: ${mode}, auth: ${authMode})
|
|
|
1047
1047
|
}
|
|
1048
1048
|
console.log("\nDone");
|
|
1049
1049
|
}
|
|
1050
|
-
var __defProp2, __getOwnPropNames2, __esm2, __export2, primitiveFieldTypes, fieldTypes, init_types, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, permissionTargetSchema, pathPermissionSchema, init_permissions, init_normalize, init_types2, init_flatten, init_validation, init_helpers, init_config2, init_error2, init_fs2, templates_exports, __dirname, TEMPLATES_DIR, SCAFFOLD_STACK_NAME, init_templates, project_detect_exports, COMMANDS, MAX_WORKSPACE_DEPTH, CDK_DEPENDENCIES, init_project_detect, task_queue_config_exports, init_task_queue_config, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, init_task_queue2, task_queue_exports, debugLogger, cmsTaskQueueLogger, init_task_queue3, ProdClientSafeStrategy, DevClientSafeStrategy,
|
|
1050
|
+
var __defProp2, __getOwnPropNames2, __esm2, __export2, active, init_logger, primitiveFieldTypes, fieldTypes, init_types, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, permissionTargetSchema, pathPermissionSchema, init_permissions, init_normalize, init_types2, init_flatten, init_mode_env, init_validation, init_helpers, init_config2, init_error2, init_fs2, templates_exports, __dirname, TEMPLATES_DIR, SCAFFOLD_STACK_NAME, init_templates, project_detect_exports, COMMANDS, MAX_WORKSPACE_DEPTH, CDK_DEPENDENCIES, init_project_detect, task_queue_config_exports, init_task_queue_config, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, init_task_queue2, task_queue_exports, debugLogger, cmsTaskQueueLogger, init_task_queue3, ProdClientSafeStrategy, DevClientSafeStrategy, warned, VALID_DEPLOYMENT_NAME, isValidDeploymentName, ProdStrategy, DevStrategy, strategyCache;
|
|
1051
1051
|
var init_init = __esm({
|
|
1052
1052
|
"dist/cli/init.js"() {
|
|
1053
1053
|
"use strict";
|
|
@@ -1060,6 +1060,12 @@ var init_init = __esm({
|
|
|
1060
1060
|
for (var name in all)
|
|
1061
1061
|
__defProp2(target, name, { get: all[name], enumerable: true });
|
|
1062
1062
|
};
|
|
1063
|
+
init_logger = __esm2({
|
|
1064
|
+
"dist/utils/logger.js"() {
|
|
1065
|
+
"use strict";
|
|
1066
|
+
active = console;
|
|
1067
|
+
}
|
|
1068
|
+
});
|
|
1063
1069
|
init_types = __esm2({
|
|
1064
1070
|
"dist/config/types.js"() {
|
|
1065
1071
|
"use strict";
|
|
@@ -1068,7 +1074,6 @@ var init_init = __esm({
|
|
|
1068
1074
|
"number",
|
|
1069
1075
|
"boolean",
|
|
1070
1076
|
"datetime",
|
|
1071
|
-
"rich-text",
|
|
1072
1077
|
"markdown",
|
|
1073
1078
|
"mdx",
|
|
1074
1079
|
"code"
|
|
@@ -1353,11 +1358,18 @@ var init_init = __esm({
|
|
|
1353
1358
|
init_types2();
|
|
1354
1359
|
}
|
|
1355
1360
|
});
|
|
1361
|
+
init_mode_env = __esm2({
|
|
1362
|
+
"dist/operating-mode/mode-env.js"() {
|
|
1363
|
+
"use strict";
|
|
1364
|
+
init_logger();
|
|
1365
|
+
}
|
|
1366
|
+
});
|
|
1356
1367
|
init_validation = __esm2({
|
|
1357
1368
|
"dist/config/validation.js"() {
|
|
1358
1369
|
"use strict";
|
|
1359
1370
|
init_config();
|
|
1360
1371
|
init_flatten();
|
|
1372
|
+
init_mode_env();
|
|
1361
1373
|
}
|
|
1362
1374
|
});
|
|
1363
1375
|
init_helpers = __esm2({
|
|
@@ -1700,7 +1712,7 @@ var init_init = __esm({
|
|
|
1700
1712
|
return true;
|
|
1701
1713
|
}
|
|
1702
1714
|
};
|
|
1703
|
-
|
|
1715
|
+
init_logger();
|
|
1704
1716
|
warned = false;
|
|
1705
1717
|
VALID_DEPLOYMENT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
1706
1718
|
isValidDeploymentName = (name) => VALID_DEPLOYMENT_NAME.test(name) && !name.includes("..") && !name.endsWith(".") && !name.endsWith(".lock");
|
|
@@ -1814,6 +1826,7 @@ var init_init = __esm({
|
|
|
1814
1826
|
}
|
|
1815
1827
|
};
|
|
1816
1828
|
strategyCache = /* @__PURE__ */ new Map();
|
|
1829
|
+
init_mode_env();
|
|
1817
1830
|
init_fs2();
|
|
1818
1831
|
init_error2();
|
|
1819
1832
|
}
|
|
@@ -1831,62 +1844,63 @@ import { isAbsolute as isAbsolute2 } from "pathe";
|
|
|
1831
1844
|
import { z as z42 } from "zod";
|
|
1832
1845
|
import { z as z52 } from "zod";
|
|
1833
1846
|
import { z as z6 } from "zod";
|
|
1834
|
-
import
|
|
1835
|
-
import
|
|
1847
|
+
import path82 from "node:path";
|
|
1848
|
+
import path19 from "node:path";
|
|
1836
1849
|
import { createJiti as createJiti2 } from "jiti";
|
|
1837
1850
|
import fs16 from "node:fs/promises";
|
|
1838
|
-
import
|
|
1839
|
-
import
|
|
1840
|
-
import
|
|
1851
|
+
import path18 from "node:path";
|
|
1852
|
+
import fs9 from "node:fs/promises";
|
|
1853
|
+
import path10 from "node:path";
|
|
1841
1854
|
import matter from "gray-matter";
|
|
1842
1855
|
import { parse as yamlParse, stringify as yamlStringify } from "yaml";
|
|
1843
1856
|
import fs7 from "node:fs/promises";
|
|
1844
1857
|
import path9 from "node:path";
|
|
1858
|
+
import path32 from "node:path";
|
|
1845
1859
|
import fs23 from "node:fs/promises";
|
|
1846
1860
|
import path22 from "node:path";
|
|
1847
|
-
import
|
|
1848
|
-
import path32 from "node:path";
|
|
1861
|
+
import lockfile from "proper-lockfile";
|
|
1849
1862
|
import fs32 from "node:fs/promises";
|
|
1850
1863
|
import path42 from "node:path";
|
|
1851
|
-
import {
|
|
1852
|
-
import fs52 from "node:fs/promises";
|
|
1864
|
+
import { generate } from "short-uuid";
|
|
1853
1865
|
import path52 from "node:path";
|
|
1854
|
-
import
|
|
1866
|
+
import fs42 from "node:fs/promises";
|
|
1867
|
+
import path62 from "node:path";
|
|
1868
|
+
import { randomUUID } from "node:crypto";
|
|
1869
|
+
import fs62 from "node:fs/promises";
|
|
1870
|
+
import path72 from "node:path";
|
|
1871
|
+
import { promises as fs52 } from "fs";
|
|
1855
1872
|
import { join as join3 } from "pathe";
|
|
1856
1873
|
import { z as z7 } from "zod";
|
|
1857
1874
|
import chokidar from "chokidar";
|
|
1858
|
-
import fs62 from "node:fs/promises";
|
|
1859
|
-
import { sep, resolve, relative } from "node:path";
|
|
1860
1875
|
import fs72 from "node:fs/promises";
|
|
1861
|
-
import
|
|
1876
|
+
import { sep, resolve, relative } from "node:path";
|
|
1877
|
+
import fs8 from "node:fs/promises";
|
|
1862
1878
|
import path92 from "node:path";
|
|
1863
|
-
import
|
|
1864
|
-
import { minimatch } from "minimatch";
|
|
1865
|
-
import path15 from "node:path";
|
|
1866
|
-
import fs12 from "node:fs/promises";
|
|
1867
|
-
import path12 from "node:path";
|
|
1879
|
+
import path11 from "node:path";
|
|
1868
1880
|
import fs10 from "node:fs/promises";
|
|
1869
|
-
import
|
|
1881
|
+
import { minimatch } from "minimatch";
|
|
1882
|
+
import path16 from "node:path";
|
|
1883
|
+
import fs13 from "node:fs/promises";
|
|
1884
|
+
import path14 from "node:path";
|
|
1870
1885
|
import fs11 from "node:fs/promises";
|
|
1871
|
-
import
|
|
1886
|
+
import path12 from "node:path";
|
|
1887
|
+
import fs12 from "node:fs/promises";
|
|
1888
|
+
import path13 from "node:path";
|
|
1872
1889
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1873
|
-
import
|
|
1890
|
+
import lockfile2 from "proper-lockfile";
|
|
1874
1891
|
import fs14 from "node:fs/promises";
|
|
1875
|
-
import
|
|
1892
|
+
import path15 from "node:path";
|
|
1876
1893
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1877
1894
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
1878
|
-
import fs13 from "node:fs/promises";
|
|
1879
|
-
import path13 from "node:path";
|
|
1880
|
-
import lockfile2 from "proper-lockfile";
|
|
1881
1895
|
import fs15 from "node:fs/promises";
|
|
1882
|
-
import
|
|
1896
|
+
import path17 from "node:path";
|
|
1883
1897
|
import matter2 from "gray-matter";
|
|
1884
1898
|
import { parse as yamlParse2 } from "yaml";
|
|
1885
|
-
function normalizeFilesystemPath(
|
|
1886
|
-
return
|
|
1899
|
+
function normalizeFilesystemPath(path202) {
|
|
1900
|
+
return path202.split(/[\\/]+/).filter(Boolean).join("/");
|
|
1887
1901
|
}
|
|
1888
|
-
function hasTraversalSequence(
|
|
1889
|
-
const normalized = normalizeFilesystemPath(
|
|
1902
|
+
function hasTraversalSequence(path202) {
|
|
1903
|
+
const normalized = normalizeFilesystemPath(path202);
|
|
1890
1904
|
return normalized.includes("..");
|
|
1891
1905
|
}
|
|
1892
1906
|
function createLogicalPath(...segments) {
|
|
@@ -1896,14 +1910,14 @@ function createLogicalPath(...segments) {
|
|
|
1896
1910
|
}
|
|
1897
1911
|
return normalized;
|
|
1898
1912
|
}
|
|
1899
|
-
function trimSlashes(
|
|
1913
|
+
function trimSlashes(path202) {
|
|
1900
1914
|
let start = 0;
|
|
1901
|
-
let end =
|
|
1902
|
-
while (start < end &&
|
|
1915
|
+
let end = path202.length;
|
|
1916
|
+
while (start < end && path202[start] === "/")
|
|
1903
1917
|
start++;
|
|
1904
|
-
while (end > start &&
|
|
1918
|
+
while (end > start && path202[end - 1] === "/")
|
|
1905
1919
|
end--;
|
|
1906
|
-
return
|
|
1920
|
+
return path202.slice(start, end);
|
|
1907
1921
|
}
|
|
1908
1922
|
function canopyLogWarn2(...args) {
|
|
1909
1923
|
active2.warn(...args);
|
|
@@ -1911,6 +1925,28 @@ function canopyLogWarn2(...args) {
|
|
|
1911
1925
|
function canopyLogError(...args) {
|
|
1912
1926
|
active2.error(...args);
|
|
1913
1927
|
}
|
|
1928
|
+
function readModeEnv() {
|
|
1929
|
+
if (typeof process === "undefined" || typeof process.env === "undefined")
|
|
1930
|
+
return void 0;
|
|
1931
|
+
const inBrowser = typeof window !== "undefined";
|
|
1932
|
+
const name = inBrowser ? BROWSER_MODE_ENV_VAR : SERVER_MODE_ENV_VAR;
|
|
1933
|
+
const raw = inBrowser ? process.env.NEXT_PUBLIC_CANOPY_MODE : process.env.CANOPY_MODE;
|
|
1934
|
+
const value = raw?.trim();
|
|
1935
|
+
return value ? { name, value } : void 0;
|
|
1936
|
+
}
|
|
1937
|
+
function resolveOperatingMode(configMode) {
|
|
1938
|
+
const env = readModeEnv();
|
|
1939
|
+
if (!env)
|
|
1940
|
+
return configMode;
|
|
1941
|
+
if (env.value !== "prod" && env.value !== "dev") {
|
|
1942
|
+
throw new Error(`CanopyCMS: invalid ${env.name}=${JSON.stringify(env.value)}. It must be exactly "prod" or "dev". Refusing to fall back to the configured mode (${JSON.stringify(configMode)}): a typo here would silently deploy dev auth semantics.`);
|
|
1943
|
+
}
|
|
1944
|
+
if (env.value !== configMode && !warned2) {
|
|
1945
|
+
canopyLogWarn2(`CanopyCMS: ${env.name}="${env.value}" overrides config.mode="${configMode}". The environment wins by design \u2014 it is the per-deployment value, while canopycms.config.ts is shared by local dev, the image build and the deployment. See operating-mode/mode-env.ts.`);
|
|
1946
|
+
warned2 = true;
|
|
1947
|
+
}
|
|
1948
|
+
return env.value;
|
|
1949
|
+
}
|
|
1914
1950
|
function clientOperatingStrategy(mode) {
|
|
1915
1951
|
const cached = clientStrategyCache.get(mode);
|
|
1916
1952
|
if (cached)
|
|
@@ -1937,9 +1973,9 @@ function clearClientStrategyCache() {
|
|
|
1937
1973
|
function resolveDeploymentName2(config, modeDefault) {
|
|
1938
1974
|
const envValue = process.env.CANOPYCMS_DEPLOYMENT_NAME?.trim();
|
|
1939
1975
|
const configValue = config.deploymentName;
|
|
1940
|
-
if (envValue && configValue && envValue !== configValue && !
|
|
1976
|
+
if (envValue && configValue && envValue !== configValue && !warned22) {
|
|
1941
1977
|
canopyLogWarn2(`CanopyCMS: CANOPYCMS_DEPLOYMENT_NAME ("${envValue}") differs from config.deploymentName ("${configValue}") \u2014 using the env var (infra-stamped env wins over shared-repo config by design; see resolveDeploymentName's doc comment). Update config.deploymentName to match if this mismatch was unintentional.`);
|
|
1942
|
-
|
|
1978
|
+
warned22 = true;
|
|
1943
1979
|
}
|
|
1944
1980
|
const resolved = envValue || configValue || modeDefault;
|
|
1945
1981
|
if (!isValidDeploymentName2(resolved)) {
|
|
@@ -2000,6 +2036,70 @@ async function withLock(key, fn) {
|
|
|
2000
2036
|
locks.delete(key);
|
|
2001
2037
|
}
|
|
2002
2038
|
}
|
|
2039
|
+
function getErrorMessage3(err) {
|
|
2040
|
+
if (err instanceof Error) {
|
|
2041
|
+
return err.message;
|
|
2042
|
+
}
|
|
2043
|
+
if (typeof err === "string") {
|
|
2044
|
+
return err;
|
|
2045
|
+
}
|
|
2046
|
+
return String(err);
|
|
2047
|
+
}
|
|
2048
|
+
function isNodeError3(err) {
|
|
2049
|
+
return err instanceof Error && "code" in err;
|
|
2050
|
+
}
|
|
2051
|
+
function isNotFoundError3(err) {
|
|
2052
|
+
return isNodeError3(err) && err.code === "ENOENT";
|
|
2053
|
+
}
|
|
2054
|
+
function isFileExistsError2(err) {
|
|
2055
|
+
return isNodeError3(err) && err.code === "EEXIST";
|
|
2056
|
+
}
|
|
2057
|
+
async function acquireProvisioningLock(lockTargetDir2, lockName) {
|
|
2058
|
+
await fs23.mkdir(lockTargetDir2, { recursive: true });
|
|
2059
|
+
return lockfile.lock(lockTargetDir2, {
|
|
2060
|
+
lockfilePath: path22.join(lockTargetDir2, lockName),
|
|
2061
|
+
retries: { retries: 600, factor: 1, minTimeout: 300, maxTimeout: 800, randomize: true },
|
|
2062
|
+
stale: 3e4
|
|
2063
|
+
});
|
|
2064
|
+
}
|
|
2065
|
+
async function tryAcquireProvisioningLock(lockTargetDir2, lockName) {
|
|
2066
|
+
await fs23.mkdir(lockTargetDir2, { recursive: true });
|
|
2067
|
+
return lockfile.lock(lockTargetDir2, {
|
|
2068
|
+
lockfilePath: path22.join(lockTargetDir2, lockName),
|
|
2069
|
+
retries: 0,
|
|
2070
|
+
stale: 3e4
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
function lockTargetDir(branchRoot) {
|
|
2074
|
+
return path32.join(path32.resolve(branchRoot), META_DIR);
|
|
2075
|
+
}
|
|
2076
|
+
function tryAcquireContentWriteLock(branchRoot) {
|
|
2077
|
+
return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME);
|
|
2078
|
+
}
|
|
2079
|
+
async function acquireContentWriteLock(branchRoot, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS) {
|
|
2080
|
+
const startedAt = Date.now();
|
|
2081
|
+
for (let attempt = 0; ; attempt++) {
|
|
2082
|
+
try {
|
|
2083
|
+
return await tryAcquireContentWriteLock(branchRoot);
|
|
2084
|
+
} catch (err) {
|
|
2085
|
+
if (!isNodeError3(err) || err.code !== "ELOCKED")
|
|
2086
|
+
throw err;
|
|
2087
|
+
const remaining = waitMs - (Date.now() - startedAt);
|
|
2088
|
+
if (remaining <= 0)
|
|
2089
|
+
throw new ContentWriteLockBusyError();
|
|
2090
|
+
const base = Math.min(40 * 2 ** attempt, 250);
|
|
2091
|
+
await sleep(Math.min(base * (0.5 + Math.random()), remaining));
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
async function withContentWriteLock(branchRoot, fn, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS) {
|
|
2096
|
+
const release = await acquireContentWriteLock(branchRoot, waitMs);
|
|
2097
|
+
try {
|
|
2098
|
+
return await fn();
|
|
2099
|
+
} finally {
|
|
2100
|
+
await release();
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2003
2103
|
function flattenGroupFields(fields) {
|
|
2004
2104
|
const result = [];
|
|
2005
2105
|
for (const field of fields) {
|
|
@@ -2025,24 +2125,6 @@ function generateId() {
|
|
|
2025
2125
|
const full = generate();
|
|
2026
2126
|
return full.substring(0, 12);
|
|
2027
2127
|
}
|
|
2028
|
-
function getErrorMessage3(err) {
|
|
2029
|
-
if (err instanceof Error) {
|
|
2030
|
-
return err.message;
|
|
2031
|
-
}
|
|
2032
|
-
if (typeof err === "string") {
|
|
2033
|
-
return err;
|
|
2034
|
-
}
|
|
2035
|
-
return String(err);
|
|
2036
|
-
}
|
|
2037
|
-
function isNodeError3(err) {
|
|
2038
|
-
return err instanceof Error && "code" in err;
|
|
2039
|
-
}
|
|
2040
|
-
function isNotFoundError3(err) {
|
|
2041
|
-
return isNodeError3(err) && err.code === "ENOENT";
|
|
2042
|
-
}
|
|
2043
|
-
function isFileExistsError2(err) {
|
|
2044
|
-
return isNodeError3(err) && err.code === "EEXIST";
|
|
2045
|
-
}
|
|
2046
2128
|
function toLogicalCollectionPath(physicalPath) {
|
|
2047
2129
|
if (physicalPath === ".")
|
|
2048
2130
|
return EMPTY_LOGICAL_PATH;
|
|
@@ -2067,7 +2149,7 @@ function extractIdFromFilename(filename) {
|
|
|
2067
2149
|
}
|
|
2068
2150
|
async function resolveCollectionPath(root, logicalPath) {
|
|
2069
2151
|
const fs172 = await import("node:fs/promises");
|
|
2070
|
-
const
|
|
2152
|
+
const path202 = await import("node:path");
|
|
2071
2153
|
const segments = logicalPath.split("/").filter(Boolean);
|
|
2072
2154
|
let currentPath = root;
|
|
2073
2155
|
for (const segment of segments) {
|
|
@@ -2080,7 +2162,7 @@ async function resolveCollectionPath(root, logicalPath) {
|
|
|
2080
2162
|
return logicalName === segment.toLowerCase();
|
|
2081
2163
|
});
|
|
2082
2164
|
if (matchingDir) {
|
|
2083
|
-
currentPath =
|
|
2165
|
+
currentPath = path202.join(currentPath, matchingDir.name);
|
|
2084
2166
|
} else {
|
|
2085
2167
|
return null;
|
|
2086
2168
|
}
|
|
@@ -2130,7 +2212,7 @@ function extractSlugFromFilename(filename, entryTypeName) {
|
|
|
2130
2212
|
return filename.toLowerCase();
|
|
2131
2213
|
}
|
|
2132
2214
|
function registerContentIndexForInvalidation(root, target) {
|
|
2133
|
-
const rootKey =
|
|
2215
|
+
const rootKey = path52.resolve(root);
|
|
2134
2216
|
let refs = registry.get(rootKey);
|
|
2135
2217
|
if (!refs) {
|
|
2136
2218
|
refs = /* @__PURE__ */ new Set();
|
|
@@ -2141,8 +2223,8 @@ function registerContentIndexForInvalidation(root, target) {
|
|
|
2141
2223
|
finalization.register(target, { rootKey, ref });
|
|
2142
2224
|
}
|
|
2143
2225
|
function invalidateContentIndexesForRoot(root) {
|
|
2144
|
-
const rootKey =
|
|
2145
|
-
const prefix = rootKey +
|
|
2226
|
+
const rootKey = path52.resolve(root);
|
|
2227
|
+
const prefix = rootKey + path52.sep;
|
|
2146
2228
|
for (const [storeRoot, refs] of registry) {
|
|
2147
2229
|
if (storeRoot !== rootKey && !storeRoot.startsWith(prefix))
|
|
2148
2230
|
continue;
|
|
@@ -2162,7 +2244,7 @@ function createDebugLogger2(options) {
|
|
|
2162
2244
|
return new DebugLogger2(options);
|
|
2163
2245
|
}
|
|
2164
2246
|
function resourceGenerationPath(root, resource) {
|
|
2165
|
-
return
|
|
2247
|
+
return path62.resolve(root, META_DIR2, `${resource}.generation`);
|
|
2166
2248
|
}
|
|
2167
2249
|
async function bumpResourceGeneration(root, resource, options) {
|
|
2168
2250
|
const token = randomUUID();
|
|
@@ -2180,7 +2262,7 @@ async function bumpResourceGeneration(root, resource, options) {
|
|
|
2180
2262
|
}
|
|
2181
2263
|
async function readResourceGeneration(root, resource) {
|
|
2182
2264
|
try {
|
|
2183
|
-
const token = await
|
|
2265
|
+
const token = await fs42.readFile(resourceGenerationPath(root, resource), "utf-8");
|
|
2184
2266
|
return { ok: true, token };
|
|
2185
2267
|
} catch (err) {
|
|
2186
2268
|
if (isNodeError3(err) && err.code === "ENOENT")
|
|
@@ -2200,7 +2282,7 @@ function stripEmbeddedIdFromName(name) {
|
|
|
2200
2282
|
async function scanForCollectionMeta(baseDir, relativePath = "") {
|
|
2201
2283
|
const collections = [];
|
|
2202
2284
|
try {
|
|
2203
|
-
const entries = await
|
|
2285
|
+
const entries = await fs52.readdir(baseDir, { withFileTypes: true });
|
|
2204
2286
|
for (const entry of entries) {
|
|
2205
2287
|
if (!entry.isDirectory())
|
|
2206
2288
|
continue;
|
|
@@ -2211,8 +2293,8 @@ async function scanForCollectionMeta(baseDir, relativePath = "") {
|
|
|
2211
2293
|
const absolutePath = join3(baseDir, folderName);
|
|
2212
2294
|
const metaPath = join3(absolutePath, ".collection.json");
|
|
2213
2295
|
try {
|
|
2214
|
-
await
|
|
2215
|
-
const content = await
|
|
2296
|
+
await fs52.access(metaPath);
|
|
2297
|
+
const content = await fs52.readFile(metaPath, "utf-8");
|
|
2216
2298
|
const parsed = JSON.parse(content);
|
|
2217
2299
|
const meta = collectionMetaSchema.parse(parsed);
|
|
2218
2300
|
collections.push({
|
|
@@ -2244,7 +2326,7 @@ async function loadCollectionMetaFiles(contentRoot) {
|
|
|
2244
2326
|
let root = null;
|
|
2245
2327
|
const rootMetaPath = join3(contentRoot, ".collection.json");
|
|
2246
2328
|
try {
|
|
2247
|
-
await
|
|
2329
|
+
await fs52.access(rootMetaPath);
|
|
2248
2330
|
} catch (err) {
|
|
2249
2331
|
if (err.code === "ENOENT") {
|
|
2250
2332
|
} else {
|
|
@@ -2252,7 +2334,7 @@ async function loadCollectionMetaFiles(contentRoot) {
|
|
|
2252
2334
|
}
|
|
2253
2335
|
}
|
|
2254
2336
|
try {
|
|
2255
|
-
const content = await
|
|
2337
|
+
const content = await fs52.readFile(rootMetaPath, "utf-8");
|
|
2256
2338
|
const parsed = JSON.parse(content);
|
|
2257
2339
|
root = rootCollectionMetaSchema.parse(parsed);
|
|
2258
2340
|
} catch (err) {
|
|
@@ -2342,16 +2424,16 @@ function isValidSchema(schema) {
|
|
|
2342
2424
|
async function isStaleByMtime(dir, cachedAt) {
|
|
2343
2425
|
let entries;
|
|
2344
2426
|
try {
|
|
2345
|
-
entries = await
|
|
2427
|
+
entries = await fs62.readdir(dir, { recursive: true, encoding: "utf-8" });
|
|
2346
2428
|
} catch {
|
|
2347
2429
|
return true;
|
|
2348
2430
|
}
|
|
2349
2431
|
for (const entry of entries) {
|
|
2350
2432
|
if (!entry.endsWith(".collection.json"))
|
|
2351
2433
|
continue;
|
|
2352
|
-
const full =
|
|
2434
|
+
const full = path72.join(dir, entry);
|
|
2353
2435
|
try {
|
|
2354
|
-
const stat = await
|
|
2436
|
+
const stat = await fs62.stat(full);
|
|
2355
2437
|
if (stat.mtimeMs > cachedAt.getTime())
|
|
2356
2438
|
return true;
|
|
2357
2439
|
} catch {
|
|
@@ -2374,7 +2456,7 @@ async function invalidateBranchContentCaches(root) {
|
|
|
2374
2456
|
}
|
|
2375
2457
|
async function filePathExists3(filePath) {
|
|
2376
2458
|
try {
|
|
2377
|
-
await
|
|
2459
|
+
await fs72.stat(filePath);
|
|
2378
2460
|
return true;
|
|
2379
2461
|
} catch (err) {
|
|
2380
2462
|
if (isNotFoundError3(err))
|
|
@@ -2411,11 +2493,11 @@ function resolveBranchPath(options) {
|
|
|
2411
2493
|
const safeBranch = sanitizeBranchName(options.branchName);
|
|
2412
2494
|
const strategy = operatingStrategy2(options.mode);
|
|
2413
2495
|
const baseRoot = resolveContentBranchesRoot(options.mode, options.basePathOverride);
|
|
2414
|
-
const normalizedBase =
|
|
2415
|
-
const baseWithSep = normalizedBase.endsWith(
|
|
2496
|
+
const normalizedBase = path92.resolve(baseRoot);
|
|
2497
|
+
const baseWithSep = normalizedBase.endsWith(path92.sep) ? normalizedBase : `${normalizedBase}${path92.sep}`;
|
|
2416
2498
|
const branchRoot = strategy.getContentBranchRoot(safeBranch, options.basePathOverride);
|
|
2417
2499
|
const withinBase = (target) => {
|
|
2418
|
-
const resolved =
|
|
2500
|
+
const resolved = path92.resolve(target);
|
|
2419
2501
|
return resolved === normalizedBase || resolved.startsWith(baseWithSep);
|
|
2420
2502
|
};
|
|
2421
2503
|
if (!withinBase(branchRoot)) {
|
|
@@ -2425,7 +2507,7 @@ function resolveBranchPath(options) {
|
|
|
2425
2507
|
}
|
|
2426
2508
|
async function ensureBranchRoot(options) {
|
|
2427
2509
|
const result = resolveBranchPath(options);
|
|
2428
|
-
await
|
|
2510
|
+
await fs8.mkdir(result.branchRoot, { recursive: true });
|
|
2429
2511
|
return result;
|
|
2430
2512
|
}
|
|
2431
2513
|
async function withLocks(keys, fn) {
|
|
@@ -2626,7 +2708,7 @@ function entryToMarkdown(entry, config) {
|
|
|
2626
2708
|
}
|
|
2627
2709
|
function renderMarkdownEntry(entry, config, skipFields) {
|
|
2628
2710
|
const parts = [];
|
|
2629
|
-
const bodyFieldTypes = /* @__PURE__ */ new Set(["
|
|
2711
|
+
const bodyFieldTypes = /* @__PURE__ */ new Set(["markdown", "mdx"]);
|
|
2630
2712
|
const metadataFields = flattenGroupFields(entry.fields).filter((f) => !bodyFieldTypes.has(f.type) && !skipFields.has(f.name));
|
|
2631
2713
|
for (const field of metadataFields) {
|
|
2632
2714
|
const value = entry.data[field.name];
|
|
@@ -2698,7 +2780,6 @@ ${String(value)}`;
|
|
|
2698
2780
|
return `${heading} ${label}${descriptionLine}
|
|
2699
2781
|
|
|
2700
2782
|
${value ? "Yes" : "No"}`;
|
|
2701
|
-
case "rich-text":
|
|
2702
2783
|
case "markdown":
|
|
2703
2784
|
case "mdx":
|
|
2704
2785
|
return `${heading} ${label}${descriptionLine}
|
|
@@ -2957,8 +3038,8 @@ function computeEntryUrl(collection, slug, contentRoot) {
|
|
|
2957
3038
|
if (slug && slug !== "index") {
|
|
2958
3039
|
segments.push(slug);
|
|
2959
3040
|
}
|
|
2960
|
-
const
|
|
2961
|
-
return
|
|
3041
|
+
const path202 = segments.length > 0 ? `/${segments.join("/")}` : "/";
|
|
3042
|
+
return path202.toLowerCase();
|
|
2962
3043
|
}
|
|
2963
3044
|
function resolveEntryUrl(location, contentRoot) {
|
|
2964
3045
|
return computeEntryUrl(location.collection ?? "", location.slug ?? "", contentRoot);
|
|
@@ -3126,7 +3207,7 @@ async function processCollection(store, collection, flatSchema, contentRoot, con
|
|
|
3126
3207
|
const listed = await store.getCollectionEntryPaths(collection.logicalPath);
|
|
3127
3208
|
const directEntries = listed.filter((e) => e.collection === collection.logicalPath);
|
|
3128
3209
|
for (const listEntry of directEntries) {
|
|
3129
|
-
const entryTypeName = extractEntryTypeFromFilename(
|
|
3210
|
+
const entryTypeName = extractEntryTypeFromFilename(path11.basename(listEntry.relativePath));
|
|
3130
3211
|
if (!entryTypeName)
|
|
3131
3212
|
continue;
|
|
3132
3213
|
if (config?.exclude?.entryTypes?.includes(entryTypeName))
|
|
@@ -3144,7 +3225,7 @@ async function processCollection(store, collection, flatSchema, contentRoot, con
|
|
|
3144
3225
|
}
|
|
3145
3226
|
if (config?.exclude?.where?.(aiEntry))
|
|
3146
3227
|
continue;
|
|
3147
|
-
await runEntryTransform(aiEntry, doc.absolutePath, extractIdFromFilename(
|
|
3228
|
+
await runEntryTransform(aiEntry, doc.absolutePath, extractIdFromFilename(path11.basename(listEntry.relativePath)), config);
|
|
3148
3229
|
entries.push(aiEntry);
|
|
3149
3230
|
const entryFilePath = `${cleanPath}/${listEntry.slug}.md`;
|
|
3150
3231
|
const entryMarkdown = entryToMarkdown(aiEntry, config);
|
|
@@ -3195,7 +3276,7 @@ async function processRootEntries(store, rootCollection, contentRoot, config, id
|
|
|
3195
3276
|
const listed = await store.getCollectionEntryPaths(rootCollection.logicalPath);
|
|
3196
3277
|
const directEntries = listed.filter((e) => e.collection === rootCollection.logicalPath);
|
|
3197
3278
|
for (const listEntry of directEntries) {
|
|
3198
|
-
const entryTypeName = extractEntryTypeFromFilename(
|
|
3279
|
+
const entryTypeName = extractEntryTypeFromFilename(path11.basename(listEntry.relativePath));
|
|
3199
3280
|
if (!entryTypeName)
|
|
3200
3281
|
continue;
|
|
3201
3282
|
if (config?.exclude?.entryTypes?.includes(entryTypeName))
|
|
@@ -3213,7 +3294,7 @@ async function processRootEntries(store, rootCollection, contentRoot, config, id
|
|
|
3213
3294
|
}
|
|
3214
3295
|
if (config?.exclude?.where?.(aiEntry))
|
|
3215
3296
|
continue;
|
|
3216
|
-
await runEntryTransform(aiEntry, doc.absolutePath, extractIdFromFilename(
|
|
3297
|
+
await runEntryTransform(aiEntry, doc.absolutePath, extractIdFromFilename(path11.basename(listEntry.relativePath)), config);
|
|
3217
3298
|
entries.push(aiEntry);
|
|
3218
3299
|
const entryFilePath = `${listEntry.slug}.md`;
|
|
3219
3300
|
const entryMarkdown = entryToMarkdown(aiEntry, config);
|
|
@@ -3261,19 +3342,19 @@ function docToAIEntry(doc, slug, entryTypeName, entryTypeConfig, cleanCollection
|
|
|
3261
3342
|
};
|
|
3262
3343
|
}
|
|
3263
3344
|
function makeReadSibling(dir) {
|
|
3264
|
-
const resolvedDir =
|
|
3345
|
+
const resolvedDir = path11.resolve(dir);
|
|
3265
3346
|
return async (name) => {
|
|
3266
|
-
if (!name || name.includes("/") || name.includes("\\") ||
|
|
3347
|
+
if (!name || name.includes("/") || name.includes("\\") || path11.isAbsolute(name) || hasTraversalSequence(name)) {
|
|
3267
3348
|
return null;
|
|
3268
3349
|
}
|
|
3269
|
-
const abs =
|
|
3270
|
-
if (
|
|
3350
|
+
const abs = path11.resolve(resolvedDir, name);
|
|
3351
|
+
if (path11.dirname(abs) !== resolvedDir)
|
|
3271
3352
|
return null;
|
|
3272
3353
|
try {
|
|
3273
|
-
const stats = await
|
|
3354
|
+
const stats = await fs10.lstat(abs);
|
|
3274
3355
|
if (!stats.isFile())
|
|
3275
3356
|
return null;
|
|
3276
|
-
return await
|
|
3357
|
+
return await fs10.readFile(abs, "utf8");
|
|
3277
3358
|
} catch (err) {
|
|
3278
3359
|
if (isNodeError3(err) && (err.code === "ENOENT" || err.code === "ENOTDIR"))
|
|
3279
3360
|
return null;
|
|
@@ -3287,7 +3368,7 @@ async function runEntryTransform(entry, absolutePath, contentId, config) {
|
|
|
3287
3368
|
return;
|
|
3288
3369
|
const ctx = {
|
|
3289
3370
|
contentId: contentId ?? "",
|
|
3290
|
-
readSibling: makeReadSibling(
|
|
3371
|
+
readSibling: makeReadSibling(path11.dirname(absolutePath))
|
|
3291
3372
|
};
|
|
3292
3373
|
try {
|
|
3293
3374
|
const appended = await transform(entry, ctx);
|
|
@@ -3328,29 +3409,29 @@ async function writeOccJsonFile(filePath, payload, options) {
|
|
|
3328
3409
|
const writeId = randomUUID2();
|
|
3329
3410
|
const data = { ...payload, version: newVersion, writeId };
|
|
3330
3411
|
const content = JSON.stringify(data, null, 2) + (trailingNewline ? "\n" : "");
|
|
3331
|
-
const dir =
|
|
3332
|
-
await
|
|
3412
|
+
const dir = path13.dirname(filePath);
|
|
3413
|
+
await fs12.mkdir(dir, { recursive: true });
|
|
3333
3414
|
const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
3334
|
-
await
|
|
3415
|
+
await fs12.writeFile(tempPath, content, "utf-8");
|
|
3335
3416
|
if (expectedVersion === null) {
|
|
3336
3417
|
try {
|
|
3337
|
-
await
|
|
3418
|
+
await fs12.link(tempPath, filePath);
|
|
3338
3419
|
} catch (err) {
|
|
3339
|
-
await
|
|
3420
|
+
await fs12.unlink(tempPath).catch(() => {
|
|
3340
3421
|
});
|
|
3341
3422
|
if (isFileExistsError2(err)) {
|
|
3342
3423
|
throw new OccWriteConflictError();
|
|
3343
3424
|
}
|
|
3344
3425
|
throw err;
|
|
3345
3426
|
}
|
|
3346
|
-
await
|
|
3427
|
+
await fs12.unlink(tempPath).catch(() => {
|
|
3347
3428
|
});
|
|
3348
3429
|
return { version: newVersion, writeId };
|
|
3349
3430
|
}
|
|
3350
3431
|
try {
|
|
3351
3432
|
let currentVersion = null;
|
|
3352
3433
|
try {
|
|
3353
|
-
const current = JSON.parse(await
|
|
3434
|
+
const current = JSON.parse(await fs12.readFile(filePath, "utf-8"));
|
|
3354
3435
|
currentVersion = current.version ?? 0;
|
|
3355
3436
|
} catch {
|
|
3356
3437
|
currentVersion = null;
|
|
@@ -3358,16 +3439,16 @@ async function writeOccJsonFile(filePath, payload, options) {
|
|
|
3358
3439
|
if (currentVersion !== expectedVersion) {
|
|
3359
3440
|
throw new OccWriteConflictError();
|
|
3360
3441
|
}
|
|
3361
|
-
await
|
|
3442
|
+
await fs12.rename(tempPath, filePath);
|
|
3362
3443
|
if (settleMs > 0) {
|
|
3363
3444
|
await new Promise((resolve2) => setTimeout(resolve2, settleMs));
|
|
3364
3445
|
}
|
|
3365
|
-
const afterWrite = JSON.parse(await
|
|
3446
|
+
const afterWrite = JSON.parse(await fs12.readFile(filePath, "utf-8"));
|
|
3366
3447
|
if (afterWrite.writeId !== writeId) {
|
|
3367
3448
|
throw new OccWriteConflictError();
|
|
3368
3449
|
}
|
|
3369
3450
|
} catch (err) {
|
|
3370
|
-
await
|
|
3451
|
+
await fs12.unlink(tempPath).catch(() => {
|
|
3371
3452
|
});
|
|
3372
3453
|
throw err;
|
|
3373
3454
|
}
|
|
@@ -3393,13 +3474,13 @@ async function withOccRetry(operation, options) {
|
|
|
3393
3474
|
throw new Error("Unreachable");
|
|
3394
3475
|
}
|
|
3395
3476
|
async function withOccFileLock(filePath, fn) {
|
|
3396
|
-
const dir =
|
|
3397
|
-
await
|
|
3477
|
+
const dir = path13.dirname(filePath);
|
|
3478
|
+
await fs12.mkdir(dir, { recursive: true });
|
|
3398
3479
|
const MAX_ATTEMPTS = 14;
|
|
3399
3480
|
let release = null;
|
|
3400
3481
|
for (let attempt = 1; release === null; attempt++) {
|
|
3401
3482
|
try {
|
|
3402
|
-
release = await
|
|
3483
|
+
release = await lockfile2.lock(filePath, {
|
|
3403
3484
|
lockfilePath: `${filePath}.lock`,
|
|
3404
3485
|
realpath: false,
|
|
3405
3486
|
stale: 1e4,
|
|
@@ -3449,6 +3530,9 @@ function isNetworkRemoteUrl(url) {
|
|
|
3449
3530
|
return false;
|
|
3450
3531
|
return COLON_BEFORE_SLASH_PATTERN.test(url);
|
|
3451
3532
|
}
|
|
3533
|
+
function isMissingRemoteRefFailure(message) {
|
|
3534
|
+
return MISSING_REMOTE_REF_REASONS.some((reason) => message.includes(reason));
|
|
3535
|
+
}
|
|
3452
3536
|
async function detectHeadBranch(repoRoot, fallback = "main") {
|
|
3453
3537
|
try {
|
|
3454
3538
|
const git = simpleGit3({ baseDir: repoRoot });
|
|
@@ -3466,14 +3550,6 @@ async function resolveBaseBranch(options) {
|
|
|
3466
3550
|
}
|
|
3467
3551
|
return "main";
|
|
3468
3552
|
}
|
|
3469
|
-
async function acquireProvisioningLock(lockTargetDir, lockName) {
|
|
3470
|
-
await fs13.mkdir(lockTargetDir, { recursive: true });
|
|
3471
|
-
return lockfile2.lock(lockTargetDir, {
|
|
3472
|
-
lockfilePath: path13.join(lockTargetDir, lockName),
|
|
3473
|
-
retries: { retries: 600, factor: 1, minTimeout: 300, maxTimeout: 800, randomize: true },
|
|
3474
|
-
stale: 3e4
|
|
3475
|
-
});
|
|
3476
|
-
}
|
|
3477
3553
|
function gitChildEnv(overrides) {
|
|
3478
3554
|
const env = {};
|
|
3479
3555
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -3483,8 +3559,8 @@ function gitChildEnv(overrides) {
|
|
|
3483
3559
|
return { ...env, ...FORCE_C_LOCALE, ...overrides };
|
|
3484
3560
|
}
|
|
3485
3561
|
async function ensureGitExcludePattern(repoPath, pattern) {
|
|
3486
|
-
const excludePath =
|
|
3487
|
-
await fs14.mkdir(
|
|
3562
|
+
const excludePath = path15.join(repoPath, ".git", "info", "exclude");
|
|
3563
|
+
await fs14.mkdir(path15.dirname(excludePath), { recursive: true });
|
|
3488
3564
|
let content = "";
|
|
3489
3565
|
try {
|
|
3490
3566
|
content = await fs14.readFile(excludePath, "utf-8");
|
|
@@ -3644,30 +3720,30 @@ function isValidImageCropValue(value) {
|
|
|
3644
3720
|
}
|
|
3645
3721
|
return isValidCropRect(x, y, w, h);
|
|
3646
3722
|
}
|
|
3647
|
-
function validateImageValue(field, value,
|
|
3723
|
+
function validateImageValue(field, value, path202) {
|
|
3648
3724
|
if (!isPlainRecord2(value)) {
|
|
3649
|
-
return [{ fieldPath:
|
|
3725
|
+
return [{ fieldPath: path202, message: "Expected an image object with { src, alt }" }];
|
|
3650
3726
|
}
|
|
3651
3727
|
const errors = [];
|
|
3652
3728
|
if (typeof value.src !== "string" || value.src.trim() === "") {
|
|
3653
|
-
errors.push({ fieldPath: `${
|
|
3729
|
+
errors.push({ fieldPath: `${path202}.src`, message: "Image src is required" });
|
|
3654
3730
|
}
|
|
3655
3731
|
if (typeof value.alt !== "string") {
|
|
3656
|
-
errors.push({ fieldPath: `${
|
|
3732
|
+
errors.push({ fieldPath: `${path202}.alt`, message: "Image alt text is required" });
|
|
3657
3733
|
} else if (value.alt.trim() === "" && field.altOptional !== true) {
|
|
3658
|
-
errors.push({ fieldPath: `${
|
|
3734
|
+
errors.push({ fieldPath: `${path202}.alt`, message: "Image alt text is required" });
|
|
3659
3735
|
}
|
|
3660
3736
|
if (value.width !== void 0 && !isPositiveInt(value.width)) {
|
|
3661
|
-
errors.push({ fieldPath: `${
|
|
3737
|
+
errors.push({ fieldPath: `${path202}.width`, message: "Image width must be a positive integer" });
|
|
3662
3738
|
}
|
|
3663
3739
|
if (value.height !== void 0 && !isPositiveInt(value.height)) {
|
|
3664
3740
|
errors.push({
|
|
3665
|
-
fieldPath: `${
|
|
3741
|
+
fieldPath: `${path202}.height`,
|
|
3666
3742
|
message: "Image height must be a positive integer"
|
|
3667
3743
|
});
|
|
3668
3744
|
}
|
|
3669
3745
|
if (value.crop !== void 0 && !isValidImageCropValue(value.crop)) {
|
|
3670
|
-
errors.push({ fieldPath: `${
|
|
3746
|
+
errors.push({ fieldPath: `${path202}.crop`, message: "Invalid image crop rect" });
|
|
3671
3747
|
}
|
|
3672
3748
|
return errors;
|
|
3673
3749
|
}
|
|
@@ -3678,44 +3754,44 @@ function referenceValueId(value) {
|
|
|
3678
3754
|
return value.id;
|
|
3679
3755
|
return void 0;
|
|
3680
3756
|
}
|
|
3681
|
-
function validateScalar(field, value,
|
|
3757
|
+
function validateScalar(field, value, path202) {
|
|
3682
3758
|
switch (field.type) {
|
|
3683
3759
|
case "number":
|
|
3684
3760
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
3685
|
-
return [{ fieldPath:
|
|
3761
|
+
return [{ fieldPath: path202, message: "Expected a number" }];
|
|
3686
3762
|
}
|
|
3687
3763
|
return [];
|
|
3688
3764
|
case "boolean":
|
|
3689
3765
|
if (typeof value !== "boolean") {
|
|
3690
|
-
return [{ fieldPath:
|
|
3766
|
+
return [{ fieldPath: path202, message: "Expected true or false" }];
|
|
3691
3767
|
}
|
|
3692
3768
|
return [];
|
|
3693
3769
|
case "datetime":
|
|
3694
3770
|
if (typeof value !== "string" || value !== "" && Number.isNaN(Date.parse(value))) {
|
|
3695
|
-
return [{ fieldPath:
|
|
3771
|
+
return [{ fieldPath: path202, message: "Expected a valid date/time string" }];
|
|
3696
3772
|
}
|
|
3697
3773
|
return [];
|
|
3698
3774
|
case "select": {
|
|
3699
3775
|
if (typeof value !== "string") {
|
|
3700
|
-
return [{ fieldPath:
|
|
3776
|
+
return [{ fieldPath: path202, message: "Expected a selection" }];
|
|
3701
3777
|
}
|
|
3702
3778
|
const options = selectOptionValues(field);
|
|
3703
3779
|
if (value !== "" && !options.includes(value)) {
|
|
3704
|
-
return [{ fieldPath:
|
|
3780
|
+
return [{ fieldPath: path202, message: `Must be one of: ${options.join(", ")}` }];
|
|
3705
3781
|
}
|
|
3706
3782
|
return [];
|
|
3707
3783
|
}
|
|
3708
3784
|
case "reference":
|
|
3709
3785
|
if (referenceValueId(value) === void 0) {
|
|
3710
|
-
return [{ fieldPath:
|
|
3786
|
+
return [{ fieldPath: path202, message: "Expected a reference id" }];
|
|
3711
3787
|
}
|
|
3712
3788
|
return [];
|
|
3713
3789
|
case "image":
|
|
3714
|
-
return validateImageValue(field, value,
|
|
3790
|
+
return validateImageValue(field, value, path202);
|
|
3715
3791
|
default:
|
|
3716
3792
|
if (STRING_FIELD_TYPES.has(field.type)) {
|
|
3717
3793
|
if (typeof value !== "string") {
|
|
3718
|
-
return [{ fieldPath:
|
|
3794
|
+
return [{ fieldPath: path202, message: "Expected text" }];
|
|
3719
3795
|
}
|
|
3720
3796
|
}
|
|
3721
3797
|
return [];
|
|
@@ -3738,12 +3814,12 @@ function validateEntryData(fields, data, pathPrefix = "") {
|
|
|
3738
3814
|
errors.push(...validateEntryData(field.fields, data, pathPrefix));
|
|
3739
3815
|
continue;
|
|
3740
3816
|
}
|
|
3741
|
-
const
|
|
3817
|
+
const path202 = pathPrefix ? `${pathPrefix}.${field.name}` : field.name;
|
|
3742
3818
|
const value = data[field.name];
|
|
3743
3819
|
const isRequired = "required" in field && field.required === true;
|
|
3744
3820
|
if (isRequired && isEmptyForRequired(field, value)) {
|
|
3745
3821
|
const message = Array.isArray(value) ? "At least one item is required" : "This field is required";
|
|
3746
|
-
errors.push({ fieldPath:
|
|
3822
|
+
errors.push({ fieldPath: path202, message });
|
|
3747
3823
|
continue;
|
|
3748
3824
|
}
|
|
3749
3825
|
if (value === void 0 || value === null)
|
|
@@ -3751,11 +3827,11 @@ function validateEntryData(fields, data, pathPrefix = "") {
|
|
|
3751
3827
|
if (field.type === "block") {
|
|
3752
3828
|
const blockField = field;
|
|
3753
3829
|
if (!Array.isArray(value)) {
|
|
3754
|
-
errors.push({ fieldPath:
|
|
3830
|
+
errors.push({ fieldPath: path202, message: "Expected a list of blocks" });
|
|
3755
3831
|
continue;
|
|
3756
3832
|
}
|
|
3757
3833
|
value.forEach((item, index) => {
|
|
3758
|
-
const itemPath = `${
|
|
3834
|
+
const itemPath = `${path202}[${index}]`;
|
|
3759
3835
|
if (!isPlainRecord2(item)) {
|
|
3760
3836
|
errors.push({ fieldPath: itemPath, message: "Expected a block object" });
|
|
3761
3837
|
return;
|
|
@@ -3777,11 +3853,11 @@ function validateEntryData(fields, data, pathPrefix = "") {
|
|
|
3777
3853
|
const objectField = field;
|
|
3778
3854
|
if (objectField.list) {
|
|
3779
3855
|
if (!Array.isArray(value)) {
|
|
3780
|
-
errors.push({ fieldPath:
|
|
3856
|
+
errors.push({ fieldPath: path202, message: "Expected a list of items" });
|
|
3781
3857
|
continue;
|
|
3782
3858
|
}
|
|
3783
3859
|
value.forEach((item, index) => {
|
|
3784
|
-
const itemPath = `${
|
|
3860
|
+
const itemPath = `${path202}[${index}]`;
|
|
3785
3861
|
if (!isPlainRecord2(item)) {
|
|
3786
3862
|
errors.push({ fieldPath: itemPath, message: "Expected an object" });
|
|
3787
3863
|
return;
|
|
@@ -3791,27 +3867,27 @@ function validateEntryData(fields, data, pathPrefix = "") {
|
|
|
3791
3867
|
}
|
|
3792
3868
|
});
|
|
3793
3869
|
} else if (!isPlainRecord2(value)) {
|
|
3794
|
-
errors.push({ fieldPath:
|
|
3870
|
+
errors.push({ fieldPath: path202, message: "Expected an object" });
|
|
3795
3871
|
} else if (objectField.fields) {
|
|
3796
|
-
errors.push(...validateEntryData(objectField.fields, value,
|
|
3872
|
+
errors.push(...validateEntryData(objectField.fields, value, path202));
|
|
3797
3873
|
}
|
|
3798
3874
|
continue;
|
|
3799
3875
|
}
|
|
3800
3876
|
if ("list" in field && field.list) {
|
|
3801
3877
|
if (!Array.isArray(value)) {
|
|
3802
|
-
errors.push({ fieldPath:
|
|
3878
|
+
errors.push({ fieldPath: path202, message: "Expected a list of values" });
|
|
3803
3879
|
continue;
|
|
3804
3880
|
}
|
|
3805
3881
|
value.forEach((item, index) => {
|
|
3806
|
-
errors.push(...validateScalar(field, item, `${
|
|
3882
|
+
errors.push(...validateScalar(field, item, `${path202}[${index}]`));
|
|
3807
3883
|
});
|
|
3808
3884
|
continue;
|
|
3809
3885
|
}
|
|
3810
3886
|
if (Array.isArray(value) && KNOWN_FIELD_TYPES.has(field.type)) {
|
|
3811
|
-
errors.push({ fieldPath:
|
|
3887
|
+
errors.push({ fieldPath: path202, message: "Expected a single value, not a list" });
|
|
3812
3888
|
continue;
|
|
3813
3889
|
}
|
|
3814
|
-
errors.push(...validateScalar(field, value,
|
|
3890
|
+
errors.push(...validateScalar(field, value, path202));
|
|
3815
3891
|
}
|
|
3816
3892
|
return errors;
|
|
3817
3893
|
}
|
|
@@ -3877,14 +3953,14 @@ async function generateAIContentFiles(options) {
|
|
|
3877
3953
|
config: aiConfig2,
|
|
3878
3954
|
entryLinkUrl: config.entryLinkUrl
|
|
3879
3955
|
});
|
|
3880
|
-
const absoluteOutputDir =
|
|
3956
|
+
const absoluteOutputDir = path18.resolve(outputDir) + path18.sep;
|
|
3881
3957
|
let fileCount = 0;
|
|
3882
3958
|
for (const [filePath, content] of result.files) {
|
|
3883
|
-
const absolutePath =
|
|
3959
|
+
const absolutePath = path18.resolve(path18.join(absoluteOutputDir, filePath));
|
|
3884
3960
|
if (!absolutePath.startsWith(absoluteOutputDir)) {
|
|
3885
3961
|
throw new Error(`Path traversal detected in AI content output: ${filePath}`);
|
|
3886
3962
|
}
|
|
3887
|
-
await fs16.mkdir(
|
|
3963
|
+
await fs16.mkdir(path18.dirname(absolutePath), { recursive: true });
|
|
3888
3964
|
await fs16.writeFile(absolutePath, content, "utf-8");
|
|
3889
3965
|
fileCount++;
|
|
3890
3966
|
}
|
|
@@ -3893,7 +3969,7 @@ async function generateAIContentFiles(options) {
|
|
|
3893
3969
|
async function generateAIContentCLI(options) {
|
|
3894
3970
|
const { projectDir, outputDir = "public/ai", configPath, appDir = "app" } = options;
|
|
3895
3971
|
console.log("\nCanopyCMS generate-ai-content\n");
|
|
3896
|
-
const canopyConfigPath =
|
|
3972
|
+
const canopyConfigPath = path19.join(projectDir, "canopycms.config.ts");
|
|
3897
3973
|
let canopyConfigModule;
|
|
3898
3974
|
try {
|
|
3899
3975
|
canopyConfigModule = await jiti.import(canopyConfigPath);
|
|
@@ -3904,7 +3980,7 @@ async function generateAIContentCLI(options) {
|
|
|
3904
3980
|
}
|
|
3905
3981
|
const configExport = canopyConfigModule.default ?? canopyConfigModule.config ?? canopyConfigModule;
|
|
3906
3982
|
const serverConfig = typeof configExport === "object" && configExport !== null && "server" in configExport ? configExport.server : configExport;
|
|
3907
|
-
const schemasPath =
|
|
3983
|
+
const schemasPath = path19.join(projectDir, appDir, "schemas.ts");
|
|
3908
3984
|
let entrySchemaRegistry = {};
|
|
3909
3985
|
try {
|
|
3910
3986
|
const schemasModule = await jiti.import(schemasPath);
|
|
@@ -3915,7 +3991,7 @@ async function generateAIContentCLI(options) {
|
|
|
3915
3991
|
let aiConfig2;
|
|
3916
3992
|
if (configPath) {
|
|
3917
3993
|
try {
|
|
3918
|
-
const aiConfigModule = await jiti.import(
|
|
3994
|
+
const aiConfigModule = await jiti.import(path19.resolve(configPath));
|
|
3919
3995
|
aiConfig2 = aiConfigModule.aiContentConfig ?? aiConfigModule.default ?? aiConfigModule.config;
|
|
3920
3996
|
} catch (err) {
|
|
3921
3997
|
console.error(`Could not load AI config from ${configPath}`);
|
|
@@ -3932,7 +4008,7 @@ async function generateAIContentCLI(options) {
|
|
|
3932
4008
|
console.error("Make sure canopycms.config.ts uses defineCanopyConfig().");
|
|
3933
4009
|
process.exit(1);
|
|
3934
4010
|
}
|
|
3935
|
-
const resolvedOutput =
|
|
4011
|
+
const resolvedOutput = path19.resolve(projectDir, outputDir);
|
|
3936
4012
|
console.log(` Output: ${resolvedOutput}`);
|
|
3937
4013
|
console.log(` Mode: ${serverConfig.mode ?? "dev"}`);
|
|
3938
4014
|
const result = await generateAIContentFiles({
|
|
@@ -3946,7 +4022,7 @@ async function generateAIContentCLI(options) {
|
|
|
3946
4022
|
console.log(` Output: ${result.outputDir}
|
|
3947
4023
|
`);
|
|
3948
4024
|
}
|
|
3949
|
-
var __defProp3, __getOwnPropNames3, __esm3, __export3, init_normalize2, active2,
|
|
4025
|
+
var __defProp3, __getOwnPropNames3, __esm3, __export3, init_normalize2, active2, init_logger2, 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, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, 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, locks, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, registry, finalization, LOG_LEVELS2, DebugLogger2, testLogger2, log2, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, STATIC_DEPLOY_USER, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_GENERATION_RESOURCE, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, BranchSyncingError, DuplicateContentIdError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, 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, log3, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, BranchRegistry, log4, OccWriteConflictError, BRANCH_META_DIR, BRANCH_META_FILE, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataCorruptError, 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, parseTypedFilename, listCollectionEntries, MAX_INPUT_PIXELS, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, jiti;
|
|
3950
4026
|
var init_generate_ai_content = __esm({
|
|
3951
4027
|
"dist/cli/generate-ai-content.js"() {
|
|
3952
4028
|
"use strict";
|
|
@@ -3964,7 +4040,7 @@ var init_generate_ai_content = __esm({
|
|
|
3964
4040
|
"use strict";
|
|
3965
4041
|
}
|
|
3966
4042
|
});
|
|
3967
|
-
|
|
4043
|
+
init_logger2 = __esm3({
|
|
3968
4044
|
"dist/utils/logger.js"() {
|
|
3969
4045
|
"use strict";
|
|
3970
4046
|
active2 = console;
|
|
@@ -4083,7 +4159,6 @@ var init_generate_ai_content = __esm({
|
|
|
4083
4159
|
"number",
|
|
4084
4160
|
"boolean",
|
|
4085
4161
|
"datetime",
|
|
4086
|
-
"rich-text",
|
|
4087
4162
|
"markdown",
|
|
4088
4163
|
"mdx",
|
|
4089
4164
|
"code"
|
|
@@ -4336,11 +4411,21 @@ var init_generate_ai_content = __esm({
|
|
|
4336
4411
|
DEFAULT_PROD_WORKSPACE2 = "/mnt/efs/workspace";
|
|
4337
4412
|
}
|
|
4338
4413
|
});
|
|
4414
|
+
init_mode_env2 = __esm3({
|
|
4415
|
+
"dist/operating-mode/mode-env.js"() {
|
|
4416
|
+
"use strict";
|
|
4417
|
+
init_logger2();
|
|
4418
|
+
SERVER_MODE_ENV_VAR = "CANOPY_MODE";
|
|
4419
|
+
BROWSER_MODE_ENV_VAR = "NEXT_PUBLIC_CANOPY_MODE";
|
|
4420
|
+
warned2 = false;
|
|
4421
|
+
}
|
|
4422
|
+
});
|
|
4339
4423
|
init_validation2 = __esm3({
|
|
4340
4424
|
"dist/config/validation.js"() {
|
|
4341
4425
|
"use strict";
|
|
4342
4426
|
init_config3();
|
|
4343
4427
|
init_flatten2();
|
|
4428
|
+
init_mode_env2();
|
|
4344
4429
|
forEachReferenceField = (fields, visit) => {
|
|
4345
4430
|
if (!Array.isArray(fields))
|
|
4346
4431
|
return;
|
|
@@ -4432,8 +4517,8 @@ var init_generate_ai_content = __esm({
|
|
|
4432
4517
|
init_deployment_name = __esm3({
|
|
4433
4518
|
"dist/operating-mode/deployment-name.js"() {
|
|
4434
4519
|
"use strict";
|
|
4435
|
-
|
|
4436
|
-
|
|
4520
|
+
init_logger2();
|
|
4521
|
+
warned22 = false;
|
|
4437
4522
|
VALID_DEPLOYMENT_NAME2 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
4438
4523
|
isValidDeploymentName2 = (name) => VALID_DEPLOYMENT_NAME2.test(name) && !name.includes("..") && !name.endsWith(".") && !name.endsWith(".lock");
|
|
4439
4524
|
}
|
|
@@ -4486,32 +4571,32 @@ var init_generate_ai_content = __esm({
|
|
|
4486
4571
|
// - shouldCommit(), shouldPush()
|
|
4487
4572
|
// Add client-unsafe methods (use Node.js APIs)
|
|
4488
4573
|
getWorkspaceRoot(_sourceRoot) {
|
|
4489
|
-
return
|
|
4574
|
+
return path82.resolve(process.env.CANOPYCMS_WORKSPACE_ROOT ?? DEFAULT_PROD_WORKSPACE2);
|
|
4490
4575
|
}
|
|
4491
4576
|
getContentRoot(contentRoot, sourceRoot) {
|
|
4492
|
-
return
|
|
4577
|
+
return path82.resolve(sourceRoot ?? process.cwd(), contentRoot);
|
|
4493
4578
|
}
|
|
4494
4579
|
getContentBranchesRoot(sourceRoot) {
|
|
4495
|
-
return
|
|
4580
|
+
return path82.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
4496
4581
|
}
|
|
4497
4582
|
getContentBranchRoot(branchName, sourceRoot) {
|
|
4498
|
-
return
|
|
4583
|
+
return path82.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
4499
4584
|
}
|
|
4500
4585
|
getGitExcludePattern() {
|
|
4501
4586
|
return ".canopy-meta/";
|
|
4502
4587
|
}
|
|
4503
4588
|
getPermissionsFilePath(root) {
|
|
4504
|
-
return
|
|
4589
|
+
return path82.join(root, this.getPermissionsFileName());
|
|
4505
4590
|
}
|
|
4506
4591
|
getGroupsFilePath(root) {
|
|
4507
|
-
return
|
|
4592
|
+
return path82.join(root, this.getGroupsFileName());
|
|
4508
4593
|
}
|
|
4509
4594
|
getRemoteUrlConfig() {
|
|
4510
4595
|
return {
|
|
4511
4596
|
shouldAutoInitLocal: false,
|
|
4512
4597
|
defaultRemotePath: "",
|
|
4513
4598
|
envVarName: "CANOPYCMS_REMOTE_URL",
|
|
4514
|
-
autoDetectRemotePath:
|
|
4599
|
+
autoDetectRemotePath: path82.join(this.getWorkspaceRoot(), "remote.git")
|
|
4515
4600
|
};
|
|
4516
4601
|
}
|
|
4517
4602
|
requiresExistingRepo() {
|
|
@@ -4523,7 +4608,7 @@ var init_generate_ai_content = __esm({
|
|
|
4523
4608
|
return `canopycms-settings-${resolveDeploymentName2(config, "prod")}`;
|
|
4524
4609
|
}
|
|
4525
4610
|
getSettingsRoot(sourceRoot) {
|
|
4526
|
-
return
|
|
4611
|
+
return path82.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
4527
4612
|
}
|
|
4528
4613
|
usesSeparateSettingsBranch() {
|
|
4529
4614
|
return true;
|
|
@@ -4540,25 +4625,25 @@ var init_generate_ai_content = __esm({
|
|
|
4540
4625
|
DevStrategy2 = class extends DevClientSafeStrategy2 {
|
|
4541
4626
|
// Inherits client-safe methods from DevClientSafeStrategy
|
|
4542
4627
|
getWorkspaceRoot(sourceRoot) {
|
|
4543
|
-
return
|
|
4628
|
+
return path82.resolve(sourceRoot ?? process.cwd(), ".canopy-dev");
|
|
4544
4629
|
}
|
|
4545
4630
|
getContentRoot(contentRoot, sourceRoot) {
|
|
4546
|
-
return
|
|
4631
|
+
return path82.resolve(sourceRoot ?? process.cwd(), contentRoot);
|
|
4547
4632
|
}
|
|
4548
4633
|
getContentBranchesRoot(sourceRoot) {
|
|
4549
|
-
return
|
|
4634
|
+
return path82.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
4550
4635
|
}
|
|
4551
4636
|
getContentBranchRoot(branchName, sourceRoot) {
|
|
4552
|
-
return
|
|
4637
|
+
return path82.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
4553
4638
|
}
|
|
4554
4639
|
getGitExcludePattern() {
|
|
4555
4640
|
return ".canopy-meta/";
|
|
4556
4641
|
}
|
|
4557
4642
|
getPermissionsFilePath(root) {
|
|
4558
|
-
return
|
|
4643
|
+
return path82.join(root, this.getPermissionsFileName());
|
|
4559
4644
|
}
|
|
4560
4645
|
getGroupsFilePath(root) {
|
|
4561
|
-
return
|
|
4646
|
+
return path82.join(root, this.getGroupsFileName());
|
|
4562
4647
|
}
|
|
4563
4648
|
getRemoteUrlConfig() {
|
|
4564
4649
|
return {
|
|
@@ -4576,7 +4661,7 @@ var init_generate_ai_content = __esm({
|
|
|
4576
4661
|
return `canopycms-settings-${resolveDeploymentName2(config, "local")}`;
|
|
4577
4662
|
}
|
|
4578
4663
|
getSettingsRoot(sourceRoot) {
|
|
4579
|
-
return
|
|
4664
|
+
return path82.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
4580
4665
|
}
|
|
4581
4666
|
usesSeparateSettingsBranch() {
|
|
4582
4667
|
return true;
|
|
@@ -4592,11 +4677,15 @@ var init_generate_ai_content = __esm({
|
|
|
4592
4677
|
});
|
|
4593
4678
|
operating_mode_exports = {};
|
|
4594
4679
|
__export3(operating_mode_exports, {
|
|
4680
|
+
BROWSER_MODE_ENV_VAR: () => BROWSER_MODE_ENV_VAR,
|
|
4681
|
+
SERVER_MODE_ENV_VAR: () => SERVER_MODE_ENV_VAR,
|
|
4595
4682
|
clearClientStrategyCache: () => clearClientStrategyCache,
|
|
4596
4683
|
clearStrategyCache: () => clearStrategyCache,
|
|
4597
4684
|
clientOperatingStrategy: () => clientOperatingStrategy,
|
|
4685
|
+
isValidDeploymentName: () => isValidDeploymentName2,
|
|
4598
4686
|
operatingStrategy: () => operatingStrategy2,
|
|
4599
|
-
resolveDeploymentName: () => resolveDeploymentName2
|
|
4687
|
+
resolveDeploymentName: () => resolveDeploymentName2,
|
|
4688
|
+
resolveOperatingMode: () => resolveOperatingMode
|
|
4600
4689
|
});
|
|
4601
4690
|
init_operating_mode = __esm3({
|
|
4602
4691
|
"dist/operating-mode/index.js"() {
|
|
@@ -4604,46 +4693,89 @@ var init_generate_ai_content = __esm({
|
|
|
4604
4693
|
init_client_safe_strategy();
|
|
4605
4694
|
init_client_unsafe_strategy();
|
|
4606
4695
|
init_deployment_name();
|
|
4696
|
+
init_mode_env2();
|
|
4607
4697
|
}
|
|
4608
4698
|
});
|
|
4609
4699
|
locks = /* @__PURE__ */ new Map();
|
|
4700
|
+
META_DIR = ".canopy-meta";
|
|
4701
|
+
CONTENT_WRITE_LOCK_NAME = "content-write.lock";
|
|
4702
|
+
DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2e3;
|
|
4703
|
+
ContentWriteLockBusyError = class extends Error {
|
|
4704
|
+
constructor(message = "This branch is busy (syncing with the base branch, or another save is in flight); the change was not saved. Try again in a moment.") {
|
|
4705
|
+
super(message);
|
|
4706
|
+
this.name = "ContentWriteLockBusyError";
|
|
4707
|
+
}
|
|
4708
|
+
};
|
|
4709
|
+
sleep = (ms) => new Promise((resolve2) => {
|
|
4710
|
+
setTimeout(resolve2, ms);
|
|
4711
|
+
});
|
|
4610
4712
|
init_normalize2();
|
|
4611
4713
|
BASE58_PATTERN = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
4612
4714
|
CONTENT_ID_PATTERN = new RegExp(`^${BASE58_PATTERN}{12}$`);
|
|
4613
4715
|
PHYSICAL_SEGMENT_PATTERN = new RegExp(`\\.${BASE58_PATTERN}{12}(?:\\.[a-z]+)?$`);
|
|
4614
4716
|
isValidId = isValidContentId;
|
|
4717
|
+
init_logger2();
|
|
4615
4718
|
EMPTY_LOGICAL_PATH = "";
|
|
4616
4719
|
ContentIdIndex = class {
|
|
4617
4720
|
constructor(root) {
|
|
4618
4721
|
this.idToLocation = /* @__PURE__ */ new Map();
|
|
4619
4722
|
this.pathToId = /* @__PURE__ */ new Map();
|
|
4620
4723
|
this.byCollection = /* @__PURE__ */ new Map();
|
|
4621
|
-
this.
|
|
4724
|
+
this.duplicateIds = /* @__PURE__ */ new Map();
|
|
4725
|
+
this.root = path42.resolve(root);
|
|
4622
4726
|
}
|
|
4623
4727
|
/**
|
|
4624
|
-
* Build index by scanning filenames recursively.
|
|
4625
|
-
*
|
|
4728
|
+
* Build index by scanning filenames recursively. Duplicate embedded IDs
|
|
4729
|
+
* are quarantined, not thrown on -- see the class doc comment's
|
|
4730
|
+
* "Duplicate-ID quarantine" section and {@link getDuplicateIds}.
|
|
4626
4731
|
*/
|
|
4627
4732
|
async buildFromFilenames(startPath = "") {
|
|
4628
4733
|
await this.scanDirectory(startPath);
|
|
4629
4734
|
}
|
|
4735
|
+
/** Insert a location into all three indexes. Caller must ensure no existing entry for this id. */
|
|
4736
|
+
insertLocation(id, location) {
|
|
4737
|
+
this.idToLocation.set(id, location);
|
|
4738
|
+
this.pathToId.set(location.relativePath, id);
|
|
4739
|
+
if (location.type === "entry" && location.collection) {
|
|
4740
|
+
if (!this.byCollection.has(location.collection)) {
|
|
4741
|
+
this.byCollection.set(location.collection, /* @__PURE__ */ new Set());
|
|
4742
|
+
}
|
|
4743
|
+
this.byCollection.get(location.collection).add(id);
|
|
4744
|
+
}
|
|
4745
|
+
}
|
|
4746
|
+
/** Remove a location from all three indexes -- used when a lexicographically-earlier duplicate displaces it. */
|
|
4747
|
+
evictLocation(location) {
|
|
4748
|
+
this.idToLocation.delete(location.id);
|
|
4749
|
+
this.pathToId.delete(location.relativePath);
|
|
4750
|
+
if (location.type === "entry" && location.collection) {
|
|
4751
|
+
const idSet = this.byCollection.get(location.collection);
|
|
4752
|
+
if (idSet) {
|
|
4753
|
+
idSet.delete(location.id);
|
|
4754
|
+
if (idSet.size === 0)
|
|
4755
|
+
this.byCollection.delete(location.collection);
|
|
4756
|
+
}
|
|
4757
|
+
}
|
|
4758
|
+
}
|
|
4759
|
+
/** Record a quarantined duplicate path for admin-facing health reporting (see getDuplicateIds). */
|
|
4760
|
+
recordDuplicate(id, droppedPath) {
|
|
4761
|
+
let dropped = this.duplicateIds.get(id);
|
|
4762
|
+
if (!dropped) {
|
|
4763
|
+
dropped = /* @__PURE__ */ new Set();
|
|
4764
|
+
this.duplicateIds.set(id, dropped);
|
|
4765
|
+
}
|
|
4766
|
+
dropped.add(droppedPath);
|
|
4767
|
+
}
|
|
4630
4768
|
async scanDirectory(relativePath) {
|
|
4631
|
-
const absoluteDir =
|
|
4769
|
+
const absoluteDir = path42.join(this.root, relativePath);
|
|
4632
4770
|
try {
|
|
4633
|
-
const entries = await
|
|
4771
|
+
const entries = await fs32.readdir(absoluteDir, { withFileTypes: true });
|
|
4634
4772
|
for (const entry of entries) {
|
|
4635
4773
|
if (entry.name.startsWith(".") || entry.name === "_ids_") {
|
|
4636
4774
|
continue;
|
|
4637
4775
|
}
|
|
4638
|
-
const fullRelativePath =
|
|
4776
|
+
const fullRelativePath = path42.join(relativePath, entry.name);
|
|
4639
4777
|
const id = extractIdFromFilename(entry.name);
|
|
4640
4778
|
if (id) {
|
|
4641
|
-
if (this.idToLocation.has(id)) {
|
|
4642
|
-
const existing = this.idToLocation.get(id);
|
|
4643
|
-
throw new Error(`ID collision detected: ${id}
|
|
4644
|
-
File 1: ${existing.relativePath}
|
|
4645
|
-
File 2: ${fullRelativePath}`);
|
|
4646
|
-
}
|
|
4647
4779
|
const location = {
|
|
4648
4780
|
id,
|
|
4649
4781
|
// already ContentId from extractIdFromFilename
|
|
@@ -4653,17 +4785,27 @@ var init_generate_ai_content = __esm({
|
|
|
4653
4785
|
};
|
|
4654
4786
|
if (!entry.isDirectory()) {
|
|
4655
4787
|
const slug = extractSlugFromFilename(entry.name);
|
|
4656
|
-
const physicalCollection =
|
|
4788
|
+
const physicalCollection = path42.dirname(fullRelativePath);
|
|
4657
4789
|
const collectionPath = toLogicalCollectionPath(physicalCollection);
|
|
4658
4790
|
location.slug = slug;
|
|
4659
4791
|
location.collection = collectionPath;
|
|
4660
|
-
|
|
4661
|
-
|
|
4792
|
+
}
|
|
4793
|
+
const existing = this.idToLocation.get(id);
|
|
4794
|
+
if (existing) {
|
|
4795
|
+
const newWins = fullRelativePath < existing.relativePath;
|
|
4796
|
+
const kept = newWins ? fullRelativePath : existing.relativePath;
|
|
4797
|
+
const dropped = newWins ? existing.relativePath : fullRelativePath;
|
|
4798
|
+
if (newWins) {
|
|
4799
|
+
this.evictLocation(existing);
|
|
4800
|
+
this.recordDuplicate(id, existing.relativePath);
|
|
4801
|
+
this.insertLocation(id, location);
|
|
4802
|
+
} else {
|
|
4803
|
+
this.recordDuplicate(id, fullRelativePath);
|
|
4662
4804
|
}
|
|
4663
|
-
this.
|
|
4805
|
+
canopyLogWarn2(`[ContentIdIndex] Duplicate content ID ${id}: "${kept}" and "${dropped}" both embed this ID. Keeping "${kept}" for ID-based lookups (reads, references, listings); "${dropped}" is excluded from those lookups for now but has NOT been deleted -- it is still on disk at that path, and saves addressed to it are refused until this is resolved. An admin can resolve this via the repair-content-duplicates admin action for this branch, which archives "${dropped}" with a dot-prefixed name so future scans stop flagging it. Root: ${this.root}`);
|
|
4806
|
+
} else {
|
|
4807
|
+
this.insertLocation(id, location);
|
|
4664
4808
|
}
|
|
4665
|
-
this.idToLocation.set(id, location);
|
|
4666
|
-
this.pathToId.set(fullRelativePath, id);
|
|
4667
4809
|
}
|
|
4668
4810
|
if (entry.isDirectory()) {
|
|
4669
4811
|
await this.scanDirectory(fullRelativePath);
|
|
@@ -4675,6 +4817,42 @@ var init_generate_ai_content = __esm({
|
|
|
4675
4817
|
}
|
|
4676
4818
|
}
|
|
4677
4819
|
}
|
|
4820
|
+
/**
|
|
4821
|
+
* IDs discovered on more than one file during the last buildFromFilenames
|
|
4822
|
+
* scan. Empty unless the content tree currently has a duplicate-ID pair
|
|
4823
|
+
* (see the class doc comment). Consumed by branch-health.ts's admin scan
|
|
4824
|
+
* and by the repair-content-duplicates admin action
|
|
4825
|
+
* (api/admin-branch-health.ts).
|
|
4826
|
+
*/
|
|
4827
|
+
getDuplicateIds() {
|
|
4828
|
+
const result = [];
|
|
4829
|
+
for (const id of this.duplicateIds.keys()) {
|
|
4830
|
+
const duplicate = this.getDuplicateFor(id);
|
|
4831
|
+
if (duplicate)
|
|
4832
|
+
result.push(duplicate);
|
|
4833
|
+
}
|
|
4834
|
+
return result;
|
|
4835
|
+
}
|
|
4836
|
+
/**
|
|
4837
|
+
* The quarantine record for ONE id, or null when that id is not
|
|
4838
|
+
* duplicated. The O(1) counterpart to {@link getDuplicateIds} for the
|
|
4839
|
+
* write path, which has to ask this question on every save whose resolved
|
|
4840
|
+
* path disagrees with the index (ContentStore.write()'s duplicate-ID
|
|
4841
|
+
* guard) and must not pay for materializing the whole list.
|
|
4842
|
+
*/
|
|
4843
|
+
getDuplicateFor(id) {
|
|
4844
|
+
const dropped = this.duplicateIds.get(id);
|
|
4845
|
+
if (!dropped)
|
|
4846
|
+
return null;
|
|
4847
|
+
const kept = this.idToLocation.get(id);
|
|
4848
|
+
if (!kept)
|
|
4849
|
+
return null;
|
|
4850
|
+
return {
|
|
4851
|
+
id,
|
|
4852
|
+
keptPath: kept.relativePath,
|
|
4853
|
+
droppedPaths: Array.from(dropped).sort()
|
|
4854
|
+
};
|
|
4855
|
+
}
|
|
4678
4856
|
/**
|
|
4679
4857
|
* Forward lookup: ID → location (O(1))
|
|
4680
4858
|
*/
|
|
@@ -4767,7 +4945,7 @@ var init_generate_ai_content = __esm({
|
|
|
4767
4945
|
* must already exist on disk (created by ContentStore).
|
|
4768
4946
|
*/
|
|
4769
4947
|
add(location) {
|
|
4770
|
-
const id = extractIdFromFilename(
|
|
4948
|
+
const id = extractIdFromFilename(path42.basename(location.relativePath));
|
|
4771
4949
|
if (!id) {
|
|
4772
4950
|
throw new Error(`Cannot add location without ID in filename: ${location.relativePath}`);
|
|
4773
4951
|
}
|
|
@@ -4824,8 +5002,8 @@ var init_generate_ai_content = __esm({
|
|
|
4824
5002
|
location.relativePath = newRelativePath;
|
|
4825
5003
|
if (location.type === "entry") {
|
|
4826
5004
|
const oldCollection = location.collection;
|
|
4827
|
-
location.slug = extractSlugFromFilename(
|
|
4828
|
-
const physicalCollection =
|
|
5005
|
+
location.slug = extractSlugFromFilename(path42.basename(newRelativePath));
|
|
5006
|
+
const physicalCollection = path42.dirname(newRelativePath);
|
|
4829
5007
|
location.collection = toLogicalCollectionPath(physicalCollection);
|
|
4830
5008
|
if (oldCollection !== location.collection) {
|
|
4831
5009
|
if (oldCollection) {
|
|
@@ -4955,8 +5133,8 @@ var init_generate_ai_content = __esm({
|
|
|
4955
5133
|
throwOnError: false
|
|
4956
5134
|
});
|
|
4957
5135
|
log2 = createDebugLogger2({ prefix: "ResourceGeneration" });
|
|
4958
|
-
|
|
4959
|
-
|
|
5136
|
+
META_DIR2 = ".canopy-meta";
|
|
5137
|
+
init_logger2();
|
|
4960
5138
|
entryTypeMetaSchema = z7.object({
|
|
4961
5139
|
name: z7.string().min(1),
|
|
4962
5140
|
format: z7.enum(["md", "mdx", "json", "yaml"]),
|
|
@@ -5085,7 +5263,7 @@ var init_generate_ai_content = __esm({
|
|
|
5085
5263
|
* the project root, regardless of which entrypoint produced the cwd branchRoot.
|
|
5086
5264
|
*/
|
|
5087
5265
|
skipDiskCache(branchRoot) {
|
|
5088
|
-
return isBuildMode() ||
|
|
5266
|
+
return isBuildMode() || path72.resolve(branchRoot) === path72.resolve(process.cwd());
|
|
5089
5267
|
}
|
|
5090
5268
|
/**
|
|
5091
5269
|
* Get schema for a branch (loads from cache or resolves fresh).
|
|
@@ -5111,14 +5289,14 @@ var init_generate_ai_content = __esm({
|
|
|
5111
5289
|
* Load schema from cache or resolve fresh if cache is missing or stale.
|
|
5112
5290
|
*/
|
|
5113
5291
|
async loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
5114
|
-
const contentRoot =
|
|
5292
|
+
const contentRoot = path72.join(branchRoot, contentRootName);
|
|
5115
5293
|
const skipDiskCache = this.skipDiskCache(branchRoot);
|
|
5116
5294
|
if (!skipDiskCache) {
|
|
5117
|
-
const cacheDir =
|
|
5118
|
-
const cachePath =
|
|
5295
|
+
const cacheDir = path72.join(branchRoot, ".canopy-meta");
|
|
5296
|
+
const cachePath = path72.join(cacheDir, "schema-cache.json");
|
|
5119
5297
|
let cacheData = null;
|
|
5120
5298
|
try {
|
|
5121
|
-
const cacheContent = await
|
|
5299
|
+
const cacheContent = await fs62.readFile(cachePath, "utf-8");
|
|
5122
5300
|
cacheData = JSON.parse(cacheContent);
|
|
5123
5301
|
} catch {
|
|
5124
5302
|
cacheData = null;
|
|
@@ -5155,7 +5333,7 @@ var init_generate_ai_content = __esm({
|
|
|
5155
5333
|
*/
|
|
5156
5334
|
async resolveFreshAndPersist(branchRoot, entrySchemaRegistry, contentRootName, options) {
|
|
5157
5335
|
const { skipDiskCache } = options;
|
|
5158
|
-
const contentRoot =
|
|
5336
|
+
const contentRoot = path72.join(branchRoot, contentRootName);
|
|
5159
5337
|
const read = skipDiskCache ? null : await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
|
|
5160
5338
|
const result = await this.resolveFresh(contentRoot, entrySchemaRegistry);
|
|
5161
5339
|
if (!isValidSchema(result.schema)) {
|
|
@@ -5168,9 +5346,9 @@ var init_generate_ai_content = __esm({
|
|
|
5168
5346
|
}
|
|
5169
5347
|
const flatSchema = flattenSchema(result.schema, contentRootName);
|
|
5170
5348
|
if (!skipDiskCache) {
|
|
5171
|
-
const cacheDir =
|
|
5172
|
-
const cachePath =
|
|
5173
|
-
await
|
|
5349
|
+
const cacheDir = path72.join(branchRoot, ".canopy-meta");
|
|
5350
|
+
const cachePath = path72.join(cacheDir, "schema-cache.json");
|
|
5351
|
+
await fs62.unlink(path72.join(cacheDir, "schema-cache.stale")).catch(() => {
|
|
5174
5352
|
});
|
|
5175
5353
|
if (read && read.ok) {
|
|
5176
5354
|
const newCache = {
|
|
@@ -5180,13 +5358,13 @@ var init_generate_ai_content = __esm({
|
|
|
5180
5358
|
cachedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
5181
5359
|
generation: read.token
|
|
5182
5360
|
};
|
|
5183
|
-
await
|
|
5184
|
-
const tmpPath =
|
|
5185
|
-
await
|
|
5361
|
+
await fs62.mkdir(cacheDir, { recursive: true });
|
|
5362
|
+
const tmpPath = path72.join(cacheDir, `schema-cache.tmp.${Date.now()}.${Math.random()}.json`);
|
|
5363
|
+
await fs62.writeFile(tmpPath, JSON.stringify(newCache, null, 2), "utf-8");
|
|
5186
5364
|
try {
|
|
5187
|
-
await
|
|
5365
|
+
await fs62.rename(tmpPath, cachePath);
|
|
5188
5366
|
} catch (err) {
|
|
5189
|
-
await
|
|
5367
|
+
await fs62.unlink(tmpPath).catch(() => {
|
|
5190
5368
|
});
|
|
5191
5369
|
throw err;
|
|
5192
5370
|
}
|
|
@@ -5267,11 +5445,35 @@ var init_generate_ai_content = __esm({
|
|
|
5267
5445
|
}
|
|
5268
5446
|
};
|
|
5269
5447
|
ContentConflictError = class extends Error {
|
|
5270
|
-
constructor() {
|
|
5271
|
-
super(
|
|
5448
|
+
constructor(message = "Content was modified by another editor") {
|
|
5449
|
+
super(message);
|
|
5272
5450
|
this.name = "ContentConflictError";
|
|
5273
5451
|
}
|
|
5274
5452
|
};
|
|
5453
|
+
BranchSyncingError = class extends ContentConflictError {
|
|
5454
|
+
constructor(message) {
|
|
5455
|
+
super(message);
|
|
5456
|
+
this.name = "BranchSyncingError";
|
|
5457
|
+
}
|
|
5458
|
+
};
|
|
5459
|
+
DuplicateContentIdError = class extends ContentConflictError {
|
|
5460
|
+
constructor(contentId, paths) {
|
|
5461
|
+
const sorted = Array.from(new Set(paths)).sort();
|
|
5462
|
+
super(
|
|
5463
|
+
// Names the STATE, not an action. The repair-content-duplicates
|
|
5464
|
+
// endpoint exists but nothing in the editor renders it, so telling an
|
|
5465
|
+
// editor "an admin can run X" sent them to an admin who could neither
|
|
5466
|
+
// run X nor see that the branch was affected. Say what is true; the
|
|
5467
|
+
// admin panel's read-only duplicate list (SystemHealthPanel) is the
|
|
5468
|
+
// diagnosis half, and the repair UI is tracked in
|
|
5469
|
+
// .claude/future-tasks/duplicate-content-id-repair-ui.md.
|
|
5470
|
+
`Content ID ${contentId} is on more than one file (${sorted.map((p5) => `"${p5}"`).join(" and ")}), so this save was refused rather than risk overwriting or deleting the wrong one. An administrator needs to resolve the duplicate on the server before this entry can be saved.`
|
|
5471
|
+
);
|
|
5472
|
+
this.name = "DuplicateContentIdError";
|
|
5473
|
+
this.contentId = contentId;
|
|
5474
|
+
this.paths = sorted;
|
|
5475
|
+
}
|
|
5476
|
+
};
|
|
5275
5477
|
DEFAULT_INDEX_FRESHNESS_INTERVAL_MS = 1e3;
|
|
5276
5478
|
FORCED_REFRESH_MIN_INTERVAL_MS = 5e3;
|
|
5277
5479
|
STALE_LOOKUP = Symbol("stale-index-lookup");
|
|
@@ -5283,13 +5485,40 @@ var init_generate_ai_content = __esm({
|
|
|
5283
5485
|
this.lastDiskProbeMs = 0;
|
|
5284
5486
|
this.lastForcedRefreshMs = 0;
|
|
5285
5487
|
this.indexBuild = null;
|
|
5286
|
-
this.root =
|
|
5488
|
+
this.root = path10.resolve(root);
|
|
5287
5489
|
this.contentRootName = options.contentRootName || "content";
|
|
5288
5490
|
this.indexFreshnessIntervalMs = options.indexFreshnessIntervalMs ?? DEFAULT_INDEX_FRESHNESS_INTERVAL_MS;
|
|
5491
|
+
this.contentWriteLockWaitMs = options.contentWriteLockWaitMs ?? DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS;
|
|
5289
5492
|
this.schemaIndex = new Map(flatSchema.map((item) => [item.logicalPath, item]));
|
|
5290
5493
|
this._idIndex = new ContentIdIndex(this.root);
|
|
5291
5494
|
registerContentIndexForInvalidation(this.root, this);
|
|
5292
5495
|
}
|
|
5496
|
+
/**
|
|
5497
|
+
* [SYNC-C1] Run a working-tree mutation under the branch's cross-host
|
|
5498
|
+
* content-write lock (utils/content-write-lock.ts), on top of the
|
|
5499
|
+
* in-process locks the callee takes for itself.
|
|
5500
|
+
*
|
|
5501
|
+
* The in-process mutex serializes writers inside ONE process; it says
|
|
5502
|
+
* nothing about the EC2 worker rebasing this same tree on shared EFS, which
|
|
5503
|
+
* destroys an in-flight save (`checkout --theirs` overwrites it and the
|
|
5504
|
+
* rebase then reports success; `rebase --abort` hard-resets it). This is the
|
|
5505
|
+
* layer that actually excludes the two.
|
|
5506
|
+
*
|
|
5507
|
+
* Reads deliberately do NOT take it -- an extra EFS round-trip per read is
|
|
5508
|
+
* not an acceptable cost, and reads cannot be destroyed by a rebase.
|
|
5509
|
+
*
|
|
5510
|
+
* Acquisition order is always content lock -> `withLock`, never the reverse,
|
|
5511
|
+
* so the two cannot deadlock.
|
|
5512
|
+
*/
|
|
5513
|
+
async withContentWriteExclusion(fn) {
|
|
5514
|
+
try {
|
|
5515
|
+
return await withContentWriteLock(this.root, fn, this.contentWriteLockWaitMs);
|
|
5516
|
+
} catch (err) {
|
|
5517
|
+
if (err instanceof ContentWriteLockBusyError)
|
|
5518
|
+
throw new BranchSyncingError(err.message);
|
|
5519
|
+
throw err;
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5293
5522
|
/**
|
|
5294
5523
|
* Mark the ID index stale so the next idIndex() access rebuilds it from disk.
|
|
5295
5524
|
*
|
|
@@ -5393,26 +5622,32 @@ var init_generate_ai_content = __esm({
|
|
|
5393
5622
|
return true;
|
|
5394
5623
|
}
|
|
5395
5624
|
/**
|
|
5396
|
-
*
|
|
5397
|
-
*
|
|
5625
|
+
* Every file in `dir` whose filename embeds `id`, as root-relative paths
|
|
5626
|
+
* (empty when there is no such file, or no such dir).
|
|
5627
|
+
*
|
|
5628
|
+
* [F1] Returns ALL matches, not the first: more than one match is a
|
|
5629
|
+
* duplicate-ID pair, and callers must be able to tell that apart from a
|
|
5630
|
+
* clean single hit rather than silently picking whichever one `readdir()`
|
|
5631
|
+
* happened to yield first.
|
|
5398
5632
|
*/
|
|
5399
|
-
async
|
|
5633
|
+
async findEntryPathsById(dir, id) {
|
|
5400
5634
|
let entries;
|
|
5401
5635
|
try {
|
|
5402
|
-
entries = await
|
|
5636
|
+
entries = await fs9.readdir(dir, { withFileTypes: true });
|
|
5403
5637
|
} catch (err) {
|
|
5404
5638
|
if (isNodeError3(err) && err.code === "ENOENT")
|
|
5405
|
-
return
|
|
5639
|
+
return [];
|
|
5406
5640
|
throw err;
|
|
5407
5641
|
}
|
|
5642
|
+
const matches = [];
|
|
5408
5643
|
for (const entry of entries) {
|
|
5409
5644
|
if (entry.isDirectory())
|
|
5410
5645
|
continue;
|
|
5411
5646
|
if (extractIdFromFilename(entry.name) === id) {
|
|
5412
|
-
|
|
5647
|
+
matches.push(path10.relative(this.root, path10.join(dir, entry.name)));
|
|
5413
5648
|
}
|
|
5414
5649
|
}
|
|
5415
|
-
return
|
|
5650
|
+
return matches.sort();
|
|
5416
5651
|
}
|
|
5417
5652
|
/**
|
|
5418
5653
|
* Get all schema items for iteration.
|
|
@@ -5421,11 +5656,11 @@ var init_generate_ai_content = __esm({
|
|
|
5421
5656
|
getSchemaItems() {
|
|
5422
5657
|
return this.schemaIndex.values();
|
|
5423
5658
|
}
|
|
5424
|
-
assertSchemaItem(
|
|
5425
|
-
const normalized = normalizeFilesystemPath(
|
|
5659
|
+
assertSchemaItem(path202) {
|
|
5660
|
+
const normalized = normalizeFilesystemPath(path202);
|
|
5426
5661
|
const item = this.schemaIndex.get(normalized);
|
|
5427
5662
|
if (!item) {
|
|
5428
|
-
throw new ContentStoreError(`Unknown schema item: ${
|
|
5663
|
+
throw new ContentStoreError(`Unknown schema item: ${path202}`, "NO_SCHEMA_ITEM");
|
|
5429
5664
|
}
|
|
5430
5665
|
return item;
|
|
5431
5666
|
}
|
|
@@ -5508,7 +5743,7 @@ var init_generate_ai_content = __esm({
|
|
|
5508
5743
|
* @param options.entryTypeName - For collections with multiple entry types, specify which one to use. Defaults to the default entry type.
|
|
5509
5744
|
*/
|
|
5510
5745
|
async buildPaths(schemaItem, slug, options = {}) {
|
|
5511
|
-
const rootWithSep = this.root.endsWith(
|
|
5746
|
+
const rootWithSep = this.root.endsWith(path10.sep) ? this.root : `${this.root}${path10.sep}`;
|
|
5512
5747
|
if (schemaItem.type === "entry-type") {
|
|
5513
5748
|
const parentPath = schemaItem.parentPath || "";
|
|
5514
5749
|
const parentCollection = this.schemaIndex.get(parentPath);
|
|
@@ -5541,7 +5776,7 @@ var init_generate_ai_content = __esm({
|
|
|
5541
5776
|
const entryTypeName = entryTypeConfig?.name || "entry";
|
|
5542
5777
|
let collectionRoot = await resolveCollectionPath(this.root, schemaItem.logicalPath);
|
|
5543
5778
|
if (!collectionRoot) {
|
|
5544
|
-
collectionRoot =
|
|
5779
|
+
collectionRoot = path10.resolve(this.root, schemaItem.logicalPath);
|
|
5545
5780
|
}
|
|
5546
5781
|
if (!collectionRoot.startsWith(rootWithSep)) {
|
|
5547
5782
|
throw new ContentStoreError("Path traversal detected", "VALIDATION");
|
|
@@ -5551,7 +5786,7 @@ var init_generate_ai_content = __esm({
|
|
|
5551
5786
|
let existingEntryType;
|
|
5552
5787
|
let foundExisting = false;
|
|
5553
5788
|
if (!id) {
|
|
5554
|
-
const entries = await
|
|
5789
|
+
const entries = await fs9.readdir(collectionRoot, { withFileTypes: true }).catch(() => []);
|
|
5555
5790
|
const existingFile = entries.find((entry) => {
|
|
5556
5791
|
if (entry.isDirectory())
|
|
5557
5792
|
return false;
|
|
@@ -5577,14 +5812,14 @@ var init_generate_ai_content = __esm({
|
|
|
5577
5812
|
}
|
|
5578
5813
|
filename = `${finalEntryTypeName}.${safeSlug}.${id}${ext}`;
|
|
5579
5814
|
}
|
|
5580
|
-
const resolved =
|
|
5581
|
-
const collectionRootWithSep = collectionRoot.endsWith(
|
|
5815
|
+
const resolved = path10.resolve(collectionRoot, filename);
|
|
5816
|
+
const collectionRootWithSep = collectionRoot.endsWith(path10.sep) ? collectionRoot : `${collectionRoot}${path10.sep}`;
|
|
5582
5817
|
if (!resolved.startsWith(collectionRootWithSep)) {
|
|
5583
5818
|
throw new ContentStoreError("Path traversal detected", "VALIDATION");
|
|
5584
5819
|
}
|
|
5585
5820
|
return {
|
|
5586
5821
|
absolutePath: resolved,
|
|
5587
|
-
relativePath:
|
|
5822
|
+
relativePath: path10.relative(this.root, resolved),
|
|
5588
5823
|
id,
|
|
5589
5824
|
entryTypeName: finalEntryTypeName,
|
|
5590
5825
|
existed
|
|
@@ -5620,8 +5855,8 @@ var init_generate_ai_content = __esm({
|
|
|
5620
5855
|
async read(collectionPath, slug = "", options = {}) {
|
|
5621
5856
|
const schemaItem = this.assertSchemaItem(collectionPath);
|
|
5622
5857
|
const { absolutePath, relativePath, entryTypeName: resolvedEntryTypeName } = await this.buildPaths(schemaItem, slug);
|
|
5623
|
-
const stat = await
|
|
5624
|
-
const raw = await
|
|
5858
|
+
const stat = await fs9.stat(absolutePath);
|
|
5859
|
+
const raw = await fs9.readFile(absolutePath, "utf8");
|
|
5625
5860
|
let doc;
|
|
5626
5861
|
let format;
|
|
5627
5862
|
let fields;
|
|
@@ -5704,103 +5939,128 @@ var init_generate_ai_content = __esm({
|
|
|
5704
5939
|
}
|
|
5705
5940
|
const prePass = await this.buildPaths(schemaItem, slug, { entryTypeName, existingId });
|
|
5706
5941
|
let lockKey = this.entryLockKey(schemaItem, slug, prePass);
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
const
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
lockKey
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
const
|
|
5724
|
-
if (
|
|
5725
|
-
|
|
5942
|
+
return this.withContentWriteExclusion(async () => {
|
|
5943
|
+
const RETRY_KEY = Symbol("retry-with-new-lock-key");
|
|
5944
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
5945
|
+
const outcome = await withLock(lockKey, async () => {
|
|
5946
|
+
const inLock = await this.buildPaths(schemaItem, slug, {
|
|
5947
|
+
entryTypeName,
|
|
5948
|
+
existingId
|
|
5949
|
+
});
|
|
5950
|
+
const currentKey = this.entryLockKey(schemaItem, slug, inLock);
|
|
5951
|
+
if (currentKey !== lockKey) {
|
|
5952
|
+
lockKey = currentKey;
|
|
5953
|
+
return RETRY_KEY;
|
|
5954
|
+
}
|
|
5955
|
+
const { absolutePath, relativePath, id } = inLock;
|
|
5956
|
+
if (id) {
|
|
5957
|
+
const guardIndex = this._idIndex;
|
|
5958
|
+
const indexedPath = guardIndex.findById(id)?.relativePath ?? null;
|
|
5959
|
+
if (indexedPath && indexedPath !== relativePath) {
|
|
5960
|
+
const quarantined = guardIndex.getDuplicateFor(id);
|
|
5961
|
+
const bothOnDisk = await filePathExists3(absolutePath) && await filePathExists3(path10.join(this.root, indexedPath));
|
|
5962
|
+
if (quarantined || bothOnDisk) {
|
|
5963
|
+
throw new DuplicateContentIdError(id, [
|
|
5964
|
+
indexedPath,
|
|
5965
|
+
relativePath,
|
|
5966
|
+
...quarantined?.droppedPaths ?? [],
|
|
5967
|
+
...quarantined ? [quarantined.keptPath] : []
|
|
5968
|
+
]);
|
|
5969
|
+
}
|
|
5726
5970
|
}
|
|
5727
|
-
}
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5971
|
+
}
|
|
5972
|
+
await fs9.mkdir(path10.dirname(absolutePath), { recursive: true });
|
|
5973
|
+
if (input.expectedVersion !== void 0) {
|
|
5974
|
+
try {
|
|
5975
|
+
const existing = await fs9.stat(absolutePath);
|
|
5976
|
+
if (input.expectedVersion === null) {
|
|
5977
|
+
throw new ContentConflictError("An entry with this slug already exists");
|
|
5978
|
+
}
|
|
5979
|
+
if (existing.mtimeMs !== input.expectedVersion) {
|
|
5980
|
+
throw new ContentConflictError();
|
|
5981
|
+
}
|
|
5982
|
+
} catch (err) {
|
|
5983
|
+
if (err instanceof ContentConflictError)
|
|
5984
|
+
throw err;
|
|
5985
|
+
if (isNodeError3(err) && err.code === "ENOENT") {
|
|
5986
|
+
} else {
|
|
5987
|
+
throw err;
|
|
5988
|
+
}
|
|
5733
5989
|
}
|
|
5734
5990
|
}
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5991
|
+
if (existingId && !await filePathExists3(absolutePath)) {
|
|
5992
|
+
const actualRelPaths = await this.findEntryPathsById(path10.dirname(absolutePath), existingId);
|
|
5993
|
+
if (actualRelPaths.length > 1) {
|
|
5994
|
+
throw new DuplicateContentIdError(existingId, actualRelPaths);
|
|
5995
|
+
}
|
|
5996
|
+
const actualRelPath = actualRelPaths[0] ?? null;
|
|
5997
|
+
const indexedRelPath = this._idIndex.findById(existingId)?.relativePath ?? null;
|
|
5998
|
+
if (actualRelPath !== null && actualRelPath !== indexedRelPath) {
|
|
5999
|
+
throw new ContentConflictError();
|
|
6000
|
+
}
|
|
5741
6001
|
}
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
content = `${JSON.stringify(input.data ?? {}, null, 2)}
|
|
6002
|
+
let content;
|
|
6003
|
+
if (input.format === "json") {
|
|
6004
|
+
content = `${JSON.stringify(input.data ?? {}, null, 2)}
|
|
5746
6005
|
`;
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
} else {
|
|
5750
|
-
content = matter.stringify(input.body, input.data ?? {});
|
|
5751
|
-
}
|
|
5752
|
-
await atomicWriteFile2(absolutePath, content);
|
|
5753
|
-
const liveIndex = this._idIndex;
|
|
5754
|
-
let staleOldAbsPath = null;
|
|
5755
|
-
if (id) {
|
|
5756
|
-
const existing = liveIndex.findById(id);
|
|
5757
|
-
if (existing) {
|
|
5758
|
-
if (existing.relativePath !== relativePath) {
|
|
5759
|
-
staleOldAbsPath = path82.join(this.root, existing.relativePath);
|
|
5760
|
-
liveIndex.updatePath(existing.id, relativePath);
|
|
5761
|
-
}
|
|
6006
|
+
} else if (input.format === "yaml") {
|
|
6007
|
+
content = yamlStringify(input.data ?? {});
|
|
5762
6008
|
} else {
|
|
5763
|
-
|
|
5764
|
-
|
|
5765
|
-
|
|
5766
|
-
|
|
5767
|
-
|
|
6009
|
+
content = matter.stringify(input.body, input.data ?? {});
|
|
6010
|
+
}
|
|
6011
|
+
await atomicWriteFile2(absolutePath, content);
|
|
6012
|
+
const liveIndex = this._idIndex;
|
|
6013
|
+
let staleOldAbsPath = null;
|
|
6014
|
+
if (id) {
|
|
6015
|
+
const existing = liveIndex.findById(id);
|
|
6016
|
+
if (existing) {
|
|
6017
|
+
if (existing.relativePath !== relativePath) {
|
|
6018
|
+
staleOldAbsPath = path10.join(this.root, existing.relativePath);
|
|
6019
|
+
liveIndex.updatePath(existing.id, relativePath);
|
|
6020
|
+
}
|
|
6021
|
+
} else {
|
|
6022
|
+
liveIndex.add({
|
|
6023
|
+
type: "entry",
|
|
6024
|
+
relativePath,
|
|
6025
|
+
collection: collectionPath,
|
|
6026
|
+
slug: slug || void 0
|
|
6027
|
+
});
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
if (staleOldAbsPath) {
|
|
6031
|
+
await fs9.unlink(staleOldAbsPath).catch((err) => {
|
|
6032
|
+
if (!isNodeError3(err) || err.code !== "ENOENT")
|
|
6033
|
+
throw err;
|
|
5768
6034
|
});
|
|
5769
6035
|
}
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
};
|
|
5799
|
-
});
|
|
5800
|
-
if (typeof outcome !== "symbol")
|
|
5801
|
-
return outcome;
|
|
5802
|
-
}
|
|
5803
|
-
throw new ContentConflictError();
|
|
6036
|
+
await this.recordOwnMutation(liveIndex);
|
|
6037
|
+
const afterStat = await fs9.stat(absolutePath);
|
|
6038
|
+
const base = {
|
|
6039
|
+
collection: schemaItem.logicalPath,
|
|
6040
|
+
collectionName: schemaItem.name,
|
|
6041
|
+
relativePath,
|
|
6042
|
+
absolutePath,
|
|
6043
|
+
version: afterStat.mtimeMs
|
|
6044
|
+
};
|
|
6045
|
+
if (input.format === "json") {
|
|
6046
|
+
return { ...base, format: "json", data: input.data ?? {} };
|
|
6047
|
+
}
|
|
6048
|
+
if (input.format === "yaml") {
|
|
6049
|
+
return { ...base, format: "yaml", data: input.data ?? {} };
|
|
6050
|
+
}
|
|
6051
|
+
return {
|
|
6052
|
+
...base,
|
|
6053
|
+
format: input.format,
|
|
6054
|
+
data: input.data ?? {},
|
|
6055
|
+
body: input.body,
|
|
6056
|
+
bodyFieldName: findBodyFieldName(fields)
|
|
6057
|
+
};
|
|
6058
|
+
});
|
|
6059
|
+
if (typeof outcome !== "symbol")
|
|
6060
|
+
return outcome;
|
|
6061
|
+
}
|
|
6062
|
+
throw new ContentConflictError();
|
|
6063
|
+
});
|
|
5804
6064
|
}
|
|
5805
6065
|
/**
|
|
5806
6066
|
* Read an entry by its ID (UUID).
|
|
@@ -5852,7 +6112,7 @@ var init_generate_ai_content = __esm({
|
|
|
5852
6112
|
const schemaItem = this.assertSchemaItem(collectionPath);
|
|
5853
6113
|
const { absolutePath } = await this.buildPaths(schemaItem, slug);
|
|
5854
6114
|
try {
|
|
5855
|
-
await
|
|
6115
|
+
await fs9.access(absolutePath);
|
|
5856
6116
|
return true;
|
|
5857
6117
|
} catch (err) {
|
|
5858
6118
|
if (isNodeError3(err) && err.code === "ENOENT")
|
|
@@ -5891,7 +6151,7 @@ var init_generate_ai_content = __esm({
|
|
|
5891
6151
|
const collectionRoot = await resolveCollectionPath(this.root, collectionPath);
|
|
5892
6152
|
if (!collectionRoot)
|
|
5893
6153
|
return 0;
|
|
5894
|
-
const entries = await
|
|
6154
|
+
const entries = await fs9.readdir(collectionRoot, { withFileTypes: true }).catch(() => []);
|
|
5895
6155
|
return entries.filter((entry) => !entry.isDirectory() && extractEntryTypeFromFilename(entry.name) === entryTypeName).length;
|
|
5896
6156
|
}
|
|
5897
6157
|
/**
|
|
@@ -5915,34 +6175,36 @@ var init_generate_ai_content = __esm({
|
|
|
5915
6175
|
const collection = this.assertCollection(collectionPath);
|
|
5916
6176
|
const prePass = await this.buildPaths(collection, slug);
|
|
5917
6177
|
if (!prePass.existed) {
|
|
5918
|
-
await
|
|
6178
|
+
await fs9.unlink(prePass.absolutePath);
|
|
5919
6179
|
return;
|
|
5920
6180
|
}
|
|
5921
6181
|
let lockKey = this.entryLockKey(collection, slug, prePass);
|
|
5922
6182
|
await this.afterPrePassForTesting();
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
const
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
lockKey
|
|
5930
|
-
|
|
5931
|
-
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
6183
|
+
return this.withContentWriteExclusion(async () => {
|
|
6184
|
+
const RETRY_KEY = Symbol("retry-with-new-lock-key");
|
|
6185
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
6186
|
+
const outcome = await withLock(lockKey, async () => {
|
|
6187
|
+
const inLock = await this.buildPaths(collection, slug);
|
|
6188
|
+
const currentKey = this.entryLockKey(collection, slug, inLock);
|
|
6189
|
+
if (currentKey !== lockKey) {
|
|
6190
|
+
lockKey = currentKey;
|
|
6191
|
+
return RETRY_KEY;
|
|
6192
|
+
}
|
|
6193
|
+
const { absolutePath, relativePath } = inLock;
|
|
6194
|
+
await fs9.unlink(absolutePath);
|
|
6195
|
+
const liveIndex = this._idIndex;
|
|
6196
|
+
const id = liveIndex.findByPath(relativePath);
|
|
6197
|
+
if (id) {
|
|
6198
|
+
liveIndex.remove(id);
|
|
6199
|
+
}
|
|
6200
|
+
await this.recordOwnMutation(liveIndex);
|
|
6201
|
+
return void 0;
|
|
6202
|
+
});
|
|
6203
|
+
if (outcome !== RETRY_KEY)
|
|
6204
|
+
return;
|
|
6205
|
+
}
|
|
6206
|
+
throw new ContentConflictError();
|
|
6207
|
+
});
|
|
5946
6208
|
}
|
|
5947
6209
|
/**
|
|
5948
6210
|
* Rename an entry by changing its slug (middle segment of filename).
|
|
@@ -5973,70 +6235,72 @@ var init_generate_ai_content = __esm({
|
|
|
5973
6235
|
let sourceId = prePass.id;
|
|
5974
6236
|
let destLockKey = this.createLockKey(collection, safeNewSlug);
|
|
5975
6237
|
await this.afterPrePassForTesting();
|
|
5976
|
-
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
const
|
|
5980
|
-
|
|
5981
|
-
|
|
5982
|
-
|
|
5983
|
-
|
|
5984
|
-
|
|
5985
|
-
|
|
5986
|
-
sourceLockKey
|
|
6238
|
+
return this.withContentWriteExclusion(async () => {
|
|
6239
|
+
const RETRY_KEY = Symbol("retry-with-new-lock-key");
|
|
6240
|
+
for (let attempt = 0; attempt < 10; attempt++) {
|
|
6241
|
+
const outcome = await withLocks([sourceLockKey, destLockKey], async () => {
|
|
6242
|
+
const inLock = await this.buildPaths(collection, currentSlug);
|
|
6243
|
+
if (!inLock.existed) {
|
|
6244
|
+
throw new ContentStoreError(`Entry not found: ${currentSlug}`, "NOT_FOUND");
|
|
6245
|
+
}
|
|
6246
|
+
const currentSourceKey = this.entryLockKey(collection, currentSlug, inLock);
|
|
6247
|
+
const currentDestKey = this.createLockKey(collection, safeNewSlug);
|
|
6248
|
+
if (currentSourceKey !== sourceLockKey || inLock.id !== sourceId) {
|
|
6249
|
+
sourceLockKey = currentSourceKey;
|
|
6250
|
+
destLockKey = currentDestKey;
|
|
6251
|
+
sourceId = inLock.id;
|
|
6252
|
+
return RETRY_KEY;
|
|
6253
|
+
}
|
|
5987
6254
|
destLockKey = currentDestKey;
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
|
|
5997
|
-
|
|
5998
|
-
|
|
5999
|
-
|
|
6000
|
-
|
|
6001
|
-
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
|
|
6005
|
-
|
|
6006
|
-
|
|
6007
|
-
|
|
6008
|
-
|
|
6009
|
-
const existingSlug = extractSlugFromFilename(entry.name, entryTypeName);
|
|
6010
|
-
if (existingSlug === safeNewSlug) {
|
|
6011
|
-
throw new ContentStoreError(`Entry with slug "${safeNewSlug}" already exists in collection "${collectionPath}"`, "VALIDATION");
|
|
6255
|
+
const { absolutePath: currentPath, relativePath: currentRelPath } = inLock;
|
|
6256
|
+
const currentFilename = path10.basename(currentPath);
|
|
6257
|
+
const parts = currentFilename.split(".");
|
|
6258
|
+
if (parts.length < 4) {
|
|
6259
|
+
throw new ContentStoreError(`Invalid entry filename format: ${currentFilename}`, "VALIDATION");
|
|
6260
|
+
}
|
|
6261
|
+
const entryTypeName = parts[0];
|
|
6262
|
+
const contentId = parts[parts.length - 2];
|
|
6263
|
+
const ext = `.${parts[parts.length - 1]}`;
|
|
6264
|
+
const newFilename = `${entryTypeName}.${safeNewSlug}.${contentId}${ext}`;
|
|
6265
|
+
const parentDir = path10.dirname(currentPath);
|
|
6266
|
+
const newPath = path10.join(parentDir, newFilename);
|
|
6267
|
+
try {
|
|
6268
|
+
const entries = await fs9.readdir(parentDir, { withFileTypes: true });
|
|
6269
|
+
for (const entry of entries) {
|
|
6270
|
+
if (entry.isDirectory())
|
|
6271
|
+
continue;
|
|
6272
|
+
const existingSlug = extractSlugFromFilename(entry.name, entryTypeName);
|
|
6273
|
+
if (existingSlug === safeNewSlug) {
|
|
6274
|
+
throw new ContentStoreError(`Entry with slug "${safeNewSlug}" already exists in collection "${collectionPath}"`, "VALIDATION");
|
|
6275
|
+
}
|
|
6012
6276
|
}
|
|
6277
|
+
} catch (err) {
|
|
6278
|
+
if (err instanceof ContentStoreError)
|
|
6279
|
+
throw err;
|
|
6013
6280
|
}
|
|
6014
|
-
|
|
6015
|
-
|
|
6281
|
+
try {
|
|
6282
|
+
await fs9.link(currentPath, newPath);
|
|
6283
|
+
} catch (err) {
|
|
6284
|
+
if (isNodeError3(err) && err.code === "EEXIST") {
|
|
6285
|
+
throw new ContentStoreError(`Entry with slug "${safeNewSlug}" already exists in collection "${collectionPath}"`, "VALIDATION");
|
|
6286
|
+
}
|
|
6016
6287
|
throw err;
|
|
6017
|
-
}
|
|
6018
|
-
try {
|
|
6019
|
-
await fs8.link(currentPath, newPath);
|
|
6020
|
-
} catch (err) {
|
|
6021
|
-
if (isNodeError3(err) && err.code === "EEXIST") {
|
|
6022
|
-
throw new ContentStoreError(`Entry with slug "${safeNewSlug}" already exists in collection "${collectionPath}"`, "VALIDATION");
|
|
6023
6288
|
}
|
|
6024
|
-
|
|
6025
|
-
|
|
6026
|
-
|
|
6027
|
-
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
}
|
|
6039
|
-
throw new ContentConflictError();
|
|
6289
|
+
await fs9.unlink(currentPath);
|
|
6290
|
+
const newRelativePath = path10.relative(this.root, newPath);
|
|
6291
|
+
const liveIndex = this._idIndex;
|
|
6292
|
+
const entryId = liveIndex.findByPath(currentRelPath);
|
|
6293
|
+
if (entryId) {
|
|
6294
|
+
liveIndex.updatePath(entryId, newRelativePath);
|
|
6295
|
+
}
|
|
6296
|
+
await this.recordOwnMutation(liveIndex);
|
|
6297
|
+
return { newPath: `${collectionPath}/${safeNewSlug}` };
|
|
6298
|
+
});
|
|
6299
|
+
if (typeof outcome !== "symbol")
|
|
6300
|
+
return outcome;
|
|
6301
|
+
}
|
|
6302
|
+
throw new ContentConflictError();
|
|
6303
|
+
});
|
|
6040
6304
|
}
|
|
6041
6305
|
/**
|
|
6042
6306
|
* List all entries in a collection tree (including subcollections).
|
|
@@ -6211,7 +6475,7 @@ var init_generate_ai_content = __esm({
|
|
|
6211
6475
|
log22 = createDebugLogger2({ prefix: "EntryLinks" });
|
|
6212
6476
|
BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
6213
6477
|
ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
|
|
6214
|
-
|
|
6478
|
+
init_logger2();
|
|
6215
6479
|
log3 = createDebugLogger2({ prefix: "BranchRegistry" });
|
|
6216
6480
|
REGISTRY_FILE = "branches.json";
|
|
6217
6481
|
REGISTRY_STALE_FILE = "branches.stale.json";
|
|
@@ -6223,10 +6487,10 @@ var init_generate_ai_content = __esm({
|
|
|
6223
6487
|
constructor(root) {
|
|
6224
6488
|
this.regenInFlight = null;
|
|
6225
6489
|
this.lastForcedRefreshMs = 0;
|
|
6226
|
-
this.root =
|
|
6227
|
-
this.registryPath =
|
|
6228
|
-
this.stalePath =
|
|
6229
|
-
this.tempPath =
|
|
6490
|
+
this.root = path12.resolve(root);
|
|
6491
|
+
this.registryPath = path12.join(this.root, REGISTRY_FILE);
|
|
6492
|
+
this.stalePath = path12.join(this.root, REGISTRY_STALE_FILE);
|
|
6493
|
+
this.tempPath = path12.join(this.root, REGISTRY_TEMP_FILE);
|
|
6230
6494
|
}
|
|
6231
6495
|
/**
|
|
6232
6496
|
* Returns all branches. Uses the cached snapshot if its embedded generation
|
|
@@ -6235,7 +6499,7 @@ var init_generate_ai_content = __esm({
|
|
|
6235
6499
|
async list() {
|
|
6236
6500
|
let parsed;
|
|
6237
6501
|
try {
|
|
6238
|
-
const raw = await
|
|
6502
|
+
const raw = await fs11.readFile(this.registryPath, "utf8");
|
|
6239
6503
|
parsed = JSON.parse(raw);
|
|
6240
6504
|
} catch (err) {
|
|
6241
6505
|
if (isNotFoundError3(err)) {
|
|
@@ -6324,7 +6588,7 @@ var init_generate_ai_content = __esm({
|
|
|
6324
6588
|
async performRegenerate() {
|
|
6325
6589
|
const read = await readResourceGeneration(this.root, RESOURCE2);
|
|
6326
6590
|
const branches = await this.scanBranchDirectories();
|
|
6327
|
-
await
|
|
6591
|
+
await fs11.unlink(this.stalePath).catch(() => {
|
|
6328
6592
|
});
|
|
6329
6593
|
if (!read.ok) {
|
|
6330
6594
|
return branches;
|
|
@@ -6335,12 +6599,12 @@ var init_generate_ai_content = __esm({
|
|
|
6335
6599
|
generation: read.token
|
|
6336
6600
|
};
|
|
6337
6601
|
const uniqueTempPath = `${this.tempPath}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
6338
|
-
await
|
|
6339
|
-
await
|
|
6602
|
+
await fs11.mkdir(this.root, { recursive: true });
|
|
6603
|
+
await fs11.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
|
|
6340
6604
|
try {
|
|
6341
|
-
await
|
|
6605
|
+
await fs11.rename(uniqueTempPath, this.registryPath);
|
|
6342
6606
|
} catch (err) {
|
|
6343
|
-
await
|
|
6607
|
+
await fs11.unlink(uniqueTempPath).catch(() => {
|
|
6344
6608
|
});
|
|
6345
6609
|
throw err;
|
|
6346
6610
|
}
|
|
@@ -6354,12 +6618,12 @@ var init_generate_ai_content = __esm({
|
|
|
6354
6618
|
async scanBranchDirectories() {
|
|
6355
6619
|
const branches = [];
|
|
6356
6620
|
try {
|
|
6357
|
-
const entries = await
|
|
6621
|
+
const entries = await fs11.readdir(this.root, { withFileTypes: true });
|
|
6358
6622
|
for (const entry of entries) {
|
|
6359
6623
|
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
6360
6624
|
continue;
|
|
6361
6625
|
}
|
|
6362
|
-
const branchRoot =
|
|
6626
|
+
const branchRoot = path12.join(this.root, entry.name);
|
|
6363
6627
|
let meta;
|
|
6364
6628
|
try {
|
|
6365
6629
|
meta = await BranchMetadataFileManager.loadOnly(branchRoot);
|
|
@@ -6410,8 +6674,8 @@ var init_generate_ai_content = __esm({
|
|
|
6410
6674
|
};
|
|
6411
6675
|
BranchMetadataFileManager = class _BranchMetadataFileManager {
|
|
6412
6676
|
constructor(branchRoot, baseRoot, options) {
|
|
6413
|
-
this.branchRoot =
|
|
6414
|
-
this.filePath =
|
|
6677
|
+
this.branchRoot = path14.resolve(branchRoot);
|
|
6678
|
+
this.filePath = path14.join(this.branchRoot, BRANCH_META_DIR, BRANCH_META_FILE);
|
|
6415
6679
|
this.baseRoot = baseRoot;
|
|
6416
6680
|
this.settleMs = options?.settleMs;
|
|
6417
6681
|
}
|
|
@@ -6420,10 +6684,10 @@ var init_generate_ai_content = __esm({
|
|
|
6420
6684
|
* Use this for read-only access (e.g., in registry scanning or loadBranchContext).
|
|
6421
6685
|
*/
|
|
6422
6686
|
static async loadOnly(branchRoot) {
|
|
6423
|
-
const resolvedRoot =
|
|
6424
|
-
const filePath =
|
|
6687
|
+
const resolvedRoot = path14.resolve(branchRoot);
|
|
6688
|
+
const filePath = path14.join(resolvedRoot, BRANCH_META_DIR, BRANCH_META_FILE);
|
|
6425
6689
|
try {
|
|
6426
|
-
const raw = await
|
|
6690
|
+
const raw = await fs13.readFile(filePath, "utf8");
|
|
6427
6691
|
return JSON.parse(raw);
|
|
6428
6692
|
} catch (err) {
|
|
6429
6693
|
if (isNotFoundError3(err)) {
|
|
@@ -6444,7 +6708,7 @@ var init_generate_ai_content = __esm({
|
|
|
6444
6708
|
}
|
|
6445
6709
|
async load() {
|
|
6446
6710
|
try {
|
|
6447
|
-
const raw = await
|
|
6711
|
+
const raw = await fs13.readFile(this.filePath, "utf8");
|
|
6448
6712
|
const parsed = JSON.parse(raw);
|
|
6449
6713
|
const version = parsed.version ?? 0;
|
|
6450
6714
|
return { meta: parsed, version };
|
|
@@ -6506,7 +6770,7 @@ var init_generate_ai_content = __esm({
|
|
|
6506
6770
|
*/
|
|
6507
6771
|
async save(incoming) {
|
|
6508
6772
|
try {
|
|
6509
|
-
await
|
|
6773
|
+
await fs13.stat(this.branchRoot);
|
|
6510
6774
|
} catch (err) {
|
|
6511
6775
|
if (isNotFoundError3(err)) {
|
|
6512
6776
|
throw new BranchMetadataConflictError("Branch no longer exists");
|
|
@@ -6595,6 +6859,7 @@ var init_generate_ai_content = __esm({
|
|
|
6595
6859
|
LEADING_OPTION_PATTERN = /^-/;
|
|
6596
6860
|
WINDOWS_DRIVE_PATTERN = /^[a-zA-Z]:[\\/]/;
|
|
6597
6861
|
COLON_BEFORE_SLASH_PATTERN = /^[^/\\\s]+:/;
|
|
6862
|
+
MISSING_REMOTE_REF_REASONS = ["couldn't find remote ref", "Couldn't find remote ref"];
|
|
6598
6863
|
log5 = createDebugLogger2({ prefix: "GitManager" });
|
|
6599
6864
|
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_]*)$/;
|
|
6600
6865
|
FORCE_C_LOCALE = { LC_ALL: "C", LANG: "C" };
|
|
@@ -6607,14 +6872,23 @@ var init_generate_ai_content = __esm({
|
|
|
6607
6872
|
this.name = "GitConflictError";
|
|
6608
6873
|
}
|
|
6609
6874
|
};
|
|
6875
|
+
GitRemoteRefMissingError = class extends Error {
|
|
6876
|
+
constructor(branch, remote, gitError) {
|
|
6877
|
+
super(`Remote '${remote}' has no ref for branch '${branch}' yet`);
|
|
6878
|
+
this.branch = branch;
|
|
6879
|
+
this.remote = remote;
|
|
6880
|
+
this.gitError = gitError;
|
|
6881
|
+
this.name = "GitRemoteRefMissingError";
|
|
6882
|
+
}
|
|
6883
|
+
};
|
|
6610
6884
|
GitManager = class _GitManager {
|
|
6611
6885
|
constructor(options, gitOptions) {
|
|
6612
|
-
this.repoPath =
|
|
6886
|
+
this.repoPath = path15.resolve(options.repoPath);
|
|
6613
6887
|
this.baseBranch = options.baseBranch ?? "main";
|
|
6614
6888
|
this.remote = options.remote ?? "origin";
|
|
6615
6889
|
this.skipIndexMarker = options.skipIndexMarker ?? false;
|
|
6616
6890
|
this.git = simpleGit2({ baseDir: this.repoPath, ...gitOptions });
|
|
6617
|
-
this.git.env(gitChildEnv({ GIT_CEILING_DIRECTORIES:
|
|
6891
|
+
this.git.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path15.dirname(this.repoPath) }));
|
|
6618
6892
|
}
|
|
6619
6893
|
static async cloneRepo(remoteUrl, targetPath, baseBranch = "main") {
|
|
6620
6894
|
log5.debug("git", "Cloning repository", {
|
|
@@ -6656,7 +6930,7 @@ var init_generate_ai_content = __esm({
|
|
|
6656
6930
|
remotePath: options.remotePath,
|
|
6657
6931
|
baseBranch: options.baseBranch
|
|
6658
6932
|
});
|
|
6659
|
-
const remoteParent =
|
|
6933
|
+
const remoteParent = path15.dirname(options.remotePath);
|
|
6660
6934
|
releaseLock = await acquireProvisioningLock(remoteParent, ".remote-init.lock");
|
|
6661
6935
|
let remoteExists = false;
|
|
6662
6936
|
try {
|
|
@@ -6762,8 +7036,8 @@ var init_generate_ai_content = __esm({
|
|
|
6762
7036
|
* --list` instead of `rev-parse --verify --quiet`: simple-git only fails a
|
|
6763
7037
|
* task on stderr output, and `--quiet` suppresses exactly that).
|
|
6764
7038
|
* `--end-of-options` guards the ref-name positionals the same way
|
|
6765
|
-
* `push
|
|
6766
|
-
*
|
|
7039
|
+
* `push` below does, since `branch` here can be a sanitized but otherwise
|
|
7040
|
+
* caller-influenced string.
|
|
6767
7041
|
*
|
|
6768
7042
|
* A failure here therefore means the remote itself is unreadable and is
|
|
6769
7043
|
* surfaced, NOT treated as "branch absent" — that would route
|
|
@@ -6912,7 +7186,7 @@ var init_generate_ai_content = __esm({
|
|
|
6912
7186
|
*/
|
|
6913
7187
|
static async validateGitRepoExists(repoPath) {
|
|
6914
7188
|
try {
|
|
6915
|
-
const stat = await fs14.stat(
|
|
7189
|
+
const stat = await fs14.stat(path15.join(repoPath, ".git"));
|
|
6916
7190
|
if (!stat.isDirectory()) {
|
|
6917
7191
|
throw new Error(`Expected git repo at ${repoPath}`);
|
|
6918
7192
|
}
|
|
@@ -7003,8 +7277,8 @@ var init_generate_ai_content = __esm({
|
|
|
7003
7277
|
if (config.shouldAutoInitLocal) {
|
|
7004
7278
|
const gitRoot = await this.findGitRoot();
|
|
7005
7279
|
const sourceRoot = options.sourceRoot;
|
|
7006
|
-
const sourcePath = sourceRoot ?
|
|
7007
|
-
const localRemotePath =
|
|
7280
|
+
const sourcePath = sourceRoot ? path15.resolve(gitRoot, sourceRoot) : gitRoot;
|
|
7281
|
+
const localRemotePath = path15.join(sourcePath, config.defaultRemotePath);
|
|
7008
7282
|
await this.ensureLocalSimulatedRemote({
|
|
7009
7283
|
remotePath: localRemotePath,
|
|
7010
7284
|
sourcePath: gitRoot,
|
|
@@ -7032,7 +7306,7 @@ var init_generate_ai_content = __esm({
|
|
|
7032
7306
|
static async repoExistsAt(workspacePath) {
|
|
7033
7307
|
try {
|
|
7034
7308
|
const checkGit = simpleGit2({ baseDir: workspacePath });
|
|
7035
|
-
checkGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES:
|
|
7309
|
+
checkGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path15.dirname(workspacePath) }));
|
|
7036
7310
|
await checkGit.raw(["rev-parse", "--git-dir"]);
|
|
7037
7311
|
return true;
|
|
7038
7312
|
} catch {
|
|
@@ -7054,12 +7328,12 @@ var init_generate_ai_content = __esm({
|
|
|
7054
7328
|
const baseBranch = await resolveBaseBranch({
|
|
7055
7329
|
defaultBaseBranch: options.baseBranch,
|
|
7056
7330
|
mode: options.mode,
|
|
7057
|
-
detectFrom: options.sourceRoot ?
|
|
7331
|
+
detectFrom: options.sourceRoot ? path15.resolve(process.cwd(), options.sourceRoot) : process.cwd()
|
|
7058
7332
|
});
|
|
7059
7333
|
const remoteName = options.remoteName ?? "origin";
|
|
7060
7334
|
const repoExists = await _GitManager.repoExistsAt(options.workspacePath);
|
|
7061
7335
|
if (!repoExists) {
|
|
7062
|
-
const gitPath =
|
|
7336
|
+
const gitPath = path15.join(options.workspacePath, ".git");
|
|
7063
7337
|
try {
|
|
7064
7338
|
const stat = await fs14.stat(gitPath);
|
|
7065
7339
|
if (stat.isDirectory()) {
|
|
@@ -7093,7 +7367,7 @@ var init_generate_ai_content = __esm({
|
|
|
7093
7367
|
}
|
|
7094
7368
|
justCloned = true;
|
|
7095
7369
|
const freshGit = simpleGit2({ baseDir: options.workspacePath });
|
|
7096
|
-
freshGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES:
|
|
7370
|
+
freshGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path15.dirname(options.workspacePath) }));
|
|
7097
7371
|
await freshGit.addConfig("canopycms.managed", "true");
|
|
7098
7372
|
await freshGit.addConfig("user.name", options.gitBotAuthorName);
|
|
7099
7373
|
await freshGit.addConfig("user.email", options.gitBotAuthorEmail);
|
|
@@ -7236,9 +7510,16 @@ var init_generate_ai_content = __esm({
|
|
|
7236
7510
|
async pullCurrentBranchInner() {
|
|
7237
7511
|
const branches = await this.git.branch();
|
|
7238
7512
|
const currentBranch = branches.current;
|
|
7239
|
-
await this.git.fetch(this.remote, currentBranch);
|
|
7240
7513
|
try {
|
|
7241
|
-
await this.git.
|
|
7514
|
+
await this.git.fetch(this.remote, currentBranch);
|
|
7515
|
+
} catch (err) {
|
|
7516
|
+
if (!isMissingRemoteRefFailure(getErrorMessage3(err)))
|
|
7517
|
+
throw err;
|
|
7518
|
+
throw new GitRemoteRefMissingError(currentBranch, this.remote, err);
|
|
7519
|
+
}
|
|
7520
|
+
const fetchedTip = (await this.git.revparse(["FETCH_HEAD"])).trim();
|
|
7521
|
+
try {
|
|
7522
|
+
await this.git.merge([fetchedTip]);
|
|
7242
7523
|
} catch (err) {
|
|
7243
7524
|
try {
|
|
7244
7525
|
const status = await this.git.status();
|
|
@@ -7300,6 +7581,44 @@ var init_generate_ai_content = __esm({
|
|
|
7300
7581
|
`${target}:${target}`
|
|
7301
7582
|
]);
|
|
7302
7583
|
}
|
|
7584
|
+
/**
|
|
7585
|
+
* Check whether the local branch has commits the remote mirror doesn't
|
|
7586
|
+
* have -- i.e. whether push() would actually move the remote ref forward.
|
|
7587
|
+
*
|
|
7588
|
+
* Exists so callers (submitBranch) can gate pushing on "is there anything
|
|
7589
|
+
* new to send" rather than on "is the working tree dirty": committing
|
|
7590
|
+
* cleans the tree, so a dirty-tree gate around commit+push skips the push
|
|
7591
|
+
* entirely on a retry after a failed push, even though the just-created
|
|
7592
|
+
* commit never reached the remote (see services.ts submitBranch).
|
|
7593
|
+
*
|
|
7594
|
+
* Workspaces are cloned `--single-branch`, so the remote-tracking ref for
|
|
7595
|
+
* any branch other than the one cloned never exists locally -- same
|
|
7596
|
+
* constraint pullBaseInner works around. This fetches the specific branch
|
|
7597
|
+
* directly (bypassing the configured single-branch refspec, exactly like
|
|
7598
|
+
* pullBaseInner) and pins the result to FETCH_HEAD's SHA immediately after
|
|
7599
|
+
* the fetch that populated it, rather than trusting `<remote>/<branch>`:
|
|
7600
|
+
* FETCH_HEAD is a shared mutable file that any other fetch in this clone
|
|
7601
|
+
* can repoint before it's read.
|
|
7602
|
+
*
|
|
7603
|
+
* A branch that has never been pushed has no ref on the remote at all --
|
|
7604
|
+
* `git fetch` then fails ("couldn't find remote ref"), which this treats
|
|
7605
|
+
* as "ahead" (needs pushing) rather than an error.
|
|
7606
|
+
*/
|
|
7607
|
+
async hasUnpushedCommits(branch) {
|
|
7608
|
+
const target = branch ?? await this.git.revparse(["--abbrev-ref", "--end-of-options", "HEAD"]);
|
|
7609
|
+
const localSha = (await this.git.revparse(["--end-of-options", target])).trim();
|
|
7610
|
+
let fetchedTip;
|
|
7611
|
+
try {
|
|
7612
|
+
await this.git.raw(["fetch", "--end-of-options", this.remote, target]);
|
|
7613
|
+
fetchedTip = (await this.git.revparse(["--end-of-options", "FETCH_HEAD"])).trim();
|
|
7614
|
+
} catch {
|
|
7615
|
+
return true;
|
|
7616
|
+
}
|
|
7617
|
+
if (fetchedTip === localSha)
|
|
7618
|
+
return false;
|
|
7619
|
+
const aheadCount = (await this.git.raw(["rev-list", "--count", `${fetchedTip}..${localSha}`])).trim();
|
|
7620
|
+
return aheadCount !== "0";
|
|
7621
|
+
}
|
|
7303
7622
|
async ensureAuthor(author) {
|
|
7304
7623
|
const config = await this.git.listConfig();
|
|
7305
7624
|
const isManaged = config.all["canopycms.managed"] === "true";
|
|
@@ -7346,14 +7665,6 @@ var init_generate_ai_content = __esm({
|
|
|
7346
7665
|
const status = await this.status();
|
|
7347
7666
|
return status.files.map((f) => f.path);
|
|
7348
7667
|
}
|
|
7349
|
-
/**
|
|
7350
|
-
* Force push (use with caution - for PR updates only)
|
|
7351
|
-
* Uses --force-with-lease for safer force pushes
|
|
7352
|
-
*/
|
|
7353
|
-
async forcePush(branch) {
|
|
7354
|
-
const target = branch ?? await this.git.revparse(["--abbrev-ref", "HEAD"]);
|
|
7355
|
-
await this.git.raw(["push", "--force-with-lease", "--end-of-options", this.remote, target]);
|
|
7356
|
-
}
|
|
7357
7668
|
/**
|
|
7358
7669
|
* Get remote URL for current repo
|
|
7359
7670
|
*/
|
|
@@ -7407,8 +7718,8 @@ var init_generate_ai_content = __esm({
|
|
|
7407
7718
|
} catch {
|
|
7408
7719
|
}
|
|
7409
7720
|
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
7410
|
-
const absolutePath =
|
|
7411
|
-
await fs14.mkdir(
|
|
7721
|
+
const absolutePath = path15.join(this.repoPath, filePath);
|
|
7722
|
+
await fs14.mkdir(path15.dirname(absolutePath), { recursive: true });
|
|
7412
7723
|
await fs14.writeFile(absolutePath, content, "utf-8");
|
|
7413
7724
|
await this.git.add(filePath);
|
|
7414
7725
|
}
|
|
@@ -7436,7 +7747,7 @@ var init_generate_ai_content = __esm({
|
|
|
7436
7747
|
branchName: options.branchName,
|
|
7437
7748
|
mode: options.mode
|
|
7438
7749
|
});
|
|
7439
|
-
releaseLock = await acquireProvisioningLock(
|
|
7750
|
+
releaseLock = await acquireProvisioningLock(path16.dirname(options.branchRoot), `.${path16.basename(options.branchRoot)}.init.lock`);
|
|
7440
7751
|
await GitManager.initializeWorkspace({
|
|
7441
7752
|
workspacePath: options.branchRoot,
|
|
7442
7753
|
branchName: options.branchName,
|
|
@@ -7477,7 +7788,7 @@ var init_generate_ai_content = __esm({
|
|
|
7477
7788
|
const baseBranch = await resolveBaseBranch({
|
|
7478
7789
|
defaultBaseBranch: this.config.defaultBaseBranch,
|
|
7479
7790
|
mode,
|
|
7480
|
-
detectFrom: this.config.sourceRoot ?
|
|
7791
|
+
detectFrom: this.config.sourceRoot ? path16.resolve(process.cwd(), this.config.sourceRoot) : void 0
|
|
7481
7792
|
});
|
|
7482
7793
|
await this.ensureGitWorkspace({
|
|
7483
7794
|
branchRoot,
|
|
@@ -7585,7 +7896,7 @@ var init_generate_ai_content = __esm({
|
|
|
7585
7896
|
}
|
|
7586
7897
|
const files = dirents.filter((d) => d.isFile() && validExts.some((ext) => d.name.endsWith(ext)) && d.name !== ".collection.json").sort((a, b) => a.name.localeCompare(b.name));
|
|
7587
7898
|
const entries = await Promise.all(files.map(async (file) => {
|
|
7588
|
-
const absolutePath =
|
|
7899
|
+
const absolutePath = path17.join(collectionRoot, file.name);
|
|
7589
7900
|
const relativePath = normalizePath(root, absolutePath);
|
|
7590
7901
|
const parsed = parseTypedFilename(file.name, entryTypes);
|
|
7591
7902
|
if (!parsed) {
|
|
@@ -7619,7 +7930,7 @@ var init_generate_ai_content = __esm({
|
|
|
7619
7930
|
init_config22();
|
|
7620
7931
|
MAX_INPUT_PIXELS = 4096 * 4096;
|
|
7621
7932
|
KNOWN_FIELD_TYPES = new Set(fieldTypes2);
|
|
7622
|
-
STRING_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "
|
|
7933
|
+
STRING_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "markdown", "mdx", "code"]);
|
|
7623
7934
|
selectOptionValues = (field) => (field.options ?? []).map((opt) => typeof opt === "string" ? opt : opt.value);
|
|
7624
7935
|
jiti = createJiti2(import.meta.url);
|
|
7625
7936
|
}
|
|
@@ -7627,9 +7938,9 @@ var init_generate_ai_content = __esm({
|
|
|
7627
7938
|
|
|
7628
7939
|
// dist/utils/atomic-write.js
|
|
7629
7940
|
import fs17 from "node:fs/promises";
|
|
7630
|
-
import
|
|
7941
|
+
import path20 from "node:path";
|
|
7631
7942
|
async function atomicWriteFile3(filePath, content) {
|
|
7632
|
-
const dir =
|
|
7943
|
+
const dir = path20.dirname(filePath);
|
|
7633
7944
|
await fs17.mkdir(dir, { recursive: true });
|
|
7634
7945
|
const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
7635
7946
|
await fs17.writeFile(tempPath, content, typeof content === "string" ? "utf-8" : void 0);
|
|
@@ -7757,10 +8068,10 @@ var init_debug2 = __esm({
|
|
|
7757
8068
|
|
|
7758
8069
|
// dist/resource-generation.js
|
|
7759
8070
|
import fs18 from "node:fs/promises";
|
|
7760
|
-
import
|
|
8071
|
+
import path21 from "node:path";
|
|
7761
8072
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
7762
8073
|
function resourceGenerationPath2(root, resource) {
|
|
7763
|
-
return
|
|
8074
|
+
return path21.resolve(root, META_DIR3, `${resource}.generation`);
|
|
7764
8075
|
}
|
|
7765
8076
|
async function bumpResourceGeneration2(root, resource, options) {
|
|
7766
8077
|
const token = randomUUID3();
|
|
@@ -7792,7 +8103,7 @@ async function readResourceGeneration2(root, resource) {
|
|
|
7792
8103
|
function isGenerationCurrent2(snapshotToken, read) {
|
|
7793
8104
|
return read.ok && read.token === snapshotToken;
|
|
7794
8105
|
}
|
|
7795
|
-
var log8,
|
|
8106
|
+
var log8, META_DIR3;
|
|
7796
8107
|
var init_resource_generation = __esm({
|
|
7797
8108
|
"dist/resource-generation.js"() {
|
|
7798
8109
|
"use strict";
|
|
@@ -7800,15 +8111,15 @@ var init_resource_generation = __esm({
|
|
|
7800
8111
|
init_error();
|
|
7801
8112
|
init_debug2();
|
|
7802
8113
|
log8 = createDebugLogger3({ prefix: "ResourceGeneration" });
|
|
7803
|
-
|
|
8114
|
+
META_DIR3 = ".canopy-meta";
|
|
7804
8115
|
}
|
|
7805
8116
|
});
|
|
7806
8117
|
|
|
7807
8118
|
// dist/content-index-registry.js
|
|
7808
|
-
import
|
|
8119
|
+
import path23 from "node:path";
|
|
7809
8120
|
function invalidateContentIndexesForRoot2(root) {
|
|
7810
|
-
const rootKey =
|
|
7811
|
-
const prefix = rootKey +
|
|
8121
|
+
const rootKey = path23.resolve(root);
|
|
8122
|
+
const prefix = rootKey + path23.sep;
|
|
7812
8123
|
for (const [storeRoot, refs] of registry2) {
|
|
7813
8124
|
if (storeRoot !== rootKey && !storeRoot.startsWith(prefix))
|
|
7814
8125
|
continue;
|
|
@@ -7848,7 +8159,7 @@ function canopyLogError2(...args) {
|
|
|
7848
8159
|
active3.error(...args);
|
|
7849
8160
|
}
|
|
7850
8161
|
var active3;
|
|
7851
|
-
var
|
|
8162
|
+
var init_logger3 = __esm({
|
|
7852
8163
|
"dist/utils/logger.js"() {
|
|
7853
8164
|
"use strict";
|
|
7854
8165
|
active3 = console;
|
|
@@ -7971,6 +8282,7 @@ var init_content_id_index = __esm({
|
|
|
7971
8282
|
"use strict";
|
|
7972
8283
|
init_id();
|
|
7973
8284
|
init_error();
|
|
8285
|
+
init_logger3();
|
|
7974
8286
|
}
|
|
7975
8287
|
});
|
|
7976
8288
|
|
|
@@ -8054,7 +8366,7 @@ var init_meta_loader = __esm({
|
|
|
8054
8366
|
"dist/schema/meta-loader.js"() {
|
|
8055
8367
|
"use strict";
|
|
8056
8368
|
init_error();
|
|
8057
|
-
|
|
8369
|
+
init_logger3();
|
|
8058
8370
|
init_content_id_index();
|
|
8059
8371
|
entryTypeMetaSchema2 = z8.object({
|
|
8060
8372
|
name: z8.string().min(1),
|
|
@@ -8120,7 +8432,6 @@ var init_types5 = __esm({
|
|
|
8120
8432
|
"number",
|
|
8121
8433
|
"boolean",
|
|
8122
8434
|
"datetime",
|
|
8123
|
-
"rich-text",
|
|
8124
8435
|
"markdown",
|
|
8125
8436
|
"mdx",
|
|
8126
8437
|
"code"
|
|
@@ -8391,12 +8702,21 @@ var init_config4 = __esm({
|
|
|
8391
8702
|
}
|
|
8392
8703
|
});
|
|
8393
8704
|
|
|
8705
|
+
// dist/operating-mode/mode-env.js
|
|
8706
|
+
var init_mode_env3 = __esm({
|
|
8707
|
+
"dist/operating-mode/mode-env.js"() {
|
|
8708
|
+
"use strict";
|
|
8709
|
+
init_logger3();
|
|
8710
|
+
}
|
|
8711
|
+
});
|
|
8712
|
+
|
|
8394
8713
|
// dist/config/validation.js
|
|
8395
8714
|
var init_validation4 = __esm({
|
|
8396
8715
|
"dist/config/validation.js"() {
|
|
8397
8716
|
"use strict";
|
|
8398
8717
|
init_config4();
|
|
8399
8718
|
init_flatten3();
|
|
8719
|
+
init_mode_env3();
|
|
8400
8720
|
}
|
|
8401
8721
|
});
|
|
8402
8722
|
|
|
@@ -8459,12 +8779,12 @@ var init_content_index_generation = __esm({
|
|
|
8459
8779
|
|
|
8460
8780
|
// dist/sync-core.js
|
|
8461
8781
|
import fs20 from "node:fs/promises";
|
|
8462
|
-
import
|
|
8782
|
+
import path24 from "node:path";
|
|
8463
8783
|
import { simpleGit as simpleGit4 } from "simple-git";
|
|
8464
8784
|
function assertWithinDir(resolved, parent, label) {
|
|
8465
|
-
const normalizedResolved =
|
|
8466
|
-
const normalizedParent =
|
|
8467
|
-
if (!normalizedResolved.startsWith(normalizedParent +
|
|
8785
|
+
const normalizedResolved = path24.resolve(resolved);
|
|
8786
|
+
const normalizedParent = path24.resolve(parent);
|
|
8787
|
+
if (!normalizedResolved.startsWith(normalizedParent + path24.sep) && normalizedResolved !== normalizedParent) {
|
|
8468
8788
|
throw new Error(`${label} escapes the expected directory: ${resolved}`);
|
|
8469
8789
|
}
|
|
8470
8790
|
}
|
|
@@ -8503,7 +8823,7 @@ async function listFilesRecursive(dir, prefix = "") {
|
|
|
8503
8823
|
continue;
|
|
8504
8824
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
8505
8825
|
if (entry.isDirectory()) {
|
|
8506
|
-
results.push(...await listFilesRecursive(
|
|
8826
|
+
results.push(...await listFilesRecursive(path24.join(dir, entry.name), rel));
|
|
8507
8827
|
} else {
|
|
8508
8828
|
results.push(rel);
|
|
8509
8829
|
}
|
|
@@ -8518,8 +8838,8 @@ async function copyDir(src, dest) {
|
|
|
8518
8838
|
continue;
|
|
8519
8839
|
if (entry.isSymbolicLink())
|
|
8520
8840
|
continue;
|
|
8521
|
-
const srcPath =
|
|
8522
|
-
const destPath =
|
|
8841
|
+
const srcPath = path24.join(src, entry.name);
|
|
8842
|
+
const destPath = path24.join(dest, entry.name);
|
|
8523
8843
|
if (entry.isDirectory()) {
|
|
8524
8844
|
await copyDir(srcPath, destPath);
|
|
8525
8845
|
} else {
|
|
@@ -8532,7 +8852,7 @@ async function pushContentToWorkspace(options) {
|
|
|
8532
8852
|
if (!await filePathExists(srcContentDir)) {
|
|
8533
8853
|
return { fileCount: 0 };
|
|
8534
8854
|
}
|
|
8535
|
-
const wsContentDir =
|
|
8855
|
+
const wsContentDir = path24.join(branchPath, contentRoot);
|
|
8536
8856
|
assertWithinDir(wsContentDir, branchPath, "content-root");
|
|
8537
8857
|
const tmpDir = `${wsContentDir}.sync-tmp-${Date.now()}`;
|
|
8538
8858
|
try {
|
|
@@ -8588,11 +8908,11 @@ var init_provisioning_lock = __esm({
|
|
|
8588
8908
|
|
|
8589
8909
|
// dist/git-manager.js
|
|
8590
8910
|
import fs21 from "node:fs/promises";
|
|
8591
|
-
import
|
|
8911
|
+
import path25 from "node:path";
|
|
8592
8912
|
import { simpleGit as simpleGit6 } from "simple-git";
|
|
8593
8913
|
async function ensureGitExcludePattern2(repoPath, pattern) {
|
|
8594
|
-
const excludePath =
|
|
8595
|
-
await fs21.mkdir(
|
|
8914
|
+
const excludePath = path25.join(repoPath, ".git", "info", "exclude");
|
|
8915
|
+
await fs21.mkdir(path25.dirname(excludePath), { recursive: true });
|
|
8596
8916
|
let content = "";
|
|
8597
8917
|
try {
|
|
8598
8918
|
content = await fs21.readFile(excludePath, "utf-8");
|
|
@@ -8713,7 +9033,7 @@ var warned3, VALID_DEPLOYMENT_NAME3, isValidDeploymentName3;
|
|
|
8713
9033
|
var init_deployment_name2 = __esm({
|
|
8714
9034
|
"dist/operating-mode/deployment-name.js"() {
|
|
8715
9035
|
"use strict";
|
|
8716
|
-
|
|
9036
|
+
init_logger3();
|
|
8717
9037
|
warned3 = false;
|
|
8718
9038
|
VALID_DEPLOYMENT_NAME3 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
8719
9039
|
isValidDeploymentName3 = (name) => VALID_DEPLOYMENT_NAME3.test(name) && !name.includes("..") && !name.endsWith(".") && !name.endsWith(".lock");
|
|
@@ -8764,7 +9084,7 @@ var init_config5 = __esm({
|
|
|
8764
9084
|
});
|
|
8765
9085
|
|
|
8766
9086
|
// dist/operating-mode/client-unsafe-strategy.js
|
|
8767
|
-
import
|
|
9087
|
+
import path26 from "node:path";
|
|
8768
9088
|
function operatingStrategy3(mode) {
|
|
8769
9089
|
const cached = strategyCache3.get(mode);
|
|
8770
9090
|
if (cached)
|
|
@@ -8799,32 +9119,32 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
8799
9119
|
// - shouldCommit(), shouldPush()
|
|
8800
9120
|
// Add client-unsafe methods (use Node.js APIs)
|
|
8801
9121
|
getWorkspaceRoot(_sourceRoot) {
|
|
8802
|
-
return
|
|
9122
|
+
return path26.resolve(process.env.CANOPYCMS_WORKSPACE_ROOT ?? DEFAULT_PROD_WORKSPACE3);
|
|
8803
9123
|
}
|
|
8804
9124
|
getContentRoot(contentRoot, sourceRoot) {
|
|
8805
|
-
return
|
|
9125
|
+
return path26.resolve(sourceRoot ?? process.cwd(), contentRoot);
|
|
8806
9126
|
}
|
|
8807
9127
|
getContentBranchesRoot(sourceRoot) {
|
|
8808
|
-
return
|
|
9128
|
+
return path26.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
8809
9129
|
}
|
|
8810
9130
|
getContentBranchRoot(branchName, sourceRoot) {
|
|
8811
|
-
return
|
|
9131
|
+
return path26.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
8812
9132
|
}
|
|
8813
9133
|
getGitExcludePattern() {
|
|
8814
9134
|
return ".canopy-meta/";
|
|
8815
9135
|
}
|
|
8816
9136
|
getPermissionsFilePath(root) {
|
|
8817
|
-
return
|
|
9137
|
+
return path26.join(root, this.getPermissionsFileName());
|
|
8818
9138
|
}
|
|
8819
9139
|
getGroupsFilePath(root) {
|
|
8820
|
-
return
|
|
9140
|
+
return path26.join(root, this.getGroupsFileName());
|
|
8821
9141
|
}
|
|
8822
9142
|
getRemoteUrlConfig() {
|
|
8823
9143
|
return {
|
|
8824
9144
|
shouldAutoInitLocal: false,
|
|
8825
9145
|
defaultRemotePath: "",
|
|
8826
9146
|
envVarName: "CANOPYCMS_REMOTE_URL",
|
|
8827
|
-
autoDetectRemotePath:
|
|
9147
|
+
autoDetectRemotePath: path26.join(this.getWorkspaceRoot(), "remote.git")
|
|
8828
9148
|
};
|
|
8829
9149
|
}
|
|
8830
9150
|
requiresExistingRepo() {
|
|
@@ -8836,7 +9156,7 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
8836
9156
|
return `canopycms-settings-${resolveDeploymentName3(config, "prod")}`;
|
|
8837
9157
|
}
|
|
8838
9158
|
getSettingsRoot(sourceRoot) {
|
|
8839
|
-
return
|
|
9159
|
+
return path26.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
8840
9160
|
}
|
|
8841
9161
|
usesSeparateSettingsBranch() {
|
|
8842
9162
|
return true;
|
|
@@ -8853,25 +9173,25 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
8853
9173
|
DevStrategy3 = class extends DevClientSafeStrategy3 {
|
|
8854
9174
|
// Inherits client-safe methods from DevClientSafeStrategy
|
|
8855
9175
|
getWorkspaceRoot(sourceRoot) {
|
|
8856
|
-
return
|
|
9176
|
+
return path26.resolve(sourceRoot ?? process.cwd(), ".canopy-dev");
|
|
8857
9177
|
}
|
|
8858
9178
|
getContentRoot(contentRoot, sourceRoot) {
|
|
8859
|
-
return
|
|
9179
|
+
return path26.resolve(sourceRoot ?? process.cwd(), contentRoot);
|
|
8860
9180
|
}
|
|
8861
9181
|
getContentBranchesRoot(sourceRoot) {
|
|
8862
|
-
return
|
|
9182
|
+
return path26.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
8863
9183
|
}
|
|
8864
9184
|
getContentBranchRoot(branchName, sourceRoot) {
|
|
8865
|
-
return
|
|
9185
|
+
return path26.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
8866
9186
|
}
|
|
8867
9187
|
getGitExcludePattern() {
|
|
8868
9188
|
return ".canopy-meta/";
|
|
8869
9189
|
}
|
|
8870
9190
|
getPermissionsFilePath(root) {
|
|
8871
|
-
return
|
|
9191
|
+
return path26.join(root, this.getPermissionsFileName());
|
|
8872
9192
|
}
|
|
8873
9193
|
getGroupsFilePath(root) {
|
|
8874
|
-
return
|
|
9194
|
+
return path26.join(root, this.getGroupsFileName());
|
|
8875
9195
|
}
|
|
8876
9196
|
getRemoteUrlConfig() {
|
|
8877
9197
|
return {
|
|
@@ -8889,7 +9209,7 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
8889
9209
|
return `canopycms-settings-${resolveDeploymentName3(config, "local")}`;
|
|
8890
9210
|
}
|
|
8891
9211
|
getSettingsRoot(sourceRoot) {
|
|
8892
|
-
return
|
|
9212
|
+
return path26.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
8893
9213
|
}
|
|
8894
9214
|
usesSeparateSettingsBranch() {
|
|
8895
9215
|
return true;
|
|
@@ -8911,6 +9231,7 @@ var init_operating_mode2 = __esm({
|
|
|
8911
9231
|
init_client_safe_strategy2();
|
|
8912
9232
|
init_client_unsafe_strategy2();
|
|
8913
9233
|
init_deployment_name2();
|
|
9234
|
+
init_mode_env3();
|
|
8914
9235
|
}
|
|
8915
9236
|
});
|
|
8916
9237
|
|
|
@@ -8921,7 +9242,7 @@ __export(sync_exports, {
|
|
|
8921
9242
|
sync: () => sync
|
|
8922
9243
|
});
|
|
8923
9244
|
import fs24 from "node:fs/promises";
|
|
8924
|
-
import
|
|
9245
|
+
import path27 from "node:path";
|
|
8925
9246
|
import { simpleGit as simpleGit7 } from "simple-git";
|
|
8926
9247
|
import * as p2 from "@clack/prompts";
|
|
8927
9248
|
async function detectCurrentBranch(projectDir) {
|
|
@@ -8962,7 +9283,7 @@ async function selectBranch(options, branchesDir) {
|
|
|
8962
9283
|
branchName = result;
|
|
8963
9284
|
}
|
|
8964
9285
|
}
|
|
8965
|
-
const branchPath =
|
|
9286
|
+
const branchPath = path27.join(branchesDir, branchName);
|
|
8966
9287
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
8967
9288
|
if (!branches.includes(branchName)) {
|
|
8968
9289
|
if (options.autoCreate) {
|
|
@@ -8983,8 +9304,8 @@ async function selectBranch(options, branchesDir) {
|
|
|
8983
9304
|
async function syncPush(options) {
|
|
8984
9305
|
const { projectDir } = options;
|
|
8985
9306
|
const contentRoot = options.contentRoot || "content";
|
|
8986
|
-
const branchesDir =
|
|
8987
|
-
const srcContentDir =
|
|
9307
|
+
const branchesDir = path27.join(projectDir, ".canopy-dev", "content-branches");
|
|
9308
|
+
const srcContentDir = path27.join(projectDir, contentRoot);
|
|
8988
9309
|
assertWithinDir(srcContentDir, projectDir, "--content-root");
|
|
8989
9310
|
if (!await filePathExists(srcContentDir)) {
|
|
8990
9311
|
throw new SyncError(`Content directory not found: ${contentRoot}/ (expected at ${srcContentDir})`);
|
|
@@ -8992,7 +9313,7 @@ async function syncPush(options) {
|
|
|
8992
9313
|
const branchName = await selectBranch({ ...options, projectDir, autoCreate: true }, branchesDir);
|
|
8993
9314
|
if (!branchName)
|
|
8994
9315
|
return { fileCount: 0 };
|
|
8995
|
-
const branchPath =
|
|
9316
|
+
const branchPath = path27.join(branchesDir, branchName);
|
|
8996
9317
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
8997
9318
|
const wsGit = simpleGit7({ baseDir: branchPath });
|
|
8998
9319
|
const status = await wsGit.status();
|
|
@@ -9050,19 +9371,19 @@ async function syncPush(options) {
|
|
|
9050
9371
|
async function syncPull(options) {
|
|
9051
9372
|
const { projectDir } = options;
|
|
9052
9373
|
const contentRoot = options.contentRoot || "content";
|
|
9053
|
-
const branchesDir =
|
|
9374
|
+
const branchesDir = path27.join(projectDir, ".canopy-dev", "content-branches");
|
|
9054
9375
|
const branchName = await selectBranch({ ...options, projectDir }, branchesDir);
|
|
9055
9376
|
if (!branchName)
|
|
9056
9377
|
return { fileCount: 0 };
|
|
9057
|
-
const branchPath =
|
|
9378
|
+
const branchPath = path27.join(branchesDir, branchName);
|
|
9058
9379
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
9059
|
-
const branchContentDir =
|
|
9380
|
+
const branchContentDir = path27.join(branchPath, contentRoot);
|
|
9060
9381
|
assertWithinDir(branchContentDir, branchPath, "--content-root");
|
|
9061
9382
|
if (!await filePathExists(branchContentDir)) {
|
|
9062
9383
|
throw new SyncError(`Content directory not found in branch workspace: ${branchName}/${contentRoot}`);
|
|
9063
9384
|
}
|
|
9064
9385
|
p2.log.step(`Pulling content from branch: ${branchName}`);
|
|
9065
|
-
const destContentDir =
|
|
9386
|
+
const destContentDir = path27.join(projectDir, contentRoot);
|
|
9066
9387
|
assertWithinDir(destContentDir, projectDir, "--content-root");
|
|
9067
9388
|
const sourceGit = simpleGit7({ baseDir: projectDir });
|
|
9068
9389
|
const status = await sourceGit.status();
|
|
@@ -9138,8 +9459,8 @@ async function syncPull(options) {
|
|
|
9138
9459
|
async function syncBoth(options) {
|
|
9139
9460
|
const { projectDir } = options;
|
|
9140
9461
|
const contentRoot = options.contentRoot || "content";
|
|
9141
|
-
const branchesDir =
|
|
9142
|
-
const srcContentDir =
|
|
9462
|
+
const branchesDir = path27.join(projectDir, ".canopy-dev", "content-branches");
|
|
9463
|
+
const srcContentDir = path27.join(projectDir, contentRoot);
|
|
9143
9464
|
assertWithinDir(srcContentDir, projectDir, "--content-root");
|
|
9144
9465
|
if (!await filePathExists(srcContentDir)) {
|
|
9145
9466
|
throw new SyncError(`Content directory not found: ${contentRoot}/ (expected at ${srcContentDir})`);
|
|
@@ -9147,7 +9468,7 @@ async function syncBoth(options) {
|
|
|
9147
9468
|
const branchName = await selectBranch({ ...options, projectDir, autoCreate: true }, branchesDir);
|
|
9148
9469
|
if (!branchName)
|
|
9149
9470
|
return { pushed: 0, pulled: 0 };
|
|
9150
|
-
const branchPath =
|
|
9471
|
+
const branchPath = path27.join(branchesDir, branchName);
|
|
9151
9472
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
9152
9473
|
const wsGit = simpleGit7({ baseDir: branchPath });
|
|
9153
9474
|
const status = await wsGit.status();
|
|
@@ -9182,7 +9503,7 @@ async function syncBoth(options) {
|
|
|
9182
9503
|
try {
|
|
9183
9504
|
const incomingBranch = `sync-incoming-${Date.now()}`;
|
|
9184
9505
|
await wsGit.raw(["checkout", "-b", incomingBranch, baseRef]);
|
|
9185
|
-
const wsContentDir =
|
|
9506
|
+
const wsContentDir = path27.join(branchPath, contentRoot);
|
|
9186
9507
|
assertWithinDir(wsContentDir, branchPath, "--content-root");
|
|
9187
9508
|
const tmpDir = `${wsContentDir}.sync-tmp-${Date.now()}`;
|
|
9188
9509
|
try {
|
|
@@ -9246,11 +9567,11 @@ async function syncBoth(options) {
|
|
|
9246
9567
|
}
|
|
9247
9568
|
async function syncAbort(options) {
|
|
9248
9569
|
const { projectDir } = options;
|
|
9249
|
-
const branchesDir =
|
|
9570
|
+
const branchesDir = path27.join(projectDir, ".canopy-dev", "content-branches");
|
|
9250
9571
|
const branchName = await selectBranch({ ...options, projectDir }, branchesDir);
|
|
9251
9572
|
if (!branchName)
|
|
9252
9573
|
return;
|
|
9253
|
-
const branchPath =
|
|
9574
|
+
const branchPath = path27.join(branchesDir, branchName);
|
|
9254
9575
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
9255
9576
|
const wsGit = simpleGit7({ baseDir: branchPath });
|
|
9256
9577
|
const status = await wsGit.status();
|
|
@@ -9379,7 +9700,7 @@ var init_async_mutex = __esm({
|
|
|
9379
9700
|
|
|
9380
9701
|
// dist/utils/occ-json-write.js
|
|
9381
9702
|
import fs25 from "node:fs/promises";
|
|
9382
|
-
import
|
|
9703
|
+
import path28 from "node:path";
|
|
9383
9704
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
9384
9705
|
import lockfile4 from "proper-lockfile";
|
|
9385
9706
|
async function writeOccJsonFile2(filePath, payload, options) {
|
|
@@ -9388,7 +9709,7 @@ async function writeOccJsonFile2(filePath, payload, options) {
|
|
|
9388
9709
|
const writeId = randomUUID4();
|
|
9389
9710
|
const data = { ...payload, version: newVersion, writeId };
|
|
9390
9711
|
const content = JSON.stringify(data, null, 2) + (trailingNewline ? "\n" : "");
|
|
9391
|
-
const dir =
|
|
9712
|
+
const dir = path28.dirname(filePath);
|
|
9392
9713
|
await fs25.mkdir(dir, { recursive: true });
|
|
9393
9714
|
const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
9394
9715
|
await fs25.writeFile(tempPath, content, "utf-8");
|
|
@@ -9453,7 +9774,7 @@ async function withOccRetry2(operation, options) {
|
|
|
9453
9774
|
throw new Error("Unreachable");
|
|
9454
9775
|
}
|
|
9455
9776
|
async function withOccFileLock2(filePath, fn) {
|
|
9456
|
-
const dir =
|
|
9777
|
+
const dir = path28.dirname(filePath);
|
|
9457
9778
|
await fs25.mkdir(dir, { recursive: true });
|
|
9458
9779
|
const MAX_ATTEMPTS = 14;
|
|
9459
9780
|
let release = null;
|
|
@@ -9514,7 +9835,7 @@ var init_occ_json_write = __esm({
|
|
|
9514
9835
|
|
|
9515
9836
|
// dist/branch-registry.js
|
|
9516
9837
|
import fs26 from "node:fs/promises";
|
|
9517
|
-
import
|
|
9838
|
+
import path29 from "node:path";
|
|
9518
9839
|
var log12, REGISTRY_FILE2, REGISTRY_STALE_FILE2, REGISTRY_TEMP_FILE2, REGISTRY_VERSION2, RESOURCE4, GET_MISS_REFRESH_MIN_INTERVAL_MS2, BranchRegistry2;
|
|
9519
9840
|
var init_branch_registry = __esm({
|
|
9520
9841
|
"dist/branch-registry.js"() {
|
|
@@ -9522,7 +9843,7 @@ var init_branch_registry = __esm({
|
|
|
9522
9843
|
init_branch_metadata();
|
|
9523
9844
|
init_error();
|
|
9524
9845
|
init_debug2();
|
|
9525
|
-
|
|
9846
|
+
init_logger3();
|
|
9526
9847
|
init_resource_generation();
|
|
9527
9848
|
log12 = createDebugLogger3({ prefix: "BranchRegistry" });
|
|
9528
9849
|
REGISTRY_FILE2 = "branches.json";
|
|
@@ -9535,10 +9856,10 @@ var init_branch_registry = __esm({
|
|
|
9535
9856
|
constructor(root) {
|
|
9536
9857
|
this.regenInFlight = null;
|
|
9537
9858
|
this.lastForcedRefreshMs = 0;
|
|
9538
|
-
this.root =
|
|
9539
|
-
this.registryPath =
|
|
9540
|
-
this.stalePath =
|
|
9541
|
-
this.tempPath =
|
|
9859
|
+
this.root = path29.resolve(root);
|
|
9860
|
+
this.registryPath = path29.join(this.root, REGISTRY_FILE2);
|
|
9861
|
+
this.stalePath = path29.join(this.root, REGISTRY_STALE_FILE2);
|
|
9862
|
+
this.tempPath = path29.join(this.root, REGISTRY_TEMP_FILE2);
|
|
9542
9863
|
}
|
|
9543
9864
|
/**
|
|
9544
9865
|
* Returns all branches. Uses the cached snapshot if its embedded generation
|
|
@@ -9671,7 +9992,7 @@ var init_branch_registry = __esm({
|
|
|
9671
9992
|
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
9672
9993
|
continue;
|
|
9673
9994
|
}
|
|
9674
|
-
const branchRoot =
|
|
9995
|
+
const branchRoot = path29.join(this.root, entry.name);
|
|
9675
9996
|
let meta;
|
|
9676
9997
|
try {
|
|
9677
9998
|
meta = await BranchMetadataFileManager2.loadOnly(branchRoot);
|
|
@@ -9701,7 +10022,7 @@ var init_branch_registry = __esm({
|
|
|
9701
10022
|
|
|
9702
10023
|
// dist/branch-metadata.js
|
|
9703
10024
|
import fs27 from "node:fs/promises";
|
|
9704
|
-
import
|
|
10025
|
+
import path30 from "node:path";
|
|
9705
10026
|
var BRANCH_META_DIR2, BRANCH_META_FILE2, CURRENT_SCHEMA_VERSION2, BranchMetadataConflictError2, BranchMetadataCorruptError2, BranchMetadataFileManager2;
|
|
9706
10027
|
var init_branch_metadata = __esm({
|
|
9707
10028
|
"dist/branch-metadata.js"() {
|
|
@@ -9730,8 +10051,8 @@ var init_branch_metadata = __esm({
|
|
|
9730
10051
|
};
|
|
9731
10052
|
BranchMetadataFileManager2 = class _BranchMetadataFileManager2 {
|
|
9732
10053
|
constructor(branchRoot, baseRoot, options) {
|
|
9733
|
-
this.branchRoot =
|
|
9734
|
-
this.filePath =
|
|
10054
|
+
this.branchRoot = path30.resolve(branchRoot);
|
|
10055
|
+
this.filePath = path30.join(this.branchRoot, BRANCH_META_DIR2, BRANCH_META_FILE2);
|
|
9735
10056
|
this.baseRoot = baseRoot;
|
|
9736
10057
|
this.settleMs = options?.settleMs;
|
|
9737
10058
|
}
|
|
@@ -9740,8 +10061,8 @@ var init_branch_metadata = __esm({
|
|
|
9740
10061
|
* Use this for read-only access (e.g., in registry scanning or loadBranchContext).
|
|
9741
10062
|
*/
|
|
9742
10063
|
static async loadOnly(branchRoot) {
|
|
9743
|
-
const resolvedRoot =
|
|
9744
|
-
const filePath =
|
|
10064
|
+
const resolvedRoot = path30.resolve(branchRoot);
|
|
10065
|
+
const filePath = path30.join(resolvedRoot, BRANCH_META_DIR2, BRANCH_META_FILE2);
|
|
9745
10066
|
try {
|
|
9746
10067
|
const raw = await fs27.readFile(filePath, "utf8");
|
|
9747
10068
|
return JSON.parse(raw);
|
|
@@ -9902,7 +10223,7 @@ __export(migrate_exports, {
|
|
|
9902
10223
|
slugifyName: () => slugifyName
|
|
9903
10224
|
});
|
|
9904
10225
|
import fs28 from "node:fs/promises";
|
|
9905
|
-
import
|
|
10226
|
+
import path31 from "node:path";
|
|
9906
10227
|
import * as p3 from "@clack/prompts";
|
|
9907
10228
|
function slugifyName(name) {
|
|
9908
10229
|
const slug = name.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -9936,7 +10257,7 @@ async function planDirectory(dirPath, isRoot, ctx) {
|
|
|
9936
10257
|
if (entry.name.startsWith("."))
|
|
9937
10258
|
continue;
|
|
9938
10259
|
if (entry.isDirectory()) {
|
|
9939
|
-
const subPath =
|
|
10260
|
+
const subPath = path31.join(dirPath, entry.name);
|
|
9940
10261
|
const sub = await planDirectory(subPath, false, ctx);
|
|
9941
10262
|
if (!sub.hasContent)
|
|
9942
10263
|
continue;
|
|
@@ -9944,10 +10265,10 @@ async function planDirectory(dirPath, isRoot, ctx) {
|
|
|
9944
10265
|
ops.push(...sub.ops);
|
|
9945
10266
|
if (!extractIdFromFilename2(entry.name)) {
|
|
9946
10267
|
const newName = `${toValidSlug(entry.name)}.${generateId2()}`;
|
|
9947
|
-
ops.push({ kind: "rename", from: subPath, to:
|
|
10268
|
+
ops.push({ kind: "rename", from: subPath, to: path31.join(dirPath, newName) });
|
|
9948
10269
|
}
|
|
9949
10270
|
} else if (entry.isFile()) {
|
|
9950
|
-
const ext =
|
|
10271
|
+
const ext = path31.extname(entry.name).slice(1);
|
|
9951
10272
|
const matchesFormat = ext === ctx.format || ctx.format === "yaml" && ext === "yml";
|
|
9952
10273
|
if (!matchesFormat)
|
|
9953
10274
|
continue;
|
|
@@ -9958,17 +10279,17 @@ async function planDirectory(dirPath, isRoot, ctx) {
|
|
|
9958
10279
|
const newName = `${ctx.entryType}.${toValidSlug(base)}.${generateId2()}.${ext}`;
|
|
9959
10280
|
ops.push({
|
|
9960
10281
|
kind: "rename",
|
|
9961
|
-
from:
|
|
9962
|
-
to:
|
|
10282
|
+
from: path31.join(dirPath, entry.name),
|
|
10283
|
+
to: path31.join(dirPath, newName)
|
|
9963
10284
|
});
|
|
9964
10285
|
}
|
|
9965
10286
|
}
|
|
9966
10287
|
}
|
|
9967
10288
|
const needsMeta = isRoot ? hasOwnFiles : hasContent;
|
|
9968
|
-
const metaPath =
|
|
10289
|
+
const metaPath = path31.join(dirPath, ".collection.json");
|
|
9969
10290
|
if (needsMeta && !await filePathExists(metaPath)) {
|
|
9970
10291
|
const entryTypeMeta = { name: ctx.entryType, format: ctx.format, schema: ctx.schema };
|
|
9971
|
-
const meta = isRoot ? { entries: [entryTypeMeta] } : { name: toValidSlug(stripDirIdSuffix(
|
|
10292
|
+
const meta = isRoot ? { entries: [entryTypeMeta] } : { name: toValidSlug(stripDirIdSuffix(path31.basename(dirPath))), entries: [entryTypeMeta] };
|
|
9972
10293
|
ops.push({ kind: "write", filePath: metaPath, content: JSON.stringify(meta, null, 2) + "\n" });
|
|
9973
10294
|
}
|
|
9974
10295
|
return { ops, hasContent };
|
|
@@ -9977,7 +10298,7 @@ async function migrate(options) {
|
|
|
9977
10298
|
const { projectDir } = options;
|
|
9978
10299
|
const contentRoot = options.contentRoot || "content";
|
|
9979
10300
|
p3.intro("CanopyCMS migrate");
|
|
9980
|
-
const contentDir =
|
|
10301
|
+
const contentDir = path31.join(projectDir, contentRoot);
|
|
9981
10302
|
if (!await filePathExists(contentDir)) {
|
|
9982
10303
|
throw new MigrateError(`Content directory not found: ${contentRoot}/ (expected at ${contentDir})`);
|
|
9983
10304
|
}
|
|
@@ -10061,7 +10382,7 @@ async function migrate(options) {
|
|
|
10061
10382
|
}
|
|
10062
10383
|
};
|
|
10063
10384
|
if (isBranchClone) {
|
|
10064
|
-
const lockPath =
|
|
10385
|
+
const lockPath = path31.join(projectDir, ".canopy-meta", "schema");
|
|
10065
10386
|
try {
|
|
10066
10387
|
await withLock2(lockPath, () => withOccFileLock2(lockPath, applyOps));
|
|
10067
10388
|
} catch (err) {
|
|
@@ -10109,7 +10430,7 @@ var init_migrate = __esm({
|
|
|
10109
10430
|
this.name = "MigrateError";
|
|
10110
10431
|
}
|
|
10111
10432
|
};
|
|
10112
|
-
describeOp = (op, baseDir) => op.kind === "rename" ? `rename ${
|
|
10433
|
+
describeOp = (op, baseDir) => op.kind === "rename" ? `rename ${path31.relative(baseDir, op.from)} \u2192 ${path31.basename(op.to)}` : `create ${path31.relative(baseDir, op.filePath)}`;
|
|
10113
10434
|
}
|
|
10114
10435
|
});
|
|
10115
10436
|
|