canopycms 0.7.1 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +187 -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-workspace.js +22 -15
- package/dist/cli/cli.js +1060 -172
- package/dist/cli/generate-ai-content.js +732 -101
- 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.js +23 -16
- package/dist/context.js +8 -7
- 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/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 +40 -25
- 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
|
}
|
|
@@ -3315,13 +3433,17 @@ import fs13 from "node:fs/promises";
|
|
|
3315
3433
|
import path16 from "node:path";
|
|
3316
3434
|
import { randomUUID as randomUUID22 } from "node:crypto";
|
|
3317
3435
|
import lockfile2 from "proper-lockfile";
|
|
3318
|
-
import { createHash as
|
|
3436
|
+
import { createHash as createHash3, randomBytes } from "node:crypto";
|
|
3319
3437
|
import fs17 from "node:fs/promises";
|
|
3320
3438
|
import path21 from "node:path";
|
|
3439
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
3321
3440
|
import fs16 from "node:fs/promises";
|
|
3322
3441
|
import path20 from "node:path";
|
|
3323
3442
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
3324
3443
|
import { simpleGit as simpleGit22 } from "simple-git";
|
|
3444
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
3445
|
+
import { Readable as Readable2, Transform, Writable } from "node:stream";
|
|
3446
|
+
import { pipeline } from "node:stream/promises";
|
|
3325
3447
|
import fs18 from "node:fs/promises";
|
|
3326
3448
|
import path222 from "node:path";
|
|
3327
3449
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
@@ -3604,6 +3726,93 @@ function trimSlashes(path272) {
|
|
|
3604
3726
|
end--;
|
|
3605
3727
|
return path272.slice(start, end);
|
|
3606
3728
|
}
|
|
3729
|
+
function sanitizeBranchName2(branchName) {
|
|
3730
|
+
const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
3731
|
+
const squashed = replaced.replace(/-+/g, "-");
|
|
3732
|
+
const trimmedDots = squashed.replace(/^\.+/, "").replace(/(?<!\.)\.+$/, "");
|
|
3733
|
+
return trimmedDots || "branch";
|
|
3734
|
+
}
|
|
3735
|
+
function branchShortName(name, namespace) {
|
|
3736
|
+
if (namespace === null || !name.startsWith(namespace) || name.length === namespace.length) {
|
|
3737
|
+
return name;
|
|
3738
|
+
}
|
|
3739
|
+
const rest = name.slice(namespace.length);
|
|
3740
|
+
return rest.includes("/") ? name : rest;
|
|
3741
|
+
}
|
|
3742
|
+
function isSettingsBranchName(branchName, settingsBranchName) {
|
|
3743
|
+
const sanitized = sanitizeBranchName2(branchName);
|
|
3744
|
+
return sanitized.startsWith(RESERVED_SETTINGS_BRANCH_PREFIX2) || settingsBranchName !== void 0 && sanitized === sanitizeBranchName2(settingsBranchName);
|
|
3745
|
+
}
|
|
3746
|
+
function isValidContentId(id) {
|
|
3747
|
+
return CONTENT_ID_PATTERN2.test(id);
|
|
3748
|
+
}
|
|
3749
|
+
function parseBranchRef2(name) {
|
|
3750
|
+
if (!name || typeof name !== "string") {
|
|
3751
|
+
return { ok: false, error: "Branch name is required" };
|
|
3752
|
+
}
|
|
3753
|
+
if (name.length > MAX_BRANCH_NAME_LENGTH2) {
|
|
3754
|
+
return { ok: false, error: `Branch name too long (max ${MAX_BRANCH_NAME_LENGTH2} characters)` };
|
|
3755
|
+
}
|
|
3756
|
+
if (name.includes("..")) {
|
|
3757
|
+
return { ok: false, error: 'Branch name cannot contain ".."' };
|
|
3758
|
+
}
|
|
3759
|
+
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
3760
|
+
return { ok: false, error: "Invalid branch name format (invalid slashes)" };
|
|
3761
|
+
}
|
|
3762
|
+
if (name.includes(" ")) {
|
|
3763
|
+
return { ok: false, error: "Branch name cannot contain spaces" };
|
|
3764
|
+
}
|
|
3765
|
+
if (name.startsWith("-")) {
|
|
3766
|
+
return { ok: false, error: 'Branch name cannot start with "-"' };
|
|
3767
|
+
}
|
|
3768
|
+
if (name.startsWith(".") || name.endsWith(".")) {
|
|
3769
|
+
return { ok: false, error: "Branch name cannot start or end with a dot" };
|
|
3770
|
+
}
|
|
3771
|
+
if (name.includes("@{")) {
|
|
3772
|
+
return { ok: false, error: 'Branch name cannot contain "@{"' };
|
|
3773
|
+
}
|
|
3774
|
+
if (name === "HEAD" || name === "@") {
|
|
3775
|
+
return { ok: false, error: 'Branch name cannot be the reserved ref "HEAD" or "@"' };
|
|
3776
|
+
}
|
|
3777
|
+
if (/[~^:?*[\\\x00-\x1f\x7f]/.test(name)) {
|
|
3778
|
+
return { ok: false, error: "Branch name contains invalid characters" };
|
|
3779
|
+
}
|
|
3780
|
+
if (name.endsWith(".lock")) {
|
|
3781
|
+
return { ok: false, error: 'Branch name cannot end with ".lock"' };
|
|
3782
|
+
}
|
|
3783
|
+
return { ok: true, name };
|
|
3784
|
+
}
|
|
3785
|
+
function parseSlug(slug) {
|
|
3786
|
+
if (!slug) {
|
|
3787
|
+
return {
|
|
3788
|
+
ok: false,
|
|
3789
|
+
error: "Slug is required"
|
|
3790
|
+
};
|
|
3791
|
+
}
|
|
3792
|
+
if (slug.length > 64) {
|
|
3793
|
+
return { ok: false, error: "Slug too long (max 64 characters)" };
|
|
3794
|
+
}
|
|
3795
|
+
if (slug.includes("/") || slug.includes("\\")) {
|
|
3796
|
+
return {
|
|
3797
|
+
ok: false,
|
|
3798
|
+
error: "Slug cannot contain path separators"
|
|
3799
|
+
};
|
|
3800
|
+
}
|
|
3801
|
+
if (slug === "." || slug === "..") {
|
|
3802
|
+
return {
|
|
3803
|
+
ok: false,
|
|
3804
|
+
error: "Slug cannot be a traversal sequence"
|
|
3805
|
+
};
|
|
3806
|
+
}
|
|
3807
|
+
const normalized = slug.toLowerCase();
|
|
3808
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(normalized)) {
|
|
3809
|
+
return {
|
|
3810
|
+
ok: false,
|
|
3811
|
+
error: "Slug must start with a letter or number and contain only lowercase letters, numbers, and hyphens"
|
|
3812
|
+
};
|
|
3813
|
+
}
|
|
3814
|
+
return { ok: true, slug: normalized };
|
|
3815
|
+
}
|
|
3607
3816
|
function isComponentName2(name) {
|
|
3608
3817
|
return COMPONENT_NAME2.test(name) && !MDX_BINDINGS2.has(name);
|
|
3609
3818
|
}
|
|
@@ -3644,9 +3853,9 @@ function resolveOperatingMode(configMode) {
|
|
|
3644
3853
|
if (env.value !== "prod" && env.value !== "dev") {
|
|
3645
3854
|
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
3855
|
}
|
|
3647
|
-
if (env.value !== configMode && !
|
|
3856
|
+
if (env.value !== configMode && !warned22) {
|
|
3648
3857
|
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
|
-
|
|
3858
|
+
warned22 = true;
|
|
3650
3859
|
}
|
|
3651
3860
|
return env.value;
|
|
3652
3861
|
}
|
|
@@ -3687,6 +3896,64 @@ function resolveDeploymentName2(config, modeDefault) {
|
|
|
3687
3896
|
}
|
|
3688
3897
|
return resolved;
|
|
3689
3898
|
}
|
|
3899
|
+
function warnOnce2(message) {
|
|
3900
|
+
if (warned4)
|
|
3901
|
+
return;
|
|
3902
|
+
warned4 = true;
|
|
3903
|
+
canopyLogWarn2(message);
|
|
3904
|
+
}
|
|
3905
|
+
function branchNamespaceProblem2(namespace) {
|
|
3906
|
+
if (!namespace.endsWith("/"))
|
|
3907
|
+
return 'it must end with "/"';
|
|
3908
|
+
const sample = `${namespace}x`;
|
|
3909
|
+
if (!/^[\x21-\x7e]+$/.test(namespace))
|
|
3910
|
+
return "it is not printable ASCII without spaces";
|
|
3911
|
+
if (/["%{}]/.test(namespace))
|
|
3912
|
+
return 'it contains ", %, { or }';
|
|
3913
|
+
if (/^(?:refs|heads|tags|remotes)\//i.test(namespace)) {
|
|
3914
|
+
return "it starts with 'refs/', 'heads/', 'tags/' or 'remotes/'";
|
|
3915
|
+
}
|
|
3916
|
+
const parsed = parseBranchRef2(sample);
|
|
3917
|
+
if (!parsed.ok)
|
|
3918
|
+
return `a branch in it would be refused (${parsed.error})`;
|
|
3919
|
+
if (sample.split("/").some((part) => part.startsWith(".") || part.endsWith(".lock"))) {
|
|
3920
|
+
return "a part of it starts with '.' or ends with '.lock'";
|
|
3921
|
+
}
|
|
3922
|
+
const directoryPrefix = sanitizeBranchName2(namespace);
|
|
3923
|
+
if (RESERVED_SETTINGS_BRANCH_PREFIX2.startsWith(directoryPrefix) || sanitizeBranchName2(sample).startsWith(RESERVED_SETTINGS_BRANCH_PREFIX2)) {
|
|
3924
|
+
return `some branches in it would be ${RESERVED_SETTINGS_BRANCH_PREFIX2}* settings branches`;
|
|
3925
|
+
}
|
|
3926
|
+
return null;
|
|
3927
|
+
}
|
|
3928
|
+
function resolveBranchNamespaceTemplate2(template, deploymentName, source) {
|
|
3929
|
+
const unknown = template.replaceAll(DEPLOYMENT_PLACEHOLDER2, "").match(/\{[^}]*\}?/);
|
|
3930
|
+
if (unknown) {
|
|
3931
|
+
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}`);
|
|
3932
|
+
}
|
|
3933
|
+
const namespace = template.replaceAll(DEPLOYMENT_PLACEHOLDER2, deploymentName);
|
|
3934
|
+
const problem = branchNamespaceProblem2(namespace);
|
|
3935
|
+
if (problem !== null) {
|
|
3936
|
+
throw new Error(`CanopyCMS: invalid branch namespace ${JSON.stringify(namespace)} (from ${source}): ${problem}`);
|
|
3937
|
+
}
|
|
3938
|
+
return namespace;
|
|
3939
|
+
}
|
|
3940
|
+
function resolveBranchNamespace2(config, modeDefault, env = process.env) {
|
|
3941
|
+
const fromEnv = env[BRANCH_NAMESPACE_ENV2]?.trim();
|
|
3942
|
+
const [template, source] = fromEnv ? [fromEnv, BRANCH_NAMESPACE_ENV2] : config.branchNamespace !== void 0 ? [config.branchNamespace, "config.branchNamespace"] : [DEFAULT_BRANCH_NAMESPACE_TEMPLATE2, "the default"];
|
|
3943
|
+
const deploymentName = resolveDeploymentName2({ deploymentName: config.deploymentName }, modeDefault);
|
|
3944
|
+
const namespace = resolveBranchNamespaceTemplate2(template, deploymentName, source);
|
|
3945
|
+
if (fromEnv && config.branchNamespace !== void 0) {
|
|
3946
|
+
let fromConfig = null;
|
|
3947
|
+
try {
|
|
3948
|
+
fromConfig = resolveBranchNamespaceTemplate2(config.branchNamespace, deploymentName, "config.branchNamespace");
|
|
3949
|
+
} catch {
|
|
3950
|
+
}
|
|
3951
|
+
if (fromConfig !== namespace) {
|
|
3952
|
+
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.`);
|
|
3953
|
+
}
|
|
3954
|
+
}
|
|
3955
|
+
return namespace;
|
|
3956
|
+
}
|
|
3690
3957
|
function operatingStrategy2(mode) {
|
|
3691
3958
|
const cached = strategyCache2.get(mode);
|
|
3692
3959
|
if (cached)
|
|
@@ -3721,6 +3988,21 @@ function getTaskQueueDir2(config) {
|
|
|
3721
3988
|
}
|
|
3722
3989
|
}
|
|
3723
3990
|
}
|
|
3991
|
+
async function readWorkerStatus(taskDir) {
|
|
3992
|
+
let content;
|
|
3993
|
+
try {
|
|
3994
|
+
content = await fs15.readFile(path19.join(taskDir, WORKER_STATUS_FILE), "utf-8");
|
|
3995
|
+
} catch (err2) {
|
|
3996
|
+
if (isNotFoundError3(err2))
|
|
3997
|
+
return { workerStatus: null };
|
|
3998
|
+
return { workerStatus: null, statusReadError: getErrorMessage3(err2) };
|
|
3999
|
+
}
|
|
4000
|
+
try {
|
|
4001
|
+
return { workerStatus: JSON.parse(content) };
|
|
4002
|
+
} catch (err2) {
|
|
4003
|
+
return { workerStatus: null, statusReadError: getErrorMessage3(err2) };
|
|
4004
|
+
}
|
|
4005
|
+
}
|
|
3724
4006
|
async function readCarriedOverStatus(taskDir) {
|
|
3725
4007
|
let previous;
|
|
3726
4008
|
try {
|
|
@@ -4819,9 +5101,9 @@ function buildMissingReference(id) {
|
|
|
4819
5101
|
}
|
|
4820
5102
|
function warnDanglingReference(site) {
|
|
4821
5103
|
const key = `${site.root}\0${site.entry ?? ""}\0${site.path}\0${site.id}`;
|
|
4822
|
-
if (
|
|
5104
|
+
if (warned5.has(key) || warned5.size >= MAX_WARNED)
|
|
4823
5105
|
return;
|
|
4824
|
-
|
|
5106
|
+
warned5.add(key);
|
|
4825
5107
|
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
5108
|
}
|
|
4827
5109
|
function isGroupField(field) {
|
|
@@ -4884,40 +5166,6 @@ function computeEntryUrl(collection, slug, contentRoot) {
|
|
|
4884
5166
|
const path272 = segments.length > 0 ? `/${segments.join("/")}` : "/";
|
|
4885
5167
|
return path272.toLowerCase();
|
|
4886
5168
|
}
|
|
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
5169
|
function generateId() {
|
|
4922
5170
|
const full = generate();
|
|
4923
5171
|
return full.substring(0, 12);
|
|
@@ -5517,16 +5765,6 @@ function validateAndNormalizePath(root, target) {
|
|
|
5517
5765
|
normalizedPath: relativePath
|
|
5518
5766
|
};
|
|
5519
5767
|
}
|
|
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
5768
|
function resolveBranchPath(options) {
|
|
5531
5769
|
if (options.branchName.includes("..")) {
|
|
5532
5770
|
throw new BranchPathError("Branch name cannot contain traversal segments");
|
|
@@ -5534,7 +5772,7 @@ function resolveBranchPath(options) {
|
|
|
5534
5772
|
if (isSettingsBranchName(options.branchName)) {
|
|
5535
5773
|
throw new BranchPathError("Settings branches are not content branches");
|
|
5536
5774
|
}
|
|
5537
|
-
const safeBranch =
|
|
5775
|
+
const safeBranch = sanitizeBranchName2(options.branchName);
|
|
5538
5776
|
const strategy = operatingStrategy2(options.mode);
|
|
5539
5777
|
const baseRoot = resolveContentBranchesRoot(options.mode, options.basePathOverride);
|
|
5540
5778
|
const normalizedBase = path102.resolve(baseRoot);
|
|
@@ -7004,6 +7242,193 @@ function mergeBranchMetadata(existing, version, incoming, now = (/* @__PURE__ */
|
|
|
7004
7242
|
function buildInitialBranchMetadata(incoming, now) {
|
|
7005
7243
|
return mergeBranchMetadata(null, null, incoming, now);
|
|
7006
7244
|
}
|
|
7245
|
+
function cmsBranchNamespace(config) {
|
|
7246
|
+
try {
|
|
7247
|
+
return operatingStrategy2(config.mode).getBranchNamespace(config);
|
|
7248
|
+
} catch {
|
|
7249
|
+
return null;
|
|
7250
|
+
}
|
|
7251
|
+
}
|
|
7252
|
+
function namesBaseOrActive(param, config) {
|
|
7253
|
+
let base;
|
|
7254
|
+
try {
|
|
7255
|
+
base = baseBranchOf(config);
|
|
7256
|
+
} catch (err2) {
|
|
7257
|
+
if (err2 instanceof BaseBranchUnresolvedError)
|
|
7258
|
+
return false;
|
|
7259
|
+
throw err2;
|
|
7260
|
+
}
|
|
7261
|
+
return param === base || param === (config.defaultActiveBranch ?? base);
|
|
7262
|
+
}
|
|
7263
|
+
function shortNameOf(config, name) {
|
|
7264
|
+
const short = branchShortName(name, cmsBranchNamespace(config));
|
|
7265
|
+
return short !== name && namesBaseOrActive(short, config) ? name : short;
|
|
7266
|
+
}
|
|
7267
|
+
function spawnGit(args, options) {
|
|
7268
|
+
const child = spawn2("git", [...(options.config ?? []).flatMap((c) => ["-c", c]), ...args], {
|
|
7269
|
+
cwd: options.cwd,
|
|
7270
|
+
// An app's own ProcessEnv augmentation (Next.js requires NODE_ENV) says nothing about git's.
|
|
7271
|
+
env: options.env,
|
|
7272
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
7273
|
+
// Its own process group, so a stop reaches what git spawned too (bundle create's
|
|
7274
|
+
// pack-objects), which would otherwise outlive it holding the pipes open.
|
|
7275
|
+
detached: process.platform !== "win32"
|
|
7276
|
+
});
|
|
7277
|
+
const { stdout } = child;
|
|
7278
|
+
if (stdout === null)
|
|
7279
|
+
throw new Error("git was spawned without a stdout pipe");
|
|
7280
|
+
const command = args.find((arg) => !arg.startsWith("-")) ?? "git";
|
|
7281
|
+
let stderr = "";
|
|
7282
|
+
let stderrBytes = 0;
|
|
7283
|
+
let killedFor = null;
|
|
7284
|
+
let closed = false;
|
|
7285
|
+
let exitedCleanly = false;
|
|
7286
|
+
let inactivity;
|
|
7287
|
+
const idle = () => {
|
|
7288
|
+
if (stdout.readableLength > 0 && !stdout.destroyed) {
|
|
7289
|
+
inactivity = setTimeout(idle, options.inactivityTimeoutMs);
|
|
7290
|
+
return;
|
|
7291
|
+
}
|
|
7292
|
+
stop(`git ${command} wrote nothing for ${options.inactivityTimeoutMs}ms`);
|
|
7293
|
+
};
|
|
7294
|
+
const touch = () => {
|
|
7295
|
+
options.onActivity?.();
|
|
7296
|
+
clearTimeout(inactivity);
|
|
7297
|
+
if (exitedCleanly)
|
|
7298
|
+
return;
|
|
7299
|
+
inactivity = setTimeout(idle, options.inactivityTimeoutMs);
|
|
7300
|
+
};
|
|
7301
|
+
const stop = (reason) => {
|
|
7302
|
+
if (closed || killedFor !== null)
|
|
7303
|
+
return;
|
|
7304
|
+
if (child.exitCode === 0 || child.exitCode === null && child.signalCode === null) {
|
|
7305
|
+
killedFor = reason;
|
|
7306
|
+
}
|
|
7307
|
+
terminate(child);
|
|
7308
|
+
stdout.destroy();
|
|
7309
|
+
};
|
|
7310
|
+
child.on("exit", (code) => {
|
|
7311
|
+
if (code !== 0)
|
|
7312
|
+
return;
|
|
7313
|
+
exitedCleanly = true;
|
|
7314
|
+
clearTimeout(inactivity);
|
|
7315
|
+
clearTimeout(deadline);
|
|
7316
|
+
});
|
|
7317
|
+
child.stderr?.on("data", (chunk) => {
|
|
7318
|
+
touch();
|
|
7319
|
+
if (stderrBytes < STDERR_CAP_BYTES) {
|
|
7320
|
+
stderr += chunk.subarray(0, STDERR_CAP_BYTES - stderrBytes).toString("utf8");
|
|
7321
|
+
}
|
|
7322
|
+
stderrBytes += chunk.length;
|
|
7323
|
+
});
|
|
7324
|
+
stdout.on("data", touch);
|
|
7325
|
+
const deadline = options.deadlineMs === void 0 ? void 0 : setTimeout(() => stop(`git ${command} ran longer than ${options.deadlineMs}ms`), options.deadlineMs);
|
|
7326
|
+
const onAbort = () => stop(`git ${command} was aborted`);
|
|
7327
|
+
if (options.signal?.aborted)
|
|
7328
|
+
onAbort();
|
|
7329
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
7330
|
+
const input = feedStdin(child, options.stdin, touch, (err2) => stop(`git ${command}'s input failed: ${getErrorMessage3(err2)}`));
|
|
7331
|
+
input.catch(() => void 0);
|
|
7332
|
+
touch();
|
|
7333
|
+
const exited = new Promise((resolve2, reject) => {
|
|
7334
|
+
child.on("error", (err2) => {
|
|
7335
|
+
if (child.pid === void 0) {
|
|
7336
|
+
clearTimeout(inactivity);
|
|
7337
|
+
clearTimeout(deadline);
|
|
7338
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
7339
|
+
reject(new GitProcessError(`git ${command} could not run: ${err2.message}`, null, ""));
|
|
7340
|
+
}
|
|
7341
|
+
});
|
|
7342
|
+
child.on("close", (code, signal) => {
|
|
7343
|
+
closed = true;
|
|
7344
|
+
clearTimeout(inactivity);
|
|
7345
|
+
clearTimeout(deadline);
|
|
7346
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
7347
|
+
const written = stderrBytes > STDERR_CAP_BYTES ? `${stderr}
|
|
7348
|
+
[stderr truncated]` : stderr;
|
|
7349
|
+
if (killedFor === null && code === 0) {
|
|
7350
|
+
input.then(resolve2, (err2) => reject(new GitProcessError(`git ${command}'s input failed: ${getErrorMessage3(err2)}`, 0, written)));
|
|
7351
|
+
return;
|
|
7352
|
+
}
|
|
7353
|
+
const why = killedFor ?? (written.trim() || (code === null ? `git ${command} was killed by ${signal ?? "a signal"}` : `git ${command} exited with code ${code}`));
|
|
7354
|
+
reject(new GitProcessError(why, code, written));
|
|
7355
|
+
});
|
|
7356
|
+
});
|
|
7357
|
+
exited.catch(() => void 0);
|
|
7358
|
+
return { stdout, exited, kill: stop };
|
|
7359
|
+
}
|
|
7360
|
+
async function runGit(args, options) {
|
|
7361
|
+
const max = options.maxStdoutBytes ?? 64 * 1024 * 1024;
|
|
7362
|
+
const proc = spawnGit(args, options);
|
|
7363
|
+
const chunks = [];
|
|
7364
|
+
let total = 0;
|
|
7365
|
+
const collect = new Writable({
|
|
7366
|
+
write(chunk, _encoding, callback) {
|
|
7367
|
+
total += chunk.length;
|
|
7368
|
+
if (total > max) {
|
|
7369
|
+
proc.kill(`git printed more than ${max} bytes`);
|
|
7370
|
+
callback();
|
|
7371
|
+
return;
|
|
7372
|
+
}
|
|
7373
|
+
chunks.push(chunk);
|
|
7374
|
+
callback();
|
|
7375
|
+
}
|
|
7376
|
+
});
|
|
7377
|
+
try {
|
|
7378
|
+
await Promise.all([pipeline(proc.stdout, collect).catch(() => void 0), proc.exited]);
|
|
7379
|
+
} catch (err2) {
|
|
7380
|
+
if (options.stdoutInFailure && err2 instanceof GitProcessError && chunks.length > 0) {
|
|
7381
|
+
throw new GitProcessError(`${Buffer.concat(chunks).toString("utf8")}${err2.message}`, err2.exitCode, err2.stderr);
|
|
7382
|
+
}
|
|
7383
|
+
throw err2;
|
|
7384
|
+
}
|
|
7385
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
7386
|
+
}
|
|
7387
|
+
function terminate(child) {
|
|
7388
|
+
signalGroup(child, "SIGTERM");
|
|
7389
|
+
const timer = setTimeout(() => signalGroup(child, "SIGKILL"), KILL_GRACE_MS);
|
|
7390
|
+
timer.unref();
|
|
7391
|
+
child.once("close", () => clearTimeout(timer));
|
|
7392
|
+
}
|
|
7393
|
+
function signalGroup(child, signal) {
|
|
7394
|
+
if (child.pid !== void 0 && process.platform !== "win32") {
|
|
7395
|
+
try {
|
|
7396
|
+
process.kill(-child.pid, signal);
|
|
7397
|
+
return;
|
|
7398
|
+
} catch {
|
|
7399
|
+
}
|
|
7400
|
+
}
|
|
7401
|
+
child.kill(signal);
|
|
7402
|
+
}
|
|
7403
|
+
function feedStdin(child, source, touch, onSourceError) {
|
|
7404
|
+
const sink2 = child.stdin;
|
|
7405
|
+
if (sink2 === null)
|
|
7406
|
+
return Promise.resolve();
|
|
7407
|
+
sink2.on("error", () => void 0);
|
|
7408
|
+
if (source === void 0) {
|
|
7409
|
+
sink2.end();
|
|
7410
|
+
return Promise.resolve();
|
|
7411
|
+
}
|
|
7412
|
+
const readable = typeof source === "string" ? Readable2.from([Buffer.from(source)]) : source;
|
|
7413
|
+
const counted = new Transform({
|
|
7414
|
+
transform(chunk, _encoding, callback) {
|
|
7415
|
+
touch();
|
|
7416
|
+
callback(null, chunk);
|
|
7417
|
+
}
|
|
7418
|
+
});
|
|
7419
|
+
return new Promise((resolve2, reject) => {
|
|
7420
|
+
readable.once("error", (err2) => {
|
|
7421
|
+
onSourceError(err2);
|
|
7422
|
+
reject(err2);
|
|
7423
|
+
});
|
|
7424
|
+
sink2.once("close", () => {
|
|
7425
|
+
if (!readable.readableEnded)
|
|
7426
|
+
readable.destroy();
|
|
7427
|
+
resolve2();
|
|
7428
|
+
});
|
|
7429
|
+
readable.pipe(counted).pipe(sink2);
|
|
7430
|
+
});
|
|
7431
|
+
}
|
|
7007
7432
|
function gitChildEnv(overrides) {
|
|
7008
7433
|
const env = {};
|
|
7009
7434
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -7110,7 +7535,7 @@ function formatDirStamp(date) {
|
|
|
7110
7535
|
return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
7111
7536
|
}
|
|
7112
7537
|
function shortDirName(dirName) {
|
|
7113
|
-
const digest =
|
|
7538
|
+
const digest = createHash3("sha1").update(dirName).digest("hex").slice(0, 10);
|
|
7114
7539
|
return `${dirName.slice(0, 40)}-${digest}`;
|
|
7115
7540
|
}
|
|
7116
7541
|
function stampedName(prefix, dirName, now) {
|
|
@@ -7297,12 +7722,13 @@ async function stageBranchWorkspace(options) {
|
|
|
7297
7722
|
throw err2;
|
|
7298
7723
|
}
|
|
7299
7724
|
}
|
|
7300
|
-
async function lockBranchName(baseRoot, dirName) {
|
|
7725
|
+
async function lockBranchName(baseRoot, dirName, shortName = dirName) {
|
|
7301
7726
|
try {
|
|
7302
7727
|
return await acquireProvisioningLockWithin(baseRoot, branchProvisioningLockName(dirName), PUBLISH_LOCK_WAIT_MS);
|
|
7303
7728
|
} catch (err2) {
|
|
7304
|
-
if (isNodeError3(err2) && err2.code === "ELOCKED")
|
|
7305
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
7729
|
+
if (isNodeError3(err2) && err2.code === "ELOCKED") {
|
|
7730
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
7731
|
+
}
|
|
7306
7732
|
throw err2;
|
|
7307
7733
|
}
|
|
7308
7734
|
}
|
|
@@ -7315,9 +7741,9 @@ async function inspectBlocking(finalPath) {
|
|
|
7315
7741
|
return "residue";
|
|
7316
7742
|
}
|
|
7317
7743
|
async function publishStaging(options) {
|
|
7318
|
-
const { baseRoot, dirName, stagingPath } = options;
|
|
7744
|
+
const { baseRoot, dirName, stagingPath, shortName = dirName } = options;
|
|
7319
7745
|
const finalPath = path21.join(baseRoot, dirName);
|
|
7320
|
-
const release = await lockBranchName(baseRoot, dirName);
|
|
7746
|
+
const release = await lockBranchName(baseRoot, dirName, shortName);
|
|
7321
7747
|
try {
|
|
7322
7748
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
7323
7749
|
try {
|
|
@@ -7339,7 +7765,7 @@ async function publishStaging(options) {
|
|
|
7339
7765
|
if (blocking === "live")
|
|
7340
7766
|
return "exists";
|
|
7341
7767
|
if (blocking === "protected")
|
|
7342
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
7768
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
7343
7769
|
const result = await quarantineResidueAt(baseRoot, dirName, {
|
|
7344
7770
|
minQuietMs: CREATE_RESIDUE_MIN_QUIET_MS,
|
|
7345
7771
|
expectedRemoteUrl: options.expectedRemoteUrl
|
|
@@ -7354,9 +7780,9 @@ async function publishStaging(options) {
|
|
|
7354
7780
|
}
|
|
7355
7781
|
if (result.reason === "foreign")
|
|
7356
7782
|
throw new BranchDirOccupiedError(dirName);
|
|
7357
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
7783
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
7358
7784
|
}
|
|
7359
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
7785
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
7360
7786
|
} finally {
|
|
7361
7787
|
await release().catch((err2) => {
|
|
7362
7788
|
canopyLogWarn2(`[canopy] Failed to release the provisioning lock for ${dirName}:`, err2);
|
|
@@ -8006,7 +8432,7 @@ async function generateAIContentCLI(options) {
|
|
|
8006
8432
|
console.log(` Output: ${result.outputDir}
|
|
8007
8433
|
`);
|
|
8008
8434
|
}
|
|
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,
|
|
8435
|
+
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, 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
8436
|
var init_generate_ai_content = __esm({
|
|
8011
8437
|
"dist/cli/generate-ai-content.js"() {
|
|
8012
8438
|
"use strict";
|
|
@@ -8063,6 +8489,21 @@ var init_generate_ai_content = __esm({
|
|
|
8063
8489
|
"use strict";
|
|
8064
8490
|
}
|
|
8065
8491
|
});
|
|
8492
|
+
init_branch_name = __esm3({
|
|
8493
|
+
"dist/paths/branch-name.js"() {
|
|
8494
|
+
"use strict";
|
|
8495
|
+
RESERVED_SETTINGS_BRANCH_PREFIX2 = "canopycms-settings-";
|
|
8496
|
+
}
|
|
8497
|
+
});
|
|
8498
|
+
init_validation = __esm3({
|
|
8499
|
+
"dist/paths/validation.js"() {
|
|
8500
|
+
"use strict";
|
|
8501
|
+
BASE58_PATTERN2 = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
8502
|
+
CONTENT_ID_PATTERN2 = new RegExp(`^${BASE58_PATTERN2}{12}$`);
|
|
8503
|
+
PHYSICAL_SEGMENT_PATTERN2 = new RegExp(`\\.${BASE58_PATTERN2}{12}(?:\\.[a-z]+)?$`);
|
|
8504
|
+
MAX_BRANCH_NAME_LENGTH2 = 250;
|
|
8505
|
+
}
|
|
8506
|
+
});
|
|
8066
8507
|
init_types2 = __esm3({
|
|
8067
8508
|
"dist/paths/types.js"() {
|
|
8068
8509
|
"use strict";
|
|
@@ -8606,6 +9047,8 @@ var init_generate_ai_content = __esm({
|
|
|
8606
9047
|
// (operating-mode/deployment-name.ts) implements — config would always "win" over
|
|
8607
9048
|
// modeDefault, masking dev's real default of 'local'. Do not remove `.optional()`.
|
|
8608
9049
|
deploymentName: deploymentNameSchema2.optional(),
|
|
9050
|
+
// Checked where it is resolved (operating-mode/branch-namespace.ts), with the deployment name.
|
|
9051
|
+
branchNamespace: z6.string().optional(),
|
|
8609
9052
|
contentRoot: contentRootSchema2.default("content"),
|
|
8610
9053
|
sourceRoot: sourceRootSchema2.optional(),
|
|
8611
9054
|
basePath: basePathSchema2,
|
|
@@ -8629,10 +9072,10 @@ var init_generate_ai_content = __esm({
|
|
|
8629
9072
|
init_logger2();
|
|
8630
9073
|
SERVER_MODE_ENV_VAR = "CANOPY_MODE";
|
|
8631
9074
|
BROWSER_MODE_ENV_VAR = "NEXT_PUBLIC_CANOPY_MODE";
|
|
8632
|
-
|
|
9075
|
+
warned22 = false;
|
|
8633
9076
|
}
|
|
8634
9077
|
});
|
|
8635
|
-
|
|
9078
|
+
init_validation2 = __esm3({
|
|
8636
9079
|
"dist/config/validation.js"() {
|
|
8637
9080
|
"use strict";
|
|
8638
9081
|
forEachReferenceField = (fields, visit3) => {
|
|
@@ -8722,6 +9165,19 @@ var init_generate_ai_content = __esm({
|
|
|
8722
9165
|
isValidDeploymentName2 = (name) => VALID_DEPLOYMENT_NAME2.test(name) && !name.includes("..") && !name.endsWith(".") && !name.endsWith(".lock");
|
|
8723
9166
|
}
|
|
8724
9167
|
});
|
|
9168
|
+
init_branch_namespace = __esm3({
|
|
9169
|
+
"dist/operating-mode/branch-namespace.js"() {
|
|
9170
|
+
"use strict";
|
|
9171
|
+
init_branch_name();
|
|
9172
|
+
init_validation();
|
|
9173
|
+
init_logger2();
|
|
9174
|
+
init_deployment_name();
|
|
9175
|
+
warned4 = false;
|
|
9176
|
+
DEPLOYMENT_PLACEHOLDER2 = "{deployment}";
|
|
9177
|
+
DEFAULT_BRANCH_NAMESPACE_TEMPLATE2 = `canopycms/${DEPLOYMENT_PLACEHOLDER2}/`;
|
|
9178
|
+
BRANCH_NAMESPACE_ENV2 = "CANOPYCMS_BRANCH_NAMESPACE";
|
|
9179
|
+
}
|
|
9180
|
+
});
|
|
8725
9181
|
init_config22 = __esm3({
|
|
8726
9182
|
"dist/config/index.js"() {
|
|
8727
9183
|
"use strict";
|
|
@@ -8733,6 +9189,7 @@ var init_generate_ai_content = __esm({
|
|
|
8733
9189
|
"dist/operating-mode/client-unsafe-strategy.js"() {
|
|
8734
9190
|
"use strict";
|
|
8735
9191
|
init_client_safe_strategy();
|
|
9192
|
+
init_branch_namespace();
|
|
8736
9193
|
init_deployment_name();
|
|
8737
9194
|
init_config22();
|
|
8738
9195
|
ProdStrategy2 = class extends ProdClientSafeStrategy2 {
|
|
@@ -8773,6 +9230,9 @@ var init_generate_ai_content = __esm({
|
|
|
8773
9230
|
return config.settingsBranch;
|
|
8774
9231
|
return `canopycms-settings-${resolveDeploymentName2(config, "prod")}`;
|
|
8775
9232
|
}
|
|
9233
|
+
getBranchNamespace(config) {
|
|
9234
|
+
return resolveBranchNamespace2(config, "prod");
|
|
9235
|
+
}
|
|
8776
9236
|
getSettingsRoot(sourceRoot) {
|
|
8777
9237
|
return path92.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
8778
9238
|
}
|
|
@@ -8822,6 +9282,9 @@ var init_generate_ai_content = __esm({
|
|
|
8822
9282
|
return config.settingsBranch;
|
|
8823
9283
|
return `canopycms-settings-${resolveDeploymentName2(config, "local")}`;
|
|
8824
9284
|
}
|
|
9285
|
+
getBranchNamespace(config) {
|
|
9286
|
+
return resolveBranchNamespace2(config, "local");
|
|
9287
|
+
}
|
|
8825
9288
|
getSettingsRoot(sourceRoot) {
|
|
8826
9289
|
return path92.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
8827
9290
|
}
|
|
@@ -8868,6 +9331,7 @@ var init_generate_ai_content = __esm({
|
|
|
8868
9331
|
WORKER_STATUS_FILE: () => WORKER_STATUS_FILE,
|
|
8869
9332
|
readCarriedOverStatus: () => readCarriedOverStatus,
|
|
8870
9333
|
readWorkerStartupFailure: () => readWorkerStartupFailure,
|
|
9334
|
+
readWorkerStatus: () => readWorkerStatus,
|
|
8871
9335
|
readWorkerStatusStartedAt: () => readWorkerStatusStartedAt,
|
|
8872
9336
|
writeWorkerStatus: () => writeWorkerStatus
|
|
8873
9337
|
});
|
|
@@ -9937,7 +10401,7 @@ var init_generate_ai_content = __esm({
|
|
|
9937
10401
|
];
|
|
9938
10402
|
init_logger2();
|
|
9939
10403
|
MAX_WARNED = 1e3;
|
|
9940
|
-
|
|
10404
|
+
warned5 = /* @__PURE__ */ new Set();
|
|
9941
10405
|
init_logger2();
|
|
9942
10406
|
SchemaUnavailableError = class extends Error {
|
|
9943
10407
|
constructor(entryType, unavailable) {
|
|
@@ -9948,9 +10412,7 @@ var init_generate_ai_content = __esm({
|
|
|
9948
10412
|
}
|
|
9949
10413
|
};
|
|
9950
10414
|
init_normalize();
|
|
9951
|
-
|
|
9952
|
-
CONTENT_ID_PATTERN = new RegExp(`^${BASE58_PATTERN}{12}$`);
|
|
9953
|
-
PHYSICAL_SEGMENT_PATTERN = new RegExp(`\\.${BASE58_PATTERN}{12}(?:\\.[a-z]+)?$`);
|
|
10415
|
+
init_validation();
|
|
9954
10416
|
isValidId = isValidContentId;
|
|
9955
10417
|
init_error3();
|
|
9956
10418
|
init_logger2();
|
|
@@ -10311,7 +10773,7 @@ var init_generate_ai_content = __esm({
|
|
|
10311
10773
|
});
|
|
10312
10774
|
fingerprints = /* @__PURE__ */ new WeakMap();
|
|
10313
10775
|
init_flatten();
|
|
10314
|
-
|
|
10776
|
+
init_validation2();
|
|
10315
10777
|
editDistance = (a, b) => {
|
|
10316
10778
|
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
10317
10779
|
for (let i = 1; i <= a.length; i++) {
|
|
@@ -10576,13 +11038,15 @@ var init_generate_ai_content = __esm({
|
|
|
10576
11038
|
init_error3();
|
|
10577
11039
|
init_error3();
|
|
10578
11040
|
init_normalize();
|
|
11041
|
+
init_validation();
|
|
10579
11042
|
init_operating_mode();
|
|
10580
|
-
|
|
11043
|
+
init_branch_name();
|
|
10581
11044
|
BranchPathError = class extends Error {
|
|
10582
11045
|
};
|
|
10583
11046
|
resolveContentBranchesRoot = (mode, override) => {
|
|
10584
11047
|
return operatingStrategy2(mode).getContentBranchesRoot(override);
|
|
10585
11048
|
};
|
|
11049
|
+
init_branch_name();
|
|
10586
11050
|
ContentStoreError = class extends Error {
|
|
10587
11051
|
constructor(message, code) {
|
|
10588
11052
|
super(message);
|
|
@@ -11881,7 +12345,13 @@ var init_generate_ai_content = __esm({
|
|
|
11881
12345
|
historyRewrittenFrom: z72.string().optional(),
|
|
11882
12346
|
syncFailureReason: z72.string().optional(),
|
|
11883
12347
|
editors: z72.array(z72.string()).optional(),
|
|
11884
|
-
uncommittedEditors: z72.array(z72.string()).optional()
|
|
12348
|
+
uncommittedEditors: z72.array(z72.string()).optional(),
|
|
12349
|
+
// Dropped when malformed: it only decorates the PR body, so it never fails a read.
|
|
12350
|
+
review: z72.object({
|
|
12351
|
+
state: z72.enum(["approved", "changes-requested"]),
|
|
12352
|
+
by: z72.object({ userId: z72.string(), name: z72.string().optional() }),
|
|
12353
|
+
at: z72.string()
|
|
12354
|
+
}).optional().catch(void 0)
|
|
11885
12355
|
};
|
|
11886
12356
|
branchMetadataSchema = z72.object(branchMetadataShape).passthrough();
|
|
11887
12357
|
branchMetadataFileSchema = z72.object({
|
|
@@ -11978,14 +12448,23 @@ var init_generate_ai_content = __esm({
|
|
|
11978
12448
|
* genuinely-absent branch costs at most one extra scan per window.
|
|
11979
12449
|
*/
|
|
11980
12450
|
async get(name) {
|
|
11981
|
-
|
|
11982
|
-
|
|
12451
|
+
return this.find((b) => b.branch.name === name);
|
|
12452
|
+
}
|
|
12453
|
+
/**
|
|
12454
|
+
* Returns the branch whose workspace directory is `key` (`sanitizeBranchName` of its ref),
|
|
12455
|
+
* whatever its name, with {@link get}'s backstop.
|
|
12456
|
+
*/
|
|
12457
|
+
async getByKey(key) {
|
|
12458
|
+
return this.find((b) => path15.basename(b.branchRoot) === key);
|
|
12459
|
+
}
|
|
12460
|
+
async find(matches) {
|
|
12461
|
+
const found = (await this.list()).find(matches);
|
|
11983
12462
|
if (found)
|
|
11984
12463
|
return found;
|
|
11985
12464
|
if (!this.shouldForceRefresh())
|
|
11986
12465
|
return void 0;
|
|
11987
12466
|
const refreshed = await this.regenerate();
|
|
11988
|
-
return refreshed.find(
|
|
12467
|
+
return refreshed.find(matches);
|
|
11989
12468
|
}
|
|
11990
12469
|
shouldForceRefresh() {
|
|
11991
12470
|
const now = Date.now();
|
|
@@ -12286,13 +12765,33 @@ var init_generate_ai_content = __esm({
|
|
|
12286
12765
|
baseRoot
|
|
12287
12766
|
};
|
|
12288
12767
|
};
|
|
12768
|
+
init_operating_mode();
|
|
12769
|
+
init_branch_name();
|
|
12289
12770
|
init_error3();
|
|
12290
12771
|
init_logger2();
|
|
12772
|
+
init_error3();
|
|
12773
|
+
GitProcessError = class extends Error {
|
|
12774
|
+
constructor(message, exitCode, stderr) {
|
|
12775
|
+
super(message);
|
|
12776
|
+
this.exitCode = exitCode;
|
|
12777
|
+
this.stderr = stderr;
|
|
12778
|
+
this.name = "GitProcessError";
|
|
12779
|
+
}
|
|
12780
|
+
};
|
|
12781
|
+
STDERR_CAP_BYTES = 64 * 1024;
|
|
12782
|
+
KILL_GRACE_MS = 2e3;
|
|
12291
12783
|
log9 = createDebugLogger2({ prefix: "GitManager" });
|
|
12292
12784
|
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
12785
|
FORCE_C_LOCALE = { LC_ALL: "C", LANG: "C" };
|
|
12294
12786
|
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
12787
|
NO_AUTO_GC_CONFIG = ["gc.auto=0", "maintenance.auto=false"];
|
|
12788
|
+
HASH_INACTIVITY_TIMEOUT_MS = 6e4;
|
|
12789
|
+
MAX_HASH_RUNS = 50;
|
|
12790
|
+
LS_REMOTE_INACTIVITY_TIMEOUT_MS = 6e4;
|
|
12791
|
+
symlinkBlobSha = (target, likeSha) => {
|
|
12792
|
+
const body = Buffer.from(target, "utf8");
|
|
12793
|
+
return createHash2(likeSha.length === 64 ? "sha256" : "sha1").update(Buffer.concat([Buffer.from(`blob ${body.length}\0`), body])).digest("hex");
|
|
12794
|
+
};
|
|
12296
12795
|
REMOTE_GIT_CONFIG = [
|
|
12297
12796
|
["gc.auto", "0"],
|
|
12298
12797
|
["receive.autogc", "false"],
|
|
@@ -12309,6 +12808,12 @@ var init_generate_ai_content = __esm({
|
|
|
12309
12808
|
];
|
|
12310
12809
|
remoteInitLocks = /* @__PURE__ */ new Map();
|
|
12311
12810
|
GITHUB_TRACKING_REF_PREFIX = "refs/remotes/github/";
|
|
12811
|
+
BaseBranchMissingError = class extends Error {
|
|
12812
|
+
constructor(remote, baseBranch) {
|
|
12813
|
+
super(`CanopyCMS: remote '${remote}' has no branch '${baseBranch}'`);
|
|
12814
|
+
this.name = "BaseBranchMissingError";
|
|
12815
|
+
}
|
|
12816
|
+
};
|
|
12312
12817
|
GitConflictError = class extends Error {
|
|
12313
12818
|
constructor(conflictedFiles) {
|
|
12314
12819
|
super(`Git conflict in ${conflictedFiles.length} file(s): ${conflictedFiles.join(", ")}`);
|
|
@@ -12584,6 +13089,36 @@ var init_generate_ai_content = __esm({
|
|
|
12584
13089
|
}
|
|
12585
13090
|
return refs.includes(`refs/heads/${branch}`) || refs.includes(`${GITHUB_TRACKING_REF_PREFIX}${branch}`);
|
|
12586
13091
|
}
|
|
13092
|
+
/**
|
|
13093
|
+
* The branches inside `branch` (`<branch>/…`) that a bare mirror holds as local heads or in
|
|
13094
|
+
* GitHub's tracking namespace, which git cannot hold beside `branch` itself. `for-each-ref`
|
|
13095
|
+
* matches a pattern up to a slash, so the pattern `<branch>` lists them. Throws, as
|
|
13096
|
+
* {@link bareRemoteHasBranch} does, when the mirror is unreadable.
|
|
13097
|
+
*/
|
|
13098
|
+
static async bareRemoteBranchesInside(remotePath, branch) {
|
|
13099
|
+
let output;
|
|
13100
|
+
try {
|
|
13101
|
+
output = await simpleGit22().raw([
|
|
13102
|
+
"--git-dir",
|
|
13103
|
+
remotePath,
|
|
13104
|
+
"for-each-ref",
|
|
13105
|
+
"--format=%(refname)",
|
|
13106
|
+
"--end-of-options",
|
|
13107
|
+
`refs/heads/${branch}`,
|
|
13108
|
+
`${GITHUB_TRACKING_REF_PREFIX}${branch}`
|
|
13109
|
+
]);
|
|
13110
|
+
} catch (err2) {
|
|
13111
|
+
throw new Error(`Cannot inspect remote mirror at ${remotePath}: ${getErrorMessage3(err2)}`);
|
|
13112
|
+
}
|
|
13113
|
+
const inside = /* @__PURE__ */ new Set();
|
|
13114
|
+
for (const ref of output.split("\n").map((line) => line.trim())) {
|
|
13115
|
+
for (const prefix of ["refs/heads/", GITHUB_TRACKING_REF_PREFIX]) {
|
|
13116
|
+
if (ref.startsWith(`${prefix}${branch}/`))
|
|
13117
|
+
inside.add(ref.slice(prefix.length));
|
|
13118
|
+
}
|
|
13119
|
+
}
|
|
13120
|
+
return [...inside].sort();
|
|
13121
|
+
}
|
|
12587
13122
|
/**
|
|
12588
13123
|
* Delete `refs/heads/<branch>` from a bare local mirror, if present. A
|
|
12589
13124
|
* no-op (not an error) when the ref doesn't exist.
|
|
@@ -13282,6 +13817,199 @@ var init_generate_ai_content = __esm({
|
|
|
13282
13817
|
]);
|
|
13283
13818
|
return output.split("\0").filter((p5) => p5.length > 0);
|
|
13284
13819
|
}
|
|
13820
|
+
/**
|
|
13821
|
+
* Every file in `rev`'s tree below repo-relative directory `under`, with its size in bytes,
|
|
13822
|
+
* so a caller can refuse a file too large to read before reading it.
|
|
13823
|
+
*/
|
|
13824
|
+
async listFilesWithSizeAt(rev, under) {
|
|
13825
|
+
const dir = `${under.replace(/\/+$/, "")}/`;
|
|
13826
|
+
const output = await this.git.raw([
|
|
13827
|
+
"ls-tree",
|
|
13828
|
+
"-r",
|
|
13829
|
+
"-z",
|
|
13830
|
+
"-l",
|
|
13831
|
+
"--full-tree",
|
|
13832
|
+
"--end-of-options",
|
|
13833
|
+
rev,
|
|
13834
|
+
dir
|
|
13835
|
+
]);
|
|
13836
|
+
const files = [];
|
|
13837
|
+
for (const record of output.split("\0")) {
|
|
13838
|
+
const tab = record.indexOf(" ");
|
|
13839
|
+
if (tab === -1)
|
|
13840
|
+
continue;
|
|
13841
|
+
const [, type, , size] = record.slice(0, tab).split(/ +/);
|
|
13842
|
+
const filePath = record.slice(tab + 1);
|
|
13843
|
+
if (type === "blob" && filePath.startsWith(dir)) {
|
|
13844
|
+
files.push({ path: filePath, size: Number(size) });
|
|
13845
|
+
}
|
|
13846
|
+
}
|
|
13847
|
+
return files;
|
|
13848
|
+
}
|
|
13849
|
+
/**
|
|
13850
|
+
* The commit HEAD forked from the base branch at, or `undefined` when they share no history.
|
|
13851
|
+
* Takes the base tip from the remote by `ls-remote` rather than `<remote>/<base>`: the worker
|
|
13852
|
+
* fetches by path, which moves no remote-tracking ref, so that ref is left behind by every
|
|
13853
|
+
* rebase. A tip this clone lacks is fetched as objects only (`--refmap=`, `--no-tags`,
|
|
13854
|
+
* `--no-write-fetch-head`, git 2.29+): no ref and no FETCH_HEAD moves, so a concurrent fetch
|
|
13855
|
+
* pinning FETCH_HEAD is never repointed. Throws {@link BaseBranchMissingError} when the
|
|
13856
|
+
* remote has no base branch, and any other error when it can't be read.
|
|
13857
|
+
*/
|
|
13858
|
+
async mergeBaseWithBase() {
|
|
13859
|
+
const tip = await this.remoteBranchTip(this.baseBranch);
|
|
13860
|
+
if (!tip)
|
|
13861
|
+
throw new BaseBranchMissingError(this.remote, this.baseBranch);
|
|
13862
|
+
if (!await this.hasCommit(tip)) {
|
|
13863
|
+
await this.git.raw([
|
|
13864
|
+
"fetch",
|
|
13865
|
+
"--refmap=",
|
|
13866
|
+
"--no-tags",
|
|
13867
|
+
"--no-write-fetch-head",
|
|
13868
|
+
"--end-of-options",
|
|
13869
|
+
this.remote,
|
|
13870
|
+
`refs/heads/${this.baseBranch}`
|
|
13871
|
+
]);
|
|
13872
|
+
if (!await this.hasCommit(tip)) {
|
|
13873
|
+
throw new Error(`CanopyCMS: could not fetch ${this.baseBranch}'s tip ${tip}`);
|
|
13874
|
+
}
|
|
13875
|
+
}
|
|
13876
|
+
const forkPoint = (await this.git.raw(["merge-base", "--end-of-options", "HEAD", tip])).trim();
|
|
13877
|
+
return forkPoint || void 0;
|
|
13878
|
+
}
|
|
13879
|
+
/** Whether this clone holds commit `sha`. */
|
|
13880
|
+
async hasCommit(sha) {
|
|
13881
|
+
return (await this.git.raw(["rev-parse", "--verify", "--quiet", `${sha}^{commit}`])).trim() !== "";
|
|
13882
|
+
}
|
|
13883
|
+
/**
|
|
13884
|
+
* Repo-relative paths whose working-tree content differs from commit `rev`, with `T` (a type
|
|
13885
|
+
* change) reported as `M`. A sparse clone leaves a path outside its cone unchecked out, marked
|
|
13886
|
+
* skip-worktree, and git reads that path's index entry in place of the missing file: it is never
|
|
13887
|
+
* reported deleted, and differs from `rev` only as the index does.
|
|
13888
|
+
*
|
|
13889
|
+
* `diff-index`, not `git diff`: the porcelain refreshes the index and writes it back under
|
|
13890
|
+
* `index.lock`, which would fail a concurrent writer's own git step. `diff-index` writes
|
|
13891
|
+
* nothing, so it reports a file whose stat changed but whose content did not; those are
|
|
13892
|
+
* hashed here and dropped when they match `rev`.
|
|
13893
|
+
*/
|
|
13894
|
+
async diffNameStatusAgainst(rev) {
|
|
13895
|
+
const output = await this.git.raw([
|
|
13896
|
+
"diff-index",
|
|
13897
|
+
"--raw",
|
|
13898
|
+
"-z",
|
|
13899
|
+
"--no-renames",
|
|
13900
|
+
"--end-of-options",
|
|
13901
|
+
rev,
|
|
13902
|
+
"--"
|
|
13903
|
+
]);
|
|
13904
|
+
const tokens = output.split("\0");
|
|
13905
|
+
const changes = [];
|
|
13906
|
+
for (let i = 0; i + 1 < tokens.length; i += 2) {
|
|
13907
|
+
const [srcMode, dstMode, srcSha, dstSha, code] = tokens[i].slice(1).split(" ");
|
|
13908
|
+
const path272 = tokens[i + 1];
|
|
13909
|
+
const letter = code?.charAt(0);
|
|
13910
|
+
if (letter === "A" || letter === "D")
|
|
13911
|
+
changes.push({ status: letter, path: path272 });
|
|
13912
|
+
else if (letter === "M" || letter === "T") {
|
|
13913
|
+
const unhashed = /^0+$/.test(dstSha) && srcMode === dstMode ? srcSha : void 0;
|
|
13914
|
+
changes.push({ status: "M", path: path272, unhashed });
|
|
13915
|
+
}
|
|
13916
|
+
}
|
|
13917
|
+
const unchanged = await this.matchingBlobs(changes.flatMap((c) => c.unhashed ? [{ path: c.path, sha: c.unhashed }] : []));
|
|
13918
|
+
return changes.filter((c) => !unchanged.has(c.path)).map(({ status, path: path272 }) => ({ status, path: path272 }));
|
|
13919
|
+
}
|
|
13920
|
+
/**
|
|
13921
|
+
* The paths whose working-tree file hashes to `sha`, as git hashes it: a symlink's blob is its
|
|
13922
|
+
* target, a file's goes through `hash-object --stdin-paths`, so its attributes' filters apply.
|
|
13923
|
+
* Callers pass only paths whose mode is unchanged, since diff-index reports the working tree's
|
|
13924
|
+
* own mode. A path that is gone or can't be hashed matches nothing, so it stays reported as
|
|
13925
|
+
* changed. When git refuses a path (exit 128), having printed
|
|
13926
|
+
* the SHAs before it, the next run resumes past that path, for at most {@link MAX_HASH_RUNS}
|
|
13927
|
+
* runs. A run that ends any other way (killed, timed out, a signal) stops the hashing, and the
|
|
13928
|
+
* paths it didn't reach stay changed.
|
|
13929
|
+
*/
|
|
13930
|
+
async matchingBlobs(files) {
|
|
13931
|
+
const matching = /* @__PURE__ */ new Set();
|
|
13932
|
+
const regular = [];
|
|
13933
|
+
for (const file of files) {
|
|
13934
|
+
if (file.path.includes("\n"))
|
|
13935
|
+
continue;
|
|
13936
|
+
const stat = await fs16.lstat(path20.join(this.repoPath, file.path)).catch(() => void 0);
|
|
13937
|
+
if (!stat)
|
|
13938
|
+
continue;
|
|
13939
|
+
if (stat.isSymbolicLink()) {
|
|
13940
|
+
const target = await fs16.readlink(path20.join(this.repoPath, file.path)).catch(() => void 0);
|
|
13941
|
+
if (target !== void 0 && symlinkBlobSha(target, file.sha) === file.sha) {
|
|
13942
|
+
matching.add(file.path);
|
|
13943
|
+
}
|
|
13944
|
+
} else if (stat.isFile())
|
|
13945
|
+
regular.push(file);
|
|
13946
|
+
}
|
|
13947
|
+
for (let start = 0, runs = 0; start < regular.length && runs < MAX_HASH_RUNS; runs++) {
|
|
13948
|
+
const batch = regular.slice(start);
|
|
13949
|
+
const { shas, outcome } = await this.hashPaths(batch.map((f) => f.path));
|
|
13950
|
+
shas.forEach((sha, i) => {
|
|
13951
|
+
if (batch[i] && sha === batch[i].sha)
|
|
13952
|
+
matching.add(batch[i].path);
|
|
13953
|
+
});
|
|
13954
|
+
if (outcome !== "refused")
|
|
13955
|
+
break;
|
|
13956
|
+
start += shas.length + 1;
|
|
13957
|
+
}
|
|
13958
|
+
return matching;
|
|
13959
|
+
}
|
|
13960
|
+
/**
|
|
13961
|
+
* `hash-object --stdin-paths` over `paths`: one SHA per path, in order, up to where it ended.
|
|
13962
|
+
* `refused` is git's own exit 128 on a path it can't hash; `stopped` is any other ending.
|
|
13963
|
+
*/
|
|
13964
|
+
async hashPaths(paths) {
|
|
13965
|
+
const chunks = [];
|
|
13966
|
+
let proc;
|
|
13967
|
+
try {
|
|
13968
|
+
proc = spawnGit(["hash-object", "--stdin-paths"], {
|
|
13969
|
+
...this.processOptions(),
|
|
13970
|
+
stdin: `${paths.join("\n")}
|
|
13971
|
+
`,
|
|
13972
|
+
inactivityTimeoutMs: HASH_INACTIVITY_TIMEOUT_MS
|
|
13973
|
+
});
|
|
13974
|
+
} catch {
|
|
13975
|
+
return { shas: [], outcome: "stopped" };
|
|
13976
|
+
}
|
|
13977
|
+
const read = (async () => {
|
|
13978
|
+
for await (const chunk of proc.stdout)
|
|
13979
|
+
chunks.push(chunk);
|
|
13980
|
+
})().catch(() => {
|
|
13981
|
+
});
|
|
13982
|
+
const outcome = await proc.exited.then(() => "complete", (err2) => err2 instanceof GitProcessError && err2.exitCode === 128 ? "refused" : "stopped");
|
|
13983
|
+
await read;
|
|
13984
|
+
return { shas: Buffer.concat(chunks).toString("utf8").split("\n").filter(Boolean), outcome };
|
|
13985
|
+
}
|
|
13986
|
+
/** What a git run outside simple-git needs to run as `this.git` does. */
|
|
13987
|
+
processOptions() {
|
|
13988
|
+
return {
|
|
13989
|
+
cwd: this.repoPath,
|
|
13990
|
+
env: gitChildEnv({ GIT_CEILING_DIRECTORIES: path20.dirname(this.repoPath) }),
|
|
13991
|
+
config: NO_AUTO_GC_CONFIG
|
|
13992
|
+
};
|
|
13993
|
+
}
|
|
13994
|
+
/** Repo-relative paths of the working tree's untracked, non-ignored files. */
|
|
13995
|
+
async listUntrackedFiles() {
|
|
13996
|
+
const output = await this.git.raw(["ls-files", "--others", "--exclude-standard", "-z"]);
|
|
13997
|
+
return output.split("\0").filter((p5) => p5.length > 0);
|
|
13998
|
+
}
|
|
13999
|
+
/**
|
|
14000
|
+
* The text of repo-relative `filePath` at commit `rev`, or `undefined` when `rev` has no such
|
|
14001
|
+
* path. Any other failure, such as an unknown `rev`, throws.
|
|
14002
|
+
*/
|
|
14003
|
+
async showFile(rev, filePath) {
|
|
14004
|
+
try {
|
|
14005
|
+
return await this.git.raw(["show", "--end-of-options", `${rev}:${filePath}`]);
|
|
14006
|
+
} catch (err2) {
|
|
14007
|
+
if (/fatal: path '.*' (does not exist in|exists on disk, but not in) '/.test(getErrorMessage3(err2))) {
|
|
14008
|
+
return void 0;
|
|
14009
|
+
}
|
|
14010
|
+
throw err2;
|
|
14011
|
+
}
|
|
14012
|
+
}
|
|
13285
14013
|
async ensureAuthor(author) {
|
|
13286
14014
|
const config = await this.git.listConfig();
|
|
13287
14015
|
const isManaged = config.all["canopycms.managed"] === "true";
|
|
@@ -13452,7 +14180,10 @@ var init_generate_ai_content = __esm({
|
|
|
13452
14180
|
*/
|
|
13453
14181
|
async remoteBranchTip(branch) {
|
|
13454
14182
|
const ref = `refs/heads/${branch}`;
|
|
13455
|
-
const output = await
|
|
14183
|
+
const output = await runGit(["ls-remote", "--heads", "--end-of-options", this.remote, ref], {
|
|
14184
|
+
...this.processOptions(),
|
|
14185
|
+
inactivityTimeoutMs: LS_REMOTE_INACTIVITY_TIMEOUT_MS
|
|
14186
|
+
});
|
|
13456
14187
|
for (const line of output.split("\n")) {
|
|
13457
14188
|
const [sha, name] = line.trim().split(" ");
|
|
13458
14189
|
if (sha && name === ref)
|
|
@@ -13531,7 +14262,7 @@ var init_generate_ai_content = __esm({
|
|
|
13531
14262
|
DELETING_MAX_AGE_MS = 10 * 6e4;
|
|
13532
14263
|
SETTINGS_TRASH_MAX_AGE_MS = 30 * 24 * 60 * 6e4;
|
|
13533
14264
|
BranchProvisioningBusyError = class extends Error {
|
|
13534
|
-
constructor(dirName, message = `Branch '${
|
|
14265
|
+
constructor(dirName, shortName = dirName, message = `Branch '${shortName}' is still being set up. Try again in a minute.`) {
|
|
13535
14266
|
super(message);
|
|
13536
14267
|
this.dirName = dirName;
|
|
13537
14268
|
this.name = "BranchProvisioningBusyError";
|
|
@@ -13607,12 +14338,16 @@ var init_generate_ai_content = __esm({
|
|
|
13607
14338
|
if (isSettingsBranchName(options.branchName, operatingStrategy2(options.mode).getSettingsBranchName(this.config))) {
|
|
13608
14339
|
throw new BranchPathError("Settings branches are not content branches");
|
|
13609
14340
|
}
|
|
13610
|
-
const
|
|
14341
|
+
const named = parseBranchRef2(options.branchName);
|
|
14342
|
+
if (!named.ok)
|
|
14343
|
+
throw new BranchPathError(named.error);
|
|
14344
|
+
const { branchRoot, baseRoot } = resolveBranchPath(options);
|
|
13611
14345
|
const paths = {
|
|
13612
14346
|
branchRoot,
|
|
13613
14347
|
baseRoot,
|
|
13614
14348
|
dirName: path23.basename(branchRoot),
|
|
13615
|
-
|
|
14349
|
+
ref: options.branchName,
|
|
14350
|
+
shortName: shortNameOf({ ...this.config, mode: options.mode }, options.branchName)
|
|
13616
14351
|
};
|
|
13617
14352
|
const existing = await readBranchMetadataFile(branchRoot);
|
|
13618
14353
|
if (existing) {
|
|
@@ -13630,7 +14365,7 @@ var init_generate_ai_content = __esm({
|
|
|
13630
14365
|
}
|
|
13631
14366
|
}
|
|
13632
14367
|
async provisionUnshared(options, paths) {
|
|
13633
|
-
const { branchRoot, baseRoot, dirName } = paths;
|
|
14368
|
+
const { branchRoot, baseRoot, dirName, shortName } = paths;
|
|
13634
14369
|
await fs19.mkdir(baseRoot, { recursive: true });
|
|
13635
14370
|
const baseBranch = await GitManager.resolveWorkspaceBaseBranch({
|
|
13636
14371
|
...this.config,
|
|
@@ -13648,7 +14383,7 @@ var init_generate_ai_content = __esm({
|
|
|
13648
14383
|
});
|
|
13649
14384
|
const metadata = buildInitialBranchMetadata({
|
|
13650
14385
|
branch: {
|
|
13651
|
-
name: paths.
|
|
14386
|
+
name: paths.ref,
|
|
13652
14387
|
title: options.title,
|
|
13653
14388
|
description: options.description,
|
|
13654
14389
|
access: options.access,
|
|
@@ -13660,14 +14395,14 @@ var init_generate_ai_content = __esm({
|
|
|
13660
14395
|
if (state.kind === "live" || state.kind === "corrupt")
|
|
13661
14396
|
return this.existing(paths);
|
|
13662
14397
|
if (state.kind === "protected")
|
|
13663
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
14398
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
13664
14399
|
if (state.kind === "foreign") {
|
|
13665
14400
|
if (options.mode !== "dev")
|
|
13666
14401
|
throw new BranchDirOccupiedError(dirName);
|
|
13667
14402
|
return this.adoptInPlace(options, paths, baseBranch, metadata);
|
|
13668
14403
|
}
|
|
13669
14404
|
if (state.kind === "residue" && await this.recentlyActive(paths)) {
|
|
13670
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
14405
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
13671
14406
|
}
|
|
13672
14407
|
const sparseCone = sparseConeFor(this.config.contentRoot);
|
|
13673
14408
|
await recordSparseCone(baseRoot, sparseCone).catch((err2) => {
|
|
@@ -13692,11 +14427,11 @@ var init_generate_ai_content = __esm({
|
|
|
13692
14427
|
return outcome;
|
|
13693
14428
|
}
|
|
13694
14429
|
async stageAndPublish(paths, run) {
|
|
13695
|
-
const { branchRoot, baseRoot, dirName } = paths;
|
|
14430
|
+
const { branchRoot, baseRoot, dirName, shortName } = paths;
|
|
13696
14431
|
const staged = await stageBranchWorkspace({
|
|
13697
14432
|
baseRoot,
|
|
13698
14433
|
dirName,
|
|
13699
|
-
branchName: paths.
|
|
14434
|
+
branchName: paths.ref,
|
|
13700
14435
|
baseBranch: run.baseBranch,
|
|
13701
14436
|
remoteUrl: run.remoteUrl,
|
|
13702
14437
|
clone: {
|
|
@@ -13716,6 +14451,7 @@ var init_generate_ai_content = __esm({
|
|
|
13716
14451
|
published = await run.provisionLog.step("publish", () => publishStaging({
|
|
13717
14452
|
baseRoot,
|
|
13718
14453
|
dirName,
|
|
14454
|
+
shortName,
|
|
13719
14455
|
stagingPath: staged.stagingPath,
|
|
13720
14456
|
writeId: staged.meta.writeId,
|
|
13721
14457
|
expectedRemoteUrl: run.remoteUrl,
|
|
@@ -13735,16 +14471,16 @@ var init_generate_ai_content = __esm({
|
|
|
13735
14471
|
* is, given branch.json under the branch's lock.
|
|
13736
14472
|
*/
|
|
13737
14473
|
async adoptInPlace(options, paths, baseBranch, metadata) {
|
|
13738
|
-
const { branchRoot, baseRoot, dirName } = paths;
|
|
14474
|
+
const { branchRoot, baseRoot, dirName, shortName } = paths;
|
|
13739
14475
|
const provisionLog = new ProvisionLog(dirName);
|
|
13740
14476
|
let created = false;
|
|
13741
14477
|
try {
|
|
13742
|
-
const release = await provisionLog.step("lock", () => lockBranchName(baseRoot, dirName));
|
|
14478
|
+
const release = await provisionLog.step("lock", () => lockBranchName(baseRoot, dirName, shortName));
|
|
13743
14479
|
try {
|
|
13744
14480
|
if (!await readBranchMetadataFile(branchRoot)) {
|
|
13745
14481
|
await GitManager.initializeWorkspace({
|
|
13746
14482
|
workspacePath: branchRoot,
|
|
13747
|
-
branchName: paths.
|
|
14483
|
+
branchName: paths.ref,
|
|
13748
14484
|
mode: options.mode,
|
|
13749
14485
|
baseBranch,
|
|
13750
14486
|
sourceRoot: this.config.sourceRoot,
|
|
@@ -13779,7 +14515,7 @@ var init_generate_ai_content = __esm({
|
|
|
13779
14515
|
return this.existing(paths);
|
|
13780
14516
|
const meta = await readBranchMetadataFile(branchRoot);
|
|
13781
14517
|
if (!meta)
|
|
13782
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
14518
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
13783
14519
|
return { kind: "created", context: { branch: meta.branch, branchRoot, baseRoot } };
|
|
13784
14520
|
}
|
|
13785
14521
|
/** Whether residue at the branch path may still belong to a live process. */
|
|
@@ -13789,10 +14525,10 @@ var init_generate_ai_content = __esm({
|
|
|
13789
14525
|
return true;
|
|
13790
14526
|
return await quietAgeMs(branchRoot) < CREATE_RESIDUE_MIN_QUIET_MS;
|
|
13791
14527
|
}
|
|
13792
|
-
async existing({ branchRoot, baseRoot, dirName }) {
|
|
14528
|
+
async existing({ branchRoot, baseRoot, dirName, shortName }) {
|
|
13793
14529
|
const meta = await readBranchMetadataFile(branchRoot);
|
|
13794
14530
|
if (!meta)
|
|
13795
|
-
throw new BranchProvisioningBusyError(dirName);
|
|
14531
|
+
throw new BranchProvisioningBusyError(dirName, shortName);
|
|
13796
14532
|
return { kind: "exists", context: { branch: meta.branch, branchRoot, baseRoot } };
|
|
13797
14533
|
}
|
|
13798
14534
|
};
|
|
@@ -13806,21 +14542,23 @@ var init_generate_ai_content = __esm({
|
|
|
13806
14542
|
}
|
|
13807
14543
|
return result.normalizedPath;
|
|
13808
14544
|
};
|
|
14545
|
+
parseEntryParts = (raw, format) => {
|
|
14546
|
+
if (format === "json")
|
|
14547
|
+
return { data: asRecord(JSON.parse(raw)) };
|
|
14548
|
+
if (format === "yaml")
|
|
14549
|
+
return { data: asRecord(yamlParse2(raw)) };
|
|
14550
|
+
const parsed = matter3(raw);
|
|
14551
|
+
return { data: { ...parsed.data ?? {} }, body: parsed.content };
|
|
14552
|
+
};
|
|
14553
|
+
parseEntryData = (raw, format, bodyFieldName = "body") => {
|
|
14554
|
+
const { data, body } = parseEntryParts(raw, format);
|
|
14555
|
+
if (body)
|
|
14556
|
+
data[bodyFieldName] = body;
|
|
14557
|
+
return data;
|
|
14558
|
+
};
|
|
13809
14559
|
readEntryData = async (filePath, format, bodyFieldName = "body") => {
|
|
13810
14560
|
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;
|
|
14561
|
+
return parseEntryData(await fs20.readFile(filePath, "utf8"), format, bodyFieldName);
|
|
13824
14562
|
} catch (err2) {
|
|
13825
14563
|
if (isNotFoundError3(err2))
|
|
13826
14564
|
return {};
|
|
@@ -14235,30 +14973,30 @@ function computeExtractRegion(crop, width, height) {
|
|
|
14235
14973
|
const extractHeight = Math.max(1, bottom - top);
|
|
14236
14974
|
return { left, top, width: extractWidth, height: extractHeight };
|
|
14237
14975
|
}
|
|
14238
|
-
function encode(
|
|
14976
|
+
function encode(pipeline2, format, quality) {
|
|
14239
14977
|
const options = quality !== void 0 ? { quality } : void 0;
|
|
14240
14978
|
switch (format) {
|
|
14241
14979
|
case "webp":
|
|
14242
|
-
return
|
|
14980
|
+
return pipeline2.webp(options);
|
|
14243
14981
|
case "jpeg":
|
|
14244
|
-
return
|
|
14982
|
+
return pipeline2.jpeg(options);
|
|
14245
14983
|
case "png":
|
|
14246
|
-
return
|
|
14984
|
+
return pipeline2.png(options);
|
|
14247
14985
|
}
|
|
14248
14986
|
}
|
|
14249
|
-
function encodeSourceFormat(
|
|
14987
|
+
function encodeSourceFormat(pipeline2, sourceExt, quality) {
|
|
14250
14988
|
const options = quality !== void 0 ? { quality } : void 0;
|
|
14251
14989
|
switch (sourceExt) {
|
|
14252
14990
|
case "gif":
|
|
14253
|
-
return
|
|
14991
|
+
return pipeline2.gif();
|
|
14254
14992
|
case "webp":
|
|
14255
|
-
return
|
|
14993
|
+
return pipeline2.webp(options);
|
|
14256
14994
|
case "jpg":
|
|
14257
14995
|
case "jpeg":
|
|
14258
|
-
return
|
|
14996
|
+
return pipeline2.jpeg(options);
|
|
14259
14997
|
case "png":
|
|
14260
14998
|
default:
|
|
14261
|
-
return
|
|
14999
|
+
return pipeline2.png(options);
|
|
14262
15000
|
}
|
|
14263
15001
|
}
|
|
14264
15002
|
async function applyTransform(input, directives) {
|
|
@@ -14278,22 +15016,22 @@ async function applyTransform(input, directives) {
|
|
|
14278
15016
|
const probeMeta = await sharp(input.data, { limitInputPixels: MAX_INPUT_PIXELS2 }).metadata();
|
|
14279
15017
|
const totalPages = probeMeta.pages ?? 1;
|
|
14280
15018
|
const pagesToRead = Math.min(totalPages, MAX_ANIMATED_FRAMES);
|
|
14281
|
-
let
|
|
15019
|
+
let pipeline2 = sharp(input.data, {
|
|
14282
15020
|
pages: pagesToRead,
|
|
14283
15021
|
limitInputPixels: MAX_INPUT_PIXELS2
|
|
14284
15022
|
}).rotate();
|
|
14285
15023
|
if (resize?.crop) {
|
|
14286
|
-
const oriented = orientedDimensions(await
|
|
15024
|
+
const oriented = orientedDimensions(await pipeline2.metadata());
|
|
14287
15025
|
if (!oriented) {
|
|
14288
15026
|
return { ok: false, status: 422, error: "Could not read image dimensions for crop" };
|
|
14289
15027
|
}
|
|
14290
|
-
|
|
15028
|
+
pipeline2 = pipeline2.extract(computeExtractRegion(resize.crop, oriented.width, oriented.height));
|
|
14291
15029
|
}
|
|
14292
15030
|
if (resize?.width) {
|
|
14293
|
-
|
|
15031
|
+
pipeline2 = pipeline2.resize({ width: resize.width, withoutEnlargement: true });
|
|
14294
15032
|
}
|
|
14295
|
-
|
|
14296
|
-
const data = await
|
|
15033
|
+
pipeline2 = format ? encode(pipeline2, format, quality) : encodeSourceFormat(pipeline2, sourceExt, quality);
|
|
15034
|
+
const data = await pipeline2.toBuffer();
|
|
14297
15035
|
if (!directives.identity && data.byteLength > MAX_OUTPUT_BYTES) {
|
|
14298
15036
|
return {
|
|
14299
15037
|
ok: false,
|
|
@@ -16239,13 +16977,110 @@ var init_client_safe_strategy2 = __esm({
|
|
|
16239
16977
|
}
|
|
16240
16978
|
});
|
|
16241
16979
|
|
|
16980
|
+
// dist/paths/branch-name.js
|
|
16981
|
+
function sanitizeBranchName3(branchName) {
|
|
16982
|
+
const replaced = branchName.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
16983
|
+
const squashed = replaced.replace(/-+/g, "-");
|
|
16984
|
+
const trimmedDots = squashed.replace(/^\.+/, "").replace(/(?<!\.)\.+$/, "");
|
|
16985
|
+
return trimmedDots || "branch";
|
|
16986
|
+
}
|
|
16987
|
+
var RESERVED_SETTINGS_BRANCH_PREFIX3;
|
|
16988
|
+
var init_branch_name2 = __esm({
|
|
16989
|
+
"dist/paths/branch-name.js"() {
|
|
16990
|
+
"use strict";
|
|
16991
|
+
RESERVED_SETTINGS_BRANCH_PREFIX3 = "canopycms-settings-";
|
|
16992
|
+
}
|
|
16993
|
+
});
|
|
16994
|
+
|
|
16995
|
+
// dist/paths/validation.js
|
|
16996
|
+
function isValidContentId2(id) {
|
|
16997
|
+
return CONTENT_ID_PATTERN3.test(id);
|
|
16998
|
+
}
|
|
16999
|
+
function parseBranchRef3(name) {
|
|
17000
|
+
if (!name || typeof name !== "string") {
|
|
17001
|
+
return { ok: false, error: "Branch name is required" };
|
|
17002
|
+
}
|
|
17003
|
+
if (name.length > MAX_BRANCH_NAME_LENGTH3) {
|
|
17004
|
+
return { ok: false, error: `Branch name too long (max ${MAX_BRANCH_NAME_LENGTH3} characters)` };
|
|
17005
|
+
}
|
|
17006
|
+
if (name.includes("..")) {
|
|
17007
|
+
return { ok: false, error: 'Branch name cannot contain ".."' };
|
|
17008
|
+
}
|
|
17009
|
+
if (name.startsWith("/") || name.endsWith("/") || name.includes("//")) {
|
|
17010
|
+
return { ok: false, error: "Invalid branch name format (invalid slashes)" };
|
|
17011
|
+
}
|
|
17012
|
+
if (name.includes(" ")) {
|
|
17013
|
+
return { ok: false, error: "Branch name cannot contain spaces" };
|
|
17014
|
+
}
|
|
17015
|
+
if (name.startsWith("-")) {
|
|
17016
|
+
return { ok: false, error: 'Branch name cannot start with "-"' };
|
|
17017
|
+
}
|
|
17018
|
+
if (name.startsWith(".") || name.endsWith(".")) {
|
|
17019
|
+
return { ok: false, error: "Branch name cannot start or end with a dot" };
|
|
17020
|
+
}
|
|
17021
|
+
if (name.includes("@{")) {
|
|
17022
|
+
return { ok: false, error: 'Branch name cannot contain "@{"' };
|
|
17023
|
+
}
|
|
17024
|
+
if (name === "HEAD" || name === "@") {
|
|
17025
|
+
return { ok: false, error: 'Branch name cannot be the reserved ref "HEAD" or "@"' };
|
|
17026
|
+
}
|
|
17027
|
+
if (/[~^:?*[\\\x00-\x1f\x7f]/.test(name)) {
|
|
17028
|
+
return { ok: false, error: "Branch name contains invalid characters" };
|
|
17029
|
+
}
|
|
17030
|
+
if (name.endsWith(".lock")) {
|
|
17031
|
+
return { ok: false, error: 'Branch name cannot end with ".lock"' };
|
|
17032
|
+
}
|
|
17033
|
+
return { ok: true, name };
|
|
17034
|
+
}
|
|
17035
|
+
function parseSlug2(slug) {
|
|
17036
|
+
if (!slug) {
|
|
17037
|
+
return {
|
|
17038
|
+
ok: false,
|
|
17039
|
+
error: "Slug is required"
|
|
17040
|
+
};
|
|
17041
|
+
}
|
|
17042
|
+
if (slug.length > 64) {
|
|
17043
|
+
return { ok: false, error: "Slug too long (max 64 characters)" };
|
|
17044
|
+
}
|
|
17045
|
+
if (slug.includes("/") || slug.includes("\\")) {
|
|
17046
|
+
return {
|
|
17047
|
+
ok: false,
|
|
17048
|
+
error: "Slug cannot contain path separators"
|
|
17049
|
+
};
|
|
17050
|
+
}
|
|
17051
|
+
if (slug === "." || slug === "..") {
|
|
17052
|
+
return {
|
|
17053
|
+
ok: false,
|
|
17054
|
+
error: "Slug cannot be a traversal sequence"
|
|
17055
|
+
};
|
|
17056
|
+
}
|
|
17057
|
+
const normalized = slug.toLowerCase();
|
|
17058
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(normalized)) {
|
|
17059
|
+
return {
|
|
17060
|
+
ok: false,
|
|
17061
|
+
error: "Slug must start with a letter or number and contain only lowercase letters, numbers, and hyphens"
|
|
17062
|
+
};
|
|
17063
|
+
}
|
|
17064
|
+
return { ok: true, slug: normalized };
|
|
17065
|
+
}
|
|
17066
|
+
var BASE58_PATTERN3, CONTENT_ID_PATTERN3, PHYSICAL_SEGMENT_PATTERN3, MAX_BRANCH_NAME_LENGTH3;
|
|
17067
|
+
var init_validation3 = __esm({
|
|
17068
|
+
"dist/paths/validation.js"() {
|
|
17069
|
+
"use strict";
|
|
17070
|
+
BASE58_PATTERN3 = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
17071
|
+
CONTENT_ID_PATTERN3 = new RegExp(`^${BASE58_PATTERN3}{12}$`);
|
|
17072
|
+
PHYSICAL_SEGMENT_PATTERN3 = new RegExp(`\\.${BASE58_PATTERN3}{12}(?:\\.[a-z]+)?$`);
|
|
17073
|
+
MAX_BRANCH_NAME_LENGTH3 = 250;
|
|
17074
|
+
}
|
|
17075
|
+
});
|
|
17076
|
+
|
|
16242
17077
|
// dist/operating-mode/deployment-name.js
|
|
16243
17078
|
function resolveDeploymentName3(config, modeDefault) {
|
|
16244
17079
|
const envValue = process.env.CANOPYCMS_DEPLOYMENT_NAME?.trim();
|
|
16245
17080
|
const configValue = config.deploymentName;
|
|
16246
|
-
if (envValue && configValue && envValue !== configValue && !
|
|
17081
|
+
if (envValue && configValue && envValue !== configValue && !warned6) {
|
|
16247
17082
|
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
|
-
|
|
17083
|
+
warned6 = true;
|
|
16249
17084
|
}
|
|
16250
17085
|
const resolved = envValue || configValue || modeDefault;
|
|
16251
17086
|
if (!isValidDeploymentName3(resolved)) {
|
|
@@ -16254,17 +17089,91 @@ function resolveDeploymentName3(config, modeDefault) {
|
|
|
16254
17089
|
}
|
|
16255
17090
|
return resolved;
|
|
16256
17091
|
}
|
|
16257
|
-
var
|
|
17092
|
+
var warned6, VALID_DEPLOYMENT_NAME3, isValidDeploymentName3;
|
|
16258
17093
|
var init_deployment_name2 = __esm({
|
|
16259
17094
|
"dist/operating-mode/deployment-name.js"() {
|
|
16260
17095
|
"use strict";
|
|
16261
17096
|
init_logger3();
|
|
16262
|
-
|
|
17097
|
+
warned6 = false;
|
|
16263
17098
|
VALID_DEPLOYMENT_NAME3 = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
16264
17099
|
isValidDeploymentName3 = (name) => VALID_DEPLOYMENT_NAME3.test(name) && !name.includes("..") && !name.endsWith(".") && !name.endsWith(".lock");
|
|
16265
17100
|
}
|
|
16266
17101
|
});
|
|
16267
17102
|
|
|
17103
|
+
// dist/operating-mode/branch-namespace.js
|
|
17104
|
+
function warnOnce3(message) {
|
|
17105
|
+
if (warned7)
|
|
17106
|
+
return;
|
|
17107
|
+
warned7 = true;
|
|
17108
|
+
canopyLogWarn3(message);
|
|
17109
|
+
}
|
|
17110
|
+
function branchNamespaceProblem3(namespace) {
|
|
17111
|
+
if (!namespace.endsWith("/"))
|
|
17112
|
+
return 'it must end with "/"';
|
|
17113
|
+
const sample = `${namespace}x`;
|
|
17114
|
+
if (!/^[\x21-\x7e]+$/.test(namespace))
|
|
17115
|
+
return "it is not printable ASCII without spaces";
|
|
17116
|
+
if (/["%{}]/.test(namespace))
|
|
17117
|
+
return 'it contains ", %, { or }';
|
|
17118
|
+
if (/^(?:refs|heads|tags|remotes)\//i.test(namespace)) {
|
|
17119
|
+
return "it starts with 'refs/', 'heads/', 'tags/' or 'remotes/'";
|
|
17120
|
+
}
|
|
17121
|
+
const parsed = parseBranchRef3(sample);
|
|
17122
|
+
if (!parsed.ok)
|
|
17123
|
+
return `a branch in it would be refused (${parsed.error})`;
|
|
17124
|
+
if (sample.split("/").some((part) => part.startsWith(".") || part.endsWith(".lock"))) {
|
|
17125
|
+
return "a part of it starts with '.' or ends with '.lock'";
|
|
17126
|
+
}
|
|
17127
|
+
const directoryPrefix = sanitizeBranchName3(namespace);
|
|
17128
|
+
if (RESERVED_SETTINGS_BRANCH_PREFIX3.startsWith(directoryPrefix) || sanitizeBranchName3(sample).startsWith(RESERVED_SETTINGS_BRANCH_PREFIX3)) {
|
|
17129
|
+
return `some branches in it would be ${RESERVED_SETTINGS_BRANCH_PREFIX3}* settings branches`;
|
|
17130
|
+
}
|
|
17131
|
+
return null;
|
|
17132
|
+
}
|
|
17133
|
+
function resolveBranchNamespaceTemplate3(template, deploymentName, source) {
|
|
17134
|
+
const unknown = template.replaceAll(DEPLOYMENT_PLACEHOLDER3, "").match(/\{[^}]*\}?/);
|
|
17135
|
+
if (unknown) {
|
|
17136
|
+
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}`);
|
|
17137
|
+
}
|
|
17138
|
+
const namespace = template.replaceAll(DEPLOYMENT_PLACEHOLDER3, deploymentName);
|
|
17139
|
+
const problem = branchNamespaceProblem3(namespace);
|
|
17140
|
+
if (problem !== null) {
|
|
17141
|
+
throw new Error(`CanopyCMS: invalid branch namespace ${JSON.stringify(namespace)} (from ${source}): ${problem}`);
|
|
17142
|
+
}
|
|
17143
|
+
return namespace;
|
|
17144
|
+
}
|
|
17145
|
+
function resolveBranchNamespace3(config, modeDefault, env = process.env) {
|
|
17146
|
+
const fromEnv = env[BRANCH_NAMESPACE_ENV3]?.trim();
|
|
17147
|
+
const [template, source] = fromEnv ? [fromEnv, BRANCH_NAMESPACE_ENV3] : config.branchNamespace !== void 0 ? [config.branchNamespace, "config.branchNamespace"] : [DEFAULT_BRANCH_NAMESPACE_TEMPLATE3, "the default"];
|
|
17148
|
+
const deploymentName = resolveDeploymentName3({ deploymentName: config.deploymentName }, modeDefault);
|
|
17149
|
+
const namespace = resolveBranchNamespaceTemplate3(template, deploymentName, source);
|
|
17150
|
+
if (fromEnv && config.branchNamespace !== void 0) {
|
|
17151
|
+
let fromConfig = null;
|
|
17152
|
+
try {
|
|
17153
|
+
fromConfig = resolveBranchNamespaceTemplate3(config.branchNamespace, deploymentName, "config.branchNamespace");
|
|
17154
|
+
} catch {
|
|
17155
|
+
}
|
|
17156
|
+
if (fromConfig !== namespace) {
|
|
17157
|
+
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.`);
|
|
17158
|
+
}
|
|
17159
|
+
}
|
|
17160
|
+
return namespace;
|
|
17161
|
+
}
|
|
17162
|
+
var warned7, DEPLOYMENT_PLACEHOLDER3, DEFAULT_BRANCH_NAMESPACE_TEMPLATE3, BRANCH_NAMESPACE_ENV3;
|
|
17163
|
+
var init_branch_namespace2 = __esm({
|
|
17164
|
+
"dist/operating-mode/branch-namespace.js"() {
|
|
17165
|
+
"use strict";
|
|
17166
|
+
init_branch_name2();
|
|
17167
|
+
init_validation3();
|
|
17168
|
+
init_logger3();
|
|
17169
|
+
init_deployment_name2();
|
|
17170
|
+
warned7 = false;
|
|
17171
|
+
DEPLOYMENT_PLACEHOLDER3 = "{deployment}";
|
|
17172
|
+
DEFAULT_BRANCH_NAMESPACE_TEMPLATE3 = `canopycms/${DEPLOYMENT_PLACEHOLDER3}/`;
|
|
17173
|
+
BRANCH_NAMESPACE_ENV3 = "CANOPYCMS_BRANCH_NAMESPACE";
|
|
17174
|
+
}
|
|
17175
|
+
});
|
|
17176
|
+
|
|
16268
17177
|
// dist/config/schemas/config.js
|
|
16269
17178
|
import { z as z14 } from "zod";
|
|
16270
17179
|
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 +17275,8 @@ var init_config4 = __esm({
|
|
|
16366
17275
|
// (operating-mode/deployment-name.ts) implements — config would always "win" over
|
|
16367
17276
|
// modeDefault, masking dev's real default of 'local'. Do not remove `.optional()`.
|
|
16368
17277
|
deploymentName: deploymentNameSchema3.optional(),
|
|
17278
|
+
// Checked where it is resolved (operating-mode/branch-namespace.ts), with the deployment name.
|
|
17279
|
+
branchNamespace: z14.string().optional(),
|
|
16369
17280
|
contentRoot: contentRootSchema3.default("content"),
|
|
16370
17281
|
sourceRoot: sourceRootSchema3.optional(),
|
|
16371
17282
|
basePath: basePathSchema3,
|
|
@@ -16419,6 +17330,7 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
16419
17330
|
"dist/operating-mode/client-unsafe-strategy.js"() {
|
|
16420
17331
|
"use strict";
|
|
16421
17332
|
init_client_safe_strategy2();
|
|
17333
|
+
init_branch_namespace2();
|
|
16422
17334
|
init_deployment_name2();
|
|
16423
17335
|
init_config5();
|
|
16424
17336
|
ProdStrategy3 = class extends ProdClientSafeStrategy3 {
|
|
@@ -16459,6 +17371,9 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
16459
17371
|
return config.settingsBranch;
|
|
16460
17372
|
return `canopycms-settings-${resolveDeploymentName3(config, "prod")}`;
|
|
16461
17373
|
}
|
|
17374
|
+
getBranchNamespace(config) {
|
|
17375
|
+
return resolveBranchNamespace3(config, "prod");
|
|
17376
|
+
}
|
|
16462
17377
|
getSettingsRoot(sourceRoot) {
|
|
16463
17378
|
return path30.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
16464
17379
|
}
|
|
@@ -16508,6 +17423,9 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
16508
17423
|
return config.settingsBranch;
|
|
16509
17424
|
return `canopycms-settings-${resolveDeploymentName3(config, "local")}`;
|
|
16510
17425
|
}
|
|
17426
|
+
getBranchNamespace(config) {
|
|
17427
|
+
return resolveBranchNamespace3(config, "local");
|
|
17428
|
+
}
|
|
16511
17429
|
getSettingsRoot(sourceRoot) {
|
|
16512
17430
|
return path30.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
16513
17431
|
}
|
|
@@ -16951,51 +17869,6 @@ var init_content_index_registry = __esm({
|
|
|
16951
17869
|
}
|
|
16952
17870
|
});
|
|
16953
17871
|
|
|
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
17872
|
// dist/id.js
|
|
17000
17873
|
import { generate as generate2 } from "short-uuid";
|
|
17001
17874
|
function generateId2() {
|
|
@@ -17006,7 +17879,7 @@ var isValidId2;
|
|
|
17006
17879
|
var init_id = __esm({
|
|
17007
17880
|
"dist/id.js"() {
|
|
17008
17881
|
"use strict";
|
|
17009
|
-
|
|
17882
|
+
init_validation3();
|
|
17010
17883
|
isValidId2 = isValidContentId2;
|
|
17011
17884
|
}
|
|
17012
17885
|
});
|
|
@@ -17939,7 +18812,7 @@ var init_sync = __esm({
|
|
|
17939
18812
|
var init_paths = __esm({
|
|
17940
18813
|
"dist/paths/index.js"() {
|
|
17941
18814
|
"use strict";
|
|
17942
|
-
|
|
18815
|
+
init_validation3();
|
|
17943
18816
|
}
|
|
17944
18817
|
});
|
|
17945
18818
|
|
|
@@ -18282,7 +19155,13 @@ var init_branch_metadata_file = __esm({
|
|
|
18282
19155
|
historyRewrittenFrom: z18.string().optional(),
|
|
18283
19156
|
syncFailureReason: z18.string().optional(),
|
|
18284
19157
|
editors: z18.array(z18.string()).optional(),
|
|
18285
|
-
uncommittedEditors: z18.array(z18.string()).optional()
|
|
19158
|
+
uncommittedEditors: z18.array(z18.string()).optional(),
|
|
19159
|
+
// Dropped when malformed: it only decorates the PR body, so it never fails a read.
|
|
19160
|
+
review: z18.object({
|
|
19161
|
+
state: z18.enum(["approved", "changes-requested"]),
|
|
19162
|
+
by: z18.object({ userId: z18.string(), name: z18.string().optional() }),
|
|
19163
|
+
at: z18.string()
|
|
19164
|
+
}).optional().catch(void 0)
|
|
18286
19165
|
};
|
|
18287
19166
|
branchMetadataSchema2 = z18.object(branchMetadataShape2).passthrough();
|
|
18288
19167
|
branchMetadataFileSchema2 = z18.object({
|
|
@@ -18393,14 +19272,23 @@ var init_branch_registry = __esm({
|
|
|
18393
19272
|
* genuinely-absent branch costs at most one extra scan per window.
|
|
18394
19273
|
*/
|
|
18395
19274
|
async get(name) {
|
|
18396
|
-
|
|
18397
|
-
|
|
19275
|
+
return this.find((b) => b.branch.name === name);
|
|
19276
|
+
}
|
|
19277
|
+
/**
|
|
19278
|
+
* Returns the branch whose workspace directory is `key` (`sanitizeBranchName` of its ref),
|
|
19279
|
+
* whatever its name, with {@link get}'s backstop.
|
|
19280
|
+
*/
|
|
19281
|
+
async getByKey(key) {
|
|
19282
|
+
return this.find((b) => path45.basename(b.branchRoot) === key);
|
|
19283
|
+
}
|
|
19284
|
+
async find(matches) {
|
|
19285
|
+
const found = (await this.list()).find(matches);
|
|
18398
19286
|
if (found)
|
|
18399
19287
|
return found;
|
|
18400
19288
|
if (!this.shouldForceRefresh())
|
|
18401
19289
|
return void 0;
|
|
18402
19290
|
const refreshed = await this.regenerate();
|
|
18403
|
-
return refreshed.find(
|
|
19291
|
+
return refreshed.find(matches);
|
|
18404
19292
|
}
|
|
18405
19293
|
shouldForceRefresh() {
|
|
18406
19294
|
const now = Date.now();
|