canopycms 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +197 -47
- package/dist/api/admin-branch-health.js +47 -20
- package/dist/api/admin.d.ts +13 -1
- package/dist/api/admin.js +14 -24
- package/dist/api/branch-changes.d.ts +87 -0
- package/dist/api/branch-changes.js +269 -0
- package/dist/api/branch-entry-changes.d.ts +83 -0
- package/dist/api/branch-entry-changes.js +262 -0
- package/dist/api/branch-merge.js +2 -2
- package/dist/api/branch-review.js +35 -4
- package/dist/api/branch-status.js +14 -5
- package/dist/api/branch-withdraw.js +7 -0
- package/dist/api/branch.d.ts +13 -1
- package/dist/api/branch.js +171 -97
- package/dist/api/client.d.ts +15 -0
- package/dist/api/client.js +13 -1
- package/dist/api/content.d.ts +18 -18
- package/dist/api/content.js +1 -1
- package/dist/api/github-sync.js +7 -3
- package/dist/api/groups.d.ts +4 -4
- package/dist/api/pr-comment-sync.d.ts +9 -8
- package/dist/api/pr-comment-sync.js +23 -13
- package/dist/api/routes.js +4 -0
- package/dist/api/types.d.ts +3 -1
- package/dist/authorization/groups/schema.d.ts +4 -4
- package/dist/authorization/protected-branch.d.ts +3 -3
- package/dist/authorization/protected-branch.js +3 -3
- package/dist/branch-metadata-file.d.ts +84 -0
- package/dist/branch-metadata-file.js +9 -0
- package/dist/branch-metadata.d.ts +4 -4
- package/dist/branch-metadata.js +4 -5
- package/dist/branch-param.d.ts +24 -0
- package/dist/branch-param.js +87 -0
- package/dist/branch-provisioning.d.ts +6 -3
- package/dist/branch-provisioning.js +12 -10
- package/dist/branch-registry.d.ts +6 -0
- package/dist/branch-registry.js +12 -3
- package/dist/branch-schema-cache.d.ts +22 -2
- package/dist/branch-schema-cache.js +88 -12
- package/dist/branch-workspace.js +22 -15
- package/dist/cli/cli.js +1122 -181
- package/dist/cli/generate-ai-content.js +791 -109
- package/dist/cli/init.js +126 -0
- package/dist/comment-store.d.ts +3 -0
- package/dist/comment-store.js +4 -1
- package/dist/config/schemas/config.d.ts +3 -0
- package/dist/config/schemas/config.js +2 -0
- package/dist/config/types.d.ts +11 -0
- package/dist/content-id-index.d.ts +5 -0
- package/dist/content-id-index.js +1 -1
- package/dist/content-listing.d.ts +53 -0
- package/dist/content-listing.js +123 -27
- package/dist/content-reader.d.ts +24 -10
- package/dist/content-reader.js +66 -22
- package/dist/context.js +51 -48
- package/dist/editor/BranchManager.d.ts +7 -3
- package/dist/editor/BranchManager.js +51 -6
- package/dist/editor/Editor.js +166 -34
- package/dist/editor/EntryNavigator.d.ts +34 -10
- package/dist/editor/EntryNavigator.js +185 -29
- package/dist/editor/admin/SystemHealthPanel.js +36 -1
- package/dist/editor/admin/useSystemHealth.js +1 -0
- package/dist/editor/branch-names.d.ts +43 -0
- package/dist/editor/branch-names.js +75 -0
- package/dist/editor/components/BranchPill.d.ts +2 -2
- package/dist/editor/components/BranchPill.js +2 -2
- package/dist/editor/components/EditorHeader.d.ts +3 -1
- package/dist/editor/components/EditorHeader.js +2 -2
- package/dist/editor/components/EntryChangesDrawer.d.ts +51 -0
- package/dist/editor/components/EntryChangesDrawer.js +363 -0
- package/dist/editor/components/EntryChangesSkeleton.d.ts +9 -0
- package/dist/editor/components/EntryChangesSkeleton.js +7 -0
- package/dist/editor/components/EntryHeader.d.ts +9 -1
- package/dist/editor/components/EntryHeader.js +25 -3
- package/dist/editor/components/LazyEntryChangesDrawer.d.ts +16 -0
- package/dist/editor/components/LazyEntryChangesDrawer.js +45 -0
- package/dist/editor/components/ReviewDialogs.js +3 -3
- package/dist/editor/context/ApiClientContext.d.ts +9 -0
- package/dist/editor/context/ApiClientContext.js +26 -2
- package/dist/editor/context/index.d.ts +1 -1
- package/dist/editor/context/index.js +1 -1
- package/dist/editor/copy.d.ts +1 -0
- package/dist/editor/copy.js +1 -0
- package/dist/editor/editor-utils.d.ts +2 -0
- package/dist/editor/editor-utils.js +15 -0
- package/dist/editor/entry-diff.d.ts +143 -0
- package/dist/editor/entry-diff.js +593 -0
- package/dist/editor/hooks/create-branch-request.d.ts +11 -1
- package/dist/editor/hooks/create-branch-request.js +16 -6
- package/dist/editor/hooks/useBranchActions.d.ts +19 -2
- package/dist/editor/hooks/useBranchActions.js +29 -20
- package/dist/editor/hooks/useBranchChangesData.d.ts +57 -0
- package/dist/editor/hooks/useBranchChangesData.js +134 -0
- package/dist/editor/hooks/useBranchManager.d.ts +16 -0
- package/dist/editor/hooks/useBranchManager.js +142 -54
- package/dist/editor/hooks/useDraftManager.js +6 -2
- package/dist/editor/hooks/useEntryManager.d.ts +18 -2
- package/dist/editor/hooks/useEntryManager.js +36 -10
- package/dist/editor/utils/editor-errors.d.ts +5 -2
- package/dist/git-manager.d.ts +71 -0
- package/dist/git-manager.js +270 -1
- package/dist/http/handler.js +11 -9
- package/dist/operating-mode/branch-namespace-fixtures.d.ts +17 -0
- package/dist/operating-mode/branch-namespace-fixtures.js +65 -0
- package/dist/operating-mode/branch-namespace.d.ts +34 -0
- package/dist/operating-mode/branch-namespace.js +121 -0
- package/dist/operating-mode/client-unsafe-strategy.js +7 -0
- package/dist/operating-mode/types.d.ts +8 -0
- package/dist/paths/branch-name.d.ts +11 -19
- package/dist/paths/branch-name.js +17 -27
- package/dist/paths/index.d.ts +2 -2
- package/dist/paths/index.js +2 -2
- package/dist/paths/resolve.d.ts +0 -1
- package/dist/paths/resolve.js +0 -1
- package/dist/paths/validation.d.ts +40 -1
- package/dist/paths/validation.js +83 -4
- package/dist/pr-comments.d.ts +17 -9
- package/dist/pr-comments.js +53 -15
- package/dist/server.d.ts +6 -1
- package/dist/server.js +6 -1
- package/dist/services.js +2 -1
- package/dist/submission-attribution.d.ts +22 -0
- package/dist/submission-attribution.js +62 -6
- package/dist/task-queue/worker-status.d.ts +6 -1
- package/dist/task-queue/worker-status.js +23 -2
- package/dist/types.d.ts +59 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-policy.d.ts +111 -16
- package/dist/worker/branch-policy.js +222 -17
- package/dist/worker/cms-worker.d.ts +24 -2
- package/dist/worker/cms-worker.js +35 -24
- package/dist/worker/git-sync.d.ts +3 -3
- package/dist/worker/git-sync.js +42 -1
- package/dist/worker/github-gateway-http.js +2 -2
- package/dist/worker/github-gateway-wire.d.ts +42 -27
- package/dist/worker/github-gateway-wire.js +14 -3
- package/dist/worker/github-gateway.d.ts +17 -6
- package/dist/worker/github-gateway.js +62 -6
- package/dist/worker/github-mirror.d.ts +7 -4
- package/dist/worker/github-mirror.js +27 -16
- package/dist/worker/history-rewrite.d.ts +23 -10
- package/dist/worker/history-rewrite.js +43 -20
- package/dist/worker/legacy-branch-fixtures.d.ts +21 -0
- package/dist/worker/legacy-branch-fixtures.js +71 -0
- package/dist/worker/rebase.d.ts +2 -1
- package/dist/worker/rebase.js +93 -36
- package/dist/worker/task-runner.d.ts +4 -1
- package/dist/worker/task-runner.js +77 -48
- package/dist/worker/unpublishable-branches.d.ts +34 -0
- package/dist/worker/unpublishable-branches.js +182 -0
- package/package.json +2 -1
package/dist/cli/cli.js
CHANGED
|
@@ -849,6 +849,48 @@ async function moveToCorrupt(taskDir, sourceDir, fileName, reason, logger) {
|
|
|
849
849
|
});
|
|
850
850
|
}
|
|
851
851
|
}
|
|
852
|
+
function sanitizeBranchName(branchName) {
|
|
853
|
+
const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
854
|
+
const squashed = replaced.replace(/-+/g, "-");
|
|
855
|
+
const trimmedDots = squashed.replace(/^\.+/, "").replace(/(?<!\.)\.+$/, "");
|
|
856
|
+
return trimmedDots || "branch";
|
|
857
|
+
}
|
|
858
|
+
function parseBranchRef(name) {
|
|
859
|
+
if (!name || typeof name !== "string") {
|
|
860
|
+
return { ok: false, error: "Branch name is required" };
|
|
861
|
+
}
|
|
862
|
+
if (name.length > MAX_BRANCH_NAME_LENGTH) {
|
|
863
|
+
return { ok: false, error: `Branch name too long (max ${MAX_BRANCH_NAME_LENGTH} characters)` };
|
|
864
|
+
}
|
|
865
|
+
if (name.includes("..")) {
|
|
866
|
+
return { ok: false, error: 'Branch name cannot contain ".."' };
|
|
867
|
+
}
|
|
868
|
+
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
869
|
+
return { ok: false, error: "Invalid branch name format (invalid slashes)" };
|
|
870
|
+
}
|
|
871
|
+
if (name.includes(" ")) {
|
|
872
|
+
return { ok: false, error: "Branch name cannot contain spaces" };
|
|
873
|
+
}
|
|
874
|
+
if (name.startsWith("-")) {
|
|
875
|
+
return { ok: false, error: 'Branch name cannot start with "-"' };
|
|
876
|
+
}
|
|
877
|
+
if (name.startsWith(".") || name.endsWith(".")) {
|
|
878
|
+
return { ok: false, error: "Branch name cannot start or end with a dot" };
|
|
879
|
+
}
|
|
880
|
+
if (name.includes("@{")) {
|
|
881
|
+
return { ok: false, error: 'Branch name cannot contain "@{"' };
|
|
882
|
+
}
|
|
883
|
+
if (name === "HEAD" || name === "@") {
|
|
884
|
+
return { ok: false, error: 'Branch name cannot be the reserved ref "HEAD" or "@"' };
|
|
885
|
+
}
|
|
886
|
+
if (/[~^:?*[\\\x00-\x1f\x7f]/.test(name)) {
|
|
887
|
+
return { ok: false, error: "Branch name contains invalid characters" };
|
|
888
|
+
}
|
|
889
|
+
if (name.endsWith(".lock")) {
|
|
890
|
+
return { ok: false, error: 'Branch name cannot end with ".lock"' };
|
|
891
|
+
}
|
|
892
|
+
return { ok: true, name };
|
|
893
|
+
}
|
|
852
894
|
function resolveDeploymentName(config, modeDefault) {
|
|
853
895
|
const envValue = process.env.CANOPYCMS_DEPLOYMENT_NAME?.trim();
|
|
854
896
|
const configValue = config.deploymentName;
|
|
@@ -863,6 +905,64 @@ function resolveDeploymentName(config, modeDefault) {
|
|
|
863
905
|
}
|
|
864
906
|
return resolved;
|
|
865
907
|
}
|
|
908
|
+
function warnOnce(message) {
|
|
909
|
+
if (warned2)
|
|
910
|
+
return;
|
|
911
|
+
warned2 = true;
|
|
912
|
+
canopyLogWarn(message);
|
|
913
|
+
}
|
|
914
|
+
function branchNamespaceProblem(namespace) {
|
|
915
|
+
if (!namespace.endsWith("/"))
|
|
916
|
+
return 'it must end with "/"';
|
|
917
|
+
const sample = `${namespace}x`;
|
|
918
|
+
if (!/^[\x21-\x7e]+$/.test(namespace))
|
|
919
|
+
return "it is not printable ASCII without spaces";
|
|
920
|
+
if (/["%{}]/.test(namespace))
|
|
921
|
+
return 'it contains ", %, { or }';
|
|
922
|
+
if (/^(?:refs|heads|tags|remotes)\//i.test(namespace)) {
|
|
923
|
+
return "it starts with 'refs/', 'heads/', 'tags/' or 'remotes/'";
|
|
924
|
+
}
|
|
925
|
+
const parsed = parseBranchRef(sample);
|
|
926
|
+
if (!parsed.ok)
|
|
927
|
+
return `a branch in it would be refused (${parsed.error})`;
|
|
928
|
+
if (sample.split("/").some((part) => part.startsWith(".") || part.endsWith(".lock"))) {
|
|
929
|
+
return "a part of it starts with '.' or ends with '.lock'";
|
|
930
|
+
}
|
|
931
|
+
const directoryPrefix = sanitizeBranchName(namespace);
|
|
932
|
+
if (RESERVED_SETTINGS_BRANCH_PREFIX.startsWith(directoryPrefix) || sanitizeBranchName(sample).startsWith(RESERVED_SETTINGS_BRANCH_PREFIX)) {
|
|
933
|
+
return `some branches in it would be ${RESERVED_SETTINGS_BRANCH_PREFIX}* settings branches`;
|
|
934
|
+
}
|
|
935
|
+
return null;
|
|
936
|
+
}
|
|
937
|
+
function resolveBranchNamespaceTemplate(template, deploymentName, source) {
|
|
938
|
+
const unknown = template.replaceAll(DEPLOYMENT_PLACEHOLDER, "").match(/\{[^}]*\}?/);
|
|
939
|
+
if (unknown) {
|
|
940
|
+
throw new Error(`CanopyCMS: invalid branch namespace ${JSON.stringify(template)} (from ${source}): ${JSON.stringify(unknown[0])} is not a placeholder; the only one is ${DEPLOYMENT_PLACEHOLDER}`);
|
|
941
|
+
}
|
|
942
|
+
const namespace = template.replaceAll(DEPLOYMENT_PLACEHOLDER, deploymentName);
|
|
943
|
+
const problem = branchNamespaceProblem(namespace);
|
|
944
|
+
if (problem !== null) {
|
|
945
|
+
throw new Error(`CanopyCMS: invalid branch namespace ${JSON.stringify(namespace)} (from ${source}): ${problem}`);
|
|
946
|
+
}
|
|
947
|
+
return namespace;
|
|
948
|
+
}
|
|
949
|
+
function resolveBranchNamespace(config, modeDefault, env = process.env) {
|
|
950
|
+
const fromEnv = env[BRANCH_NAMESPACE_ENV]?.trim();
|
|
951
|
+
const [template, source] = fromEnv ? [fromEnv, BRANCH_NAMESPACE_ENV] : config.branchNamespace !== void 0 ? [config.branchNamespace, "config.branchNamespace"] : [DEFAULT_BRANCH_NAMESPACE_TEMPLATE, "the default"];
|
|
952
|
+
const deploymentName = resolveDeploymentName({ deploymentName: config.deploymentName }, modeDefault);
|
|
953
|
+
const namespace = resolveBranchNamespaceTemplate(template, deploymentName, source);
|
|
954
|
+
if (fromEnv && config.branchNamespace !== void 0) {
|
|
955
|
+
let fromConfig = null;
|
|
956
|
+
try {
|
|
957
|
+
fromConfig = resolveBranchNamespaceTemplate(config.branchNamespace, deploymentName, "config.branchNamespace");
|
|
958
|
+
} catch {
|
|
959
|
+
}
|
|
960
|
+
if (fromConfig !== namespace) {
|
|
961
|
+
warnOnce(`CanopyCMS: ${BRANCH_NAMESPACE_ENV} (${JSON.stringify(fromEnv)}) and config.branchNamespace (${JSON.stringify(config.branchNamespace)}) do not resolve to the same valid namespace; using the environment variable, which infrastructure stamps per stack.`);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
return namespace;
|
|
965
|
+
}
|
|
866
966
|
function operatingStrategy(mode) {
|
|
867
967
|
const cached = strategyCache.get(mode);
|
|
868
968
|
if (cached)
|
|
@@ -1227,7 +1327,7 @@ CanopyCMS worker run-once (mode: ${mode}, auth: ${authMode})
|
|
|
1227
1327
|
}
|
|
1228
1328
|
console.log("\nDone");
|
|
1229
1329
|
}
|
|
1230
|
-
var __defProp2, __getOwnPropNames2, __esm2, __export2, active, init_logger, scalarFieldTypes, markdownFieldTypes, primitiveFieldTypes, fieldTypes, init_types, SAFE_HTML_TAGS, COMPONENT_NAME, MDX_BINDINGS, FORBIDDEN_ATTRIBUTES, init_mdx_allowlist, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, mdxPropAllowSchema, mdxAllowlistSchema, markdownFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceSortFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, SENTINEL_BASE, DOT_SEGMENT, init_sanitize_href, uploadTargetUrlSchema, previewPrefixSchema, editorUrlSchema, init_url, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, basePathSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, seoFieldNameSchema, seoSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, init_config2, init_error2, init_fs2, templates_exports, __dirname, TEMPLATES_DIR, SCAFFOLD_STACK_NAME, init_templates, project_detect_exports, COMMANDS, MAX_WORKSPACE_DEPTH, PLACEHOLDER_GITHUB_REPO, CDK_DEPENDENCIES, init_project_detect, task_queue_config_exports, init_task_queue_config, envFlag, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, cms_task_queue_exports, debugLogger, cmsTaskQueueLogger, init_cms_task_queue, ProdClientSafeStrategy, DevClientSafeStrategy, warned, VALID_DEPLOYMENT_NAME, isValidDeploymentName, ProdStrategy, DevStrategy, strategyCache;
|
|
1330
|
+
var __defProp2, __getOwnPropNames2, __esm2, __export2, active, init_logger, scalarFieldTypes, markdownFieldTypes, primitiveFieldTypes, fieldTypes, init_types, SAFE_HTML_TAGS, COMPONENT_NAME, MDX_BINDINGS, FORBIDDEN_ATTRIBUTES, init_mdx_allowlist, fieldBaseSchema, selectOptionSchema, referenceOptionSchema, primitiveFieldSchema, mdxPropAllowSchema, mdxAllowlistSchema, markdownFieldSchema, ASPECT_RATIO_RE, imageFieldSchema, selectFieldSchema, referenceSortFieldSchema, referenceFieldSchema, fieldHolder, blockSchema, blockFieldSchema, objectFieldSchema, inlineGroupFieldSchema, customFieldSchema, knownFieldSchema, fieldSchema, init_field, relativePathSchema, entryTypeSchema, collectionSchema, init_collection, SENTINEL_BASE, DOT_SEGMENT, init_sanitize_href, uploadTargetUrlSchema, previewPrefixSchema, editorUrlSchema, init_url, mediaSchema, init_media, defaultBranchAccessSchema, defaultPathAccessLevelSchema, defaultPathAccessLevelsSchema, defaultPathAccessSchema, defaultBaseBranchSchema, defaultRemoteNameSchema, defaultRemoteUrlSchema, gitBotAuthorNameSchema, gitBotAuthorEmailSchema, githubTokenEnvVarSchema, operatingModeSchema, deployedAsSchema, contentRootSchema, sourceRootSchema, basePathSchema, deploymentNameSchema, devContentSyncSchema, devConfigSchema, editorConfigSchema, seoFieldNameSchema, seoSchema, CanopyConfigSchema, DEFAULT_PROD_WORKSPACE, init_config, init_config2, init_error2, init_fs2, templates_exports, __dirname, TEMPLATES_DIR, SCAFFOLD_STACK_NAME, init_templates, project_detect_exports, COMMANDS, MAX_WORKSPACE_DEPTH, PLACEHOLDER_GITHUB_REPO, CDK_DEPENDENCIES, init_project_detect, task_queue_config_exports, init_task_queue_config, envFlag, LOG_LEVELS, DebugLogger, testLogger, init_debug, init_atomic_write, DEFAULT_MAX_RETRIES, nullLogger, init_task_queue, cms_task_queue_exports, debugLogger, cmsTaskQueueLogger, init_cms_task_queue, ProdClientSafeStrategy, DevClientSafeStrategy, RESERVED_SETTINGS_BRANCH_PREFIX, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, MAX_BRANCH_NAME_LENGTH, warned, VALID_DEPLOYMENT_NAME, isValidDeploymentName, warned2, DEPLOYMENT_PLACEHOLDER, DEFAULT_BRANCH_NAMESPACE_TEMPLATE, BRANCH_NAMESPACE_ENV, ProdStrategy, DevStrategy, strategyCache;
|
|
1231
1331
|
var init_init = __esm({
|
|
1232
1332
|
"dist/cli/init.js"() {
|
|
1233
1333
|
"use strict";
|
|
@@ -1689,6 +1789,8 @@ var init_init = __esm({
|
|
|
1689
1789
|
// (operating-mode/deployment-name.ts) implements — config would always "win" over
|
|
1690
1790
|
// modeDefault, masking dev's real default of 'local'. Do not remove `.optional()`.
|
|
1691
1791
|
deploymentName: deploymentNameSchema.optional(),
|
|
1792
|
+
// Checked where it is resolved (operating-mode/branch-namespace.ts), with the deployment name.
|
|
1793
|
+
branchNamespace: z5.string().optional(),
|
|
1692
1794
|
contentRoot: contentRootSchema.default("content"),
|
|
1693
1795
|
sourceRoot: sourceRootSchema.optional(),
|
|
1694
1796
|
basePath: basePathSchema,
|
|
@@ -2021,10 +2123,20 @@ var init_init = __esm({
|
|
|
2021
2123
|
return true;
|
|
2022
2124
|
}
|
|
2023
2125
|
};
|
|
2126
|
+
RESERVED_SETTINGS_BRANCH_PREFIX = "canopycms-settings-";
|
|
2127
|
+
BASE58_PATTERN = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
2128
|
+
CONTENT_ID_PATTERN = new RegExp(`^${BASE58_PATTERN}{12}$`);
|
|
2129
|
+
PHYSICAL_SEGMENT_PATTERN = new RegExp(`\\.${BASE58_PATTERN}{12}(?:\\.[a-z]+)?$`);
|
|
2130
|
+
MAX_BRANCH_NAME_LENGTH = 250;
|
|
2131
|
+
init_logger();
|
|
2024
2132
|
init_logger();
|
|
2025
2133
|
warned = false;
|
|
2026
2134
|
VALID_DEPLOYMENT_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
2027
2135
|
isValidDeploymentName = (name) => VALID_DEPLOYMENT_NAME.test(name) && !name.includes("..") && !name.endsWith(".") && !name.endsWith(".lock");
|
|
2136
|
+
warned2 = false;
|
|
2137
|
+
DEPLOYMENT_PLACEHOLDER = "{deployment}";
|
|
2138
|
+
DEFAULT_BRANCH_NAMESPACE_TEMPLATE = `canopycms/${DEPLOYMENT_PLACEHOLDER}/`;
|
|
2139
|
+
BRANCH_NAMESPACE_ENV = "CANOPYCMS_BRANCH_NAMESPACE";
|
|
2028
2140
|
init_config2();
|
|
2029
2141
|
ProdStrategy = class extends ProdClientSafeStrategy {
|
|
2030
2142
|
getWorkspaceRoot(_sourceRoot) {
|
|
@@ -2064,6 +2176,9 @@ var init_init = __esm({
|
|
|
2064
2176
|
return config.settingsBranch;
|
|
2065
2177
|
return `canopycms-settings-${resolveDeploymentName(config, "prod")}`;
|
|
2066
2178
|
}
|
|
2179
|
+
getBranchNamespace(config) {
|
|
2180
|
+
return resolveBranchNamespace(config, "prod");
|
|
2181
|
+
}
|
|
2067
2182
|
getSettingsRoot(sourceRoot) {
|
|
2068
2183
|
return path8.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
2069
2184
|
}
|
|
@@ -2113,6 +2228,9 @@ var init_init = __esm({
|
|
|
2113
2228
|
return config.settingsBranch;
|
|
2114
2229
|
return `canopycms-settings-${resolveDeploymentName(config, "local")}`;
|
|
2115
2230
|
}
|
|
2231
|
+
getBranchNamespace(config) {
|
|
2232
|
+
return resolveBranchNamespace(config, "local");
|
|
2233
|
+
}
|
|
2116
2234
|
getSettingsRoot(sourceRoot) {
|
|
2117
2235
|
return path8.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
2118
2236
|
}
|
|
@@ -3287,6 +3405,7 @@ import path72 from "node:path";
|
|
|
3287
3405
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3288
3406
|
import fs72 from "node:fs/promises";
|
|
3289
3407
|
import path82 from "node:path";
|
|
3408
|
+
import { LRUCache } from "lru-cache";
|
|
3290
3409
|
import { promises as fs62 } from "fs";
|
|
3291
3410
|
import { join } from "pathe";
|
|
3292
3411
|
import { z as z7 } from "zod";
|
|
@@ -3315,13 +3434,17 @@ import fs13 from "node:fs/promises";
|
|
|
3315
3434
|
import path16 from "node:path";
|
|
3316
3435
|
import { randomUUID as randomUUID22 } from "node:crypto";
|
|
3317
3436
|
import lockfile2 from "proper-lockfile";
|
|
3318
|
-
import { createHash as
|
|
3437
|
+
import { createHash as createHash3, randomBytes } from "node:crypto";
|
|
3319
3438
|
import fs17 from "node:fs/promises";
|
|
3320
3439
|
import path21 from "node:path";
|
|
3440
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3321
3441
|
import fs16 from "node:fs/promises";
|
|
3322
3442
|
import path20 from "node:path";
|
|
3323
3443
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3324
3444
|
import { simpleGit as simpleGit22 } from "simple-git";
|
|
3445
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
3446
|
+
import { Readable as Readable2, Transform, Writable } from "node:stream";
|
|
3447
|
+
import { pipeline } from "node:stream/promises";
|
|
3325
3448
|
import fs18 from "node:fs/promises";
|
|
3326
3449
|
import path222 from "node:path";
|
|
3327
3450
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
@@ -3604,6 +3727,93 @@ function trimSlashes(path272) {
|
|
|
3604
3727
|
end--;
|
|
3605
3728
|
return path272.slice(start, end);
|
|
3606
3729
|
}
|
|
3730
|
+
function sanitizeBranchName2(branchName) {
|
|
3731
|
+
const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
3732
|
+
const squashed = replaced.replace(/-+/g, "-");
|
|
3733
|
+
const trimmedDots = squashed.replace(/^\.+/, "").replace(/(?<!\.)\.+$/, "");
|
|
3734
|
+
return trimmedDots || "branch";
|
|
3735
|
+
}
|
|
3736
|
+
function branchShortName(name, namespace) {
|
|
3737
|
+
if (namespace === null || !name.startsWith(namespace) || name.length === namespace.length) {
|
|
3738
|
+
return name;
|
|
3739
|
+
}
|
|
3740
|
+
const rest = name.slice(namespace.length);
|
|
3741
|
+
return rest.includes("/") ? name : rest;
|
|
3742
|
+
}
|
|
3743
|
+
function isSettingsBranchName(branchName, settingsBranchName) {
|
|
3744
|
+
const sanitized = sanitizeBranchName2(branchName);
|
|
3745
|
+
return sanitized.startsWith(RESERVED_SETTINGS_BRANCH_PREFIX2) || settingsBranchName !== void 0 && sanitized === sanitizeBranchName2(settingsBranchName);
|
|
3746
|
+
}
|
|
3747
|
+
function isValidContentId(id) {
|
|
3748
|
+
return CONTENT_ID_PATTERN2.test(id);
|
|
3749
|
+
}
|
|
3750
|
+
function parseBranchRef2(name) {
|
|
3751
|
+
if (!name || typeof name !== "string") {
|
|
3752
|
+
return { ok: false, error: "Branch name is required" };
|
|
3753
|
+
}
|
|
3754
|
+
if (name.length > MAX_BRANCH_NAME_LENGTH2) {
|
|
3755
|
+
return { ok: false, error: `Branch name too long (max ${MAX_BRANCH_NAME_LENGTH2} characters)` };
|
|
3756
|
+
}
|
|
3757
|
+
if (name.includes("..")) {
|
|
3758
|
+
return { ok: false, error: 'Branch name cannot contain ".."' };
|
|
3759
|
+
}
|
|
3760
|
+
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
3761
|
+
return { ok: false, error: "Invalid branch name format (invalid slashes)" };
|
|
3762
|
+
}
|
|
3763
|
+
if (name.includes(" ")) {
|
|
3764
|
+
return { ok: false, error: "Branch name cannot contain spaces" };
|
|
3765
|
+
}
|
|
3766
|
+
if (name.startsWith("-")) {
|
|
3767
|
+
return { ok: false, error: 'Branch name cannot start with "-"' };
|
|
3768
|
+
}
|
|
3769
|
+
if (name.startsWith(".") || name.endsWith(".")) {
|
|
3770
|
+
return { ok: false, error: "Branch name cannot start or end with a dot" };
|
|
3771
|
+
}
|
|
3772
|
+
if (name.includes("@{")) {
|
|
3773
|
+
return { ok: false, error: 'Branch name cannot contain "@{"' };
|
|
3774
|
+
}
|
|
3775
|
+
if (name === "HEAD" || name === "@") {
|
|
3776
|
+
return { ok: false, error: 'Branch name cannot be the reserved ref "HEAD" or "@"' };
|
|
3777
|
+
}
|
|
3778
|
+
if (/[~^:?*[\\\x00-\x1f\x7f]/.test(name)) {
|
|
3779
|
+
return { ok: false, error: "Branch name contains invalid characters" };
|
|
3780
|
+
}
|
|
3781
|
+
if (name.endsWith(".lock")) {
|
|
3782
|
+
return { ok: false, error: 'Branch name cannot end with ".lock"' };
|
|
3783
|
+
}
|
|
3784
|
+
return { ok: true, name };
|
|
3785
|
+
}
|
|
3786
|
+
function parseSlug(slug) {
|
|
3787
|
+
if (!slug) {
|
|
3788
|
+
return {
|
|
3789
|
+
ok: false,
|
|
3790
|
+
error: "Slug is required"
|
|
3791
|
+
};
|
|
3792
|
+
}
|
|
3793
|
+
if (slug.length > 64) {
|
|
3794
|
+
return { ok: false, error: "Slug too long (max 64 characters)" };
|
|
3795
|
+
}
|
|
3796
|
+
if (slug.includes("/") || slug.includes("\\")) {
|
|
3797
|
+
return {
|
|
3798
|
+
ok: false,
|
|
3799
|
+
error: "Slug cannot contain path separators"
|
|
3800
|
+
};
|
|
3801
|
+
}
|
|
3802
|
+
if (slug === "." || slug === "..") {
|
|
3803
|
+
return {
|
|
3804
|
+
ok: false,
|
|
3805
|
+
error: "Slug cannot be a traversal sequence"
|
|
3806
|
+
};
|
|
3807
|
+
}
|
|
3808
|
+
const normalized = slug.toLowerCase();
|
|
3809
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(normalized)) {
|
|
3810
|
+
return {
|
|
3811
|
+
ok: false,
|
|
3812
|
+
error: "Slug must start with a letter or number and contain only lowercase letters, numbers, and hyphens"
|
|
3813
|
+
};
|
|
3814
|
+
}
|
|
3815
|
+
return { ok: true, slug: normalized };
|
|
3816
|
+
}
|
|
3607
3817
|
function isComponentName2(name) {
|
|
3608
3818
|
return COMPONENT_NAME2.test(name) && !MDX_BINDINGS2.has(name);
|
|
3609
3819
|
}
|
|
@@ -3644,9 +3854,9 @@ function resolveOperatingMode(configMode) {
|
|
|
3644
3854
|
if (env.value !== "prod" && env.value !== "dev") {
|
|
3645
3855
|
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.`);
|
|
3646
3856
|
}
|
|
3647
|
-
if (env.value !== configMode && !
|
|
3857
|
+
if (env.value !== configMode && !warned22) {
|
|
3648
3858
|
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.`);
|
|
3649
|
-
|
|
3859
|
+
warned22 = true;
|
|
3650
3860
|
}
|
|
3651
3861
|
return env.value;
|
|
3652
3862
|
}
|
|
@@ -3687,6 +3897,64 @@ function resolveDeploymentName2(config, modeDefault) {
|
|
|
3687
3897
|
}
|
|
3688
3898
|
return resolved;
|
|
3689
3899
|
}
|
|
3900
|
+
function warnOnce2(message) {
|
|
3901
|
+
if (warned4)
|
|
3902
|
+
return;
|
|
3903
|
+
warned4 = true;
|
|
3904
|
+
canopyLogWarn2(message);
|
|
3905
|
+
}
|
|
3906
|
+
function branchNamespaceProblem2(namespace) {
|
|
3907
|
+
if (!namespace.endsWith("/"))
|
|
3908
|
+
return 'it must end with "/"';
|
|
3909
|
+
const sample = `${namespace}x`;
|
|
3910
|
+
if (!/^[\x21-\x7e]+$/.test(namespace))
|
|
3911
|
+
return "it is not printable ASCII without spaces";
|
|
3912
|
+
if (/["%{}]/.test(namespace))
|
|
3913
|
+
return 'it contains ", %, { or }';
|
|
3914
|
+
if (/^(?:refs|heads|tags|remotes)\//i.test(namespace)) {
|
|
3915
|
+
return "it starts with 'refs/', 'heads/', 'tags/' or 'remotes/'";
|
|
3916
|
+
}
|
|
3917
|
+
const parsed = parseBranchRef2(sample);
|
|
3918
|
+
if (!parsed.ok)
|
|
3919
|
+
return `a branch in it would be refused (${parsed.error})`;
|
|
3920
|
+
if (sample.split("/").some((part) => part.startsWith(".") || part.endsWith(".lock"))) {
|
|
3921
|
+
return "a part of it starts with '.' or ends with '.lock'";
|
|
3922
|
+
}
|
|
3923
|
+
const directoryPrefix = sanitizeBranchName2(namespace);
|
|
3924
|
+
if (RESERVED_SETTINGS_BRANCH_PREFIX2.startsWith(directoryPrefix) || sanitizeBranchName2(sample).startsWith(RESERVED_SETTINGS_BRANCH_PREFIX2)) {
|
|
3925
|
+
return `some branches in it would be ${RESERVED_SETTINGS_BRANCH_PREFIX2}* settings branches`;
|
|
3926
|
+
}
|
|
3927
|
+
return null;
|
|
3928
|
+
}
|
|
3929
|
+
function resolveBranchNamespaceTemplate2(template, deploymentName, source) {
|
|
3930
|
+
const unknown = template.replaceAll(DEPLOYMENT_PLACEHOLDER2, "").match(/\{[^}]*\}?/);
|
|
3931
|
+
if (unknown) {
|
|
3932
|
+
throw new Error(`CanopyCMS: invalid branch namespace ${JSON.stringify(template)} (from ${source}): ${JSON.stringify(unknown[0])} is not a placeholder; the only one is ${DEPLOYMENT_PLACEHOLDER2}`);
|
|
3933
|
+
}
|
|
3934
|
+
const namespace = template.replaceAll(DEPLOYMENT_PLACEHOLDER2, deploymentName);
|
|
3935
|
+
const problem = branchNamespaceProblem2(namespace);
|
|
3936
|
+
if (problem !== null) {
|
|
3937
|
+
throw new Error(`CanopyCMS: invalid branch namespace ${JSON.stringify(namespace)} (from ${source}): ${problem}`);
|
|
3938
|
+
}
|
|
3939
|
+
return namespace;
|
|
3940
|
+
}
|
|
3941
|
+
function resolveBranchNamespace2(config, modeDefault, env = process.env) {
|
|
3942
|
+
const fromEnv = env[BRANCH_NAMESPACE_ENV2]?.trim();
|
|
3943
|
+
const [template, source] = fromEnv ? [fromEnv, BRANCH_NAMESPACE_ENV2] : config.branchNamespace !== void 0 ? [config.branchNamespace, "config.branchNamespace"] : [DEFAULT_BRANCH_NAMESPACE_TEMPLATE2, "the default"];
|
|
3944
|
+
const deploymentName = resolveDeploymentName2({ deploymentName: config.deploymentName }, modeDefault);
|
|
3945
|
+
const namespace = resolveBranchNamespaceTemplate2(template, deploymentName, source);
|
|
3946
|
+
if (fromEnv && config.branchNamespace !== void 0) {
|
|
3947
|
+
let fromConfig = null;
|
|
3948
|
+
try {
|
|
3949
|
+
fromConfig = resolveBranchNamespaceTemplate2(config.branchNamespace, deploymentName, "config.branchNamespace");
|
|
3950
|
+
} catch {
|
|
3951
|
+
}
|
|
3952
|
+
if (fromConfig !== namespace) {
|
|
3953
|
+
warnOnce2(`CanopyCMS: ${BRANCH_NAMESPACE_ENV2} (${JSON.stringify(fromEnv)}) and config.branchNamespace (${JSON.stringify(config.branchNamespace)}) do not resolve to the same valid namespace; using the environment variable, which infrastructure stamps per stack.`);
|
|
3954
|
+
}
|
|
3955
|
+
}
|
|
3956
|
+
return namespace;
|
|
3957
|
+
}
|
|
3690
3958
|
function operatingStrategy2(mode) {
|
|
3691
3959
|
const cached = strategyCache2.get(mode);
|
|
3692
3960
|
if (cached)
|
|
@@ -3721,6 +3989,21 @@ function getTaskQueueDir2(config) {
|
|
|
3721
3989
|
}
|
|
3722
3990
|
}
|
|
3723
3991
|
}
|
|
3992
|
+
async function readWorkerStatus(taskDir) {
|
|
3993
|
+
let content;
|
|
3994
|
+
try {
|
|
3995
|
+
content = await fs15.readFile(path19.join(taskDir, WORKER_STATUS_FILE), "utf-8");
|
|
3996
|
+
} catch (err2) {
|
|
3997
|
+
if (isNotFoundError3(err2))
|
|
3998
|
+
return { workerStatus: null };
|
|
3999
|
+
return { workerStatus: null, statusReadError: getErrorMessage3(err2) };
|
|
4000
|
+
}
|
|
4001
|
+
try {
|
|
4002
|
+
return { workerStatus: JSON.parse(content) };
|
|
4003
|
+
} catch (err2) {
|
|
4004
|
+
return { workerStatus: null, statusReadError: getErrorMessage3(err2) };
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
3724
4007
|
async function readCarriedOverStatus(taskDir) {
|
|
3725
4008
|
let previous;
|
|
3726
4009
|
try {
|
|
@@ -4819,9 +5102,9 @@ function buildMissingReference(id) {
|
|
|
4819
5102
|
}
|
|
4820
5103
|
function warnDanglingReference(site) {
|
|
4821
5104
|
const key = `${site.root}\0${site.entry ?? ""}\0${site.path}\0${site.id}`;
|
|
4822
|
-
if (
|
|
5105
|
+
if (warned5.has(key) || warned5.size >= MAX_WARNED)
|
|
4823
5106
|
return;
|
|
4824
|
-
|
|
5107
|
+
warned5.add(key);
|
|
4825
5108
|
canopyLogWarn2(`CanopyCMS: ${site.entry ?? "an entry"} field "${site.path}" references missing entry ${site.id}; it resolves as unavailable until the reference is repointed or cleared.`);
|
|
4826
5109
|
}
|
|
4827
5110
|
function isGroupField(field) {
|
|
@@ -4884,40 +5167,6 @@ function computeEntryUrl(collection, slug, contentRoot) {
|
|
|
4884
5167
|
const path272 = segments.length > 0 ? `/${segments.join("/")}` : "/";
|
|
4885
5168
|
return path272.toLowerCase();
|
|
4886
5169
|
}
|
|
4887
|
-
function isValidContentId(id) {
|
|
4888
|
-
return CONTENT_ID_PATTERN.test(id);
|
|
4889
|
-
}
|
|
4890
|
-
function parseSlug(slug) {
|
|
4891
|
-
if (!slug) {
|
|
4892
|
-
return {
|
|
4893
|
-
ok: false,
|
|
4894
|
-
error: "Slug is required"
|
|
4895
|
-
};
|
|
4896
|
-
}
|
|
4897
|
-
if (slug.length > 64) {
|
|
4898
|
-
return { ok: false, error: "Slug too long (max 64 characters)" };
|
|
4899
|
-
}
|
|
4900
|
-
if (slug.includes("/") || slug.includes("\\")) {
|
|
4901
|
-
return {
|
|
4902
|
-
ok: false,
|
|
4903
|
-
error: "Slug cannot contain path separators"
|
|
4904
|
-
};
|
|
4905
|
-
}
|
|
4906
|
-
if (slug === "." || slug === "..") {
|
|
4907
|
-
return {
|
|
4908
|
-
ok: false,
|
|
4909
|
-
error: "Slug cannot be a traversal sequence"
|
|
4910
|
-
};
|
|
4911
|
-
}
|
|
4912
|
-
const normalized = slug.toLowerCase();
|
|
4913
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(normalized)) {
|
|
4914
|
-
return {
|
|
4915
|
-
ok: false,
|
|
4916
|
-
error: "Slug must start with a letter or number and contain only lowercase letters, numbers, and hyphens"
|
|
4917
|
-
};
|
|
4918
|
-
}
|
|
4919
|
-
return { ok: true, slug: normalized };
|
|
4920
|
-
}
|
|
4921
5170
|
function generateId() {
|
|
4922
5171
|
const full = generate();
|
|
4923
5172
|
return full.substring(0, 12);
|
|
@@ -5458,6 +5707,9 @@ async function isStaleByMtime(dir, cachedAt) {
|
|
|
5458
5707
|
}
|
|
5459
5708
|
return false;
|
|
5460
5709
|
}
|
|
5710
|
+
function fileIdentity(stat) {
|
|
5711
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}`;
|
|
5712
|
+
}
|
|
5461
5713
|
async function schemaCacheDir(branchRoot) {
|
|
5462
5714
|
const gitDir = path82.join(branchRoot, ".git");
|
|
5463
5715
|
const isClone = await fs72.stat(gitDir).then((stat) => stat.isDirectory(), () => false);
|
|
@@ -5517,16 +5769,6 @@ function validateAndNormalizePath(root, target) {
|
|
|
5517
5769
|
normalizedPath: relativePath
|
|
5518
5770
|
};
|
|
5519
5771
|
}
|
|
5520
|
-
function sanitizeBranchName(branchName) {
|
|
5521
|
-
const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
5522
|
-
const squashed = replaced.replace(/-+/g, "-");
|
|
5523
|
-
const trimmedDots = squashed.replace(/^\.+/, "").replace(/(?<!\.)\.+$/, "");
|
|
5524
|
-
return trimmedDots || "branch";
|
|
5525
|
-
}
|
|
5526
|
-
function isSettingsBranchName(branchName, settingsBranchName) {
|
|
5527
|
-
const sanitized = sanitizeBranchName(branchName);
|
|
5528
|
-
return sanitized.startsWith(RESERVED_SETTINGS_BRANCH_PREFIX) || settingsBranchName !== void 0 && sanitized === sanitizeBranchName(settingsBranchName);
|
|
5529
|
-
}
|
|
5530
5772
|
function resolveBranchPath(options) {
|
|
5531
5773
|
if (options.branchName.includes("..")) {
|
|
5532
5774
|
throw new BranchPathError("Branch name cannot contain traversal segments");
|
|
@@ -5534,7 +5776,7 @@ function resolveBranchPath(options) {
|
|
|
5534
5776
|
if (isSettingsBranchName(options.branchName)) {
|
|
5535
5777
|
throw new BranchPathError("Settings branches are not content branches");
|
|
5536
5778
|
}
|
|
5537
|
-
const safeBranch =
|
|
5779
|
+
const safeBranch = sanitizeBranchName2(options.branchName);
|
|
5538
5780
|
const strategy = operatingStrategy2(options.mode);
|
|
5539
5781
|
const baseRoot = resolveContentBranchesRoot(options.mode, options.basePathOverride);
|
|
5540
5782
|
const normalizedBase = path102.resolve(baseRoot);
|
|
@@ -7004,6 +7246,193 @@ function mergeBranchMetadata(existing, version, incoming, now = (/* @__PURE__ */
|
|
|
7004
7246
|
function buildInitialBranchMetadata(incoming, now) {
|
|
7005
7247
|
return mergeBranchMetadata(null, null, incoming, now);
|
|
7006
7248
|
}
|
|
7249
|
+
function cmsBranchNamespace(config) {
|
|
7250
|
+
try {
|
|
7251
|
+
return operatingStrategy2(config.mode).getBranchNamespace(config);
|
|
7252
|
+
} catch {
|
|
7253
|
+
return null;
|
|
7254
|
+
}
|
|
7255
|
+
}
|
|
7256
|
+
function namesBaseOrActive(param, config) {
|
|
7257
|
+
let base;
|
|
7258
|
+
try {
|
|
7259
|
+
base = baseBranchOf(config);
|
|
7260
|
+
} catch (err2) {
|
|
7261
|
+
if (err2 instanceof BaseBranchUnresolvedError)
|
|
7262
|
+
return false;
|
|
7263
|
+
throw err2;
|
|
7264
|
+
}
|
|
7265
|
+
return param === base || param === (config.defaultActiveBranch ?? base);
|
|
7266
|
+
}
|
|
7267
|
+
function shortNameOf(config, name) {
|
|
7268
|
+
const short = branchShortName(name, cmsBranchNamespace(config));
|
|
7269
|
+
return short !== name && namesBaseOrActive(short, config) ? name : short;
|
|
7270
|
+
}
|
|
7271
|
+
function spawnGit(args, options) {
|
|
7272
|
+
const child = spawn2("git", [...(options.config ?? []).flatMap((c) => ["-c", c]), ...args], {
|
|
7273
|
+
cwd: options.cwd,
|
|
7274
|
+
// An app's own ProcessEnv augmentation (Next.js requires NODE_ENV) says nothing about git's.
|
|
7275
|
+
env: options.env,
|
|
7276
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
7277
|
+
// Its own process group, so a stop reaches what git spawned too (bundle create's
|
|
7278
|
+
// pack-objects), which would otherwise outlive it holding the pipes open.
|
|
7279
|
+
detached: process.platform !== "win32"
|
|
7280
|
+
});
|
|
7281
|
+
const { stdout } = child;
|
|
7282
|
+
if (stdout === null)
|
|
7283
|
+
throw new Error("git was spawned without a stdout pipe");
|
|
7284
|
+
const command = args.find((arg) => !arg.startsWith("-")) ?? "git";
|
|
7285
|
+
let stderr = "";
|
|
7286
|
+
let stderrBytes = 0;
|
|
7287
|
+
let killedFor = null;
|
|
7288
|
+
let closed = false;
|
|
7289
|
+
let exitedCleanly = false;
|
|
7290
|
+
let inactivity;
|
|
7291
|
+
const idle = () => {
|
|
7292
|
+
if (stdout.readableLength > 0 && !stdout.destroyed) {
|
|
7293
|
+
inactivity = setTimeout(idle, options.inactivityTimeoutMs);
|
|
7294
|
+
return;
|
|
7295
|
+
}
|
|
7296
|
+
stop(`git ${command} wrote nothing for ${options.inactivityTimeoutMs}ms`);
|
|
7297
|
+
};
|
|
7298
|
+
const touch = () => {
|
|
7299
|
+
options.onActivity?.();
|
|
7300
|
+
clearTimeout(inactivity);
|
|
7301
|
+
if (exitedCleanly)
|
|
7302
|
+
return;
|
|
7303
|
+
inactivity = setTimeout(idle, options.inactivityTimeoutMs);
|
|
7304
|
+
};
|
|
7305
|
+
const stop = (reason) => {
|
|
7306
|
+
if (closed || killedFor !== null)
|
|
7307
|
+
return;
|
|
7308
|
+
if (child.exitCode === 0 || child.exitCode === null && child.signalCode === null) {
|
|
7309
|
+
killedFor = reason;
|
|
7310
|
+
}
|
|
7311
|
+
terminate(child);
|
|
7312
|
+
stdout.destroy();
|
|
7313
|
+
};
|
|
7314
|
+
child.on("exit", (code) => {
|
|
7315
|
+
if (code !== 0)
|
|
7316
|
+
return;
|
|
7317
|
+
exitedCleanly = true;
|
|
7318
|
+
clearTimeout(inactivity);
|
|
7319
|
+
clearTimeout(deadline);
|
|
7320
|
+
});
|
|
7321
|
+
child.stderr?.on("data", (chunk) => {
|
|
7322
|
+
touch();
|
|
7323
|
+
if (stderrBytes < STDERR_CAP_BYTES) {
|
|
7324
|
+
stderr += chunk.subarray(0, STDERR_CAP_BYTES - stderrBytes).toString("utf8");
|
|
7325
|
+
}
|
|
7326
|
+
stderrBytes += chunk.length;
|
|
7327
|
+
});
|
|
7328
|
+
stdout.on("data", touch);
|
|
7329
|
+
const deadline = options.deadlineMs === void 0 ? void 0 : setTimeout(() => stop(`git ${command} ran longer than ${options.deadlineMs}ms`), options.deadlineMs);
|
|
7330
|
+
const onAbort = () => stop(`git ${command} was aborted`);
|
|
7331
|
+
if (options.signal?.aborted)
|
|
7332
|
+
onAbort();
|
|
7333
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
7334
|
+
const input = feedStdin(child, options.stdin, touch, (err2) => stop(`git ${command}'s input failed: ${getErrorMessage3(err2)}`));
|
|
7335
|
+
input.catch(() => void 0);
|
|
7336
|
+
touch();
|
|
7337
|
+
const exited = new Promise((resolve2, reject) => {
|
|
7338
|
+
child.on("error", (err2) => {
|
|
7339
|
+
if (child.pid === void 0) {
|
|
7340
|
+
clearTimeout(inactivity);
|
|
7341
|
+
clearTimeout(deadline);
|
|
7342
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
7343
|
+
reject(new GitProcessError(`git ${command} could not run: ${err2.message}`, null, ""));
|
|
7344
|
+
}
|
|
7345
|
+
});
|
|
7346
|
+
child.on("close", (code, signal) => {
|
|
7347
|
+
closed = true;
|
|
7348
|
+
clearTimeout(inactivity);
|
|
7349
|
+
clearTimeout(deadline);
|
|
7350
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
7351
|
+
const written = stderrBytes > STDERR_CAP_BYTES ? `${stderr}
|
|
7352
|
+
[stderr truncated]` : stderr;
|
|
7353
|
+
if (killedFor === null && code === 0) {
|
|
7354
|
+
input.then(resolve2, (err2) => reject(new GitProcessError(`git ${command}'s input failed: ${getErrorMessage3(err2)}`, 0, written)));
|
|
7355
|
+
return;
|
|
7356
|
+
}
|
|
7357
|
+
const why = killedFor ?? (written.trim() || (code === null ? `git ${command} was killed by ${signal ?? "a signal"}` : `git ${command} exited with code ${code}`));
|
|
7358
|
+
reject(new GitProcessError(why, code, written));
|
|
7359
|
+
});
|
|
7360
|
+
});
|
|
7361
|
+
exited.catch(() => void 0);
|
|
7362
|
+
return { stdout, exited, kill: stop };
|
|
7363
|
+
}
|
|
7364
|
+
async function runGit(args, options) {
|
|
7365
|
+
const max = options.maxStdoutBytes ?? 64 * 1024 * 1024;
|
|
7366
|
+
const proc = spawnGit(args, options);
|
|
7367
|
+
const chunks = [];
|
|
7368
|
+
let total = 0;
|
|
7369
|
+
const collect = new Writable({
|
|
7370
|
+
write(chunk, _encoding, callback) {
|
|
7371
|
+
total += chunk.length;
|
|
7372
|
+
if (total > max) {
|
|
7373
|
+
proc.kill(`git printed more than ${max} bytes`);
|
|
7374
|
+
callback();
|
|
7375
|
+
return;
|
|
7376
|
+
}
|
|
7377
|
+
chunks.push(chunk);
|
|
7378
|
+
callback();
|
|
7379
|
+
}
|
|
7380
|
+
});
|
|
7381
|
+
try {
|
|
7382
|
+
await Promise.all([pipeline(proc.stdout, collect).catch(() => void 0), proc.exited]);
|
|
7383
|
+
} catch (err2) {
|
|
7384
|
+
if (options.stdoutInFailure && err2 instanceof GitProcessError && chunks.length > 0) {
|
|
7385
|
+
throw new GitProcessError(`${Buffer.concat(chunks).toString("utf8")}${err2.message}`, err2.exitCode, err2.stderr);
|
|
7386
|
+
}
|
|
7387
|
+
throw err2;
|
|
7388
|
+
}
|
|
7389
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
7390
|
+
}
|
|
7391
|
+
function terminate(child) {
|
|
7392
|
+
signalGroup(child, "SIGTERM");
|
|
7393
|
+
const timer = setTimeout(() => signalGroup(child, "SIGKILL"), KILL_GRACE_MS);
|
|
7394
|
+
timer.unref();
|
|
7395
|
+
child.once("close", () => clearTimeout(timer));
|
|
7396
|
+
}
|
|
7397
|
+
function signalGroup(child, signal) {
|
|
7398
|
+
if (child.pid !== void 0 && process.platform !== "win32") {
|
|
7399
|
+
try {
|
|
7400
|
+
process.kill(-child.pid, signal);
|
|
7401
|
+
return;
|
|
7402
|
+
} catch {
|
|
7403
|
+
}
|
|
7404
|
+
}
|
|
7405
|
+
child.kill(signal);
|
|
7406
|
+
}
|
|
7407
|
+
function feedStdin(child, source, touch, onSourceError) {
|
|
7408
|
+
const sink2 = child.stdin;
|
|
7409
|
+
if (sink2 === null)
|
|
7410
|
+
return Promise.resolve();
|
|
7411
|
+
sink2.on("error", () => void 0);
|
|
7412
|
+
if (source === void 0) {
|
|
7413
|
+
sink2.end();
|
|
7414
|
+
return Promise.resolve();
|
|
7415
|
+
}
|
|
7416
|
+
const readable = typeof source === "string" ? Readable2.from([Buffer.from(source)]) : source;
|
|
7417
|
+
const counted = new Transform({
|
|
7418
|
+
transform(chunk, _encoding, callback) {
|
|
7419
|
+
touch();
|
|
7420
|
+
callback(null, chunk);
|
|
7421
|
+
}
|
|
7422
|
+
});
|
|
7423
|
+
return new Promise((resolve2, reject) => {
|
|
7424
|
+
readable.once("error", (err2) => {
|
|
7425
|
+
onSourceError(err2);
|
|
7426
|
+
reject(err2);
|
|
7427
|
+
});
|
|
7428
|
+
sink2.once("close", () => {
|
|
7429
|
+
if (!readable.readableEnded)
|
|
7430
|
+
readable.destroy();
|
|
7431
|
+
resolve2();
|
|
7432
|
+
});
|
|
7433
|
+
readable.pipe(counted).pipe(sink2);
|
|
7434
|
+
});
|
|
7435
|
+
}
|
|
7007
7436
|
function gitChildEnv(overrides) {
|
|
7008
7437
|
const env = {};
|
|
7009
7438
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -7110,7 +7539,7 @@ function formatDirStamp(date) {
|
|
|
7110
7539
|
return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
7111
7540
|
}
|
|
7112
7541
|
function shortDirName(dirName) {
|
|
7113
|
-
const digest =
|
|
7542
|
+
const digest = createHash3("sha1").update(dirName).digest("hex").slice(0, 10);
|
|
7114
7543
|
return `${dirName.slice(0, 40)}-${digest}`;
|
|
7115
7544
|
}
|
|
7116
7545
|
function stampedName(prefix, dirName, now) {
|
|
@@ -7297,12 +7726,13 @@ async function stageBranchWorkspace(options) {
|
|
|
7297
7726
|
throw err2;
|
|
7298
7727
|
}
|
|
7299
7728
|
}
|
|
7300
|
-
async function lockBranchName(baseRoot, dirName) {
|
|
7729
|
+
async function lockBranchName(baseRoot, dirName, shortName = dirName) {
|
|
7301
7730
|
try {
|
|
7302
7731
|
return await acquireProvisioningLockWithin(baseRoot, branchProvisioningLockName(dirName), PUBLISH_LOCK_WAIT_MS);
|
|
7303
7732
|
} catch (err2) {
|
|
7304
|
-
if (isNodeError3(err2) && err2.code === "ELOCKED")
|
|
7305
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
7733
|
+
if (isNodeError3(err2) && err2.code === "ELOCKED") {
|
|
7734
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
7735
|
+
}
|
|
7306
7736
|
throw err2;
|
|
7307
7737
|
}
|
|
7308
7738
|
}
|
|
@@ -7315,9 +7745,9 @@ async function inspectBlocking(finalPath) {
|
|
|
7315
7745
|
return "residue";
|
|
7316
7746
|
}
|
|
7317
7747
|
async function publishStaging(options) {
|
|
7318
|
-
const { baseRoot, dirName, stagingPath } = options;
|
|
7748
|
+
const { baseRoot, dirName, stagingPath, shortName = dirName } = options;
|
|
7319
7749
|
const finalPath = path21.join(baseRoot, dirName);
|
|
7320
|
-
const release = await lockBranchName(baseRoot, dirName);
|
|
7750
|
+
const release = await lockBranchName(baseRoot, dirName, shortName);
|
|
7321
7751
|
try {
|
|
7322
7752
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7323
7753
|
try {
|
|
@@ -7339,7 +7769,7 @@ async function publishStaging(options) {
|
|
|
7339
7769
|
if (blocking === "live")
|
|
7340
7770
|
return "exists";
|
|
7341
7771
|
if (blocking === "protected")
|
|
7342
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
7772
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
7343
7773
|
const result = await quarantineResidueAt(baseRoot, dirName, {
|
|
7344
7774
|
minQuietMs: CREATE_RESIDUE_MIN_QUIET_MS,
|
|
7345
7775
|
expectedRemoteUrl: options.expectedRemoteUrl
|
|
@@ -7354,9 +7784,9 @@ async function publishStaging(options) {
|
|
|
7354
7784
|
}
|
|
7355
7785
|
if (result.reason === "foreign")
|
|
7356
7786
|
throw new BranchDirOccupiedError(dirName);
|
|
7357
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
7787
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
7358
7788
|
}
|
|
7359
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
7789
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
7360
7790
|
} finally {
|
|
7361
7791
|
await release().catch((err2) => {
|
|
7362
7792
|
canopyLogWarn2(`[canopy] Failed to release the provisioning lock for ${dirName}:`, err2);
|
|
@@ -8006,7 +8436,7 @@ async function generateAIContentCLI(options) {
|
|
|
8006
8436
|
console.log(` Output: ${result.outputDir}
|
|
8007
8437
|
`);
|
|
8008
8438
|
}
|
|
8009
|
-
var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceSortFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, editorUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, seoFieldNameSchema2, seoSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR,
|
|
8439
|
+
var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, RESERVED_SETTINGS_BRANCH_PREFIX2, init_branch_name, BASE58_PATTERN2, CONTENT_ID_PATTERN2, PHYSICAL_SEGMENT_PATTERN2, MAX_BRANCH_NAME_LENGTH2, init_validation, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceSortFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, editorUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, seoFieldNameSchema2, seoSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned22, init_mode_env, forEachReferenceField, init_validation2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, warned4, DEPLOYMENT_PLACEHOLDER2, DEFAULT_BRANCH_NAMESPACE_TEMPLATE2, BRANCH_NAMESPACE_ENV2, init_branch_namespace, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, envFlag2, LOG_LEVELS2, DebugLogger2, testLogger2, MARKDOWN_EXPORT_OPTIONS, BULLETS, DELIMITERS, EMPHASIS, STRONG, RULE, WORD_CHAR, GAP, Restyler, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, heldRoots, getFormatExtension, isDataOnlyFormat, asRecord, DEFAULT_SEO_FIELD_NAMES, asRecord2, asText, asFlag, asImageUrl, asOgType, asTwitterCard, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned5, SchemaUnavailableError, isValidId, parseTypedFilename, EMPTY_LOGICAL_PATH, ENTRY_EXTENSIONS, ContentIdIndex, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SNAPSHOT_MEMO_MAX_BYTES, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, INLINE_LINK_TARGET, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, GitProcessError, STDERR_CAP_BYTES, KILL_GRACE_MS, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, HASH_INACTIVITY_TIMEOUT_MS, MAX_HASH_RUNS, LS_REMOTE_INACTIVITY_TIMEOUT_MS, symlinkBlobSha, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, BaseBranchMissingError, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, parseEntryParts, parseEntryData, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
|
|
8010
8440
|
var init_generate_ai_content = __esm({
|
|
8011
8441
|
"dist/cli/generate-ai-content.js"() {
|
|
8012
8442
|
"use strict";
|
|
@@ -8063,6 +8493,21 @@ var init_generate_ai_content = __esm({
|
|
|
8063
8493
|
"use strict";
|
|
8064
8494
|
}
|
|
8065
8495
|
});
|
|
8496
|
+
init_branch_name = __esm3({
|
|
8497
|
+
"dist/paths/branch-name.js"() {
|
|
8498
|
+
"use strict";
|
|
8499
|
+
RESERVED_SETTINGS_BRANCH_PREFIX2 = "canopycms-settings-";
|
|
8500
|
+
}
|
|
8501
|
+
});
|
|
8502
|
+
init_validation = __esm3({
|
|
8503
|
+
"dist/paths/validation.js"() {
|
|
8504
|
+
"use strict";
|
|
8505
|
+
BASE58_PATTERN2 = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
8506
|
+
CONTENT_ID_PATTERN2 = new RegExp(`^${BASE58_PATTERN2}{12}$`);
|
|
8507
|
+
PHYSICAL_SEGMENT_PATTERN2 = new RegExp(`\\.${BASE58_PATTERN2}{12}(?:\\.[a-z]+)?$`);
|
|
8508
|
+
MAX_BRANCH_NAME_LENGTH2 = 250;
|
|
8509
|
+
}
|
|
8510
|
+
});
|
|
8066
8511
|
init_types2 = __esm3({
|
|
8067
8512
|
"dist/paths/types.js"() {
|
|
8068
8513
|
"use strict";
|
|
@@ -8606,6 +9051,8 @@ var init_generate_ai_content = __esm({
|
|
|
8606
9051
|
// (operating-mode/deployment-name.ts) implements — config would always "win" over
|
|
8607
9052
|
// modeDefault, masking dev's real default of 'local'. Do not remove `.optional()`.
|
|
8608
9053
|
deploymentName: deploymentNameSchema2.optional(),
|
|
9054
|
+
// Checked where it is resolved (operating-mode/branch-namespace.ts), with the deployment name.
|
|
9055
|
+
branchNamespace: z6.string().optional(),
|
|
8609
9056
|
contentRoot: contentRootSchema2.default("content"),
|
|
8610
9057
|
sourceRoot: sourceRootSchema2.optional(),
|
|
8611
9058
|
basePath: basePathSchema2,
|
|
@@ -8629,10 +9076,10 @@ var init_generate_ai_content = __esm({
|
|
|
8629
9076
|
init_logger2();
|
|
8630
9077
|
SERVER_MODE_ENV_VAR = "CANOPY_MODE";
|
|
8631
9078
|
BROWSER_MODE_ENV_VAR = "NEXT_PUBLIC_CANOPY_MODE";
|
|
8632
|
-
|
|
9079
|
+
warned22 = false;
|
|
8633
9080
|
}
|
|
8634
9081
|
});
|
|
8635
|
-
|
|
9082
|
+
init_validation2 = __esm3({
|
|
8636
9083
|
"dist/config/validation.js"() {
|
|
8637
9084
|
"use strict";
|
|
8638
9085
|
forEachReferenceField = (fields, visit3) => {
|
|
@@ -8722,6 +9169,19 @@ var init_generate_ai_content = __esm({
|
|
|
8722
9169
|
isValidDeploymentName2 = (name) => VALID_DEPLOYMENT_NAME2.test(name) && !name.includes("..") && !name.endsWith(".") && !name.endsWith(".lock");
|
|
8723
9170
|
}
|
|
8724
9171
|
});
|
|
9172
|
+
init_branch_namespace = __esm3({
|
|
9173
|
+
"dist/operating-mode/branch-namespace.js"() {
|
|
9174
|
+
"use strict";
|
|
9175
|
+
init_branch_name();
|
|
9176
|
+
init_validation();
|
|
9177
|
+
init_logger2();
|
|
9178
|
+
init_deployment_name();
|
|
9179
|
+
warned4 = false;
|
|
9180
|
+
DEPLOYMENT_PLACEHOLDER2 = "{deployment}";
|
|
9181
|
+
DEFAULT_BRANCH_NAMESPACE_TEMPLATE2 = `canopycms/${DEPLOYMENT_PLACEHOLDER2}/`;
|
|
9182
|
+
BRANCH_NAMESPACE_ENV2 = "CANOPYCMS_BRANCH_NAMESPACE";
|
|
9183
|
+
}
|
|
9184
|
+
});
|
|
8725
9185
|
init_config22 = __esm3({
|
|
8726
9186
|
"dist/config/index.js"() {
|
|
8727
9187
|
"use strict";
|
|
@@ -8733,6 +9193,7 @@ var init_generate_ai_content = __esm({
|
|
|
8733
9193
|
"dist/operating-mode/client-unsafe-strategy.js"() {
|
|
8734
9194
|
"use strict";
|
|
8735
9195
|
init_client_safe_strategy();
|
|
9196
|
+
init_branch_namespace();
|
|
8736
9197
|
init_deployment_name();
|
|
8737
9198
|
init_config22();
|
|
8738
9199
|
ProdStrategy2 = class extends ProdClientSafeStrategy2 {
|
|
@@ -8773,6 +9234,9 @@ var init_generate_ai_content = __esm({
|
|
|
8773
9234
|
return config.settingsBranch;
|
|
8774
9235
|
return `canopycms-settings-${resolveDeploymentName2(config, "prod")}`;
|
|
8775
9236
|
}
|
|
9237
|
+
getBranchNamespace(config) {
|
|
9238
|
+
return resolveBranchNamespace2(config, "prod");
|
|
9239
|
+
}
|
|
8776
9240
|
getSettingsRoot(sourceRoot) {
|
|
8777
9241
|
return path92.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
8778
9242
|
}
|
|
@@ -8822,6 +9286,9 @@ var init_generate_ai_content = __esm({
|
|
|
8822
9286
|
return config.settingsBranch;
|
|
8823
9287
|
return `canopycms-settings-${resolveDeploymentName2(config, "local")}`;
|
|
8824
9288
|
}
|
|
9289
|
+
getBranchNamespace(config) {
|
|
9290
|
+
return resolveBranchNamespace2(config, "local");
|
|
9291
|
+
}
|
|
8825
9292
|
getSettingsRoot(sourceRoot) {
|
|
8826
9293
|
return path92.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
8827
9294
|
}
|
|
@@ -8868,6 +9335,7 @@ var init_generate_ai_content = __esm({
|
|
|
8868
9335
|
WORKER_STATUS_FILE: () => WORKER_STATUS_FILE,
|
|
8869
9336
|
readCarriedOverStatus: () => readCarriedOverStatus,
|
|
8870
9337
|
readWorkerStartupFailure: () => readWorkerStartupFailure,
|
|
9338
|
+
readWorkerStatus: () => readWorkerStatus,
|
|
8871
9339
|
readWorkerStatusStartedAt: () => readWorkerStatusStartedAt,
|
|
8872
9340
|
writeWorkerStatus: () => writeWorkerStatus
|
|
8873
9341
|
});
|
|
@@ -9937,7 +10405,7 @@ var init_generate_ai_content = __esm({
|
|
|
9937
10405
|
];
|
|
9938
10406
|
init_logger2();
|
|
9939
10407
|
MAX_WARNED = 1e3;
|
|
9940
|
-
|
|
10408
|
+
warned5 = /* @__PURE__ */ new Set();
|
|
9941
10409
|
init_logger2();
|
|
9942
10410
|
SchemaUnavailableError = class extends Error {
|
|
9943
10411
|
constructor(entryType, unavailable) {
|
|
@@ -9948,9 +10416,7 @@ var init_generate_ai_content = __esm({
|
|
|
9948
10416
|
}
|
|
9949
10417
|
};
|
|
9950
10418
|
init_normalize();
|
|
9951
|
-
|
|
9952
|
-
CONTENT_ID_PATTERN = new RegExp(`^${BASE58_PATTERN}{12}$`);
|
|
9953
|
-
PHYSICAL_SEGMENT_PATTERN = new RegExp(`\\.${BASE58_PATTERN}{12}(?:\\.[a-z]+)?$`);
|
|
10419
|
+
init_validation();
|
|
9954
10420
|
isValidId = isValidContentId;
|
|
9955
10421
|
init_error3();
|
|
9956
10422
|
init_logger2();
|
|
@@ -10311,7 +10777,7 @@ var init_generate_ai_content = __esm({
|
|
|
10311
10777
|
});
|
|
10312
10778
|
fingerprints = /* @__PURE__ */ new WeakMap();
|
|
10313
10779
|
init_flatten();
|
|
10314
|
-
|
|
10780
|
+
init_validation2();
|
|
10315
10781
|
editDistance = (a, b) => {
|
|
10316
10782
|
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
10317
10783
|
for (let i = 1; i <= a.length; i++) {
|
|
@@ -10416,12 +10882,18 @@ var init_generate_ai_content = __esm({
|
|
|
10416
10882
|
init_logger2();
|
|
10417
10883
|
SCHEMA_CACHE_VERSION = 4;
|
|
10418
10884
|
MTIME_CHECK_DEBOUNCE_MS = 1e3;
|
|
10885
|
+
SNAPSHOT_MEMO_MAX_BYTES = 64 * 1024 * 1024;
|
|
10419
10886
|
SCHEMA_CACHE_FILE = "schema-cache.json";
|
|
10420
10887
|
SCHEMA_GENERATION_RESOURCE = "schema";
|
|
10421
10888
|
reportedIssues = /* @__PURE__ */ new Set();
|
|
10422
10889
|
BranchSchemaCache = class {
|
|
10423
10890
|
constructor(mode = "prod") {
|
|
10424
10891
|
this.lastMtimeCheck = /* @__PURE__ */ new Map();
|
|
10892
|
+
this.snapshots = new LRUCache({
|
|
10893
|
+
maxSize: SNAPSHOT_MEMO_MAX_BYTES,
|
|
10894
|
+
sizeCalculation: (snapshot) => Math.max(1, snapshot.bytes)
|
|
10895
|
+
});
|
|
10896
|
+
this.buildResolves = /* @__PURE__ */ new Map();
|
|
10425
10897
|
this.devMode = mode === "dev";
|
|
10426
10898
|
}
|
|
10427
10899
|
/**
|
|
@@ -10451,15 +10923,12 @@ var init_generate_ai_content = __esm({
|
|
|
10451
10923
|
async loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
10452
10924
|
const contentRoot = path82.join(branchRoot, contentRootName);
|
|
10453
10925
|
const fromCheckout = this.readsCheckout(branchRoot);
|
|
10926
|
+
if (fromCheckout && isBuildMode()) {
|
|
10927
|
+
return this.resolveForBuild(branchRoot, entrySchemaRegistry, contentRootName);
|
|
10928
|
+
}
|
|
10454
10929
|
if (!fromCheckout) {
|
|
10455
10930
|
const cachePath = path82.join(await schemaCacheDir(branchRoot), SCHEMA_CACHE_FILE);
|
|
10456
|
-
let cacheData =
|
|
10457
|
-
try {
|
|
10458
|
-
const cacheContent = await fs72.readFile(cachePath, "utf-8");
|
|
10459
|
-
cacheData = JSON.parse(cacheContent);
|
|
10460
|
-
} catch {
|
|
10461
|
-
cacheData = null;
|
|
10462
|
-
}
|
|
10931
|
+
let cacheData = await this.readSnapshot(cachePath);
|
|
10463
10932
|
if (cacheData && cacheData.version === SCHEMA_CACHE_VERSION && cacheData.registryFingerprint === registryFingerprint(entrySchemaRegistry)) {
|
|
10464
10933
|
const read = await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
|
|
10465
10934
|
if (isGenerationCurrent(cacheData.generation, read)) {
|
|
@@ -10489,6 +10958,42 @@ var init_generate_ai_content = __esm({
|
|
|
10489
10958
|
fromCheckout
|
|
10490
10959
|
});
|
|
10491
10960
|
}
|
|
10961
|
+
resolveForBuild(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
10962
|
+
const key = `${path82.resolve(branchRoot, contentRootName)}\0${registryFingerprint(entrySchemaRegistry)}`;
|
|
10963
|
+
let pending = this.buildResolves.get(key);
|
|
10964
|
+
if (!pending) {
|
|
10965
|
+
pending = this.resolveFreshAndPersist(branchRoot, entrySchemaRegistry, contentRootName, {
|
|
10966
|
+
fromCheckout: true
|
|
10967
|
+
});
|
|
10968
|
+
this.buildResolves.set(key, pending);
|
|
10969
|
+
pending.catch(() => this.buildResolves.delete(key));
|
|
10970
|
+
}
|
|
10971
|
+
return pending;
|
|
10972
|
+
}
|
|
10973
|
+
/** The parsed cache file, or null when it is absent or unreadable. */
|
|
10974
|
+
async readSnapshot(cachePath) {
|
|
10975
|
+
let handle;
|
|
10976
|
+
try {
|
|
10977
|
+
handle = await fs72.open(cachePath, "r");
|
|
10978
|
+
} catch {
|
|
10979
|
+
return null;
|
|
10980
|
+
}
|
|
10981
|
+
try {
|
|
10982
|
+
const stat = await handle.stat({ bigint: true });
|
|
10983
|
+
const identity = fileIdentity(stat);
|
|
10984
|
+
const memo = this.snapshots.get(cachePath);
|
|
10985
|
+
if (memo?.identity === identity)
|
|
10986
|
+
return memo.entry;
|
|
10987
|
+
const entry = JSON.parse(await fs72.readFile(handle, "utf-8"));
|
|
10988
|
+
this.snapshots.set(cachePath, { identity, entry, bytes: Number(stat.size) });
|
|
10989
|
+
return entry;
|
|
10990
|
+
} catch {
|
|
10991
|
+
return null;
|
|
10992
|
+
} finally {
|
|
10993
|
+
await handle.close().catch(() => {
|
|
10994
|
+
});
|
|
10995
|
+
}
|
|
10996
|
+
}
|
|
10492
10997
|
/**
|
|
10493
10998
|
* Resolve the schema fresh from disk and persist it (subject to the
|
|
10494
10999
|
* skip-persist rule below), UNCONDITIONALLY -- never through the cache-read
|
|
@@ -10530,14 +11035,22 @@ var init_generate_ai_content = __esm({
|
|
|
10530
11035
|
};
|
|
10531
11036
|
await fs72.mkdir(cacheDir, { recursive: true });
|
|
10532
11037
|
const tmpPath = path82.join(cacheDir, `schema-cache.tmp.${Date.now()}.${Math.random()}.json`);
|
|
10533
|
-
|
|
11038
|
+
const body = JSON.stringify(newCache);
|
|
11039
|
+
let identity;
|
|
10534
11040
|
try {
|
|
11041
|
+
await fs72.writeFile(tmpPath, body, "utf-8");
|
|
11042
|
+
identity = fileIdentity(await fs72.stat(tmpPath, { bigint: true }));
|
|
10535
11043
|
await fs72.rename(tmpPath, cachePath);
|
|
10536
11044
|
} catch (err2) {
|
|
10537
11045
|
await fs72.unlink(tmpPath).catch(() => {
|
|
10538
11046
|
});
|
|
10539
11047
|
throw err2;
|
|
10540
11048
|
}
|
|
11049
|
+
this.snapshots.set(cachePath, {
|
|
11050
|
+
identity,
|
|
11051
|
+
entry: JSON.parse(body),
|
|
11052
|
+
bytes: Buffer.byteLength(body)
|
|
11053
|
+
});
|
|
10541
11054
|
}
|
|
10542
11055
|
}
|
|
10543
11056
|
reportSchemaIssues(issues);
|
|
@@ -10576,13 +11089,15 @@ var init_generate_ai_content = __esm({
|
|
|
10576
11089
|
init_error3();
|
|
10577
11090
|
init_error3();
|
|
10578
11091
|
init_normalize();
|
|
11092
|
+
init_validation();
|
|
10579
11093
|
init_operating_mode();
|
|
10580
|
-
|
|
11094
|
+
init_branch_name();
|
|
10581
11095
|
BranchPathError = class extends Error {
|
|
10582
11096
|
};
|
|
10583
11097
|
resolveContentBranchesRoot = (mode, override) => {
|
|
10584
11098
|
return operatingStrategy2(mode).getContentBranchesRoot(override);
|
|
10585
11099
|
};
|
|
11100
|
+
init_branch_name();
|
|
10586
11101
|
ContentStoreError = class extends Error {
|
|
10587
11102
|
constructor(message, code) {
|
|
10588
11103
|
super(message);
|
|
@@ -11881,7 +12396,13 @@ var init_generate_ai_content = __esm({
|
|
|
11881
12396
|
historyRewrittenFrom: z72.string().optional(),
|
|
11882
12397
|
syncFailureReason: z72.string().optional(),
|
|
11883
12398
|
editors: z72.array(z72.string()).optional(),
|
|
11884
|
-
uncommittedEditors: z72.array(z72.string()).optional()
|
|
12399
|
+
uncommittedEditors: z72.array(z72.string()).optional(),
|
|
12400
|
+
// Dropped when malformed: it only decorates the PR body, so it never fails a read.
|
|
12401
|
+
review: z72.object({
|
|
12402
|
+
state: z72.enum(["approved", "changes-requested"]),
|
|
12403
|
+
by: z72.object({ userId: z72.string(), name: z72.string().optional() }),
|
|
12404
|
+
at: z72.string()
|
|
12405
|
+
}).optional().catch(void 0)
|
|
11885
12406
|
};
|
|
11886
12407
|
branchMetadataSchema = z72.object(branchMetadataShape).passthrough();
|
|
11887
12408
|
branchMetadataFileSchema = z72.object({
|
|
@@ -11978,14 +12499,23 @@ var init_generate_ai_content = __esm({
|
|
|
11978
12499
|
* genuinely-absent branch costs at most one extra scan per window.
|
|
11979
12500
|
*/
|
|
11980
12501
|
async get(name) {
|
|
11981
|
-
|
|
11982
|
-
|
|
12502
|
+
return this.find((b) => b.branch.name === name);
|
|
12503
|
+
}
|
|
12504
|
+
/**
|
|
12505
|
+
* Returns the branch whose workspace directory is `key` (`sanitizeBranchName` of its ref),
|
|
12506
|
+
* whatever its name, with {@link get}'s backstop.
|
|
12507
|
+
*/
|
|
12508
|
+
async getByKey(key) {
|
|
12509
|
+
return this.find((b) => path15.basename(b.branchRoot) === key);
|
|
12510
|
+
}
|
|
12511
|
+
async find(matches) {
|
|
12512
|
+
const found = (await this.list()).find(matches);
|
|
11983
12513
|
if (found)
|
|
11984
12514
|
return found;
|
|
11985
12515
|
if (!this.shouldForceRefresh())
|
|
11986
12516
|
return void 0;
|
|
11987
12517
|
const refreshed = await this.regenerate();
|
|
11988
|
-
return refreshed.find(
|
|
12518
|
+
return refreshed.find(matches);
|
|
11989
12519
|
}
|
|
11990
12520
|
shouldForceRefresh() {
|
|
11991
12521
|
const now = Date.now();
|
|
@@ -12286,13 +12816,33 @@ var init_generate_ai_content = __esm({
|
|
|
12286
12816
|
baseRoot
|
|
12287
12817
|
};
|
|
12288
12818
|
};
|
|
12819
|
+
init_operating_mode();
|
|
12820
|
+
init_branch_name();
|
|
12289
12821
|
init_error3();
|
|
12290
12822
|
init_logger2();
|
|
12823
|
+
init_error3();
|
|
12824
|
+
GitProcessError = class extends Error {
|
|
12825
|
+
constructor(message, exitCode, stderr) {
|
|
12826
|
+
super(message);
|
|
12827
|
+
this.exitCode = exitCode;
|
|
12828
|
+
this.stderr = stderr;
|
|
12829
|
+
this.name = "GitProcessError";
|
|
12830
|
+
}
|
|
12831
|
+
};
|
|
12832
|
+
STDERR_CAP_BYTES = 64 * 1024;
|
|
12833
|
+
KILL_GRACE_MS = 2e3;
|
|
12291
12834
|
log9 = createDebugLogger2({ prefix: "GitManager" });
|
|
12292
12835
|
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_]*)$/;
|
|
12293
12836
|
FORCE_C_LOCALE = { LC_ALL: "C", LANG: "C" };
|
|
12294
12837
|
GIT_NETWORK_ENV_PASSTHROUGH = /^((HTTPS?|ALL)_PROXY|(https?|all)_proxy|NO_PROXY|no_proxy|GIT_SSL_(CAINFO|CAPATH|NO_VERIFY|VERSION)|CURL_CA_BUNDLE|SSL_CERT_(FILE|DIR)|REQUESTS_CA_BUNDLE|NODE_EXTRA_CA_CERTS)$/;
|
|
12295
12838
|
NO_AUTO_GC_CONFIG = ["gc.auto=0", "maintenance.auto=false"];
|
|
12839
|
+
HASH_INACTIVITY_TIMEOUT_MS = 6e4;
|
|
12840
|
+
MAX_HASH_RUNS = 50;
|
|
12841
|
+
LS_REMOTE_INACTIVITY_TIMEOUT_MS = 6e4;
|
|
12842
|
+
symlinkBlobSha = (target, likeSha) => {
|
|
12843
|
+
const body = Buffer.from(target, "utf8");
|
|
12844
|
+
return createHash2(likeSha.length === 64 ? "sha256" : "sha1").update(Buffer.concat([Buffer.from(`blob ${body.length}\0`), body])).digest("hex");
|
|
12845
|
+
};
|
|
12296
12846
|
REMOTE_GIT_CONFIG = [
|
|
12297
12847
|
["gc.auto", "0"],
|
|
12298
12848
|
["receive.autogc", "false"],
|
|
@@ -12309,6 +12859,12 @@ var init_generate_ai_content = __esm({
|
|
|
12309
12859
|
];
|
|
12310
12860
|
remoteInitLocks = /* @__PURE__ */ new Map();
|
|
12311
12861
|
GITHUB_TRACKING_REF_PREFIX = "refs/remotes/github/";
|
|
12862
|
+
BaseBranchMissingError = class extends Error {
|
|
12863
|
+
constructor(remote, baseBranch) {
|
|
12864
|
+
super(`CanopyCMS: remote '${remote}' has no branch '${baseBranch}'`);
|
|
12865
|
+
this.name = "BaseBranchMissingError";
|
|
12866
|
+
}
|
|
12867
|
+
};
|
|
12312
12868
|
GitConflictError = class extends Error {
|
|
12313
12869
|
constructor(conflictedFiles) {
|
|
12314
12870
|
super(`Git conflict in ${conflictedFiles.length} file(s): ${conflictedFiles.join(", ")}`);
|
|
@@ -12584,6 +13140,36 @@ var init_generate_ai_content = __esm({
|
|
|
12584
13140
|
}
|
|
12585
13141
|
return refs.includes(`refs/heads/${branch}`) || refs.includes(`${GITHUB_TRACKING_REF_PREFIX}${branch}`);
|
|
12586
13142
|
}
|
|
13143
|
+
/**
|
|
13144
|
+
* The branches inside `branch` (`<branch>/…`) that a bare mirror holds as local heads or in
|
|
13145
|
+
* GitHub's tracking namespace, which git cannot hold beside `branch` itself. `for-each-ref`
|
|
13146
|
+
* matches a pattern up to a slash, so the pattern `<branch>` lists them. Throws, as
|
|
13147
|
+
* {@link bareRemoteHasBranch} does, when the mirror is unreadable.
|
|
13148
|
+
*/
|
|
13149
|
+
static async bareRemoteBranchesInside(remotePath, branch) {
|
|
13150
|
+
let output;
|
|
13151
|
+
try {
|
|
13152
|
+
output = await simpleGit22().raw([
|
|
13153
|
+
"--git-dir",
|
|
13154
|
+
remotePath,
|
|
13155
|
+
"for-each-ref",
|
|
13156
|
+
"--format=%(refname)",
|
|
13157
|
+
"--end-of-options",
|
|
13158
|
+
`refs/heads/${branch}`,
|
|
13159
|
+
`${GITHUB_TRACKING_REF_PREFIX}${branch}`
|
|
13160
|
+
]);
|
|
13161
|
+
} catch (err2) {
|
|
13162
|
+
throw new Error(`Cannot inspect remote mirror at ${remotePath}: ${getErrorMessage3(err2)}`);
|
|
13163
|
+
}
|
|
13164
|
+
const inside = /* @__PURE__ */ new Set();
|
|
13165
|
+
for (const ref of output.split("\n").map((line) => line.trim())) {
|
|
13166
|
+
for (const prefix of ["refs/heads/", GITHUB_TRACKING_REF_PREFIX]) {
|
|
13167
|
+
if (ref.startsWith(`${prefix}${branch}/`))
|
|
13168
|
+
inside.add(ref.slice(prefix.length));
|
|
13169
|
+
}
|
|
13170
|
+
}
|
|
13171
|
+
return [...inside].sort();
|
|
13172
|
+
}
|
|
12587
13173
|
/**
|
|
12588
13174
|
* Delete `refs/heads/<branch>` from a bare local mirror, if present. A
|
|
12589
13175
|
* no-op (not an error) when the ref doesn't exist.
|
|
@@ -13282,6 +13868,199 @@ var init_generate_ai_content = __esm({
|
|
|
13282
13868
|
]);
|
|
13283
13869
|
return output.split("\0").filter((p5) => p5.length > 0);
|
|
13284
13870
|
}
|
|
13871
|
+
/**
|
|
13872
|
+
* Every file in `rev`'s tree below repo-relative directory `under`, with its size in bytes,
|
|
13873
|
+
* so a caller can refuse a file too large to read before reading it.
|
|
13874
|
+
*/
|
|
13875
|
+
async listFilesWithSizeAt(rev, under) {
|
|
13876
|
+
const dir = `${under.replace(/\/+$/, "")}/`;
|
|
13877
|
+
const output = await this.git.raw([
|
|
13878
|
+
"ls-tree",
|
|
13879
|
+
"-r",
|
|
13880
|
+
"-z",
|
|
13881
|
+
"-l",
|
|
13882
|
+
"--full-tree",
|
|
13883
|
+
"--end-of-options",
|
|
13884
|
+
rev,
|
|
13885
|
+
dir
|
|
13886
|
+
]);
|
|
13887
|
+
const files = [];
|
|
13888
|
+
for (const record of output.split("\0")) {
|
|
13889
|
+
const tab = record.indexOf(" ");
|
|
13890
|
+
if (tab === -1)
|
|
13891
|
+
continue;
|
|
13892
|
+
const [, type, , size] = record.slice(0, tab).split(/ +/);
|
|
13893
|
+
const filePath = record.slice(tab + 1);
|
|
13894
|
+
if (type === "blob" && filePath.startsWith(dir)) {
|
|
13895
|
+
files.push({ path: filePath, size: Number(size) });
|
|
13896
|
+
}
|
|
13897
|
+
}
|
|
13898
|
+
return files;
|
|
13899
|
+
}
|
|
13900
|
+
/**
|
|
13901
|
+
* The commit HEAD forked from the base branch at, or `undefined` when they share no history.
|
|
13902
|
+
* Takes the base tip from the remote by `ls-remote` rather than `<remote>/<base>`: the worker
|
|
13903
|
+
* fetches by path, which moves no remote-tracking ref, so that ref is left behind by every
|
|
13904
|
+
* rebase. A tip this clone lacks is fetched as objects only (`--refmap=`, `--no-tags`,
|
|
13905
|
+
* `--no-write-fetch-head`, git 2.29+): no ref and no FETCH_HEAD moves, so a concurrent fetch
|
|
13906
|
+
* pinning FETCH_HEAD is never repointed. Throws {@link BaseBranchMissingError} when the
|
|
13907
|
+
* remote has no base branch, and any other error when it can't be read.
|
|
13908
|
+
*/
|
|
13909
|
+
async mergeBaseWithBase() {
|
|
13910
|
+
const tip = await this.remoteBranchTip(this.baseBranch);
|
|
13911
|
+
if (!tip)
|
|
13912
|
+
throw new BaseBranchMissingError(this.remote, this.baseBranch);
|
|
13913
|
+
if (!await this.hasCommit(tip)) {
|
|
13914
|
+
await this.git.raw([
|
|
13915
|
+
"fetch",
|
|
13916
|
+
"--refmap=",
|
|
13917
|
+
"--no-tags",
|
|
13918
|
+
"--no-write-fetch-head",
|
|
13919
|
+
"--end-of-options",
|
|
13920
|
+
this.remote,
|
|
13921
|
+
`refs/heads/${this.baseBranch}`
|
|
13922
|
+
]);
|
|
13923
|
+
if (!await this.hasCommit(tip)) {
|
|
13924
|
+
throw new Error(`CanopyCMS: could not fetch ${this.baseBranch}'s tip ${tip}`);
|
|
13925
|
+
}
|
|
13926
|
+
}
|
|
13927
|
+
const forkPoint = (await this.git.raw(["merge-base", "--end-of-options", "HEAD", tip])).trim();
|
|
13928
|
+
return forkPoint || void 0;
|
|
13929
|
+
}
|
|
13930
|
+
/** Whether this clone holds commit `sha`. */
|
|
13931
|
+
async hasCommit(sha) {
|
|
13932
|
+
return (await this.git.raw(["rev-parse", "--verify", "--quiet", `${sha}^{commit}`])).trim() !== "";
|
|
13933
|
+
}
|
|
13934
|
+
/**
|
|
13935
|
+
* Repo-relative paths whose working-tree content differs from commit `rev`, with `T` (a type
|
|
13936
|
+
* change) reported as `M`. A sparse clone leaves a path outside its cone unchecked out, marked
|
|
13937
|
+
* skip-worktree, and git reads that path's index entry in place of the missing file: it is never
|
|
13938
|
+
* reported deleted, and differs from `rev` only as the index does.
|
|
13939
|
+
*
|
|
13940
|
+
* `diff-index`, not `git diff`: the porcelain refreshes the index and writes it back under
|
|
13941
|
+
* `index.lock`, which would fail a concurrent writer's own git step. `diff-index` writes
|
|
13942
|
+
* nothing, so it reports a file whose stat changed but whose content did not; those are
|
|
13943
|
+
* hashed here and dropped when they match `rev`.
|
|
13944
|
+
*/
|
|
13945
|
+
async diffNameStatusAgainst(rev) {
|
|
13946
|
+
const output = await this.git.raw([
|
|
13947
|
+
"diff-index",
|
|
13948
|
+
"--raw",
|
|
13949
|
+
"-z",
|
|
13950
|
+
"--no-renames",
|
|
13951
|
+
"--end-of-options",
|
|
13952
|
+
rev,
|
|
13953
|
+
"--"
|
|
13954
|
+
]);
|
|
13955
|
+
const tokens = output.split("\0");
|
|
13956
|
+
const changes = [];
|
|
13957
|
+
for (let i = 0; i + 1 < tokens.length; i += 2) {
|
|
13958
|
+
const [srcMode, dstMode, srcSha, dstSha, code] = tokens[i].slice(1).split(" ");
|
|
13959
|
+
const path272 = tokens[i + 1];
|
|
13960
|
+
const letter = code?.charAt(0);
|
|
13961
|
+
if (letter === "A" || letter === "D")
|
|
13962
|
+
changes.push({ status: letter, path: path272 });
|
|
13963
|
+
else if (letter === "M" || letter === "T") {
|
|
13964
|
+
const unhashed = /^0+$/.test(dstSha) && srcMode === dstMode ? srcSha : void 0;
|
|
13965
|
+
changes.push({ status: "M", path: path272, unhashed });
|
|
13966
|
+
}
|
|
13967
|
+
}
|
|
13968
|
+
const unchanged = await this.matchingBlobs(changes.flatMap((c) => c.unhashed ? [{ path: c.path, sha: c.unhashed }] : []));
|
|
13969
|
+
return changes.filter((c) => !unchanged.has(c.path)).map(({ status, path: path272 }) => ({ status, path: path272 }));
|
|
13970
|
+
}
|
|
13971
|
+
/**
|
|
13972
|
+
* The paths whose working-tree file hashes to `sha`, as git hashes it: a symlink's blob is its
|
|
13973
|
+
* target, a file's goes through `hash-object --stdin-paths`, so its attributes' filters apply.
|
|
13974
|
+
* Callers pass only paths whose mode is unchanged, since diff-index reports the working tree's
|
|
13975
|
+
* own mode. A path that is gone or can't be hashed matches nothing, so it stays reported as
|
|
13976
|
+
* changed. When git refuses a path (exit 128), having printed
|
|
13977
|
+
* the SHAs before it, the next run resumes past that path, for at most {@link MAX_HASH_RUNS}
|
|
13978
|
+
* runs. A run that ends any other way (killed, timed out, a signal) stops the hashing, and the
|
|
13979
|
+
* paths it didn't reach stay changed.
|
|
13980
|
+
*/
|
|
13981
|
+
async matchingBlobs(files) {
|
|
13982
|
+
const matching = /* @__PURE__ */ new Set();
|
|
13983
|
+
const regular = [];
|
|
13984
|
+
for (const file of files) {
|
|
13985
|
+
if (file.path.includes("\n"))
|
|
13986
|
+
continue;
|
|
13987
|
+
const stat = await fs16.lstat(path20.join(this.repoPath, file.path)).catch(() => void 0);
|
|
13988
|
+
if (!stat)
|
|
13989
|
+
continue;
|
|
13990
|
+
if (stat.isSymbolicLink()) {
|
|
13991
|
+
const target = await fs16.readlink(path20.join(this.repoPath, file.path)).catch(() => void 0);
|
|
13992
|
+
if (target !== void 0 && symlinkBlobSha(target, file.sha) === file.sha) {
|
|
13993
|
+
matching.add(file.path);
|
|
13994
|
+
}
|
|
13995
|
+
} else if (stat.isFile())
|
|
13996
|
+
regular.push(file);
|
|
13997
|
+
}
|
|
13998
|
+
for (let start = 0, runs = 0; start < regular.length && runs < MAX_HASH_RUNS; runs++) {
|
|
13999
|
+
const batch = regular.slice(start);
|
|
14000
|
+
const { shas, outcome } = await this.hashPaths(batch.map((f) => f.path));
|
|
14001
|
+
shas.forEach((sha, i) => {
|
|
14002
|
+
if (batch[i] && sha === batch[i].sha)
|
|
14003
|
+
matching.add(batch[i].path);
|
|
14004
|
+
});
|
|
14005
|
+
if (outcome !== "refused")
|
|
14006
|
+
break;
|
|
14007
|
+
start += shas.length + 1;
|
|
14008
|
+
}
|
|
14009
|
+
return matching;
|
|
14010
|
+
}
|
|
14011
|
+
/**
|
|
14012
|
+
* `hash-object --stdin-paths` over `paths`: one SHA per path, in order, up to where it ended.
|
|
14013
|
+
* `refused` is git's own exit 128 on a path it can't hash; `stopped` is any other ending.
|
|
14014
|
+
*/
|
|
14015
|
+
async hashPaths(paths) {
|
|
14016
|
+
const chunks = [];
|
|
14017
|
+
let proc;
|
|
14018
|
+
try {
|
|
14019
|
+
proc = spawnGit(["hash-object", "--stdin-paths"], {
|
|
14020
|
+
...this.processOptions(),
|
|
14021
|
+
stdin: `${paths.join("\n")}
|
|
14022
|
+
`,
|
|
14023
|
+
inactivityTimeoutMs: HASH_INACTIVITY_TIMEOUT_MS
|
|
14024
|
+
});
|
|
14025
|
+
} catch {
|
|
14026
|
+
return { shas: [], outcome: "stopped" };
|
|
14027
|
+
}
|
|
14028
|
+
const read = (async () => {
|
|
14029
|
+
for await (const chunk of proc.stdout)
|
|
14030
|
+
chunks.push(chunk);
|
|
14031
|
+
})().catch(() => {
|
|
14032
|
+
});
|
|
14033
|
+
const outcome = await proc.exited.then(() => "complete", (err2) => err2 instanceof GitProcessError && err2.exitCode === 128 ? "refused" : "stopped");
|
|
14034
|
+
await read;
|
|
14035
|
+
return { shas: Buffer.concat(chunks).toString("utf8").split("\n").filter(Boolean), outcome };
|
|
14036
|
+
}
|
|
14037
|
+
/** What a git run outside simple-git needs to run as `this.git` does. */
|
|
14038
|
+
processOptions() {
|
|
14039
|
+
return {
|
|
14040
|
+
cwd: this.repoPath,
|
|
14041
|
+
env: gitChildEnv({ GIT_CEILING_DIRECTORIES: path20.dirname(this.repoPath) }),
|
|
14042
|
+
config: NO_AUTO_GC_CONFIG
|
|
14043
|
+
};
|
|
14044
|
+
}
|
|
14045
|
+
/** Repo-relative paths of the working tree's untracked, non-ignored files. */
|
|
14046
|
+
async listUntrackedFiles() {
|
|
14047
|
+
const output = await this.git.raw(["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
14048
|
+
return output.split("\0").filter((p5) => p5.length > 0);
|
|
14049
|
+
}
|
|
14050
|
+
/**
|
|
14051
|
+
* The text of repo-relative `filePath` at commit `rev`, or `undefined` when `rev` has no such
|
|
14052
|
+
* path. Any other failure, such as an unknown `rev`, throws.
|
|
14053
|
+
*/
|
|
14054
|
+
async showFile(rev, filePath) {
|
|
14055
|
+
try {
|
|
14056
|
+
return await this.git.raw(["show", "--end-of-options", `${rev}:${filePath}`]);
|
|
14057
|
+
} catch (err2) {
|
|
14058
|
+
if (/fatal: path '.*' (does not exist in|exists on disk, but not in) '/.test(getErrorMessage3(err2))) {
|
|
14059
|
+
return void 0;
|
|
14060
|
+
}
|
|
14061
|
+
throw err2;
|
|
14062
|
+
}
|
|
14063
|
+
}
|
|
13285
14064
|
async ensureAuthor(author) {
|
|
13286
14065
|
const config = await this.git.listConfig();
|
|
13287
14066
|
const isManaged = config.all["canopycms.managed"] === "true";
|
|
@@ -13452,7 +14231,10 @@ var init_generate_ai_content = __esm({
|
|
|
13452
14231
|
*/
|
|
13453
14232
|
async remoteBranchTip(branch) {
|
|
13454
14233
|
const ref = `refs/heads/${branch}`;
|
|
13455
|
-
const output = await
|
|
14234
|
+
const output = await runGit(["ls-remote", "--heads", "--end-of-options", this.remote, ref], {
|
|
14235
|
+
...this.processOptions(),
|
|
14236
|
+
inactivityTimeoutMs: LS_REMOTE_INACTIVITY_TIMEOUT_MS
|
|
14237
|
+
});
|
|
13456
14238
|
for (const line of output.split("\n")) {
|
|
13457
14239
|
const [sha, name] = line.trim().split(" ");
|
|
13458
14240
|
if (sha && name === ref)
|
|
@@ -13531,7 +14313,7 @@ var init_generate_ai_content = __esm({
|
|
|
13531
14313
|
DELETING_MAX_AGE_MS = 10 * 6e4;
|
|
13532
14314
|
SETTINGS_TRASH_MAX_AGE_MS = 30 * 24 * 60 * 6e4;
|
|
13533
14315
|
BranchProvisioningBusyError = class extends Error {
|
|
13534
|
-
constructor(dirName, message = `Branch '${
|
|
14316
|
+
constructor(dirName, shortName = dirName, message = `Branch '${shortName}' is still being set up. Try again in a minute.`) {
|
|
13535
14317
|
super(message);
|
|
13536
14318
|
this.dirName = dirName;
|
|
13537
14319
|
this.name = "BranchProvisioningBusyError";
|
|
@@ -13607,12 +14389,16 @@ var init_generate_ai_content = __esm({
|
|
|
13607
14389
|
if (isSettingsBranchName(options.branchName, operatingStrategy2(options.mode).getSettingsBranchName(this.config))) {
|
|
13608
14390
|
throw new BranchPathError("Settings branches are not content branches");
|
|
13609
14391
|
}
|
|
13610
|
-
const
|
|
14392
|
+
const named = parseBranchRef2(options.branchName);
|
|
14393
|
+
if (!named.ok)
|
|
14394
|
+
throw new BranchPathError(named.error);
|
|
14395
|
+
const { branchRoot, baseRoot } = resolveBranchPath(options);
|
|
13611
14396
|
const paths = {
|
|
13612
14397
|
branchRoot,
|
|
13613
14398
|
baseRoot,
|
|
13614
14399
|
dirName: path23.basename(branchRoot),
|
|
13615
|
-
|
|
14400
|
+
ref: options.branchName,
|
|
14401
|
+
shortName: shortNameOf({ ...this.config, mode: options.mode }, options.branchName)
|
|
13616
14402
|
};
|
|
13617
14403
|
const existing = await readBranchMetadataFile(branchRoot);
|
|
13618
14404
|
if (existing) {
|
|
@@ -13630,7 +14416,7 @@ var init_generate_ai_content = __esm({
|
|
|
13630
14416
|
}
|
|
13631
14417
|
}
|
|
13632
14418
|
async provisionUnshared(options, paths) {
|
|
13633
|
-
const { branchRoot, baseRoot, dirName } = paths;
|
|
14419
|
+
const { branchRoot, baseRoot, dirName, shortName } = paths;
|
|
13634
14420
|
await fs19.mkdir(baseRoot, { recursive: true });
|
|
13635
14421
|
const baseBranch = await GitManager.resolveWorkspaceBaseBranch({
|
|
13636
14422
|
...this.config,
|
|
@@ -13648,7 +14434,7 @@ var init_generate_ai_content = __esm({
|
|
|
13648
14434
|
});
|
|
13649
14435
|
const metadata = buildInitialBranchMetadata({
|
|
13650
14436
|
branch: {
|
|
13651
|
-
name: paths.
|
|
14437
|
+
name: paths.ref,
|
|
13652
14438
|
title: options.title,
|
|
13653
14439
|
description: options.description,
|
|
13654
14440
|
access: options.access,
|
|
@@ -13660,14 +14446,14 @@ var init_generate_ai_content = __esm({
|
|
|
13660
14446
|
if (state.kind === "live" || state.kind === "corrupt")
|
|
13661
14447
|
return this.existing(paths);
|
|
13662
14448
|
if (state.kind === "protected")
|
|
13663
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
14449
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
13664
14450
|
if (state.kind === "foreign") {
|
|
13665
14451
|
if (options.mode !== "dev")
|
|
13666
14452
|
throw new BranchDirOccupiedError(dirName);
|
|
13667
14453
|
return this.adoptInPlace(options, paths, baseBranch, metadata);
|
|
13668
14454
|
}
|
|
13669
14455
|
if (state.kind === "residue" && await this.recentlyActive(paths)) {
|
|
13670
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
14456
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
13671
14457
|
}
|
|
13672
14458
|
const sparseCone = sparseConeFor(this.config.contentRoot);
|
|
13673
14459
|
await recordSparseCone(baseRoot, sparseCone).catch((err2) => {
|
|
@@ -13692,11 +14478,11 @@ var init_generate_ai_content = __esm({
|
|
|
13692
14478
|
return outcome;
|
|
13693
14479
|
}
|
|
13694
14480
|
async stageAndPublish(paths, run) {
|
|
13695
|
-
const { branchRoot, baseRoot, dirName } = paths;
|
|
14481
|
+
const { branchRoot, baseRoot, dirName, shortName } = paths;
|
|
13696
14482
|
const staged = await stageBranchWorkspace({
|
|
13697
14483
|
baseRoot,
|
|
13698
14484
|
dirName,
|
|
13699
|
-
branchName: paths.
|
|
14485
|
+
branchName: paths.ref,
|
|
13700
14486
|
baseBranch: run.baseBranch,
|
|
13701
14487
|
remoteUrl: run.remoteUrl,
|
|
13702
14488
|
clone: {
|
|
@@ -13716,6 +14502,7 @@ var init_generate_ai_content = __esm({
|
|
|
13716
14502
|
published = await run.provisionLog.step("publish", () => publishStaging({
|
|
13717
14503
|
baseRoot,
|
|
13718
14504
|
dirName,
|
|
14505
|
+
shortName,
|
|
13719
14506
|
stagingPath: staged.stagingPath,
|
|
13720
14507
|
writeId: staged.meta.writeId,
|
|
13721
14508
|
expectedRemoteUrl: run.remoteUrl,
|
|
@@ -13735,16 +14522,16 @@ var init_generate_ai_content = __esm({
|
|
|
13735
14522
|
* is, given branch.json under the branch's lock.
|
|
13736
14523
|
*/
|
|
13737
14524
|
async adoptInPlace(options, paths, baseBranch, metadata) {
|
|
13738
|
-
const { branchRoot, baseRoot, dirName } = paths;
|
|
14525
|
+
const { branchRoot, baseRoot, dirName, shortName } = paths;
|
|
13739
14526
|
const provisionLog = new ProvisionLog(dirName);
|
|
13740
14527
|
let created = false;
|
|
13741
14528
|
try {
|
|
13742
|
-
const release = await provisionLog.step("lock", () => lockBranchName(baseRoot, dirName));
|
|
14529
|
+
const release = await provisionLog.step("lock", () => lockBranchName(baseRoot, dirName, shortName));
|
|
13743
14530
|
try {
|
|
13744
14531
|
if (!await readBranchMetadataFile(branchRoot)) {
|
|
13745
14532
|
await GitManager.initializeWorkspace({
|
|
13746
14533
|
workspacePath: branchRoot,
|
|
13747
|
-
branchName: paths.
|
|
14534
|
+
branchName: paths.ref,
|
|
13748
14535
|
mode: options.mode,
|
|
13749
14536
|
baseBranch,
|
|
13750
14537
|
sourceRoot: this.config.sourceRoot,
|
|
@@ -13779,7 +14566,7 @@ var init_generate_ai_content = __esm({
|
|
|
13779
14566
|
return this.existing(paths);
|
|
13780
14567
|
const meta = await readBranchMetadataFile(branchRoot);
|
|
13781
14568
|
if (!meta)
|
|
13782
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
14569
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
13783
14570
|
return { kind: "created", context: { branch: meta.branch, branchRoot, baseRoot } };
|
|
13784
14571
|
}
|
|
13785
14572
|
/** Whether residue at the branch path may still belong to a live process. */
|
|
@@ -13789,10 +14576,10 @@ var init_generate_ai_content = __esm({
|
|
|
13789
14576
|
return true;
|
|
13790
14577
|
return await quietAgeMs(branchRoot) < CREATE_RESIDUE_MIN_QUIET_MS;
|
|
13791
14578
|
}
|
|
13792
|
-
async existing({ branchRoot, baseRoot, dirName }) {
|
|
14579
|
+
async existing({ branchRoot, baseRoot, dirName, shortName }) {
|
|
13793
14580
|
const meta = await readBranchMetadataFile(branchRoot);
|
|
13794
14581
|
if (!meta)
|
|
13795
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
14582
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
13796
14583
|
return { kind: "exists", context: { branch: meta.branch, branchRoot, baseRoot } };
|
|
13797
14584
|
}
|
|
13798
14585
|
};
|
|
@@ -13806,21 +14593,23 @@ var init_generate_ai_content = __esm({
|
|
|
13806
14593
|
}
|
|
13807
14594
|
return result.normalizedPath;
|
|
13808
14595
|
};
|
|
14596
|
+
parseEntryParts = (raw, format) => {
|
|
14597
|
+
if (format === "json")
|
|
14598
|
+
return { data: asRecord(JSON.parse(raw)) };
|
|
14599
|
+
if (format === "yaml")
|
|
14600
|
+
return { data: asRecord(yamlParse2(raw)) };
|
|
14601
|
+
const parsed = matter3(raw);
|
|
14602
|
+
return { data: { ...parsed.data ?? {} }, body: parsed.content };
|
|
14603
|
+
};
|
|
14604
|
+
parseEntryData = (raw, format, bodyFieldName = "body") => {
|
|
14605
|
+
const { data, body } = parseEntryParts(raw, format);
|
|
14606
|
+
if (body)
|
|
14607
|
+
data[bodyFieldName] = body;
|
|
14608
|
+
return data;
|
|
14609
|
+
};
|
|
13809
14610
|
readEntryData = async (filePath, format, bodyFieldName = "body") => {
|
|
13810
14611
|
try {
|
|
13811
|
-
|
|
13812
|
-
if (format === "json") {
|
|
13813
|
-
return asRecord(JSON.parse(raw));
|
|
13814
|
-
}
|
|
13815
|
-
if (format === "yaml") {
|
|
13816
|
-
return asRecord(yamlParse2(raw));
|
|
13817
|
-
}
|
|
13818
|
-
const parsed = matter3(raw);
|
|
13819
|
-
const data = { ...parsed.data ?? {} };
|
|
13820
|
-
if (parsed.content) {
|
|
13821
|
-
data[bodyFieldName] = parsed.content;
|
|
13822
|
-
}
|
|
13823
|
-
return data;
|
|
14612
|
+
return parseEntryData(await fs20.readFile(filePath, "utf8"), format, bodyFieldName);
|
|
13824
14613
|
} catch (err2) {
|
|
13825
14614
|
if (isNotFoundError3(err2))
|
|
13826
14615
|
return {};
|
|
@@ -14235,30 +15024,30 @@ function computeExtractRegion(crop, width, height) {
|
|
|
14235
15024
|
const extractHeight = Math.max(1, bottom - top);
|
|
14236
15025
|
return { left, top, width: extractWidth, height: extractHeight };
|
|
14237
15026
|
}
|
|
14238
|
-
function encode(
|
|
15027
|
+
function encode(pipeline2, format, quality) {
|
|
14239
15028
|
const options = quality !== void 0 ? { quality } : void 0;
|
|
14240
15029
|
switch (format) {
|
|
14241
15030
|
case "webp":
|
|
14242
|
-
return
|
|
15031
|
+
return pipeline2.webp(options);
|
|
14243
15032
|
case "jpeg":
|
|
14244
|
-
return
|
|
15033
|
+
return pipeline2.jpeg(options);
|
|
14245
15034
|
case "png":
|
|
14246
|
-
return
|
|
15035
|
+
return pipeline2.png(options);
|
|
14247
15036
|
}
|
|
14248
15037
|
}
|
|
14249
|
-
function encodeSourceFormat(
|
|
15038
|
+
function encodeSourceFormat(pipeline2, sourceExt, quality) {
|
|
14250
15039
|
const options = quality !== void 0 ? { quality } : void 0;
|
|
14251
15040
|
switch (sourceExt) {
|
|
14252
15041
|
case "gif":
|
|
14253
|
-
return
|
|
15042
|
+
return pipeline2.gif();
|
|
14254
15043
|
case "webp":
|
|
14255
|
-
return
|
|
15044
|
+
return pipeline2.webp(options);
|
|
14256
15045
|
case "jpg":
|
|
14257
15046
|
case "jpeg":
|
|
14258
|
-
return
|
|
15047
|
+
return pipeline2.jpeg(options);
|
|
14259
15048
|
case "png":
|
|
14260
15049
|
default:
|
|
14261
|
-
return
|
|
15050
|
+
return pipeline2.png(options);
|
|
14262
15051
|
}
|
|
14263
15052
|
}
|
|
14264
15053
|
async function applyTransform(input, directives) {
|
|
@@ -14278,22 +15067,22 @@ async function applyTransform(input, directives) {
|
|
|
14278
15067
|
const probeMeta = await sharp(input.data, { limitInputPixels: MAX_INPUT_PIXELS2 }).metadata();
|
|
14279
15068
|
const totalPages = probeMeta.pages ?? 1;
|
|
14280
15069
|
const pagesToRead = Math.min(totalPages, MAX_ANIMATED_FRAMES);
|
|
14281
|
-
let
|
|
15070
|
+
let pipeline2 = sharp(input.data, {
|
|
14282
15071
|
pages: pagesToRead,
|
|
14283
15072
|
limitInputPixels: MAX_INPUT_PIXELS2
|
|
14284
15073
|
}).rotate();
|
|
14285
15074
|
if (resize?.crop) {
|
|
14286
|
-
const oriented = orientedDimensions(await
|
|
15075
|
+
const oriented = orientedDimensions(await pipeline2.metadata());
|
|
14287
15076
|
if (!oriented) {
|
|
14288
15077
|
return { ok: false, status: 422, error: "Could not read image dimensions for crop" };
|
|
14289
15078
|
}
|
|
14290
|
-
|
|
15079
|
+
pipeline2 = pipeline2.extract(computeExtractRegion(resize.crop, oriented.width, oriented.height));
|
|
14291
15080
|
}
|
|
14292
15081
|
if (resize?.width) {
|
|
14293
|
-
|
|
15082
|
+
pipeline2 = pipeline2.resize({ width: resize.width, withoutEnlargement: true });
|
|
14294
15083
|
}
|
|
14295
|
-
|
|
14296
|
-
const data = await
|
|
15084
|
+
pipeline2 = format ? encode(pipeline2, format, quality) : encodeSourceFormat(pipeline2, sourceExt, quality);
|
|
15085
|
+
const data = await pipeline2.toBuffer();
|
|
14297
15086
|
if (!directives.identity && data.byteLength > MAX_OUTPUT_BYTES) {
|
|
14298
15087
|
return {
|
|
14299
15088
|
ok: false,
|
|
@@ -16239,13 +17028,110 @@ var init_client_safe_strategy2 = __esm({
|
|
|
16239
17028
|
}
|
|
16240
17029
|
});
|
|
16241
17030
|
|
|
17031
|
+
// dist/paths/branch-name.js
|
|
17032
|
+
function sanitizeBranchName3(branchName) {
|
|
17033
|
+
const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
17034
|
+
const squashed = replaced.replace(/-+/g, "-");
|
|
17035
|
+
const trimmedDots = squashed.replace(/^\.+/, "").replace(/(?<!\.)\.+$/, "");
|
|
17036
|
+
return trimmedDots || "branch";
|
|
17037
|
+
}
|
|
17038
|
+
var RESERVED_SETTINGS_BRANCH_PREFIX3;
|
|
17039
|
+
var init_branch_name2 = __esm({
|
|
17040
|
+
"dist/paths/branch-name.js"() {
|
|
17041
|
+
"use strict";
|
|
17042
|
+
RESERVED_SETTINGS_BRANCH_PREFIX3 = "canopycms-settings-";
|
|
17043
|
+
}
|
|
17044
|
+
});
|
|
17045
|
+
|
|
17046
|
+
// dist/paths/validation.js
|
|
17047
|
+
function isValidContentId2(id) {
|
|
17048
|
+
return CONTENT_ID_PATTERN3.test(id);
|
|
17049
|
+
}
|
|
17050
|
+
function parseBranchRef3(name) {
|
|
17051
|
+
if (!name || typeof name !== "string") {
|
|
17052
|
+
return { ok: false, error: "Branch name is required" };
|
|
17053
|
+
}
|
|
17054
|
+
if (name.length > MAX_BRANCH_NAME_LENGTH3) {
|
|
17055
|
+
return { ok: false, error: `Branch name too long (max ${MAX_BRANCH_NAME_LENGTH3} characters)` };
|
|
17056
|
+
}
|
|
17057
|
+
if (name.includes("..")) {
|
|
17058
|
+
return { ok: false, error: 'Branch name cannot contain ".."' };
|
|
17059
|
+
}
|
|
17060
|
+
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
17061
|
+
return { ok: false, error: "Invalid branch name format (invalid slashes)" };
|
|
17062
|
+
}
|
|
17063
|
+
if (name.includes(" ")) {
|
|
17064
|
+
return { ok: false, error: "Branch name cannot contain spaces" };
|
|
17065
|
+
}
|
|
17066
|
+
if (name.startsWith("-")) {
|
|
17067
|
+
return { ok: false, error: 'Branch name cannot start with "-"' };
|
|
17068
|
+
}
|
|
17069
|
+
if (name.startsWith(".") || name.endsWith(".")) {
|
|
17070
|
+
return { ok: false, error: "Branch name cannot start or end with a dot" };
|
|
17071
|
+
}
|
|
17072
|
+
if (name.includes("@{")) {
|
|
17073
|
+
return { ok: false, error: 'Branch name cannot contain "@{"' };
|
|
17074
|
+
}
|
|
17075
|
+
if (name === "HEAD" || name === "@") {
|
|
17076
|
+
return { ok: false, error: 'Branch name cannot be the reserved ref "HEAD" or "@"' };
|
|
17077
|
+
}
|
|
17078
|
+
if (/[~^:?*[\\\x00-\x1f\x7f]/.test(name)) {
|
|
17079
|
+
return { ok: false, error: "Branch name contains invalid characters" };
|
|
17080
|
+
}
|
|
17081
|
+
if (name.endsWith(".lock")) {
|
|
17082
|
+
return { ok: false, error: 'Branch name cannot end with ".lock"' };
|
|
17083
|
+
}
|
|
17084
|
+
return { ok: true, name };
|
|
17085
|
+
}
|
|
17086
|
+
function parseSlug2(slug) {
|
|
17087
|
+
if (!slug) {
|
|
17088
|
+
return {
|
|
17089
|
+
ok: false,
|
|
17090
|
+
error: "Slug is required"
|
|
17091
|
+
};
|
|
17092
|
+
}
|
|
17093
|
+
if (slug.length > 64) {
|
|
17094
|
+
return { ok: false, error: "Slug too long (max 64 characters)" };
|
|
17095
|
+
}
|
|
17096
|
+
if (slug.includes("/") || slug.includes("\\")) {
|
|
17097
|
+
return {
|
|
17098
|
+
ok: false,
|
|
17099
|
+
error: "Slug cannot contain path separators"
|
|
17100
|
+
};
|
|
17101
|
+
}
|
|
17102
|
+
if (slug === "." || slug === "..") {
|
|
17103
|
+
return {
|
|
17104
|
+
ok: false,
|
|
17105
|
+
error: "Slug cannot be a traversal sequence"
|
|
17106
|
+
};
|
|
17107
|
+
}
|
|
17108
|
+
const normalized = slug.toLowerCase();
|
|
17109
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(normalized)) {
|
|
17110
|
+
return {
|
|
17111
|
+
ok: false,
|
|
17112
|
+
error: "Slug must start with a letter or number and contain only lowercase letters, numbers, and hyphens"
|
|
17113
|
+
};
|
|
17114
|
+
}
|
|
17115
|
+
return { ok: true, slug: normalized };
|
|
17116
|
+
}
|
|
17117
|
+
var BASE58_PATTERN3, CONTENT_ID_PATTERN3, PHYSICAL_SEGMENT_PATTERN3, MAX_BRANCH_NAME_LENGTH3;
|
|
17118
|
+
var init_validation3 = __esm({
|
|
17119
|
+
"dist/paths/validation.js"() {
|
|
17120
|
+
"use strict";
|
|
17121
|
+
BASE58_PATTERN3 = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
17122
|
+
CONTENT_ID_PATTERN3 = new RegExp(`^${BASE58_PATTERN3}{12}$`);
|
|
17123
|
+
PHYSICAL_SEGMENT_PATTERN3 = new RegExp(`\\.${BASE58_PATTERN3}{12}(?:\\.[a-z]+)?$`);
|
|
17124
|
+
MAX_BRANCH_NAME_LENGTH3 = 250;
|
|
17125
|
+
}
|
|
17126
|
+
});
|
|
17127
|
+
|
|
16242
17128
|
// dist/operating-mode/deployment-name.js
|
|
16243
17129
|
function resolveDeploymentName3(config, modeDefault) {
|
|
16244
17130
|
const envValue = process.env.CANOPYCMS_DEPLOYMENT_NAME?.trim();
|
|
16245
17131
|
const configValue = config.deploymentName;
|
|
16246
|
-
if (envValue && configValue && envValue !== configValue && !
|
|
17132
|
+
if (envValue && configValue && envValue !== configValue && !warned6) {
|
|
16247
17133
|
canopyLogWarn3(`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.`);
|
|
16248
|
-
|
|
17134
|
+
warned6 = true;
|
|
16249
17135
|
}
|
|
16250
17136
|
const resolved = envValue || configValue || modeDefault;
|
|
16251
17137
|
if (!isValidDeploymentName3(resolved)) {
|
|
@@ -16254,17 +17140,91 @@ function resolveDeploymentName3(config, modeDefault) {
|
|
|
16254
17140
|
}
|
|
16255
17141
|
return resolved;
|
|
16256
17142
|
}
|
|
16257
|
-
var
|
|
17143
|
+
var warned6, VALID_DEPLOYMENT_NAME3, isValidDeploymentName3;
|
|
16258
17144
|
var init_deployment_name2 = __esm({
|
|
16259
17145
|
"dist/operating-mode/deployment-name.js"() {
|
|
16260
17146
|
"use strict";
|
|
16261
17147
|
init_logger3();
|
|
16262
|
-
|
|
17148
|
+
warned6 = false;
|
|
16263
17149
|
VALID_DEPLOYMENT_NAME3 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
16264
17150
|
isValidDeploymentName3 = (name) => VALID_DEPLOYMENT_NAME3.test(name) && !name.includes("..") && !name.endsWith(".") && !name.endsWith(".lock");
|
|
16265
17151
|
}
|
|
16266
17152
|
});
|
|
16267
17153
|
|
|
17154
|
+
// dist/operating-mode/branch-namespace.js
|
|
17155
|
+
function warnOnce3(message) {
|
|
17156
|
+
if (warned7)
|
|
17157
|
+
return;
|
|
17158
|
+
warned7 = true;
|
|
17159
|
+
canopyLogWarn3(message);
|
|
17160
|
+
}
|
|
17161
|
+
function branchNamespaceProblem3(namespace) {
|
|
17162
|
+
if (!namespace.endsWith("/"))
|
|
17163
|
+
return 'it must end with "/"';
|
|
17164
|
+
const sample = `${namespace}x`;
|
|
17165
|
+
if (!/^[\x21-\x7e]+$/.test(namespace))
|
|
17166
|
+
return "it is not printable ASCII without spaces";
|
|
17167
|
+
if (/["%{}]/.test(namespace))
|
|
17168
|
+
return 'it contains ", %, { or }';
|
|
17169
|
+
if (/^(?:refs|heads|tags|remotes)\//i.test(namespace)) {
|
|
17170
|
+
return "it starts with 'refs/', 'heads/', 'tags/' or 'remotes/'";
|
|
17171
|
+
}
|
|
17172
|
+
const parsed = parseBranchRef3(sample);
|
|
17173
|
+
if (!parsed.ok)
|
|
17174
|
+
return `a branch in it would be refused (${parsed.error})`;
|
|
17175
|
+
if (sample.split("/").some((part) => part.startsWith(".") || part.endsWith(".lock"))) {
|
|
17176
|
+
return "a part of it starts with '.' or ends with '.lock'";
|
|
17177
|
+
}
|
|
17178
|
+
const directoryPrefix = sanitizeBranchName3(namespace);
|
|
17179
|
+
if (RESERVED_SETTINGS_BRANCH_PREFIX3.startsWith(directoryPrefix) || sanitizeBranchName3(sample).startsWith(RESERVED_SETTINGS_BRANCH_PREFIX3)) {
|
|
17180
|
+
return `some branches in it would be ${RESERVED_SETTINGS_BRANCH_PREFIX3}* settings branches`;
|
|
17181
|
+
}
|
|
17182
|
+
return null;
|
|
17183
|
+
}
|
|
17184
|
+
function resolveBranchNamespaceTemplate3(template, deploymentName, source) {
|
|
17185
|
+
const unknown = template.replaceAll(DEPLOYMENT_PLACEHOLDER3, "").match(/\{[^}]*\}?/);
|
|
17186
|
+
if (unknown) {
|
|
17187
|
+
throw new Error(`CanopyCMS: invalid branch namespace ${JSON.stringify(template)} (from ${source}): ${JSON.stringify(unknown[0])} is not a placeholder; the only one is ${DEPLOYMENT_PLACEHOLDER3}`);
|
|
17188
|
+
}
|
|
17189
|
+
const namespace = template.replaceAll(DEPLOYMENT_PLACEHOLDER3, deploymentName);
|
|
17190
|
+
const problem = branchNamespaceProblem3(namespace);
|
|
17191
|
+
if (problem !== null) {
|
|
17192
|
+
throw new Error(`CanopyCMS: invalid branch namespace ${JSON.stringify(namespace)} (from ${source}): ${problem}`);
|
|
17193
|
+
}
|
|
17194
|
+
return namespace;
|
|
17195
|
+
}
|
|
17196
|
+
function resolveBranchNamespace3(config, modeDefault, env = process.env) {
|
|
17197
|
+
const fromEnv = env[BRANCH_NAMESPACE_ENV3]?.trim();
|
|
17198
|
+
const [template, source] = fromEnv ? [fromEnv, BRANCH_NAMESPACE_ENV3] : config.branchNamespace !== void 0 ? [config.branchNamespace, "config.branchNamespace"] : [DEFAULT_BRANCH_NAMESPACE_TEMPLATE3, "the default"];
|
|
17199
|
+
const deploymentName = resolveDeploymentName3({ deploymentName: config.deploymentName }, modeDefault);
|
|
17200
|
+
const namespace = resolveBranchNamespaceTemplate3(template, deploymentName, source);
|
|
17201
|
+
if (fromEnv && config.branchNamespace !== void 0) {
|
|
17202
|
+
let fromConfig = null;
|
|
17203
|
+
try {
|
|
17204
|
+
fromConfig = resolveBranchNamespaceTemplate3(config.branchNamespace, deploymentName, "config.branchNamespace");
|
|
17205
|
+
} catch {
|
|
17206
|
+
}
|
|
17207
|
+
if (fromConfig !== namespace) {
|
|
17208
|
+
warnOnce3(`CanopyCMS: ${BRANCH_NAMESPACE_ENV3} (${JSON.stringify(fromEnv)}) and config.branchNamespace (${JSON.stringify(config.branchNamespace)}) do not resolve to the same valid namespace; using the environment variable, which infrastructure stamps per stack.`);
|
|
17209
|
+
}
|
|
17210
|
+
}
|
|
17211
|
+
return namespace;
|
|
17212
|
+
}
|
|
17213
|
+
var warned7, DEPLOYMENT_PLACEHOLDER3, DEFAULT_BRANCH_NAMESPACE_TEMPLATE3, BRANCH_NAMESPACE_ENV3;
|
|
17214
|
+
var init_branch_namespace2 = __esm({
|
|
17215
|
+
"dist/operating-mode/branch-namespace.js"() {
|
|
17216
|
+
"use strict";
|
|
17217
|
+
init_branch_name2();
|
|
17218
|
+
init_validation3();
|
|
17219
|
+
init_logger3();
|
|
17220
|
+
init_deployment_name2();
|
|
17221
|
+
warned7 = false;
|
|
17222
|
+
DEPLOYMENT_PLACEHOLDER3 = "{deployment}";
|
|
17223
|
+
DEFAULT_BRANCH_NAMESPACE_TEMPLATE3 = `canopycms/${DEPLOYMENT_PLACEHOLDER3}/`;
|
|
17224
|
+
BRANCH_NAMESPACE_ENV3 = "CANOPYCMS_BRANCH_NAMESPACE";
|
|
17225
|
+
}
|
|
17226
|
+
});
|
|
17227
|
+
|
|
16268
17228
|
// dist/config/schemas/config.js
|
|
16269
17229
|
import { z as z14 } from "zod";
|
|
16270
17230
|
var defaultBranchAccessSchema3, defaultPathAccessLevelSchema3, defaultPathAccessLevelsSchema3, defaultPathAccessSchema3, defaultBaseBranchSchema3, defaultRemoteNameSchema3, defaultRemoteUrlSchema3, gitBotAuthorNameSchema3, gitBotAuthorEmailSchema3, githubTokenEnvVarSchema3, operatingModeSchema3, deployedAsSchema3, contentRootSchema3, sourceRootSchema3, basePathSchema3, deploymentNameSchema3, devContentSyncSchema3, devConfigSchema3, editorConfigSchema3, seoFieldNameSchema3, seoSchema3, CanopyConfigSchema3, DEFAULT_PROD_WORKSPACE3;
|
|
@@ -16366,6 +17326,8 @@ var init_config4 = __esm({
|
|
|
16366
17326
|
// (operating-mode/deployment-name.ts) implements — config would always "win" over
|
|
16367
17327
|
// modeDefault, masking dev's real default of 'local'. Do not remove `.optional()`.
|
|
16368
17328
|
deploymentName: deploymentNameSchema3.optional(),
|
|
17329
|
+
// Checked where it is resolved (operating-mode/branch-namespace.ts), with the deployment name.
|
|
17330
|
+
branchNamespace: z14.string().optional(),
|
|
16369
17331
|
contentRoot: contentRootSchema3.default("content"),
|
|
16370
17332
|
sourceRoot: sourceRootSchema3.optional(),
|
|
16371
17333
|
basePath: basePathSchema3,
|
|
@@ -16419,6 +17381,7 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
16419
17381
|
"dist/operating-mode/client-unsafe-strategy.js"() {
|
|
16420
17382
|
"use strict";
|
|
16421
17383
|
init_client_safe_strategy2();
|
|
17384
|
+
init_branch_namespace2();
|
|
16422
17385
|
init_deployment_name2();
|
|
16423
17386
|
init_config5();
|
|
16424
17387
|
ProdStrategy3 = class extends ProdClientSafeStrategy3 {
|
|
@@ -16459,6 +17422,9 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
16459
17422
|
return config.settingsBranch;
|
|
16460
17423
|
return `canopycms-settings-${resolveDeploymentName3(config, "prod")}`;
|
|
16461
17424
|
}
|
|
17425
|
+
getBranchNamespace(config) {
|
|
17426
|
+
return resolveBranchNamespace3(config, "prod");
|
|
17427
|
+
}
|
|
16462
17428
|
getSettingsRoot(sourceRoot) {
|
|
16463
17429
|
return path30.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
16464
17430
|
}
|
|
@@ -16508,6 +17474,9 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
16508
17474
|
return config.settingsBranch;
|
|
16509
17475
|
return `canopycms-settings-${resolveDeploymentName3(config, "local")}`;
|
|
16510
17476
|
}
|
|
17477
|
+
getBranchNamespace(config) {
|
|
17478
|
+
return resolveBranchNamespace3(config, "local");
|
|
17479
|
+
}
|
|
16511
17480
|
getSettingsRoot(sourceRoot) {
|
|
16512
17481
|
return path30.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
16513
17482
|
}
|
|
@@ -16951,51 +17920,6 @@ var init_content_index_registry = __esm({
|
|
|
16951
17920
|
}
|
|
16952
17921
|
});
|
|
16953
17922
|
|
|
16954
|
-
// dist/paths/validation.js
|
|
16955
|
-
function isValidContentId2(id) {
|
|
16956
|
-
return CONTENT_ID_PATTERN2.test(id);
|
|
16957
|
-
}
|
|
16958
|
-
function parseSlug2(slug) {
|
|
16959
|
-
if (!slug) {
|
|
16960
|
-
return {
|
|
16961
|
-
ok: false,
|
|
16962
|
-
error: "Slug is required"
|
|
16963
|
-
};
|
|
16964
|
-
}
|
|
16965
|
-
if (slug.length > 64) {
|
|
16966
|
-
return { ok: false, error: "Slug too long (max 64 characters)" };
|
|
16967
|
-
}
|
|
16968
|
-
if (slug.includes("/") || slug.includes("\\")) {
|
|
16969
|
-
return {
|
|
16970
|
-
ok: false,
|
|
16971
|
-
error: "Slug cannot contain path separators"
|
|
16972
|
-
};
|
|
16973
|
-
}
|
|
16974
|
-
if (slug === "." || slug === "..") {
|
|
16975
|
-
return {
|
|
16976
|
-
ok: false,
|
|
16977
|
-
error: "Slug cannot be a traversal sequence"
|
|
16978
|
-
};
|
|
16979
|
-
}
|
|
16980
|
-
const normalized = slug.toLowerCase();
|
|
16981
|
-
if (!/^[a-z0-9][a-z0-9-]*$/.test(normalized)) {
|
|
16982
|
-
return {
|
|
16983
|
-
ok: false,
|
|
16984
|
-
error: "Slug must start with a letter or number and contain only lowercase letters, numbers, and hyphens"
|
|
16985
|
-
};
|
|
16986
|
-
}
|
|
16987
|
-
return { ok: true, slug: normalized };
|
|
16988
|
-
}
|
|
16989
|
-
var BASE58_PATTERN2, CONTENT_ID_PATTERN2, PHYSICAL_SEGMENT_PATTERN2;
|
|
16990
|
-
var init_validation2 = __esm({
|
|
16991
|
-
"dist/paths/validation.js"() {
|
|
16992
|
-
"use strict";
|
|
16993
|
-
BASE58_PATTERN2 = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
16994
|
-
CONTENT_ID_PATTERN2 = new RegExp(`^${BASE58_PATTERN2}{12}$`);
|
|
16995
|
-
PHYSICAL_SEGMENT_PATTERN2 = new RegExp(`\\.${BASE58_PATTERN2}{12}(?:\\.[a-z]+)?$`);
|
|
16996
|
-
}
|
|
16997
|
-
});
|
|
16998
|
-
|
|
16999
17923
|
// dist/id.js
|
|
17000
17924
|
import { generate as generate2 } from "short-uuid";
|
|
17001
17925
|
function generateId2() {
|
|
@@ -17006,7 +17930,7 @@ var isValidId2;
|
|
|
17006
17930
|
var init_id = __esm({
|
|
17007
17931
|
"dist/id.js"() {
|
|
17008
17932
|
"use strict";
|
|
17009
|
-
|
|
17933
|
+
init_validation3();
|
|
17010
17934
|
isValidId2 = isValidContentId2;
|
|
17011
17935
|
}
|
|
17012
17936
|
});
|
|
@@ -17191,10 +18115,12 @@ var init_meta_loader = __esm({
|
|
|
17191
18115
|
});
|
|
17192
18116
|
|
|
17193
18117
|
// dist/branch-schema-cache.js
|
|
17194
|
-
|
|
18118
|
+
import { LRUCache as LRUCache2 } from "lru-cache";
|
|
18119
|
+
var SNAPSHOT_MEMO_MAX_BYTES2, SCHEMA_GENERATION_RESOURCE2;
|
|
17195
18120
|
var init_branch_schema_cache = __esm({
|
|
17196
18121
|
"dist/branch-schema-cache.js"() {
|
|
17197
18122
|
"use strict";
|
|
18123
|
+
SNAPSHOT_MEMO_MAX_BYTES2 = 64 * 1024 * 1024;
|
|
17198
18124
|
SCHEMA_GENERATION_RESOURCE2 = "schema";
|
|
17199
18125
|
}
|
|
17200
18126
|
});
|
|
@@ -17939,7 +18865,7 @@ var init_sync = __esm({
|
|
|
17939
18865
|
var init_paths = __esm({
|
|
17940
18866
|
"dist/paths/index.js"() {
|
|
17941
18867
|
"use strict";
|
|
17942
|
-
|
|
18868
|
+
init_validation3();
|
|
17943
18869
|
}
|
|
17944
18870
|
});
|
|
17945
18871
|
|
|
@@ -18282,7 +19208,13 @@ var init_branch_metadata_file = __esm({
|
|
|
18282
19208
|
historyRewrittenFrom: z18.string().optional(),
|
|
18283
19209
|
syncFailureReason: z18.string().optional(),
|
|
18284
19210
|
editors: z18.array(z18.string()).optional(),
|
|
18285
|
-
uncommittedEditors: z18.array(z18.string()).optional()
|
|
19211
|
+
uncommittedEditors: z18.array(z18.string()).optional(),
|
|
19212
|
+
// Dropped when malformed: it only decorates the PR body, so it never fails a read.
|
|
19213
|
+
review: z18.object({
|
|
19214
|
+
state: z18.enum(["approved", "changes-requested"]),
|
|
19215
|
+
by: z18.object({ userId: z18.string(), name: z18.string().optional() }),
|
|
19216
|
+
at: z18.string()
|
|
19217
|
+
}).optional().catch(void 0)
|
|
18286
19218
|
};
|
|
18287
19219
|
branchMetadataSchema2 = z18.object(branchMetadataShape2).passthrough();
|
|
18288
19220
|
branchMetadataFileSchema2 = z18.object({
|
|
@@ -18393,14 +19325,23 @@ var init_branch_registry = __esm({
|
|
|
18393
19325
|
* genuinely-absent branch costs at most one extra scan per window.
|
|
18394
19326
|
*/
|
|
18395
19327
|
async get(name) {
|
|
18396
|
-
|
|
18397
|
-
|
|
19328
|
+
return this.find((b) => b.branch.name === name);
|
|
19329
|
+
}
|
|
19330
|
+
/**
|
|
19331
|
+
* Returns the branch whose workspace directory is `key` (`sanitizeBranchName` of its ref),
|
|
19332
|
+
* whatever its name, with {@link get}'s backstop.
|
|
19333
|
+
*/
|
|
19334
|
+
async getByKey(key) {
|
|
19335
|
+
return this.find((b) => path45.basename(b.branchRoot) === key);
|
|
19336
|
+
}
|
|
19337
|
+
async find(matches) {
|
|
19338
|
+
const found = (await this.list()).find(matches);
|
|
18398
19339
|
if (found)
|
|
18399
19340
|
return found;
|
|
18400
19341
|
if (!this.shouldForceRefresh())
|
|
18401
19342
|
return void 0;
|
|
18402
19343
|
const refreshed = await this.regenerate();
|
|
18403
|
-
return refreshed.find(
|
|
19344
|
+
return refreshed.find(matches);
|
|
18404
19345
|
}
|
|
18405
19346
|
shouldForceRefresh() {
|
|
18406
19347
|
const now = Date.now();
|