canopycms 0.0.67 → 0.0.68-int.101
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 +24 -17
- 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 +118 -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 +19 -1
- package/dist/api/github-sync.js +57 -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/finalize.js +3 -0
- package/dist/assets/index.d.ts +1 -1
- package/dist/assets/materialize.d.ts +153 -0
- package/dist/assets/materialize.js +402 -0
- package/dist/assets/store-local.d.ts +19 -5
- package/dist/assets/store-local.js +69 -10
- package/dist/assets/store-s3.d.ts +38 -7
- package/dist/assets/store-s3.js +128 -22
- 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 +31 -4
- 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 +52 -0
- package/dist/cli/asset-refs.js +194 -0
- package/dist/cli/cli.d.ts +15 -1
- package/dist/cli/cli.js +5928 -2195
- package/dist/cli/configured-asset-store.d.ts +12 -0
- package/dist/cli/configured-asset-store.js +46 -0
- package/dist/cli/generate-ai-content.js +2185 -587
- 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/client.d.ts +1 -1
- package/dist/client.js +1 -1
- 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 +32 -22
- package/dist/editor/FormRenderer.js +34 -18
- package/dist/editor/PreviewFrame.d.ts +24 -0
- package/dist/editor/PreviewFrame.js +140 -0
- 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 +21 -0
- package/dist/editor/hooks/useBranchManager.js +204 -37
- 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 +9 -21
- package/dist/editor/preview-bridge.js +19 -139
- 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 +15 -0
- package/dist/github-service.js +19 -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/preview.d.ts +8 -0
- package/dist/preview.js +7 -0
- 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 +64 -9
- package/dist/server.js +48 -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-serialize.d.ts +5 -2
- package/dist/utils/content-serialize.js +98 -24
- 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/utils/yaml-source-splice.d.ts +58 -0
- package/dist/utils/yaml-source-splice.js +515 -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 +99 -14
- package/dist/worker/worker-context.d.ts +6 -1
- package/package.json +8 -2
package/dist/assets/types.d.ts
CHANGED
|
@@ -41,6 +41,12 @@ export interface PublicObject {
|
|
|
41
41
|
contentDisposition?: string;
|
|
42
42
|
cacheControl?: string;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* What a create-only write did. Every key the store writes this way is content-addressed, so no
|
|
46
|
+
* correct writer ever replaces an object: `already-exists` means an object was already at the key
|
|
47
|
+
* and was left alone.
|
|
48
|
+
*/
|
|
49
|
+
export type CreateOnlyResult = 'created' | 'already-exists';
|
|
44
50
|
export interface AssetStore {
|
|
45
51
|
readonly capabilities: {
|
|
46
52
|
directUpload: boolean;
|
|
@@ -49,26 +55,47 @@ export interface AssetStore {
|
|
|
49
55
|
writeStaging(stagingKey: string, data: Uint8Array, contentType?: string): Promise<void>;
|
|
50
56
|
readStaging(stagingKey: string): Promise<Uint8Array | null>;
|
|
51
57
|
deleteStaging(stagingKey: string): Promise<void>;
|
|
58
|
+
/** Create-only; see `CreateOnlyResult`. */
|
|
52
59
|
putOriginal(input: {
|
|
53
60
|
hash32: string;
|
|
54
61
|
ext: string;
|
|
55
62
|
data: Uint8Array;
|
|
56
63
|
contentType: string;
|
|
57
|
-
}): Promise<
|
|
58
|
-
|
|
64
|
+
}): Promise<CreateOnlyResult>;
|
|
65
|
+
/**
|
|
66
|
+
* `ext` is where the original is expected (its meta's `ext`): that key is read first, and any
|
|
67
|
+
* `{hash32}.*` is looked for only on a miss. A hit needs no list permission; on S3 without
|
|
68
|
+
* `s3:ListBucket` a miss is a 403, which throws.
|
|
69
|
+
*/
|
|
70
|
+
readOriginal(hash32: string, ext?: string): Promise<{
|
|
59
71
|
data: Uint8Array;
|
|
60
72
|
ext: string;
|
|
61
73
|
contentType?: string;
|
|
62
74
|
} | null>;
|
|
75
|
+
/** Create-only; see `CreateOnlyResult`. */
|
|
63
76
|
putPublicObject(input: {
|
|
64
77
|
key: string;
|
|
65
78
|
data: Uint8Array;
|
|
66
79
|
contentType: string;
|
|
67
80
|
contentDisposition?: string;
|
|
68
81
|
cacheControl?: string;
|
|
69
|
-
|
|
82
|
+
/** Object tags; bucket lifecycle rules can filter on them. */
|
|
83
|
+
tags?: Readonly<Record<string, string>>;
|
|
84
|
+
}): Promise<CreateOnlyResult>;
|
|
85
|
+
/**
|
|
86
|
+
* Copy the public object at `sourceKey` to `destKey` inside the store, create-only like
|
|
87
|
+
* `putPublicObject`. The copy keeps the source's headers and carries no tags. `source-missing`
|
|
88
|
+
* when nothing is at `sourceKey`.
|
|
89
|
+
*/
|
|
90
|
+
copyPublicObject(sourceKey: string, destKey: string): Promise<CreateOnlyResult | 'source-missing'>;
|
|
70
91
|
readPublicObject(key: string): Promise<PublicObject | null>;
|
|
71
|
-
|
|
92
|
+
/** Whether a public object exists at `key`, without reading its body. */
|
|
93
|
+
hasPublicObject(key: string): Promise<boolean>;
|
|
94
|
+
/** Every public object key under `prefix`, in the store's own pages. S3 only. */
|
|
95
|
+
listPublicObjectKeys?(prefix: string): AsyncIterable<string>;
|
|
96
|
+
/** A short-lived URL a browser can GET `key` from directly, or `null` if absent. S3 only. */
|
|
97
|
+
presignPublicObjectRead?(key: string): Promise<string | null>;
|
|
98
|
+
putMetaIfAbsent(hash32: string, meta: AssetMeta): Promise<CreateOnlyResult>;
|
|
72
99
|
getMeta(hash32: string): Promise<AssetMeta | null>;
|
|
73
100
|
listMeta(input?: {
|
|
74
101
|
cursor?: string;
|
|
@@ -168,14 +168,13 @@ export async function writeAuthCacheSnapshot(cachePath, files) {
|
|
|
168
168
|
await fs.rename(tmpPath, finalPath);
|
|
169
169
|
}
|
|
170
170
|
// The symlink target MUST be relative (the bare `snapshot-<ts>` basename),
|
|
171
|
-
// because writer and reader
|
|
172
|
-
//
|
|
173
|
-
//
|
|
174
|
-
//
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
// was given, so it is correct from both.
|
|
171
|
+
// because writer and reader are separate hosts (in prod, the EC2 worker and
|
|
172
|
+
// the CMS Lambda) that each mount EFS themselves, so nothing guarantees they
|
|
173
|
+
// reach this directory by the same path. An absolute target recorded by one
|
|
174
|
+
// can be a nonexistent path to the other, and `resolveActiveCacheDir`'s
|
|
175
|
+
// escape guard then falls back to the flat layout, where the worker never
|
|
176
|
+
// writes, leaving the reader a permanently empty cache. A relative target
|
|
177
|
+
// resolves against whichever cachePath the reader was given.
|
|
179
178
|
const currentLink = path.join(cachePath, 'current');
|
|
180
179
|
const tmpLink = path.join(cachePath, `current-${timestamp}`);
|
|
181
180
|
await fs.symlink(path.basename(snapshotDir), tmpLink);
|
|
@@ -6,12 +6,13 @@ import type { BranchContext } from '../types.js';
|
|
|
6
6
|
import type { PermissionLevel } from '../config/index.js';
|
|
7
7
|
import type { CanopyUser } from '../user.js';
|
|
8
8
|
import type { ContentAccessResult, ContentAccessDeps } from './types.js';
|
|
9
|
-
import type {
|
|
9
|
+
import type { LogicalPath } from '../paths/types.js';
|
|
10
10
|
/**
|
|
11
11
|
* A bound content-access checker: evaluates a single path/level synchronously
|
|
12
12
|
* against permissions that were already loaded when the checker was created.
|
|
13
|
+
* The path is the entry's logical path (see `checkPathAccess`).
|
|
13
14
|
*/
|
|
14
|
-
export type ContentAccessChecker = (
|
|
15
|
+
export type ContentAccessChecker = (logicalPath: LogicalPath, level: PermissionLevel) => ContentAccessResult;
|
|
15
16
|
/**
|
|
16
17
|
* Resolve the request-constant work once -- branch access, the
|
|
17
18
|
* settings/permissions root and the path-permission rules, all identical for a
|
|
@@ -26,5 +27,5 @@ export declare function createContentAccessChecker(deps: ContentAccessDeps, cont
|
|
|
26
27
|
* Single-path check; loads the path permissions from the branch root per call.
|
|
27
28
|
* @internal Exported for tests.
|
|
28
29
|
*/
|
|
29
|
-
export declare function checkContentAccess(deps: ContentAccessDeps, context: BranchContext, branchRoot: string,
|
|
30
|
-
export declare function createCheckContentAccess(deps: ContentAccessDeps): (context: BranchContext, branchRoot: string,
|
|
30
|
+
export declare function checkContentAccess(deps: ContentAccessDeps, context: BranchContext, branchRoot: string, logicalPath: LogicalPath, user: CanopyUser, level: PermissionLevel): Promise<ContentAccessResult>;
|
|
31
|
+
export declare function createCheckContentAccess(deps: ContentAccessDeps): (context: BranchContext, branchRoot: string, logicalPath: LogicalPath, user: CanopyUser, level: PermissionLevel) => Promise<ContentAccessResult>;
|
|
@@ -28,9 +28,9 @@ export async function createContentAccessChecker(deps, context, branchRoot, user
|
|
|
28
28
|
}
|
|
29
29
|
const rules = await deps.loadPathPermissions(permissionsRoot, deps.mode);
|
|
30
30
|
const pathChecker = createCheckPathAccess(rules, deps.defaultPathAccess);
|
|
31
|
-
return (
|
|
31
|
+
return (logicalPath, level) => {
|
|
32
32
|
const path = pathChecker({
|
|
33
|
-
|
|
33
|
+
logicalPath,
|
|
34
34
|
user,
|
|
35
35
|
level,
|
|
36
36
|
});
|
|
@@ -45,10 +45,10 @@ export async function createContentAccessChecker(deps, context, branchRoot, user
|
|
|
45
45
|
* Single-path check; loads the path permissions from the branch root per call.
|
|
46
46
|
* @internal Exported for tests.
|
|
47
47
|
*/
|
|
48
|
-
export async function checkContentAccess(deps, context, branchRoot,
|
|
48
|
+
export async function checkContentAccess(deps, context, branchRoot, logicalPath, user, level) {
|
|
49
49
|
const check = await createContentAccessChecker(deps, context, branchRoot, user);
|
|
50
|
-
return check(
|
|
50
|
+
return check(logicalPath, level);
|
|
51
51
|
}
|
|
52
52
|
export function createCheckContentAccess(deps) {
|
|
53
|
-
return (context, branchRoot,
|
|
53
|
+
return (context, branchRoot, logicalPath, user, level) => checkContentAccess(deps, context, branchRoot, logicalPath, user, level);
|
|
54
54
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { PathPermission, DefaultPathAccess, PermissionLevel } from '../config/index.js';
|
|
2
2
|
import type { CanopyUser } from '../user.js';
|
|
3
3
|
import type { PathPermissionResult } from './types.js';
|
|
4
|
-
import type {
|
|
4
|
+
import type { LogicalPath } from '../paths/types.js';
|
|
5
5
|
/**
|
|
6
6
|
* Resolve `defaultPathAccess` to an 'allow'/'deny' verdict for one permission
|
|
7
7
|
* level. String form applies to every level; object form looks up the level,
|
|
@@ -11,19 +11,24 @@ import type { PhysicalPath } from '../paths/types.js';
|
|
|
11
11
|
*/
|
|
12
12
|
export declare function resolveDefaultPathAccess(defaultAccess: DefaultPathAccess, level: PermissionLevel): 'allow' | 'deny';
|
|
13
13
|
/**
|
|
14
|
-
* Evaluate access for a
|
|
15
|
-
*
|
|
14
|
+
* Evaluate access for a path against config-defined rules. First matching rule wins;
|
|
15
|
+
* defaultAccess applies when none matches.
|
|
16
|
+
*
|
|
17
|
+
* Rule globs and the checked path are both LOGICAL paths: content-root-prefixed and
|
|
18
|
+
* id-free, the space the Permission Manager writes rules in. An entry's logical path is
|
|
19
|
+
* `<collection logical path>/<slug>` (`entryLogicalPath`), e.g. `content/blog/my-post`, never
|
|
20
|
+
* its on-disk `content/blog.<id>/post.my-post.<id>.json`.
|
|
16
21
|
* @internal Exported for tests.
|
|
17
22
|
*/
|
|
18
|
-
export declare function checkPathAccess({ rules,
|
|
23
|
+
export declare function checkPathAccess({ rules, logicalPath, user, defaultAccess, level, }: {
|
|
19
24
|
rules: PathPermission[];
|
|
20
|
-
|
|
25
|
+
logicalPath: LogicalPath;
|
|
21
26
|
user: CanopyUser;
|
|
22
27
|
defaultAccess: DefaultPathAccess;
|
|
23
28
|
level: PermissionLevel;
|
|
24
29
|
}): PathPermissionResult;
|
|
25
30
|
export declare function createCheckPathAccess(rules: PathPermission[], defaultAccess: DefaultPathAccess): (input: {
|
|
26
|
-
|
|
31
|
+
logicalPath: LogicalPath;
|
|
27
32
|
user: CanopyUser;
|
|
28
33
|
level: PermissionLevel;
|
|
29
34
|
}) => PathPermissionResult;
|
|
@@ -5,8 +5,8 @@ function normalize(p) {
|
|
|
5
5
|
const normalized = p.split(path.sep).join('/');
|
|
6
6
|
return normalized.replace(/^\.?\/*/, '');
|
|
7
7
|
}
|
|
8
|
-
function matchesRule(rule,
|
|
9
|
-
return minimatch(
|
|
8
|
+
function matchesRule(rule, logicalPath) {
|
|
9
|
+
return minimatch(logicalPath, rule.path, { dot: true });
|
|
10
10
|
}
|
|
11
11
|
function isAllowedByTarget(target, user) {
|
|
12
12
|
const hasUserConstraint = !!target.allowedUsers?.length;
|
|
@@ -32,12 +32,17 @@ export function resolveDefaultPathAccess(defaultAccess, level) {
|
|
|
32
32
|
return defaultAccess[level] ?? 'deny';
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
* Evaluate access for a
|
|
36
|
-
*
|
|
35
|
+
* Evaluate access for a path against config-defined rules. First matching rule wins;
|
|
36
|
+
* defaultAccess applies when none matches.
|
|
37
|
+
*
|
|
38
|
+
* Rule globs and the checked path are both LOGICAL paths: content-root-prefixed and
|
|
39
|
+
* id-free, the space the Permission Manager writes rules in. An entry's logical path is
|
|
40
|
+
* `<collection logical path>/<slug>` (`entryLogicalPath`), e.g. `content/blog/my-post`, never
|
|
41
|
+
* its on-disk `content/blog.<id>/post.my-post.<id>.json`.
|
|
37
42
|
* @internal Exported for tests.
|
|
38
43
|
*/
|
|
39
|
-
export function checkPathAccess({ rules,
|
|
40
|
-
const normalizedPath = normalize(
|
|
44
|
+
export function checkPathAccess({ rules, logicalPath, user, defaultAccess, level, }) {
|
|
45
|
+
const normalizedPath = normalize(logicalPath);
|
|
41
46
|
// Only Admins bypass all path permissions
|
|
42
47
|
if (isAdmin(user.groups)) {
|
|
43
48
|
return { allowed: true, reason: 'admin' };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* A path used in permission rules, e.g. "content/posts"
|
|
3
|
-
* SECURITY CRITICAL: always validated to prevent path traversal.
|
|
2
|
+
* A logical-path glob used in permission rules, e.g. "content/posts/**" (see
|
|
3
|
+
* `PathPermission.path`). SECURITY CRITICAL: always validated to prevent path traversal.
|
|
4
4
|
*/
|
|
5
5
|
export type PermissionPath = string & {
|
|
6
6
|
readonly __brand: 'PermissionPath';
|
package/dist/branch-health.d.ts
CHANGED
|
@@ -6,13 +6,19 @@ import { type DuplicateContentId } from './content-id-index.js';
|
|
|
6
6
|
* Without it two stuck states have no in-product recovery, and prod admins have
|
|
7
7
|
* no filesystem access to see them: a branch dir whose `.canopy-meta/branch.json`
|
|
8
8
|
* is corrupt (the registry drops it silently) and an orphan dir with no
|
|
9
|
-
* `branch.json` at all (a
|
|
9
|
+
* `branch.json` at all (a clone or delete killed in place, until the worker
|
|
10
|
+
* quarantines it — see branch-provisioning.ts).
|
|
10
11
|
*
|
|
11
12
|
* `scanBranchHealth` mirrors the registry's own directory-listing rules (skip
|
|
12
13
|
* non-directories and dot-prefixed names) so the two never disagree about what
|
|
13
14
|
* counts as missing.
|
|
14
15
|
*/
|
|
15
16
|
type BranchHealthKind = 'healthy' | 'corrupt-metadata' | 'orphan';
|
|
17
|
+
/**
|
|
18
|
+
* An orphan younger than this may still be a clone in progress: admin purge refuses it and the
|
|
19
|
+
* worker does not quarantine it. Corrupt-metadata dirs are exempt.
|
|
20
|
+
*/
|
|
21
|
+
export declare const ORPHAN_YOUTH_THRESHOLD_MS: number;
|
|
16
22
|
export interface BranchHealthEntry {
|
|
17
23
|
dirName: string;
|
|
18
24
|
kind: BranchHealthKind;
|
package/dist/branch-health.js
CHANGED
|
@@ -5,16 +5,21 @@ import { ContentIdIndex } from './content-id-index.js';
|
|
|
5
5
|
import { sanitizeBranchName } from './paths/branch-name.js';
|
|
6
6
|
import { getErrorMessage, isNodeError, isNotFoundError } from './utils/error.js';
|
|
7
7
|
import { isRebaseInProgress } from './utils/git.js';
|
|
8
|
+
import { branchProvisioningLockName } from './utils/provisioning-lock.js';
|
|
8
9
|
/**
|
|
9
|
-
*
|
|
10
|
-
*
|
|
10
|
+
* An orphan younger than this may still be a clone in progress: admin purge refuses it and the
|
|
11
|
+
* worker does not quarantine it. Corrupt-metadata dirs are exempt.
|
|
12
|
+
*/
|
|
13
|
+
export const ORPHAN_YOUTH_THRESHOLD_MS = 15 * 60_000;
|
|
14
|
+
/**
|
|
15
|
+
* The provisioning lock marker's path for a branch directory.
|
|
11
16
|
*
|
|
12
17
|
* `acquireProvisioningLock` passes the name as `lockfilePath`, overriding
|
|
13
18
|
* proper-lockfile's default `${target}.lock`, so the marker (a mkdir-based
|
|
14
19
|
* directory) sits at exactly this path — no extra `.lock` suffix.
|
|
15
20
|
*/
|
|
16
21
|
function provisioningLockPath(baseRoot, dirName) {
|
|
17
|
-
return path.join(baseRoot,
|
|
22
|
+
return path.join(baseRoot, branchProvisioningLockName(dirName));
|
|
18
23
|
}
|
|
19
24
|
/** Stat the provisioning lock marker, if present. Never throws. */
|
|
20
25
|
async function readProvisioningLock(baseRoot, dirName) {
|
|
@@ -63,16 +63,16 @@ export declare class BranchMetadataFileManager {
|
|
|
63
63
|
* entry with no clone behind it. It runs BEFORE the lock stack so a doomed
|
|
64
64
|
* save fails fast instead of paying for a lock.
|
|
65
65
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* lockfile taken next lives INSIDE `branchRoot`, so it can promise no more
|
|
70
|
-
* than "the directory existed a moment ago".
|
|
66
|
+
* A save that passes the check just before a delete still cannot write: the
|
|
67
|
+
* delete renames the tree away under the same lockfile, and that lockfile
|
|
68
|
+
* creates only its own directory (`withOccFileLock`), so the save fails there.
|
|
71
69
|
*/
|
|
72
70
|
save(incoming: BranchMetadataUpdate): Promise<BranchMetadataFile>;
|
|
73
71
|
/** Invalidate the registry cache so the next list() regenerates. */
|
|
74
72
|
private invalidateRegistry;
|
|
75
73
|
}
|
|
74
|
+
/** The branch.json a brand-new branch starts with: {@link mergeBranchMetadata} over nothing. */
|
|
75
|
+
export declare function buildInitialBranchMetadata(incoming: BranchMetadataUpdate, now?: string): BranchMetadataFile;
|
|
76
76
|
/**
|
|
77
77
|
* Fields save() accepts. `createdBy` applies on creation only and is ignored
|
|
78
78
|
* once metadata exists; createdAt/updatedAt are managed here, not by callers.
|
package/dist/branch-metadata.js
CHANGED
|
@@ -101,11 +101,9 @@ export class BranchMetadataFileManager {
|
|
|
101
101
|
* entry with no clone behind it. It runs BEFORE the lock stack so a doomed
|
|
102
102
|
* save fails fast instead of paying for a lock.
|
|
103
103
|
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
* lockfile taken next lives INSIDE `branchRoot`, so it can promise no more
|
|
108
|
-
* than "the directory existed a moment ago".
|
|
104
|
+
* A save that passes the check just before a delete still cannot write: the
|
|
105
|
+
* delete renames the tree away under the same lockfile, and that lockfile
|
|
106
|
+
* creates only its own directory (`withOccFileLock`), so the save fails there.
|
|
109
107
|
*/
|
|
110
108
|
async save(incoming) {
|
|
111
109
|
try {
|
|
@@ -121,37 +119,7 @@ export class BranchMetadataFileManager {
|
|
|
121
119
|
try {
|
|
122
120
|
saved = await withLock(this.filePath, () => withOccFileLock(this.filePath, () => withOccRetry(async () => {
|
|
123
121
|
const { meta: existing, version } = await this.load();
|
|
124
|
-
const
|
|
125
|
-
const defaults = {
|
|
126
|
-
name: 'unknown',
|
|
127
|
-
status: 'editing',
|
|
128
|
-
access: {},
|
|
129
|
-
createdBy: 'unknown',
|
|
130
|
-
createdAt: now,
|
|
131
|
-
updatedAt: now,
|
|
132
|
-
};
|
|
133
|
-
const merged = {
|
|
134
|
-
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
135
|
-
version: version ?? 0,
|
|
136
|
-
branch: {
|
|
137
|
-
...defaults,
|
|
138
|
-
...existing?.branch,
|
|
139
|
-
...incoming.branch,
|
|
140
|
-
access: {
|
|
141
|
-
...existing?.branch?.access,
|
|
142
|
-
...incoming.branch?.access,
|
|
143
|
-
},
|
|
144
|
-
// Immutable after creation
|
|
145
|
-
createdBy: existing?.branch.createdBy ?? incoming.branch?.createdBy ?? defaults.createdBy,
|
|
146
|
-
createdAt: existing?.branch.createdAt ?? defaults.createdAt,
|
|
147
|
-
// Fork point is recorded once at creation; later saves must not move it
|
|
148
|
-
baseBranch: existing?.branch.baseBranch ?? incoming.branch?.baseBranch,
|
|
149
|
-
// Always stamped fresh; the spreads above would otherwise let
|
|
150
|
-
// the creation-time value win forever, freezing the timestamp
|
|
151
|
-
// the editor's Branches panel sorts and displays by
|
|
152
|
-
updatedAt: now,
|
|
153
|
-
},
|
|
154
|
-
};
|
|
122
|
+
const merged = mergeBranchMetadata(existing, version, incoming);
|
|
155
123
|
const written = await this.write(merged, version);
|
|
156
124
|
merged.version = written.version;
|
|
157
125
|
merged.writeId = written.writeId;
|
|
@@ -178,6 +146,46 @@ export class BranchMetadataFileManager {
|
|
|
178
146
|
await registry.invalidate();
|
|
179
147
|
}
|
|
180
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* The branch.json that {@link BranchMetadataFileManager.save} writes for `incoming` over
|
|
151
|
+
* `existing`. Pure, so a staged workspace's first branch.json (branch-provisioning.ts) is built by
|
|
152
|
+
* the same rules as a save and the two cannot drift.
|
|
153
|
+
*/
|
|
154
|
+
function mergeBranchMetadata(existing, version, incoming, now = new Date().toISOString()) {
|
|
155
|
+
const defaults = {
|
|
156
|
+
name: 'unknown',
|
|
157
|
+
status: 'editing',
|
|
158
|
+
access: {},
|
|
159
|
+
createdBy: 'unknown',
|
|
160
|
+
createdAt: now,
|
|
161
|
+
updatedAt: now,
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
schemaVersion: CURRENT_SCHEMA_VERSION,
|
|
165
|
+
version: version ?? 0,
|
|
166
|
+
branch: {
|
|
167
|
+
...defaults,
|
|
168
|
+
...existing?.branch,
|
|
169
|
+
...incoming.branch,
|
|
170
|
+
access: {
|
|
171
|
+
...existing?.branch?.access,
|
|
172
|
+
...incoming.branch?.access,
|
|
173
|
+
},
|
|
174
|
+
// Immutable after creation
|
|
175
|
+
createdBy: existing?.branch.createdBy ?? incoming.branch?.createdBy ?? defaults.createdBy,
|
|
176
|
+
createdAt: existing?.branch.createdAt ?? defaults.createdAt,
|
|
177
|
+
// Fork point is recorded once at creation; later saves must not move it
|
|
178
|
+
baseBranch: existing?.branch.baseBranch ?? incoming.branch?.baseBranch,
|
|
179
|
+
// Always stamped fresh; the spreads above would otherwise let the creation-time value win
|
|
180
|
+
// forever, freezing the timestamp the editor's Branches panel sorts and displays by
|
|
181
|
+
updatedAt: now,
|
|
182
|
+
},
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
/** The branch.json a brand-new branch starts with: {@link mergeBranchMetadata} over nothing. */
|
|
186
|
+
export function buildInitialBranchMetadata(incoming, now) {
|
|
187
|
+
return mergeBranchMetadata(null, null, incoming, now);
|
|
188
|
+
}
|
|
181
189
|
/**
|
|
182
190
|
* The metadata update for archiving a branch whose PR merged. Shared by the
|
|
183
191
|
* worker's merge-poll (CmsWorker.pollMergeState) and the manual markAsMerged
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { type BranchMetadataFile } from './branch-metadata-file.js';
|
|
2
|
+
import { type CloneRepoOptions } from './git-manager.js';
|
|
3
|
+
import type { ProvisionLog } from './utils/provision-log.js';
|
|
4
|
+
/** How long residue must have been untouched before a create request moves it aside. */
|
|
5
|
+
export declare const CREATE_RESIDUE_MIN_QUIET_MS = 60000;
|
|
6
|
+
/**
|
|
7
|
+
* Retriable: another process is setting this branch up, or what is at its path is too recently
|
|
8
|
+
* touched (or under an admin repair) to judge. Callers answer 503.
|
|
9
|
+
*/
|
|
10
|
+
export declare class BranchProvisioningBusyError extends Error {
|
|
11
|
+
readonly dirName: string;
|
|
12
|
+
constructor(dirName: string, message?: string);
|
|
13
|
+
}
|
|
14
|
+
/** The branch's path holds a git repository this deployment did not create (prod only). */
|
|
15
|
+
export declare class BranchDirOccupiedError extends Error {
|
|
16
|
+
readonly dirName: string;
|
|
17
|
+
constructor(dirName: string);
|
|
18
|
+
}
|
|
19
|
+
/** `YYYYMMDDTHHMMSSZ` in UTC, with no colons. */
|
|
20
|
+
export declare function formatDirStamp(date: Date): string;
|
|
21
|
+
/**
|
|
22
|
+
* The date in a name ending `-{STAMP}`, or null. Leftovers are aged by this alone, never by
|
|
23
|
+
* mtime: `rename()` keeps a directory's mtime, so a months-old residue would look old the
|
|
24
|
+
* moment it was moved.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseDirStamp(name: string): Date | null;
|
|
27
|
+
/**
|
|
28
|
+
* A branch directory name cut to a bounded, still-unique form: branch names run to 250 chars.
|
|
29
|
+
* @internal Exported for tests.
|
|
30
|
+
*/
|
|
31
|
+
export declare function shortDirName(dirName: string): string;
|
|
32
|
+
/** Where a branch directory goes the moment it is deleted, before its `rm`. */
|
|
33
|
+
export declare const deletingDirName: (dirName: string, now?: Date) => string;
|
|
34
|
+
/** The settings workspace's staging and trash siblings, under the workspace root. */
|
|
35
|
+
export declare const settingsStagingDirName: (now?: Date) => string;
|
|
36
|
+
export declare const settingsTrashDirName: (now?: Date) => string;
|
|
37
|
+
interface ResidueSignature {
|
|
38
|
+
hasGitDir: boolean;
|
|
39
|
+
configLock: boolean;
|
|
40
|
+
hasIndex: boolean;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* What occupies a branch's final path.
|
|
44
|
+
*
|
|
45
|
+
* - `live` / `corrupt`: branch.json is there. Never touched by provisioning.
|
|
46
|
+
* - `protected`: an admin repair-metadata is mid-way (`branch.json.corrupt-*` with no
|
|
47
|
+
* branch.json), or branch.json vanished between the listing and the read. Left alone.
|
|
48
|
+
* - `residue`: nothing this deployment could still be using — no `.git`, or a `.git` whose config
|
|
49
|
+
* is missing or unreadable (an interrupted clone), or one whose remote is this deployment's own
|
|
50
|
+
* (an interrupted clone or delete).
|
|
51
|
+
* - `foreign`: any other repository, such as a dev CLI-sync workspace, which has no remote.
|
|
52
|
+
*/
|
|
53
|
+
export type FinalDirState = {
|
|
54
|
+
kind: 'vacant';
|
|
55
|
+
} | {
|
|
56
|
+
kind: 'live';
|
|
57
|
+
} | {
|
|
58
|
+
kind: 'corrupt';
|
|
59
|
+
} | {
|
|
60
|
+
kind: 'protected';
|
|
61
|
+
} | {
|
|
62
|
+
kind: 'foreign';
|
|
63
|
+
} | {
|
|
64
|
+
kind: 'residue';
|
|
65
|
+
signature: ResidueSignature;
|
|
66
|
+
};
|
|
67
|
+
/** Classify what is at `dirPath`; see {@link FinalDirState}. */
|
|
68
|
+
export declare function classifyFinalDir(dirPath: string, expectedRemoteUrl: string): Promise<FinalDirState>;
|
|
69
|
+
/**
|
|
70
|
+
* Milliseconds since anything was last created or removed in the directory, its `.git` or its
|
|
71
|
+
* `.canopy-meta`. A clone killed inside `git config` touched `.git` when it made `config.lock`.
|
|
72
|
+
*/
|
|
73
|
+
export declare function quietAgeMs(dirPath: string, now?: number): Promise<number>;
|
|
74
|
+
type KeptReason = 'live' | 'corrupt' | 'protected' | 'foreign' | 'too-young' | 'replaced';
|
|
75
|
+
export type QuarantineResult = {
|
|
76
|
+
kind: 'quarantined';
|
|
77
|
+
trashName: string;
|
|
78
|
+
} | {
|
|
79
|
+
kind: 'vacant';
|
|
80
|
+
} | {
|
|
81
|
+
kind: 'kept';
|
|
82
|
+
reason: KeptReason;
|
|
83
|
+
};
|
|
84
|
+
export interface QuarantineOptions {
|
|
85
|
+
minQuietMs: number;
|
|
86
|
+
expectedRemoteUrl: string;
|
|
87
|
+
now?: number;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Move residue at `baseRoot/dirName` to `.trash-*`. The caller MUST hold the branch's
|
|
91
|
+
* provisioning lock: admin repair-metadata holds it while it legitimately leaves a directory
|
|
92
|
+
* with real edits and no branch.json.
|
|
93
|
+
*
|
|
94
|
+
* Move-then-verify: the name is renamed aside first and only what arrived at the new name is
|
|
95
|
+
* judged — the same inode the old name showed, still residue, quiet for `minQuietMs` — because
|
|
96
|
+
* a look through the old name can come from a stale NFS dentry for a directory another host has
|
|
97
|
+
* since replaced with a live clone. Anything that fails a check, or cannot be checked, is
|
|
98
|
+
* renamed straight back: the sweep would trash a `.repair-*` without branch.json.
|
|
99
|
+
*/
|
|
100
|
+
export declare function quarantineResidueAt(baseRoot: string, dirName: string, options: QuarantineOptions): Promise<QuarantineResult>;
|
|
101
|
+
export interface StageBranchOptions {
|
|
102
|
+
baseRoot: string;
|
|
103
|
+
dirName: string;
|
|
104
|
+
branchName: string;
|
|
105
|
+
baseBranch: string;
|
|
106
|
+
remoteUrl: string;
|
|
107
|
+
clone: Pick<CloneRepoOptions, 'remoteName' | 'config'>;
|
|
108
|
+
gitExcludePattern: string;
|
|
109
|
+
metadata: BranchMetadataFile;
|
|
110
|
+
provisionLog: ProvisionLog;
|
|
111
|
+
/** The sparse-checkout cone (branch-sparse.ts), set before the checkout; none for a full clone. */
|
|
112
|
+
sparseCone?: readonly string[] | null;
|
|
113
|
+
}
|
|
114
|
+
export type StageResult = {
|
|
115
|
+
kind: 'staged';
|
|
116
|
+
stagingPath: string;
|
|
117
|
+
meta: BranchMetadataFile;
|
|
118
|
+
} | {
|
|
119
|
+
kind: 'exists';
|
|
120
|
+
};
|
|
121
|
+
/** Remove a staging or deleted directory, leaving it to the worker's sweep on failure. */
|
|
122
|
+
export declare function removeLeftoverDir(dirPath: string): Promise<void>;
|
|
123
|
+
/**
|
|
124
|
+
* Build a complete branch workspace, branch.json included, in a staging sibling of the final
|
|
125
|
+
* path. Takes no lock. Returns `exists` without checking out when a competitor published first.
|
|
126
|
+
* A failure removes the staging directory; a kill leaves it for the worker.
|
|
127
|
+
*/
|
|
128
|
+
export declare function stageBranchWorkspace(options: StageBranchOptions): Promise<StageResult>;
|
|
129
|
+
/**
|
|
130
|
+
* The branch's provisioning lock with a bounded wait; contention is
|
|
131
|
+
* {@link BranchProvisioningBusyError}.
|
|
132
|
+
*/
|
|
133
|
+
export declare function lockBranchName(baseRoot: string, dirName: string): Promise<() => Promise<void>>;
|
|
134
|
+
/** What blocks a refused publish rename, as {@link publishStaging} sees it. */
|
|
135
|
+
export type BlockingKind = 'live' | 'protected' | 'residue';
|
|
136
|
+
export interface PublishOptions {
|
|
137
|
+
baseRoot: string;
|
|
138
|
+
dirName: string;
|
|
139
|
+
stagingPath: string;
|
|
140
|
+
/** branch.json's `writeId` in the staged workspace: proof the rename was ours. */
|
|
141
|
+
writeId: string | undefined;
|
|
142
|
+
expectedRemoteUrl: string;
|
|
143
|
+
/** Test seam replacing the look at what blocks a refused rename. */
|
|
144
|
+
inspectBlocked?: (finalPath: string) => Promise<BlockingKind>;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Publish a staged workspace at its final path, under the branch's provisioning lock.
|
|
148
|
+
*
|
|
149
|
+
* A refused rename is inspected: branch.json there means another request won (`exists`);
|
|
150
|
+
* residue is quarantined and the rename retried once. ENOENT can be an NFS retransmission of a
|
|
151
|
+
* rename that already landed, so it counts as published when the final branch.json carries our
|
|
152
|
+
* `writeId`. The caller removes the staging directory on any outcome but `published`.
|
|
153
|
+
*/
|
|
154
|
+
export declare function publishStaging(options: PublishOptions): Promise<'published' | 'exists'>;
|
|
155
|
+
export interface LeftoverAction {
|
|
156
|
+
name: string;
|
|
157
|
+
action: 'removed' | 'restored' | 'trashed' | 'failed';
|
|
158
|
+
detail?: string;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Clear what killed processes left beside the branch directories (`baseRoot`) and the settings
|
|
162
|
+
* workspace (`workspaceRoot`): stale `.prov-*` builds, `.deleting-*` directories whose `rm` never
|
|
163
|
+
* finished, `.repair-*` from an interrupted quarantine, and expired settings trash. Ages come
|
|
164
|
+
* from the stamp in each name.
|
|
165
|
+
*/
|
|
166
|
+
export declare function sweepProvisioningLeftovers(baseRoot: string, workspaceRoot: string, now?: number): Promise<LeftoverAction[]>;
|
|
167
|
+
export {};
|