canopycms 0.0.67 → 0.0.68-int.100
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/README.md +21 -14
- package/dist/ai/handler.js +8 -0
- package/dist/api/admin-branch-health.js +17 -20
- package/dist/api/admin.d.ts +30 -9
- package/dist/api/admin.js +32 -2
- package/dist/api/assets.js +84 -76
- package/dist/api/branch-create-window.d.ts +12 -0
- package/dist/api/branch-create-window.js +13 -0
- package/dist/api/branch-review.d.ts +3 -3
- package/dist/api/branch-review.js +14 -5
- package/dist/api/branch-status.d.ts +5 -5
- package/dist/api/branch-status.js +26 -6
- package/dist/api/branch-withdraw.d.ts +2 -2
- package/dist/api/branch-withdraw.js +7 -2
- package/dist/api/branch.d.ts +15 -2
- package/dist/api/branch.js +111 -92
- package/dist/api/client.d.ts +18 -6
- package/dist/api/client.js +79 -12
- package/dist/api/content.d.ts +6 -5
- package/dist/api/content.js +44 -41
- package/dist/api/entries.js +16 -15
- package/dist/api/github-sync.d.ts +10 -1
- package/dist/api/github-sync.js +20 -10
- package/dist/api/index.d.ts +1 -1
- package/dist/api/reference-options.js +1 -1
- package/dist/api/resolve-references.js +14 -40
- package/dist/api/settings-helpers.d.ts +1 -1
- package/dist/api/settings-helpers.js +1 -4
- package/dist/api/user.d.ts +3 -0
- package/dist/api/user.js +3 -0
- package/dist/assets/asset-url.d.ts +22 -16
- package/dist/assets/asset-url.js +42 -25
- package/dist/assets/factory.d.ts +5 -0
- package/dist/assets/factory.js +5 -0
- package/dist/assets/index.d.ts +1 -1
- package/dist/assets/materialize.d.ts +122 -0
- package/dist/assets/materialize.js +305 -0
- package/dist/assets/store-local.d.ts +4 -0
- package/dist/assets/store-local.js +11 -0
- package/dist/assets/store-s3.d.ts +10 -1
- package/dist/assets/store-s3.js +55 -3
- package/dist/assets/transform-directives.d.ts +52 -13
- package/dist/assets/transform-directives.js +87 -27
- package/dist/assets/transform.d.ts +3 -3
- package/dist/assets/transform.js +13 -8
- package/dist/assets/types.d.ts +14 -1
- package/dist/auth/file-based-auth-cache.js +7 -8
- package/dist/authorization/content.d.ts +5 -4
- package/dist/authorization/content.js +5 -5
- package/dist/authorization/path.d.ts +11 -6
- package/dist/authorization/path.js +11 -6
- package/dist/authorization/types.d.ts +2 -2
- package/dist/branch-health.d.ts +7 -1
- package/dist/branch-health.js +8 -3
- package/dist/branch-metadata.d.ts +5 -5
- package/dist/branch-metadata.js +44 -36
- package/dist/branch-provisioning.d.ts +167 -0
- package/dist/branch-provisioning.js +501 -0
- package/dist/branch-schema-cache.d.ts +5 -3
- package/dist/branch-schema-cache.js +29 -11
- package/dist/branch-sparse.d.ts +23 -0
- package/dist/branch-sparse.js +118 -0
- package/dist/branch-workspace.d.ts +42 -3
- package/dist/branch-workspace.js +240 -94
- package/dist/build/asset-refs.d.ts +93 -0
- package/dist/build/asset-refs.js +251 -0
- package/dist/build-identity.d.ts +3 -0
- package/dist/build-identity.js +13 -0
- package/dist/cli/asset-refs.d.ts +30 -0
- package/dist/cli/asset-refs.js +168 -0
- package/dist/cli/cli.d.ts +2 -1
- package/dist/cli/cli.js +4843 -1870
- package/dist/cli/generate-ai-content.js +1584 -444
- package/dist/cli/github-app-manifest.d.ts +4 -3
- package/dist/cli/github-app-manifest.js +4 -3
- package/dist/cli/init.js +30 -27
- package/dist/cli/migrate.js +12 -8
- package/dist/cli/sync.js +83 -37
- package/dist/cli/template-files/Dockerfile.cms.template +8 -0
- package/dist/cli/template-files/cdk-app.ts.template +4 -0
- package/dist/cli/template-files/cms-stack.ts.template +23 -12
- package/dist/cli/template-files/deploy-cms.yml.template +2 -0
- package/dist/config/helpers.js +1 -2
- package/dist/config/schemas/config.d.ts +19 -32
- package/dist/config/schemas/config.js +9 -11
- package/dist/config/schemas/field.js +1 -0
- package/dist/config/schemas/media.d.ts +4 -13
- package/dist/config/schemas/media.js +5 -8
- package/dist/config/schemas/url.d.ts +4 -20
- package/dist/config/schemas/url.js +6 -22
- package/dist/config/types.d.ts +30 -20
- package/dist/config/validation.d.ts +6 -0
- package/dist/config/validation.js +37 -0
- package/dist/content-listing.d.ts +12 -12
- package/dist/content-listing.js +10 -6
- package/dist/content-reader.d.ts +11 -2
- package/dist/content-reader.js +22 -14
- package/dist/content-store.d.ts +37 -9
- package/dist/content-store.js +70 -43
- package/dist/content-tree.d.ts +1 -1
- package/dist/content-tree.js +2 -2
- package/dist/context.d.ts +27 -9
- package/dist/context.js +154 -70
- package/dist/editor/BranchManager.d.ts +7 -1
- package/dist/editor/BranchManager.js +29 -14
- package/dist/editor/CanopyEditor.d.ts +1 -1
- package/dist/editor/CanopyEditor.js +2 -6
- package/dist/editor/Editor.d.ts +4 -3
- package/dist/editor/Editor.js +24 -20
- package/dist/editor/FormRenderer.js +34 -18
- package/dist/editor/admin/SystemHealthPanel.js +47 -7
- package/dist/editor/components/BranchesDrawer.d.ts +9 -0
- package/dist/editor/components/BranchesDrawer.js +4 -0
- package/dist/editor/components/NoEditPermissionNotice.d.ts +10 -0
- package/dist/editor/components/NoEditPermissionNotice.js +17 -0
- package/dist/editor/context/AssetContext.d.ts +18 -35
- package/dist/editor/context/AssetContext.js +20 -29
- package/dist/editor/context/index.d.ts +1 -1
- package/dist/editor/context/index.js +1 -1
- package/dist/editor/editor-config.d.ts +1 -2
- package/dist/editor/editor-config.js +0 -23
- package/dist/editor/editor-utils.d.ts +24 -21
- package/dist/editor/editor-utils.js +75 -62
- package/dist/editor/fields/BlockField.d.ts +1 -0
- package/dist/editor/fields/BlockField.js +6 -4
- package/dist/editor/fields/CodeField.d.ts +1 -0
- package/dist/editor/fields/CodeField.js +2 -2
- package/dist/editor/fields/DateTimeField.d.ts +1 -0
- package/dist/editor/fields/DateTimeField.js +2 -2
- package/dist/editor/fields/FieldDescription.d.ts +16 -0
- package/dist/editor/fields/FieldDescription.js +11 -0
- package/dist/editor/fields/ImageField.d.ts +1 -0
- package/dist/editor/fields/ImageField.js +3 -2
- package/dist/editor/fields/InlineGroupField.js +4 -1
- package/dist/editor/fields/MarkdownField.d.ts +1 -0
- package/dist/editor/fields/MarkdownField.js +115 -20
- package/dist/editor/fields/NumberField.d.ts +1 -0
- package/dist/editor/fields/NumberField.js +2 -2
- package/dist/editor/fields/NumberListField.d.ts +1 -0
- package/dist/editor/fields/NumberListField.js +2 -2
- package/dist/editor/fields/ObjectField.d.ts +1 -0
- package/dist/editor/fields/ObjectField.js +5 -2
- package/dist/editor/fields/ReferenceField.d.ts +1 -0
- package/dist/editor/fields/ReferenceField.js +5 -4
- package/dist/editor/fields/SelectField.d.ts +1 -0
- package/dist/editor/fields/SelectField.js +2 -2
- package/dist/editor/fields/StringListField.d.ts +1 -0
- package/dist/editor/fields/StringListField.js +2 -2
- package/dist/editor/fields/TextField.d.ts +1 -0
- package/dist/editor/fields/TextField.js +2 -2
- package/dist/editor/fields/ToggleField.d.ts +1 -0
- package/dist/editor/fields/ToggleField.js +7 -4
- package/dist/editor/fields/entry-link/InsertEntryLink.d.ts +1 -1
- package/dist/editor/fields/entry-link/InsertEntryLink.js +3 -2
- package/dist/editor/fields/mdx-jsx-support.d.ts +1 -0
- package/dist/editor/fields/mdx-jsx-support.js +137 -0
- package/dist/editor/hooks/create-branch-request.d.ts +20 -0
- package/dist/editor/hooks/create-branch-request.js +52 -0
- package/dist/editor/hooks/useBranchActions.d.ts +13 -2
- package/dist/editor/hooks/useBranchActions.js +48 -17
- package/dist/editor/hooks/useBranchManager.d.ts +11 -0
- package/dist/editor/hooks/useBranchManager.js +80 -2
- package/dist/editor/hooks/useBranchesData.d.ts +6 -0
- package/dist/editor/hooks/useBranchesData.js +9 -3
- package/dist/editor/hooks/useCommentSystem.js +25 -6
- package/dist/editor/hooks/useDraftManager.d.ts +16 -1
- package/dist/editor/hooks/useDraftManager.js +193 -36
- package/dist/editor/hooks/useEntriesData.js +3 -3
- package/dist/editor/hooks/useEntryManager.d.ts +2 -1
- package/dist/editor/hooks/useEntryManager.js +34 -19
- package/dist/editor/media/AssetCard.d.ts +1 -1
- package/dist/editor/media/crop-math.d.ts +3 -4
- package/dist/editor/media/crop-math.js +11 -22
- package/dist/editor/media/editor-image-src.d.ts +7 -0
- package/dist/editor/media/editor-image-src.js +12 -0
- package/dist/editor/preview-asset-base.d.ts +8 -0
- package/dist/editor/preview-asset-base.js +13 -0
- package/dist/editor/preview-bridge.d.ts +8 -1
- package/dist/editor/preview-bridge.js +15 -2
- package/dist/editor/preview-path.d.ts +10 -0
- package/dist/editor/preview-path.js +20 -0
- package/dist/editor/theme.js +2 -2
- package/dist/entry-schema-registry.d.ts +4 -4
- package/dist/entry-schema-registry.js +10 -6
- package/dist/entry-schema.d.ts +27 -4
- package/dist/entry-schema.js +20 -3
- package/dist/git-manager.d.ts +173 -6
- package/dist/git-manager.js +594 -76
- package/dist/github-service.d.ts +8 -0
- package/dist/github-service.js +5 -1
- package/dist/http/handler.js +37 -13
- package/dist/http/index.d.ts +2 -0
- package/dist/http/index.js +2 -0
- package/dist/http/router.d.ts +2 -0
- package/dist/http/router.js +2 -1
- package/dist/http/types.d.ts +2 -0
- package/dist/http/worker-not-ready.d.ts +10 -0
- package/dist/http/worker-not-ready.js +24 -0
- package/dist/operating-mode/client-unsafe-strategy.js +0 -6
- package/dist/operating-mode/types.d.ts +1 -4
- package/dist/paths/branch-name.d.ts +5 -0
- package/dist/paths/branch-name.js +9 -0
- package/dist/paths/branch.d.ts +6 -2
- package/dist/paths/branch.js +16 -10
- package/dist/paths/index.d.ts +3 -3
- package/dist/paths/index.js +3 -3
- package/dist/paths/normalize.d.ts +7 -0
- package/dist/paths/normalize.js +9 -0
- package/dist/paths/validation.js +0 -2
- package/dist/reference-resolver.d.ts +5 -21
- package/dist/reference-resolver.js +9 -44
- package/dist/resolve-canopy-user.js +2 -1
- package/dist/schema/schema-store.d.ts +54 -31
- package/dist/schema/schema-store.js +77 -31
- package/dist/server.d.ts +24 -9
- package/dist/server.js +22 -8
- package/dist/services.d.ts +18 -6
- package/dist/services.js +85 -70
- package/dist/settings-workspace.d.ts +23 -3
- package/dist/settings-workspace.js +197 -45
- package/dist/static/seo.d.ts +2 -14
- package/dist/static/seo.js +2 -25
- package/dist/submission-attribution.d.ts +73 -0
- package/dist/submission-attribution.js +221 -0
- package/dist/sync-core.d.ts +12 -1
- package/dist/sync-core.js +31 -13
- package/dist/task-queue/worker-status.d.ts +8 -0
- package/dist/task-queue/worker-status.js +17 -0
- package/dist/types.d.ts +31 -2
- package/dist/utils/content-write-lock.d.ts +18 -7
- package/dist/utils/content-write-lock.js +18 -9
- package/dist/utils/debug.d.ts +8 -0
- package/dist/utils/debug.js +10 -2
- package/dist/utils/git.d.ts +32 -0
- package/dist/utils/git.js +42 -0
- package/dist/utils/occ-json-write.js +10 -2
- package/dist/utils/provision-log.d.ts +25 -0
- package/dist/utils/provision-log.js +49 -0
- package/dist/utils/provisioning-lock.d.ts +35 -5
- package/dist/utils/provisioning-lock.js +73 -12
- package/dist/utils/request-timing.d.ts +25 -0
- package/dist/utils/request-timing.js +101 -0
- package/dist/utils/sanitize-href.d.ts +8 -22
- package/dist/utils/sanitize-href.js +11 -28
- package/dist/utils/url-prefix.d.ts +30 -0
- package/dist/utils/url-prefix.js +61 -0
- package/dist/validation/entry-validator.js +9 -3
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/worker/canopy-state.d.ts +45 -0
- package/dist/worker/canopy-state.js +75 -0
- package/dist/worker/cms-worker.d.ts +8 -3
- package/dist/worker/cms-worker.js +38 -2
- package/dist/worker/git-sync.d.ts +30 -12
- package/dist/worker/git-sync.js +253 -58
- package/dist/worker/history-rewrite.d.ts +1 -1
- package/dist/worker/history-rewrite.js +1 -1
- package/dist/worker/provisioned-workspace.d.ts +35 -0
- package/dist/worker/provisioned-workspace.js +50 -0
- package/dist/worker/rebase.d.ts +1 -1
- package/dist/worker/rebase.js +95 -23
- package/dist/worker/remote-git-maintenance.d.ts +3 -0
- package/dist/worker/remote-git-maintenance.js +11 -0
- package/dist/worker/sparse-cone.d.ts +20 -0
- package/dist/worker/sparse-cone.js +132 -0
- package/dist/worker/task-runner.js +39 -4
- package/dist/worker/worker-context.d.ts +6 -1
- package/package.json +2 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type
|
|
2
|
+
import { type BranchListItemResponse } from './branch.js';
|
|
3
3
|
export declare const requestChanges: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
4
4
|
branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
|
|
5
5
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -10,7 +10,7 @@ export declare const requestChanges: import("./route-builder.js").RouteDefinitio
|
|
|
10
10
|
branch: string & {
|
|
11
11
|
readonly __brand: "BranchName";
|
|
12
12
|
};
|
|
13
|
-
}>, undefined,
|
|
13
|
+
}>, undefined, BranchListItemResponse>;
|
|
14
14
|
export declare const approveBranch: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
15
15
|
branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
|
|
16
16
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -21,4 +21,4 @@ export declare const approveBranch: import("./route-builder.js").RouteDefinition
|
|
|
21
21
|
branch: string & {
|
|
22
22
|
readonly __brand: "BranchName";
|
|
23
23
|
};
|
|
24
|
-
}>, undefined,
|
|
24
|
+
}>, undefined, BranchListItemResponse>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { branchParamSchema } from './validators.js';
|
|
2
|
+
import { toBranchListItem } from './branch.js';
|
|
2
3
|
import { getBranchMetadataFileManager } from '../branch-metadata.js';
|
|
3
4
|
import { defineEndpoint } from './route-builder.js';
|
|
4
5
|
import { syncConvertToDraft } from './github-sync.js';
|
|
@@ -17,9 +18,13 @@ const requestChangesHandler = async (gc, ctx, _req, _params) => {
|
|
|
17
18
|
branch: { name: branchContext.branch.name, status: 'editing' },
|
|
18
19
|
});
|
|
19
20
|
// TODO: Optionally record comment in .canopycms/comments.json when comment system is implemented
|
|
20
|
-
return {
|
|
21
|
+
return {
|
|
22
|
+
ok: true,
|
|
23
|
+
status: 200,
|
|
24
|
+
data: { branch: toBranchListItem(ctx.services.config, updated.branch) },
|
|
25
|
+
};
|
|
21
26
|
};
|
|
22
|
-
const approveBranchHandler = async (gc,
|
|
27
|
+
const approveBranchHandler = async (gc, ctx, _req, _params) => {
|
|
23
28
|
const { branchContext } = gc;
|
|
24
29
|
if (branchContext.branch.status !== 'submitted') {
|
|
25
30
|
return {
|
|
@@ -33,7 +38,11 @@ const approveBranchHandler = async (gc, _ctx, _req, _params) => {
|
|
|
33
38
|
branch: { name: branchContext.branch.name, status: 'approved' },
|
|
34
39
|
});
|
|
35
40
|
// TODO: Optionally call githubService.approvePullRequest() when GitHub integration is needed
|
|
36
|
-
return {
|
|
41
|
+
return {
|
|
42
|
+
ok: true,
|
|
43
|
+
status: 200,
|
|
44
|
+
data: { branch: toBranchListItem(ctx.services.config, updated.branch) },
|
|
45
|
+
};
|
|
37
46
|
};
|
|
38
47
|
export const requestChanges = defineEndpoint({
|
|
39
48
|
namespace: 'workflow',
|
|
@@ -43,7 +52,7 @@ export const requestChanges = defineEndpoint({
|
|
|
43
52
|
params: branchParamSchema,
|
|
44
53
|
// body/bodyType removed: comment field was declared but never stored.
|
|
45
54
|
// Re-add when comment storage is implemented (see TODO in handler).
|
|
46
|
-
responseType: '
|
|
55
|
+
responseType: 'BranchListItemResponse',
|
|
47
56
|
response: {},
|
|
48
57
|
defaultMockData: {
|
|
49
58
|
branch: {
|
|
@@ -64,7 +73,7 @@ export const approveBranch = defineEndpoint({
|
|
|
64
73
|
method: 'POST',
|
|
65
74
|
path: '/:branch/approve',
|
|
66
75
|
params: branchParamSchema,
|
|
67
|
-
responseType: '
|
|
76
|
+
responseType: 'BranchListItemResponse',
|
|
68
77
|
response: {},
|
|
69
78
|
defaultMockData: {
|
|
70
79
|
branch: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type
|
|
2
|
+
import { type BranchListItemResponse, type BranchResponse } from './branch.js';
|
|
3
3
|
export type { BranchMergeResponse } from './branch-merge.js';
|
|
4
4
|
export declare const WORKFLOW_ROUTES: {
|
|
5
5
|
readonly getStatus: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
@@ -23,7 +23,7 @@ export declare const WORKFLOW_ROUTES: {
|
|
|
23
23
|
branch: string & {
|
|
24
24
|
readonly __brand: "BranchName";
|
|
25
25
|
};
|
|
26
|
-
}>, undefined,
|
|
26
|
+
}>, undefined, BranchListItemResponse>;
|
|
27
27
|
readonly withdraw: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
28
28
|
branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
|
|
29
29
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -34,7 +34,7 @@ export declare const WORKFLOW_ROUTES: {
|
|
|
34
34
|
branch: string & {
|
|
35
35
|
readonly __brand: "BranchName";
|
|
36
36
|
};
|
|
37
|
-
}>, undefined,
|
|
37
|
+
}>, undefined, BranchListItemResponse>;
|
|
38
38
|
readonly requestChanges: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
39
39
|
branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
|
|
40
40
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -45,7 +45,7 @@ export declare const WORKFLOW_ROUTES: {
|
|
|
45
45
|
branch: string & {
|
|
46
46
|
readonly __brand: "BranchName";
|
|
47
47
|
};
|
|
48
|
-
}>, undefined,
|
|
48
|
+
}>, undefined, BranchListItemResponse>;
|
|
49
49
|
readonly approve: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
50
50
|
branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
|
|
51
51
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -56,7 +56,7 @@ export declare const WORKFLOW_ROUTES: {
|
|
|
56
56
|
branch: string & {
|
|
57
57
|
readonly __brand: "BranchName";
|
|
58
58
|
};
|
|
59
|
-
}>, undefined,
|
|
59
|
+
}>, undefined, BranchListItemResponse>;
|
|
60
60
|
readonly markMerged: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
61
61
|
branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
|
|
62
62
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { branchParamSchema } from './validators.js';
|
|
2
|
+
import { toBranchListItem } from './branch.js';
|
|
2
3
|
import { getBranchMetadataFileManager } from '../branch-metadata.js';
|
|
3
4
|
import { withdrawBranch } from './branch-withdraw.js';
|
|
4
5
|
import { requestChanges, approveBranch } from './branch-review.js';
|
|
@@ -8,6 +9,8 @@ import { canPerformWorkflowAction, getBranchProtection } from '../authorization/
|
|
|
8
9
|
import { syncSubmitPr } from './github-sync.js';
|
|
9
10
|
import { getErrorMessage, redactCredentials, sanitizeErrorMessage } from '../utils/error.js';
|
|
10
11
|
import { isNonFastForwardRejection } from '../utils/git.js';
|
|
12
|
+
import { ContentWriteLockBusyError } from '../utils/content-write-lock.js';
|
|
13
|
+
import { submissionEditorFromUser } from '../submission-attribution.js';
|
|
11
14
|
const getBranchStatusHandler = async (gc, _ctx, _req, _params) => {
|
|
12
15
|
const { branchContext } = gc;
|
|
13
16
|
return { ok: true, status: 200, data: { branch: branchContext.branch } };
|
|
@@ -49,10 +52,24 @@ const submitBranchForMergeHandler = async (gc, ctx, req, _params) => {
|
|
|
49
52
|
};
|
|
50
53
|
}
|
|
51
54
|
// Commit and push changes
|
|
55
|
+
const submitter = submissionEditorFromUser(req.user);
|
|
56
|
+
let changedPaths;
|
|
52
57
|
try {
|
|
53
|
-
|
|
58
|
+
;
|
|
59
|
+
({ changedPaths } = await ctx.services.submitBranch({ context: branchContext, submitter }));
|
|
54
60
|
}
|
|
55
61
|
catch (err) {
|
|
62
|
+
// Retriable either way: a resubmit commits nothing new and pushes only
|
|
63
|
+
// what has not reached the remote.
|
|
64
|
+
if (err instanceof ContentWriteLockBusyError) {
|
|
65
|
+
return {
|
|
66
|
+
ok: false,
|
|
67
|
+
status: 409,
|
|
68
|
+
error: err.outcome === 'not-run'
|
|
69
|
+
? `Could not submit "${branchContext.branch.name}" right now: it is being synced with its base branch, or a save is in flight. Nothing was submitted; try again in a moment.`
|
|
70
|
+
: `"${branchContext.branch.name}" was being synced while it was submitted, so the submit may not have completed. Try submitting again.`,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
56
73
|
const message = getErrorMessage(err);
|
|
57
74
|
// Full path detail (including branchRoot, an absolute path) to server logs
|
|
58
75
|
// only; the client only ever sees the sanitized form (API-H2). Credentials
|
|
@@ -62,8 +79,7 @@ const submitBranchForMergeHandler = async (gc, ctx, req, _params) => {
|
|
|
62
79
|
// A non-fast-forward rejection means this branch and the deployment's
|
|
63
80
|
// local repository have diverged. Retrying the identical push can never
|
|
64
81
|
// succeed (see isNonFastForwardRejection), so surface 409 instead of the
|
|
65
|
-
// generic 500 below. Everything else (network, auth
|
|
66
|
-
// keeps the existing 500 path unchanged.
|
|
82
|
+
// generic 500 below. Everything else (network, auth) keeps the 500 path.
|
|
67
83
|
//
|
|
68
84
|
// This push targets the deployment's OWN local origin (remote.git), not
|
|
69
85
|
// GitHub, so the message deliberately states only the observable fact and
|
|
@@ -91,7 +107,7 @@ const submitBranchForMergeHandler = async (gc, ctx, req, _params) => {
|
|
|
91
107
|
};
|
|
92
108
|
}
|
|
93
109
|
// Create or update PR (sync via githubService, or async via task queue)
|
|
94
|
-
const prResult = await syncSubmitPr(ctx, branchContext);
|
|
110
|
+
const prResult = await syncSubmitPr(ctx, branchContext, { submitter, changedPaths });
|
|
95
111
|
// Update metadata with status and PR info
|
|
96
112
|
const meta = getBranchMetadataFileManager(branchContext.branchRoot, branchContext.baseRoot);
|
|
97
113
|
const updated = await meta.save({
|
|
@@ -107,7 +123,11 @@ const submitBranchForMergeHandler = async (gc, ctx, req, _params) => {
|
|
|
107
123
|
rebaseFailure: undefined,
|
|
108
124
|
},
|
|
109
125
|
});
|
|
110
|
-
return {
|
|
126
|
+
return {
|
|
127
|
+
ok: true,
|
|
128
|
+
status: 200,
|
|
129
|
+
data: { branch: toBranchListItem(ctx.services.config, updated.branch) },
|
|
130
|
+
};
|
|
111
131
|
};
|
|
112
132
|
const getBranchStatus = defineEndpoint({
|
|
113
133
|
namespace: 'workflow',
|
|
@@ -136,7 +156,7 @@ const submitBranchForMerge = defineEndpoint({
|
|
|
136
156
|
method: 'POST',
|
|
137
157
|
path: '/:branch/submit',
|
|
138
158
|
params: branchParamSchema,
|
|
139
|
-
responseType: '
|
|
159
|
+
responseType: 'BranchListItemResponse',
|
|
140
160
|
response: {},
|
|
141
161
|
defaultMockData: {
|
|
142
162
|
branch: {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import type
|
|
2
|
+
import { type BranchListItemResponse } from './branch.js';
|
|
3
3
|
/**
|
|
4
4
|
* Withdraw a submitted branch, converting the PR to draft and unlocking editing
|
|
5
5
|
*
|
|
@@ -19,4 +19,4 @@ export declare const withdrawBranch: import("./route-builder.js").RouteDefinitio
|
|
|
19
19
|
branch: string & {
|
|
20
20
|
readonly __brand: "BranchName";
|
|
21
21
|
};
|
|
22
|
-
}>, undefined,
|
|
22
|
+
}>, undefined, BranchListItemResponse>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { branchParamSchema } from './validators.js';
|
|
2
|
+
import { toBranchListItem } from './branch.js';
|
|
2
3
|
import { getBranchMetadataFileManager } from '../branch-metadata.js';
|
|
3
4
|
import { defineEndpoint } from './route-builder.js';
|
|
4
5
|
import { canPerformWorkflowAction, getBranchProtection } from '../authorization/index.js';
|
|
@@ -60,7 +61,11 @@ const withdrawBranchHandler = async (gc, ctx, req, _params) => {
|
|
|
60
61
|
: {}),
|
|
61
62
|
},
|
|
62
63
|
});
|
|
63
|
-
return {
|
|
64
|
+
return {
|
|
65
|
+
ok: true,
|
|
66
|
+
status: 200,
|
|
67
|
+
data: { branch: toBranchListItem(ctx.services.config, updated.branch) },
|
|
68
|
+
};
|
|
64
69
|
};
|
|
65
70
|
/**
|
|
66
71
|
* Withdraw a submitted branch, converting the PR to draft and unlocking editing
|
|
@@ -77,7 +82,7 @@ export const withdrawBranch = defineEndpoint({
|
|
|
77
82
|
method: 'POST',
|
|
78
83
|
path: '/:branch/withdraw',
|
|
79
84
|
params: branchParamSchema,
|
|
80
|
-
responseType: '
|
|
85
|
+
responseType: 'BranchListItemResponse',
|
|
81
86
|
response: {},
|
|
82
87
|
defaultMockData: {
|
|
83
88
|
branch: {
|
package/dist/api/branch.d.ts
CHANGED
|
@@ -45,6 +45,14 @@ export interface BranchListItem extends BranchMetadata {
|
|
|
45
45
|
*/
|
|
46
46
|
submitBlocked?: boolean;
|
|
47
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* Response type for branch creation and the workflow transitions. Carries the
|
|
50
|
+
* list-item shape (server-computed flags included) so the editor can show the
|
|
51
|
+
* result without waiting for a listing that may lag behind it.
|
|
52
|
+
*/
|
|
53
|
+
export type BranchListItemResponse = ApiResponse<{
|
|
54
|
+
branch: BranchListItem;
|
|
55
|
+
}>;
|
|
48
56
|
/** Response type for listing branches */
|
|
49
57
|
export type BranchListResponse = ApiResponse<{
|
|
50
58
|
branches: BranchListItem[];
|
|
@@ -129,8 +137,13 @@ export interface CreateBranchBody {
|
|
|
129
137
|
description?: string;
|
|
130
138
|
access?: BranchMetadata['access'];
|
|
131
139
|
}
|
|
140
|
+
/**
|
|
141
|
+
* Attach server-computed protected-base-branch flags to a branch. Reads config
|
|
142
|
+
* per call so dev-mode refreshActiveBranch() updates are reflected.
|
|
143
|
+
*/
|
|
144
|
+
export declare const toBranchListItem: (config: ApiContext["services"]["config"], branch: BranchMetadata) => BranchListItem;
|
|
132
145
|
/** @internal Exported for tests. */
|
|
133
|
-
export declare const createBranchHandler: (ctx: ApiContext, req: ApiRequest, body: z.infer<typeof createBranchBodySchema>) => Promise<
|
|
146
|
+
export declare const createBranchHandler: (ctx: ApiContext, req: ApiRequest, body: z.infer<typeof createBranchBodySchema>) => Promise<BranchListItemResponse>;
|
|
134
147
|
/** @internal Exported for tests. */
|
|
135
148
|
export declare const listBranchesHandler: (ctx: ApiContext, req: ApiRequest) => Promise<BranchListResponse>;
|
|
136
149
|
/** @internal Exported for tests. */
|
|
@@ -187,7 +200,7 @@ export declare const BRANCH_ROUTES: {
|
|
|
187
200
|
allowedUsers?: string[] | undefined;
|
|
188
201
|
allowedGroups?: string[] | undefined;
|
|
189
202
|
} | undefined;
|
|
190
|
-
}>,
|
|
203
|
+
}>, BranchListItemResponse>;
|
|
191
204
|
readonly delete: import("./route-builder.js").RouteDefinition<z.ZodObject<{
|
|
192
205
|
branch: z.ZodType<import("../paths/index.js").BranchName, z.ZodTypeDef, import("../paths/index.js").BranchName>;
|
|
193
206
|
}, "strip", z.ZodTypeAny, {
|
package/dist/api/branch.js
CHANGED
|
@@ -2,15 +2,17 @@ import fs from 'node:fs/promises';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
4
|
import { BranchWorkspaceManager } from '../branch-workspace.js';
|
|
5
|
+
import { BranchDirOccupiedError, BranchProvisioningBusyError, deletingDirName, removeLeftoverDir, } from '../branch-provisioning.js';
|
|
5
6
|
import { getBranchMetadataFileManager } from '../branch-metadata.js';
|
|
6
7
|
import { withOccFileLock } from '../utils/occ-json-write.js';
|
|
8
|
+
import { isCreatorsRecentBranch } from './branch-create-window.js';
|
|
7
9
|
import { defineEndpoint } from './route-builder.js';
|
|
8
10
|
import { createDebugLogger } from '../utils/debug.js';
|
|
9
11
|
import { clientOperatingStrategy } from '../operating-mode/index.js';
|
|
10
12
|
import { isNotFoundError, getErrorMessage, sanitizeErrorMessage } from '../utils/error.js';
|
|
11
13
|
import { filePathExists } from '../utils/fs.js';
|
|
12
14
|
import { isNetworkRemoteUrl } from '../utils/git.js';
|
|
13
|
-
import { sanitizeBranchName, RESERVED_SETTINGS_BRANCH_PREFIX, RESERVED_ROUTE_BRANCH_NAMES, } from '../paths/index.js';
|
|
15
|
+
import { sanitizeBranchName, RESERVED_SETTINGS_BRANCH_PREFIX, RESERVED_ROUTE_BRANCH_NAMES, isSettingsBranchName, } from '../paths/index.js';
|
|
14
16
|
import { GitManager } from '../git-manager.js';
|
|
15
17
|
import { branchNameSchema, branchParamSchema } from './validators.js';
|
|
16
18
|
const log = createDebugLogger({ prefix: 'BranchAPI' });
|
|
@@ -87,6 +89,20 @@ const resolveReadOnlyMirrorPath = (ctx, strategy) => {
|
|
|
87
89
|
process.env[remoteUrlConfig.envVarName] ??
|
|
88
90
|
remoteUrlConfig.autoDetectRemotePath);
|
|
89
91
|
};
|
|
92
|
+
/**
|
|
93
|
+
* Attach server-computed protected-base-branch flags to a branch. Reads config
|
|
94
|
+
* per call so dev-mode refreshActiveBranch() updates are reflected.
|
|
95
|
+
*/
|
|
96
|
+
export const toBranchListItem = (config, branch) => {
|
|
97
|
+
const protection = getBranchWriteProtection(config, branch.name, branch.baseBranch, branch.status);
|
|
98
|
+
return {
|
|
99
|
+
...branch,
|
|
100
|
+
isProtected: protection.isProtected,
|
|
101
|
+
readOnly: protection.readOnly,
|
|
102
|
+
writeBlocked: protection.writeBlocked,
|
|
103
|
+
submitBlocked: protection.submitBlockedIncludingStatus,
|
|
104
|
+
};
|
|
105
|
+
};
|
|
90
106
|
/** @internal Exported for tests. */
|
|
91
107
|
export const createBranchHandler = async (ctx, req, body) => {
|
|
92
108
|
return log.timed('api', 'createBranch', async () => {
|
|
@@ -95,15 +111,12 @@ export const createBranchHandler = async (ctx, req, body) => {
|
|
|
95
111
|
branchName,
|
|
96
112
|
userId: req.user.userId,
|
|
97
113
|
});
|
|
98
|
-
// Scope note: the
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
104
|
-
// and never provision) provision system/known branch names, not
|
|
105
|
-
// user-chosen ones, and deliberately stay uncovered -- intentional, not
|
|
106
|
-
// an oversight.
|
|
114
|
+
// Scope note: the remote-mirror check further down applies ONLY to this
|
|
115
|
+
// user-facing creation path; http/handler.ts's auto-create (base/active
|
|
116
|
+
// branches) and loadOrCreateBranchContext provision known names. The two
|
|
117
|
+
// settings-branch checks below give this path a specific 400; every
|
|
118
|
+
// provisioning path, this one included, also refuses a settings branch in
|
|
119
|
+
// BranchWorkspaceManager.openOrCreateBranch.
|
|
107
120
|
// Prevent git branch name collision with the settings branch. Settings
|
|
108
121
|
// live in a separate directory but share the same git remote, and
|
|
109
122
|
// openOrCreateBranch (branch-workspace.ts) uses the SANITIZED name as the
|
|
@@ -154,13 +167,9 @@ export const createBranchHandler = async (ctx, req, body) => {
|
|
|
154
167
|
error: `Branch name "${branchName}" is reserved: it collides with the /${branchName} API route namespace`,
|
|
155
168
|
};
|
|
156
169
|
}
|
|
157
|
-
// Reject the base branch name outright
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// naming the base branch would let the caller inject themselves into the
|
|
161
|
-
// protected base branch's ACL (gaining e.g. withdraw rights via the
|
|
162
|
-
// allowed_by_acl path). No recorded fork point exists yet for a
|
|
163
|
-
// not-yet-created branch, so this checks config protection only.
|
|
170
|
+
// Reject the base branch name outright, even before the base branch is
|
|
171
|
+
// provisioned. No recorded fork point exists yet for a not-yet-created
|
|
172
|
+
// branch, so this checks config protection only.
|
|
164
173
|
const { isProtected } = getBranchProtection(ctx.services.config, branchName);
|
|
165
174
|
if (isProtected) {
|
|
166
175
|
return {
|
|
@@ -169,14 +178,10 @@ export const createBranchHandler = async (ctx, req, body) => {
|
|
|
169
178
|
error: 'Cannot create a branch with the base branch name',
|
|
170
179
|
};
|
|
171
180
|
}
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
// since that's what's persisted in branch.json (see
|
|
177
|
-
// BranchWorkspaceManager.openOrCreateBranch). System branches
|
|
178
|
-
// auto-provisioned via http/handler.ts's getBranchContext don't go
|
|
179
|
-
// through this handler, so rejecting collisions here doesn't affect them.
|
|
181
|
+
// A name collision with ANY existing branch answers before a clone is paid
|
|
182
|
+
// for. Provisioning never merges into an existing branch either: it reports
|
|
183
|
+
// `exists`, mapped the same way below. Compared sanitized, the form
|
|
184
|
+
// branch.json records.
|
|
180
185
|
if (!ctx.services.registry) {
|
|
181
186
|
return {
|
|
182
187
|
ok: false,
|
|
@@ -186,11 +191,7 @@ export const createBranchHandler = async (ctx, req, body) => {
|
|
|
186
191
|
}
|
|
187
192
|
const existingBranch = await ctx.services.registry.get(sanitizeBranchName(branchName));
|
|
188
193
|
if (existingBranch) {
|
|
189
|
-
return
|
|
190
|
-
ok: false,
|
|
191
|
-
status: 409,
|
|
192
|
-
error: 'A branch with this name already exists',
|
|
193
|
-
};
|
|
194
|
+
return existingBranchResponse(ctx, req, existingBranch);
|
|
194
195
|
}
|
|
195
196
|
// L2: create-time collision check against this deployment's local
|
|
196
197
|
// GitHub mirror (`remote.git`). The CMS Lambda has no internet access
|
|
@@ -315,19 +316,47 @@ export const createBranchHandler = async (ctx, req, body) => {
|
|
|
315
316
|
};
|
|
316
317
|
}
|
|
317
318
|
const manager = new BranchWorkspaceManager(ctx.services.config);
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
319
|
+
let outcome;
|
|
320
|
+
try {
|
|
321
|
+
outcome = await manager.provisionBranch({
|
|
322
|
+
branchName,
|
|
323
|
+
mode: ctx.services.config.mode,
|
|
324
|
+
createdBy: req.user.userId,
|
|
325
|
+
title: body.title,
|
|
326
|
+
description: body.description,
|
|
327
|
+
access: body.access,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
if (err instanceof BranchProvisioningBusyError) {
|
|
332
|
+
return { ok: false, status: 503, error: err.message };
|
|
333
|
+
}
|
|
334
|
+
if (err instanceof BranchDirOccupiedError) {
|
|
335
|
+
return { ok: false, status: 409, error: err.message };
|
|
336
|
+
}
|
|
337
|
+
throw err;
|
|
338
|
+
}
|
|
339
|
+
if (outcome.kind === 'exists')
|
|
340
|
+
return existingBranchResponse(ctx, req, outcome.context);
|
|
341
|
+
log.debug('api', 'Branch created', { branchName: outcome.context.branch.name });
|
|
342
|
+
return {
|
|
343
|
+
ok: true,
|
|
344
|
+
status: 200,
|
|
345
|
+
data: { branch: toBranchListItem(ctx.services.config, outcome.context.branch) },
|
|
346
|
+
};
|
|
329
347
|
});
|
|
330
348
|
};
|
|
349
|
+
/** A create that names an existing branch: 409, unless it is that creator's recent retry. */
|
|
350
|
+
const existingBranchResponse = (ctx, req, existing) => {
|
|
351
|
+
if (isCreatorsRecentBranch(existing.branch, req.user.userId)) {
|
|
352
|
+
return {
|
|
353
|
+
ok: true,
|
|
354
|
+
status: 200,
|
|
355
|
+
data: { branch: toBranchListItem(ctx.services.config, existing.branch) },
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
return { ok: false, status: 409, error: 'A branch with this name already exists' };
|
|
359
|
+
};
|
|
331
360
|
/** @internal Exported for tests. */
|
|
332
361
|
export const listBranchesHandler = async (ctx, req) => {
|
|
333
362
|
if (!ctx.services.registry) {
|
|
@@ -337,7 +366,10 @@ export const listBranchesHandler = async (ctx, req) => {
|
|
|
337
366
|
error: 'Branch registry not initialized — ensure the workspace has been initialized',
|
|
338
367
|
};
|
|
339
368
|
}
|
|
340
|
-
|
|
369
|
+
// A settings-branch workspace is never resolvable as a content branch (see
|
|
370
|
+
// http/handler.ts), so one left on disk is hidden rather than listed unopenable.
|
|
371
|
+
const settingsBranch = operatingStrategy(ctx.services.config.mode).getSettingsBranchName(ctx.services.config);
|
|
372
|
+
const allBranches = (await ctx.services.registry.list()).filter((context) => !isSettingsBranchName(context.branch.name, settingsBranch));
|
|
341
373
|
// The branch the editor should open when none is pinned via URL/config.
|
|
342
374
|
// Read per-request so dev-mode refreshActiveBranch() updates are reflected.
|
|
343
375
|
// Sanitized: dev mode detects the RAW git HEAD name (e.g. 'claude/foo'),
|
|
@@ -345,24 +377,15 @@ export const listBranchesHandler = async (ctx, req) => {
|
|
|
345
377
|
// editor matches defaultBranch against registry names, so return the form
|
|
346
378
|
// that can actually be found there.
|
|
347
379
|
const defaultBranch = sanitizeBranchName(ctx.services.config.defaultActiveBranch ?? ctx.services.config.defaultBaseBranch ?? 'main');
|
|
348
|
-
// Attach server-computed protected-base-branch flags; read config per-request
|
|
349
|
-
// so dev-mode refreshActiveBranch() updates are reflected here too.
|
|
350
|
-
const toListItem = (context) => {
|
|
351
|
-
const protection = getBranchWriteProtection(ctx.services.config, context.branch.name, context.branch.baseBranch, context.branch.status);
|
|
352
|
-
return {
|
|
353
|
-
...context.branch,
|
|
354
|
-
isProtected: protection.isProtected,
|
|
355
|
-
readOnly: protection.readOnly,
|
|
356
|
-
writeBlocked: protection.writeBlocked,
|
|
357
|
-
submitBlocked: protection.submitBlockedIncludingStatus,
|
|
358
|
-
};
|
|
359
|
-
};
|
|
360
380
|
// Admins and Reviewers see all branches
|
|
361
381
|
if (isPrivileged(req.user.groups)) {
|
|
362
382
|
return {
|
|
363
383
|
ok: true,
|
|
364
384
|
status: 200,
|
|
365
|
-
data: {
|
|
385
|
+
data: {
|
|
386
|
+
branches: allBranches.map((context) => toBranchListItem(ctx.services.config, context.branch)),
|
|
387
|
+
defaultBranch,
|
|
388
|
+
},
|
|
366
389
|
};
|
|
367
390
|
}
|
|
368
391
|
// Regular users only see branches they created or have explicit access to
|
|
@@ -390,7 +413,10 @@ export const listBranchesHandler = async (ctx, req) => {
|
|
|
390
413
|
return {
|
|
391
414
|
ok: true,
|
|
392
415
|
status: 200,
|
|
393
|
-
data: {
|
|
416
|
+
data: {
|
|
417
|
+
branches: visibleBranches.map((context) => toBranchListItem(ctx.services.config, context.branch)),
|
|
418
|
+
defaultBranch,
|
|
419
|
+
},
|
|
394
420
|
};
|
|
395
421
|
};
|
|
396
422
|
/** @internal Exported for tests. */
|
|
@@ -456,48 +482,39 @@ export const deleteBranchHandler = async (ctx, req, params) => {
|
|
|
456
482
|
error: 'Cannot delete branch with open pull request',
|
|
457
483
|
};
|
|
458
484
|
}
|
|
459
|
-
//
|
|
460
|
-
//
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
//
|
|
464
|
-
//
|
|
465
|
-
//
|
|
485
|
+
// The branch directory leaves its name by one rename, under the same
|
|
486
|
+
// server-enforced lockfile branch-metadata saves hold (see
|
|
487
|
+
// utils/occ-json-write.ts), so a save() that reaches that lock after the
|
|
488
|
+
// rename fails rather than recreating the tree, and a process killed during
|
|
489
|
+
// the `rm` leaves
|
|
490
|
+
// only a `.deleting-*` directory the worker sweeps, never residue under a
|
|
491
|
+
// name that may be created again.
|
|
466
492
|
const metadataFile = path.join(branchContext.branchRoot, '.canopy-meta', 'branch.json');
|
|
467
493
|
let cleanupWarning;
|
|
494
|
+
let deletingPath;
|
|
468
495
|
try {
|
|
469
496
|
await withOccFileLock(metadataFile, async () => {
|
|
497
|
+
if (branchContext.branchRoot === branchContext.baseRoot) {
|
|
498
|
+
await fs.unlink(metadataFile).catch((err) => {
|
|
499
|
+
if (!isNotFoundError(err))
|
|
500
|
+
throw err;
|
|
501
|
+
});
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
const target = path.join(branchContext.baseRoot, deletingDirName(path.basename(branchContext.branchRoot)));
|
|
470
505
|
try {
|
|
471
|
-
await fs.
|
|
506
|
+
await fs.rename(branchContext.branchRoot, target);
|
|
507
|
+
deletingPath = target;
|
|
472
508
|
}
|
|
473
509
|
catch (err) {
|
|
474
|
-
if (
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
// deleted and will no longer appear in listings), but silently
|
|
483
|
-
// succeeding here would leave a full orphan clone on disk with
|
|
484
|
-
// nothing in the API surfacing its existence.
|
|
485
|
-
if (branchContext.branchRoot !== branchContext.baseRoot) {
|
|
486
|
-
try {
|
|
487
|
-
await fs.rm(branchContext.branchRoot, {
|
|
488
|
-
recursive: true,
|
|
489
|
-
force: true,
|
|
490
|
-
maxRetries: 3,
|
|
491
|
-
retryDelay: 100,
|
|
492
|
-
});
|
|
493
|
-
}
|
|
494
|
-
catch (err) {
|
|
495
|
-
// sanitizeErrorMessage: this string goes back to the browser in the
|
|
496
|
-
// delete response (API-H2 — no absolute EFS paths to clients);
|
|
497
|
-
// the console line below keeps the raw detail for server logs.
|
|
498
|
-
cleanupWarning = `Failed to fully remove branch directory: ${sanitizeErrorMessage(getErrorMessage(err))}`;
|
|
499
|
-
console.error(`CanopyCMS: Failed to delete branch directory for ${branchName}:`, getErrorMessage(err));
|
|
500
|
-
}
|
|
510
|
+
if (isNotFoundError(err))
|
|
511
|
+
return;
|
|
512
|
+
// Still deleted logically: without branch.json the branch is unlisted,
|
|
513
|
+
// and the worker quarantines what is left.
|
|
514
|
+
await fs.unlink(metadataFile).catch(() => { });
|
|
515
|
+
// [REDACT] Returned to the browser; the console line keeps the path.
|
|
516
|
+
cleanupWarning = `Failed to remove branch directory: ${sanitizeErrorMessage(getErrorMessage(err))}`;
|
|
517
|
+
console.error(`CanopyCMS: Failed to move branch directory for ${branchName} aside:`, getErrorMessage(err));
|
|
501
518
|
}
|
|
502
519
|
});
|
|
503
520
|
}
|
|
@@ -510,6 +527,8 @@ export const deleteBranchHandler = async (ctx, req, params) => {
|
|
|
510
527
|
error: `Branch is busy, try again: ${getErrorMessage(err)}`,
|
|
511
528
|
};
|
|
512
529
|
}
|
|
530
|
+
if (deletingPath)
|
|
531
|
+
await removeLeftoverDir(deletingPath);
|
|
513
532
|
// Also delete the branch's local head from the remote.git mirror (the
|
|
514
533
|
// deployment's local origin). The sync loop deliberately never deletes a
|
|
515
534
|
// local head (see reconcileTrackedBranches), so THIS is the one explicit
|
|
@@ -646,7 +665,7 @@ const createBranch = defineEndpoint({
|
|
|
646
665
|
path: '/branches',
|
|
647
666
|
body: createBranchBodySchema,
|
|
648
667
|
bodyType: 'CreateBranchBody',
|
|
649
|
-
responseType: '
|
|
668
|
+
responseType: 'BranchListItemResponse',
|
|
650
669
|
response: {},
|
|
651
670
|
defaultMockData: {
|
|
652
671
|
branch: {
|