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
|
@@ -90,34 +90,22 @@ export function neutralizeImplicitOffOrigin(url) {
|
|
|
90
90
|
* this file either way, so the two surfaces cannot drift into disagreeing about which schemes
|
|
91
91
|
* are acceptable (that drift is exactly why `utils/url-prefix.ts` exists — see its header).
|
|
92
92
|
*
|
|
93
|
-
* `
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
*
|
|
99
|
-
* a working configuration. A bad value costs a broken `<img>`.
|
|
100
|
-
* - A WRITE-side endpoint (`media.uploadUrl`, where the browser POSTs a presigned upload) must
|
|
101
|
-
* not be protocol-relative even when spelled literally, because such a URL is AMBIGUOUS: it
|
|
102
|
-
* resolves to http or https depending on the scheme of whichever editor page happens to
|
|
103
|
-
* issue the upload, so the stored config does not determine where a live presigned credential
|
|
104
|
-
* and the user's file bytes are sent. That is the same principle every other rule here
|
|
105
|
-
* enforces — a config value must describe what actually happens.
|
|
106
|
-
*
|
|
107
|
-
* Note this is NOT an argument that http is forbidden on the write side; bare `http://` is
|
|
108
|
-
* accepted, because a loopback or in-cluster S3-compatible endpoint is a real need and a
|
|
109
|
-
* browser on an https editor blocks the mixed-content request anyway, so it fails closed. The
|
|
110
|
-
* objection to `//host` is that it is undetermined, not that it might be insecure.
|
|
93
|
+
* A protocol-relative `//host` is refused even when spelled literally, because it is AMBIGUOUS:
|
|
94
|
+
* it resolves to http or https depending on the page issuing the request, so the stored config
|
|
95
|
+
* does not determine where, for `media.uploadUrl`, a live presigned credential and the user's
|
|
96
|
+
* file bytes are sent. Bare `http://` is accepted: a loopback or in-cluster S3-compatible
|
|
97
|
+
* endpoint is a real need, and a browser on an https editor blocks the mixed-content request
|
|
98
|
+
* anyway, so it fails closed.
|
|
111
99
|
*
|
|
112
100
|
* Every spelling that READS as site-relative while redefining the authority — `/\host`,
|
|
113
|
-
* `\\host`, `\/host`, `///host` — is rejected
|
|
101
|
+
* `\\host`, `\/host`, `///host` — is rejected too. Those look site-relative
|
|
114
102
|
* to a human and to a naive `startsWith('/')` check, but WHATWG URL resolves each to a
|
|
115
103
|
* different authority (measured: `///x` resolves to host `x`, not to pathname `/x`), so they
|
|
116
104
|
* are never what an adopter meant. A scheme-qualified absolute URL is a separate case and is
|
|
117
105
|
* accepted — `https:///cdn.example.com/` is unusual but unambiguous, resolving identically
|
|
118
106
|
* standalone and against any base.
|
|
119
107
|
*/
|
|
120
|
-
export function isHttpUrlOrSameOriginPath(value
|
|
108
|
+
export function isHttpUrlOrSameOriginPath(value) {
|
|
121
109
|
if (value === '')
|
|
122
110
|
return false;
|
|
123
111
|
// Reject ASCII control characters and spaces ANYWHERE in the value, not only at the edges.
|
|
@@ -132,7 +120,7 @@ export function isHttpUrlOrSameOriginPath(value, opts = {}) {
|
|
|
132
120
|
}
|
|
133
121
|
// A query or fragment is meaningless on both sides this predicate serves. S3's POST Object
|
|
134
122
|
// takes no query parameters and a browser never transmits a fragment; and on the read side a
|
|
135
|
-
// prefix carrying either produces `https://cdn.example.com/?x=1
|
|
123
|
+
// prefix carrying either produces `https://cdn.example.com/?x=1/…` once joined.
|
|
136
124
|
if (value.includes('?') || value.includes('#'))
|
|
137
125
|
return false;
|
|
138
126
|
// A backslash is never legitimate in a configured URL, and it is not merely cosmetic: WHATWG
|
|
@@ -169,13 +157,8 @@ export function isHttpUrlOrSameOriginPath(value, opts = {}) {
|
|
|
169
157
|
return false;
|
|
170
158
|
}
|
|
171
159
|
}
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
// off-origin" and also start with `//`, but resolve to a host named `x` rather than to a
|
|
175
|
-
// path — so they must not ride in on the opt-in.
|
|
176
|
-
if (isImplicitlyOffOrigin(value)) {
|
|
177
|
-
return opts.allowProtocolRelative === true && value.startsWith('//') && value[2] !== '/';
|
|
178
|
-
}
|
|
160
|
+
if (isImplicitlyOffOrigin(value))
|
|
161
|
+
return false;
|
|
179
162
|
// Site-relative, with exactly one leading slash. The second clause also covers `//`, which
|
|
180
163
|
// `isImplicitlyOffOrigin` reports as false only because `new URL('//', base)` throws.
|
|
181
164
|
return value.startsWith('/') && value[1] !== '/';
|
|
@@ -119,3 +119,33 @@ export declare function sanitizeUnprefixedPath(url: string): string;
|
|
|
119
119
|
* neutralizing it would silently rewrite a legitimate protocol-relative CDN base into a path.
|
|
120
120
|
*/
|
|
121
121
|
export declare function joinUrlPrefix(prefix: string | undefined, path: string): string;
|
|
122
|
+
/**
|
|
123
|
+
* Append a trailing slash to a site-relative path, matching a site that serves `/contact/`.
|
|
124
|
+
*
|
|
125
|
+
* Leaves the root (`/`) and file-like paths (a last segment containing a dot, e.g.
|
|
126
|
+
* `/blog/rss.xml`) alone, and never doubles an existing slash. So it never produces a URL that
|
|
127
|
+
* Next's `trailingSlash: true` redirects (`next/dist/lib/load-custom-routes.js:489,502` in
|
|
128
|
+
* 15.5.21): Next adds a slash only to a last segment with no dot, and strips one from a segment
|
|
129
|
+
* ending `.ext`.
|
|
130
|
+
*
|
|
131
|
+
* A query string and/or fragment (`?page=2`, `#section`) is split off BEFORE the slash decision
|
|
132
|
+
* and placement, then reattached after — so `/blog?page=2` becomes `/blog/?page=2`, never
|
|
133
|
+
* `/blog?page=2/` (a literal trailing slash inside the query string, which is not what "serve
|
|
134
|
+
* with a trailing slash" means and breaks the URL).
|
|
135
|
+
*/
|
|
136
|
+
export declare function withTrailingSlash(path: string): string;
|
|
137
|
+
/**
|
|
138
|
+
* Give a URL's path the trailing-slash form a Next host serves, so loading it draws no 308:
|
|
139
|
+
* `withTrailingSlash`'s rule when `trailingSlash` is true, else no trailing slash on any path
|
|
140
|
+
* but the root (Next also redirects `<basePath>/` to `<basePath>`, `load-custom-routes.js:528`). An
|
|
141
|
+
* absolute URL's origin, and any query or fragment, are kept as they are.
|
|
142
|
+
*/
|
|
143
|
+
export declare function matchTrailingSlash(url: string, trailingSlash: boolean): string;
|
|
144
|
+
/**
|
|
145
|
+
* Whether the host is built with Next's `trailingSlash: true`. `withCanopy` sets
|
|
146
|
+
* `CANOPY_TRAILING_SLASH` in Next's `env` config, which Next substitutes for this literal member
|
|
147
|
+
* expression in server and browser bundles (`getNextConfigEnv`, `next/dist/build/define-env.js:54`).
|
|
148
|
+
* The try/catch covers a host whose bundler neither substitutes it nor shims `process` in the
|
|
149
|
+
* browser.
|
|
150
|
+
*/
|
|
151
|
+
export declare function readTrailingSlashEnv(): boolean;
|
package/dist/utils/url-prefix.js
CHANGED
|
@@ -156,3 +156,64 @@ export function joinUrlPrefix(prefix, path) {
|
|
|
156
156
|
: `/${trimmedPrefix}`;
|
|
157
157
|
return `${normalizedPrefix}${normalizedPath}`;
|
|
158
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Append a trailing slash to a site-relative path, matching a site that serves `/contact/`.
|
|
161
|
+
*
|
|
162
|
+
* Leaves the root (`/`) and file-like paths (a last segment containing a dot, e.g.
|
|
163
|
+
* `/blog/rss.xml`) alone, and never doubles an existing slash. So it never produces a URL that
|
|
164
|
+
* Next's `trailingSlash: true` redirects (`next/dist/lib/load-custom-routes.js:489,502` in
|
|
165
|
+
* 15.5.21): Next adds a slash only to a last segment with no dot, and strips one from a segment
|
|
166
|
+
* ending `.ext`.
|
|
167
|
+
*
|
|
168
|
+
* A query string and/or fragment (`?page=2`, `#section`) is split off BEFORE the slash decision
|
|
169
|
+
* and placement, then reattached after — so `/blog?page=2` becomes `/blog/?page=2`, never
|
|
170
|
+
* `/blog?page=2/` (a literal trailing slash inside the query string, which is not what "serve
|
|
171
|
+
* with a trailing slash" means and breaks the URL).
|
|
172
|
+
*/
|
|
173
|
+
export function withTrailingSlash(path) {
|
|
174
|
+
const splitIndex = path.search(/[?#]/);
|
|
175
|
+
const base = splitIndex === -1 ? path : path.slice(0, splitIndex);
|
|
176
|
+
const suffix = splitIndex === -1 ? '' : path.slice(splitIndex);
|
|
177
|
+
const withLeading = base.startsWith('/') ? base : `/${base}`;
|
|
178
|
+
if (withLeading === '/' || withLeading.endsWith('/'))
|
|
179
|
+
return withLeading + suffix;
|
|
180
|
+
const lastSegment = withLeading.slice(withLeading.lastIndexOf('/') + 1);
|
|
181
|
+
if (lastSegment.includes('.'))
|
|
182
|
+
return withLeading + suffix;
|
|
183
|
+
return `${withLeading}/${suffix}`;
|
|
184
|
+
}
|
|
185
|
+
// Linear: `[^/?#]*` cannot overlap the literal `//` before it, so there is one way to match.
|
|
186
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
187
|
+
const URL_ORIGIN = /^(?:[a-z][a-z0-9+.-]*:)?\/\/[^/?#]*/i;
|
|
188
|
+
/**
|
|
189
|
+
* Give a URL's path the trailing-slash form a Next host serves, so loading it draws no 308:
|
|
190
|
+
* `withTrailingSlash`'s rule when `trailingSlash` is true, else no trailing slash on any path
|
|
191
|
+
* but the root (Next also redirects `<basePath>/` to `<basePath>`, `load-custom-routes.js:528`). An
|
|
192
|
+
* absolute URL's origin, and any query or fragment, are kept as they are.
|
|
193
|
+
*/
|
|
194
|
+
export function matchTrailingSlash(url, trailingSlash) {
|
|
195
|
+
const origin = isAbsoluteUrl(url) ? (URL_ORIGIN.exec(url)?.[0] ?? '') : '';
|
|
196
|
+
const rest = url.slice(origin.length);
|
|
197
|
+
if (trailingSlash)
|
|
198
|
+
return origin + withTrailingSlash(rest);
|
|
199
|
+
const splitIndex = rest.search(/[?#]/);
|
|
200
|
+
const base = splitIndex === -1 ? rest : rest.slice(0, splitIndex);
|
|
201
|
+
const suffix = splitIndex === -1 ? '' : rest.slice(splitIndex);
|
|
202
|
+
const trimmed = stripTrailingSlashes(base);
|
|
203
|
+
return origin + (trimmed || (origin ? '' : '/')) + suffix;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Whether the host is built with Next's `trailingSlash: true`. `withCanopy` sets
|
|
207
|
+
* `CANOPY_TRAILING_SLASH` in Next's `env` config, which Next substitutes for this literal member
|
|
208
|
+
* expression in server and browser bundles (`getNextConfigEnv`, `next/dist/build/define-env.js:54`).
|
|
209
|
+
* The try/catch covers a host whose bundler neither substitutes it nor shims `process` in the
|
|
210
|
+
* browser.
|
|
211
|
+
*/
|
|
212
|
+
export function readTrailingSlashEnv() {
|
|
213
|
+
try {
|
|
214
|
+
return process.env.CANOPY_TRAILING_SLASH === 'true';
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* `ReferenceValidator` at the write boundary.
|
|
13
13
|
*/
|
|
14
14
|
import { fieldTypes } from '../config/index.js';
|
|
15
|
-
import { isValidCropRect } from '../assets/transform-directives.js';
|
|
15
|
+
import { isValidCropRect, roundCropRect } from '../assets/transform-directives.js';
|
|
16
16
|
import { BLOCK_STRUCTURAL_KEYS } from './block-structural-keys.js';
|
|
17
17
|
import { resolveBlockItem, traverseFields } from './field-traversal.js';
|
|
18
18
|
import { findBodyFieldName } from '../utils/body-field.js';
|
|
@@ -27,7 +27,10 @@ function isPlainRecord(value) {
|
|
|
27
27
|
function isPositiveInt(value) {
|
|
28
28
|
return typeof value === 'number' && Number.isInteger(value) && value > 0;
|
|
29
29
|
}
|
|
30
|
-
/**
|
|
30
|
+
/**
|
|
31
|
+
* True when `value` is a well-formed `{ x, y, w, h }` normalized crop rect that is still valid
|
|
32
|
+
* after `roundCropRect`, the rounding its transform URL applies.
|
|
33
|
+
*/
|
|
31
34
|
function isValidImageCropValue(value) {
|
|
32
35
|
if (!isPlainRecord(value))
|
|
33
36
|
return false;
|
|
@@ -38,7 +41,10 @@ function isValidImageCropValue(value) {
|
|
|
38
41
|
typeof h !== 'number') {
|
|
39
42
|
return false;
|
|
40
43
|
}
|
|
41
|
-
|
|
44
|
+
if (!isValidCropRect(x, y, w, h))
|
|
45
|
+
return false;
|
|
46
|
+
const rounded = roundCropRect({ x, y, w, h });
|
|
47
|
+
return isValidCropRect(rounded.x, rounded.y, rounded.w, rounded.h);
|
|
42
48
|
}
|
|
43
49
|
/**
|
|
44
50
|
* Validate a structured `image` field value: `{ src, alt, width?, height?, crop? }`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const CANOPYCMS_VERSION = "0.0.68-int.100";
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { FileStatusResult, SimpleGit, StatusResult } from 'simple-git';
|
|
2
|
+
/**
|
|
3
|
+
* How the sync loop treats canopycms's own state (`.canopy-meta/`) in a branch clone. It is
|
|
4
|
+
* never content, so untracked state never makes a clone "dirty" for sync, but git refuses some
|
|
5
|
+
* operations over it while the adopter's repo tracks it. Only the adopter can stop that; once
|
|
6
|
+
* they have, the base clone follows on its next fast-forward ({@link untrackInIndex}), and a
|
|
7
|
+
* branch clone with modified tracked state needs an operator.
|
|
8
|
+
*/
|
|
9
|
+
/** The fix an operator applies when an adopter repo tracks `.canopy-meta/`. */
|
|
10
|
+
export declare const TRACKED_CANOPY_STATE_FIX = "run `git rm -r --cached .canopy-meta` in the site repo, add `.canopy-meta/` to its .gitignore, and commit";
|
|
11
|
+
/** Paths listed in worker-status.json per category; the admin panel shows them in a tooltip. */
|
|
12
|
+
export declare const MAX_REPORTED_PATHS = 10;
|
|
13
|
+
export declare function isUntracked(file: FileStatusResult): boolean;
|
|
14
|
+
/** Files under `.canopy-meta/` that the clone's index tracks. */
|
|
15
|
+
export declare function listTrackedCanopyState(git: SimpleGit): Promise<string[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Discard local changes to the schema cache's retired in-tree copy, so a clone of a repo that
|
|
18
|
+
* committed it can sync again. Nothing reads or writes that path in a clone any more, so its
|
|
19
|
+
* content is dead; but while it differs from HEAD, `git rebase` refuses to start and a
|
|
20
|
+
* fast-forward that touches it (including the adopter's own commit untracking it) refuses too.
|
|
21
|
+
* An untracked copy blocks neither and a newly staged one has nothing in HEAD to restore, so
|
|
22
|
+
* both are left alone. Returns whether it restored anything; the caller re-reads status if so.
|
|
23
|
+
*/
|
|
24
|
+
export declare function restoreRetiredSchemaCache(git: SimpleGit, status: StatusResult): Promise<boolean>;
|
|
25
|
+
/**
|
|
26
|
+
* The changed tracked `.canopy-meta/` files in `status`: what git refuses to rebase over, and
|
|
27
|
+
* what a fast-forward refuses to overwrite where it touches them.
|
|
28
|
+
*/
|
|
29
|
+
export declare function trackedCanopyStateChanges(status: StatusResult): string[];
|
|
30
|
+
/**
|
|
31
|
+
* Split `paths` by whether the commit `tip` still tracks them. Those it no longer tracks, the
|
|
32
|
+
* adopter has untracked upstream, so a fast-forward onto `tip` would delete them from disk unless
|
|
33
|
+
* {@link untrackInIndex} takes them out of the index first.
|
|
34
|
+
*/
|
|
35
|
+
export declare function splitByUpstreamTracking(git: SimpleGit, paths: string[], tip: string): Promise<{
|
|
36
|
+
droppedUpstream: string[];
|
|
37
|
+
stillTracked: string[];
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Remove `paths` from this clone's index, leaving them on disk as untracked, excluded state.
|
|
41
|
+
* Index-only, so a concurrent write to them (branch metadata, comments) is not lost. Only for a
|
|
42
|
+
* clone with no commits of its own: a rebase that replays a commit touching these paths writes
|
|
43
|
+
* that commit's bytes over them. Paths already out of the index are ignored.
|
|
44
|
+
*/
|
|
45
|
+
export declare function untrackInIndex(git: SimpleGit, paths: string[]): Promise<void>;
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { SCHEMA_CACHE_FILE } from '../branch-schema-cache.js';
|
|
2
|
+
import { CANOPY_META_DIR, isCanopyInternalPath } from '../utils/git.js';
|
|
3
|
+
/**
|
|
4
|
+
* How the sync loop treats canopycms's own state (`.canopy-meta/`) in a branch clone. It is
|
|
5
|
+
* never content, so untracked state never makes a clone "dirty" for sync, but git refuses some
|
|
6
|
+
* operations over it while the adopter's repo tracks it. Only the adopter can stop that; once
|
|
7
|
+
* they have, the base clone follows on its next fast-forward ({@link untrackInIndex}), and a
|
|
8
|
+
* branch clone with modified tracked state needs an operator.
|
|
9
|
+
*/
|
|
10
|
+
/** The fix an operator applies when an adopter repo tracks `.canopy-meta/`. */
|
|
11
|
+
export const TRACKED_CANOPY_STATE_FIX = 'run `git rm -r --cached .canopy-meta` in the site repo, add `.canopy-meta/` to its .gitignore, and commit';
|
|
12
|
+
/** Paths listed in worker-status.json per category; the admin panel shows them in a tooltip. */
|
|
13
|
+
export const MAX_REPORTED_PATHS = 10;
|
|
14
|
+
/** The schema cache's location in a clone before it moved under `.git/` (branch-schema-cache.ts). */
|
|
15
|
+
const RETIRED_SCHEMA_CACHE_PATH = `${CANOPY_META_DIR}/${SCHEMA_CACHE_FILE}`;
|
|
16
|
+
export function isUntracked(file) {
|
|
17
|
+
return file.index === '?' && file.working_dir === '?';
|
|
18
|
+
}
|
|
19
|
+
/** NUL-separated git output, which is never quoted, unlike `core.quotePath` line output. */
|
|
20
|
+
function splitNul(out) {
|
|
21
|
+
return out.split('\0').filter((entry) => entry.length > 0);
|
|
22
|
+
}
|
|
23
|
+
/** Files under `.canopy-meta/` that the clone's index tracks. */
|
|
24
|
+
export async function listTrackedCanopyState(git) {
|
|
25
|
+
return splitNul(await git.raw(['ls-files', '-z', '--', CANOPY_META_DIR]));
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Discard local changes to the schema cache's retired in-tree copy, so a clone of a repo that
|
|
29
|
+
* committed it can sync again. Nothing reads or writes that path in a clone any more, so its
|
|
30
|
+
* content is dead; but while it differs from HEAD, `git rebase` refuses to start and a
|
|
31
|
+
* fast-forward that touches it (including the adopter's own commit untracking it) refuses too.
|
|
32
|
+
* An untracked copy blocks neither and a newly staged one has nothing in HEAD to restore, so
|
|
33
|
+
* both are left alone. Returns whether it restored anything; the caller re-reads status if so.
|
|
34
|
+
*/
|
|
35
|
+
export async function restoreRetiredSchemaCache(git, status) {
|
|
36
|
+
const entry = status.files.find((f) => f.path === RETIRED_SCHEMA_CACHE_PATH);
|
|
37
|
+
if (!entry || entry.index === '?' || entry.index === 'A')
|
|
38
|
+
return false;
|
|
39
|
+
await git.raw(['checkout', 'HEAD', '--', RETIRED_SCHEMA_CACHE_PATH]);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* The changed tracked `.canopy-meta/` files in `status`: what git refuses to rebase over, and
|
|
44
|
+
* what a fast-forward refuses to overwrite where it touches them.
|
|
45
|
+
*/
|
|
46
|
+
export function trackedCanopyStateChanges(status) {
|
|
47
|
+
return status.files
|
|
48
|
+
.filter((f) => isCanopyInternalPath(f.path) && !isUntracked(f))
|
|
49
|
+
.map((f) => f.path);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Split `paths` by whether the commit `tip` still tracks them. Those it no longer tracks, the
|
|
53
|
+
* adopter has untracked upstream, so a fast-forward onto `tip` would delete them from disk unless
|
|
54
|
+
* {@link untrackInIndex} takes them out of the index first.
|
|
55
|
+
*/
|
|
56
|
+
export async function splitByUpstreamTracking(git, paths, tip) {
|
|
57
|
+
if (paths.length === 0)
|
|
58
|
+
return { droppedUpstream: [], stillTracked: [] };
|
|
59
|
+
const tracked = new Set(splitNul(await git.raw(['ls-tree', '-r', '-z', '--name-only', tip, '--', CANOPY_META_DIR])));
|
|
60
|
+
return {
|
|
61
|
+
droppedUpstream: paths.filter((p) => !tracked.has(p)),
|
|
62
|
+
stillTracked: paths.filter((p) => tracked.has(p)),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Remove `paths` from this clone's index, leaving them on disk as untracked, excluded state.
|
|
67
|
+
* Index-only, so a concurrent write to them (branch metadata, comments) is not lost. Only for a
|
|
68
|
+
* clone with no commits of its own: a rebase that replays a commit touching these paths writes
|
|
69
|
+
* that commit's bytes over them. Paths already out of the index are ignored.
|
|
70
|
+
*/
|
|
71
|
+
export async function untrackInIndex(git, paths) {
|
|
72
|
+
if (paths.length === 0)
|
|
73
|
+
return;
|
|
74
|
+
await git.raw(['rm', '--cached', '--ignore-unmatch', '-q', '--', ...paths]);
|
|
75
|
+
}
|
|
@@ -15,11 +15,11 @@ export type AuthCacheRefresher = () => Promise<void>;
|
|
|
15
15
|
* interface for the two shapes and why an App is optional.
|
|
16
16
|
*/
|
|
17
17
|
export interface CmsWorkerConfig extends GitHubAuthConfig {
|
|
18
|
-
/** Path to workspace root on EFS (e.g., /mnt/efs
|
|
18
|
+
/** Path to workspace root on EFS (e.g., /mnt/efs) */
|
|
19
19
|
workspacePath: string;
|
|
20
|
-
/** GitHub owner (e.g., '
|
|
20
|
+
/** GitHub owner (e.g., 'acme') */
|
|
21
21
|
githubOwner: string;
|
|
22
|
-
/** GitHub repo name (e.g., '
|
|
22
|
+
/** GitHub repo name (e.g., 'site') */
|
|
23
23
|
githubRepo: string;
|
|
24
24
|
/** Called periodically to update the auth metadata cache on EFS. */
|
|
25
25
|
refreshAuthCache?: AuthCacheRefresher;
|
|
@@ -203,6 +203,11 @@ export declare class CmsWorker {
|
|
|
203
203
|
* survives, so a failed scrub is never indistinguishable from a clean one.
|
|
204
204
|
*/
|
|
205
205
|
private scrubPersistedRemote;
|
|
206
|
+
/**
|
|
207
|
+
* Best-effort: without these settings remote.git still works, and a boot that
|
|
208
|
+
* cannot write its own config has a louder problem to report elsewhere.
|
|
209
|
+
*/
|
|
210
|
+
private applyRemoteGitConfig;
|
|
206
211
|
/**
|
|
207
212
|
* Ensure the remote.git bare repo exists, cloning it from GitHub on first
|
|
208
213
|
* run.
|
|
@@ -5,10 +5,12 @@ import lockfile from 'proper-lockfile';
|
|
|
5
5
|
import { recoverOrphanedTasks, cmsTaskQueueLogger } from '../task-queue/cms-task-queue.js';
|
|
6
6
|
import { createCanopyOctokit } from '../github-service.js';
|
|
7
7
|
import { isTransientAuthFailure, resolveWorkerGitHubAuth, } from './github-auth.js';
|
|
8
|
+
import { ensureRemoteGitConfig } from '../git-manager.js';
|
|
8
9
|
import { sanitizeBranchName, RESERVED_SETTINGS_BRANCH_PREFIX } from '../paths/branch-name.js';
|
|
9
10
|
import { resolveDeploymentName } from '../operating-mode/deployment-name.js';
|
|
10
11
|
import { getErrorMessage, isNodeError, redactCredentials } from '../utils/error.js';
|
|
11
|
-
import { writeWorkerStatus } from '../task-queue/worker-status.js';
|
|
12
|
+
import { readLastFatalError, writeWorkerStatus } from '../task-queue/worker-status.js';
|
|
13
|
+
import { CANOPYCMS_VERSION } from '../version.js';
|
|
12
14
|
import { workerLog, workerLogWarn, workerLogError } from './log.js';
|
|
13
15
|
import { executeTask, orphanRecoveryMaxAgeMs, processTaskQueue, pushBranchToGitHub, updateBranchMetadata, } from './task-runner.js';
|
|
14
16
|
import { pollMergeState, runRebaseCycle } from './rebase.js';
|
|
@@ -67,7 +69,12 @@ export class CmsWorker {
|
|
|
67
69
|
ensureStatusReport() {
|
|
68
70
|
if (!this.statusReport) {
|
|
69
71
|
const now = new Date().toISOString();
|
|
70
|
-
this.statusReport = {
|
|
72
|
+
this.statusReport = {
|
|
73
|
+
version: 1,
|
|
74
|
+
workerVersion: CANOPYCMS_VERSION,
|
|
75
|
+
startedAt: now,
|
|
76
|
+
updatedAt: now,
|
|
77
|
+
};
|
|
71
78
|
}
|
|
72
79
|
return this.statusReport;
|
|
73
80
|
}
|
|
@@ -144,6 +151,21 @@ export class CmsWorker {
|
|
|
144
151
|
workerLog('CMS Worker starting...');
|
|
145
152
|
this.ensureStatusReport();
|
|
146
153
|
await this.acquireLock();
|
|
154
|
+
// Replace the previous holder's status file now: the first sync can take
|
|
155
|
+
// minutes, and until then System health would report the old worker's
|
|
156
|
+
// version. Best-effort, like the startup-failure write below. The previous
|
|
157
|
+
// `lastFatalError` rides along in this snapshot only, so a crash loop keeps
|
|
158
|
+
// its alert between restarts while the first successful sync still clears it.
|
|
159
|
+
try {
|
|
160
|
+
const lastFatalError = await readLastFatalError(this.taskDir);
|
|
161
|
+
await writeWorkerStatus(this.taskDir, {
|
|
162
|
+
...this.ensureStatusReport(),
|
|
163
|
+
...(lastFatalError ? { lastFatalError } : {}),
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
workerLogError('Failed to write worker status after acquiring the lock:', getErrorMessage(err));
|
|
168
|
+
}
|
|
147
169
|
// Everything below runs while holding the cross-host worker lock. A failure
|
|
148
170
|
// here (most notably the empty-remote guard inside ensureRemoteGit) means
|
|
149
171
|
// the process is about to exit and systemd (Restart=always) will retry —
|
|
@@ -411,6 +433,18 @@ export class CmsWorker {
|
|
|
411
433
|
`shared storage. Refusing to continue.`);
|
|
412
434
|
}
|
|
413
435
|
}
|
|
436
|
+
/**
|
|
437
|
+
* Best-effort: without these settings remote.git still works, and a boot that
|
|
438
|
+
* cannot write its own config has a louder problem to report elsewhere.
|
|
439
|
+
*/
|
|
440
|
+
async applyRemoteGitConfig(gitDir) {
|
|
441
|
+
try {
|
|
442
|
+
await ensureRemoteGitConfig(gitDir);
|
|
443
|
+
}
|
|
444
|
+
catch (err) {
|
|
445
|
+
workerLogWarn(`Could not apply remote.git config in ${gitDir}: ${getErrorMessage(err)}`);
|
|
446
|
+
}
|
|
447
|
+
}
|
|
414
448
|
/**
|
|
415
449
|
* Ensure the remote.git bare repo exists, cloning it from GitHub on first
|
|
416
450
|
* run.
|
|
@@ -450,6 +484,7 @@ export class CmsWorker {
|
|
|
450
484
|
// deletion is the operator's call.
|
|
451
485
|
throw new Error(`remote.git at ${this.remoteGitPath} has no branch '${this.baseBranch}' (likely cloned while the GitHub repo was empty). Delete ${this.remoteGitPath} and restart the worker to re-clone.`);
|
|
452
486
|
}
|
|
487
|
+
await this.applyRemoteGitConfig(this.remoteGitPath);
|
|
453
488
|
return; // Already exists and has the base branch
|
|
454
489
|
}
|
|
455
490
|
workerLog('Initializing remote.git from GitHub...');
|
|
@@ -468,6 +503,7 @@ export class CmsWorker {
|
|
|
468
503
|
// swallowing) if the scrub does not take.
|
|
469
504
|
await this.scrubPersistedRemote(stagingPath);
|
|
470
505
|
await this.verifyBaseBranchExists(stagingPath);
|
|
506
|
+
await this.applyRemoteGitConfig(stagingPath);
|
|
471
507
|
}
|
|
472
508
|
catch (err) {
|
|
473
509
|
workerLogError(`remote.git clone failed: ${redactCredentials(getErrorMessage(err))}`);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { simpleGit } from 'simple-git';
|
|
2
|
+
import type { BaseRefreshReport } from '../types.js';
|
|
2
3
|
import { type RebaseContext } from './rebase.js';
|
|
3
4
|
import type { WorkerContext } from './worker-context.js';
|
|
4
5
|
/**
|
|
@@ -6,7 +7,10 @@ import type { WorkerContext } from './worker-context.js';
|
|
|
6
7
|
* slower of the worker's two poll loops (default 5 minutes, against the task
|
|
7
8
|
* queue's 5 seconds).
|
|
8
9
|
*
|
|
9
|
-
* One cycle, in order:
|
|
10
|
+
* One cycle, in order: repair what killed provisioning and deletes left under
|
|
11
|
+
* the branches root (`repairBranchDirResidue`), move sparse clones to a changed
|
|
12
|
+
* content root's cone (sparse-cone.ts), repack `remote.git` when it needs it
|
|
13
|
+
* (remote-git-maintenance.ts), fetch every GitHub branch into the tracking namespace,
|
|
10
14
|
* bring `refs/heads/*` toward it non-destructively (`reconcileTrackedBranches`),
|
|
11
15
|
* push this deployment's own settings branch, fast-forward the base branch's
|
|
12
16
|
* workspace, rebase every branch that is behind it (rebase.ts), then sweep old
|
|
@@ -14,7 +18,7 @@ import type { WorkerContext } from './worker-context.js';
|
|
|
14
18
|
*
|
|
15
19
|
* One ordering is load-bearing and nothing enforces it: `runRebaseCycle` MUST
|
|
16
20
|
* follow `reconcileTrackedBranches`. Branch clones fetch the base tip from
|
|
17
|
-
* `remote.git
|
|
21
|
+
* `remote.git`, and `reconcileTrackedBranches` is what advances
|
|
18
22
|
* `remote.git`'s `refs/heads/*` toward what the fetch above put in the tracking
|
|
19
23
|
* namespace; reorder them and every branch rebases onto the PREVIOUS cycle's
|
|
20
24
|
* base tip -- not corrupting, but silently a cycle behind.
|
|
@@ -39,27 +43,41 @@ export type GitSyncContext = Pick<WorkerContext, 'baseBranch' | 'sanitizedBaseBr
|
|
|
39
43
|
*/
|
|
40
44
|
export declare function pushSettingsBranches(ctx: GitSyncContext, git: ReturnType<typeof simpleGit>, trackedNames: ReadonlySet<string>): Promise<void>;
|
|
41
45
|
export declare function syncGit(ctx: GitSyncContext): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* Repair what killed provisioning and deletes left under the branches root: quarantine residue
|
|
48
|
+
* at a branch's name (branch-provisioning.ts's `classifyFinalDir`) once it has been quiet for
|
|
49
|
+
* {@link ORPHAN_YOUTH_THRESHOLD_MS}, then sweep stale `.prov-*`, `.deleting-*` and `.repair-*`
|
|
50
|
+
* siblings and settings leftovers. Each directory is best-effort, like [SYNC-M2]. The provisioning
|
|
51
|
+
* lock is taken zero-retry, so a live provisioner or admin action wins and the directory is
|
|
52
|
+
* revisited next cycle.
|
|
53
|
+
* @internal Exported for tests; `syncGit` runs it.
|
|
54
|
+
*/
|
|
55
|
+
export declare function repairBranchDirResidue(ctx: Pick<GitSyncContext, 'contentBranchesPath' | 'remoteGitPath'>, now?: number): Promise<void>;
|
|
42
56
|
/**
|
|
43
57
|
* [C1] Remove `.trash-*` branch directories (created by the admin purge action,
|
|
44
|
-
* api/admin-branch-health.ts
|
|
45
|
-
* {@link TRASH_RETENTION_MS}. A name
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* is unexpected and worth a human looking rather than a silent skip.
|
|
58
|
+
* api/admin-branch-health.ts, and by residue quarantine, branch-provisioning.ts)
|
|
59
|
+
* whose name-embedded stamp is older than {@link TRASH_RETENTION_MS}. A name
|
|
60
|
+
* whose trailing stamp fails to parse is left alone and logged once per cycle:
|
|
61
|
+
* both writers end every name with one, so it is worth a human looking.
|
|
49
62
|
*/
|
|
50
63
|
export declare function cleanupTrashedBranchDirs(ctx: Pick<GitSyncContext, 'contentBranchesPath'>): Promise<number>;
|
|
51
64
|
/**
|
|
52
65
|
* Fast-forward the base branch's own working-tree clone
|
|
53
|
-
* (content-branches/<baseBranch>) to match
|
|
66
|
+
* (content-branches/<baseBranch>) to match remote.git's <baseBranch>, every sync
|
|
54
67
|
* cycle, so the drift window is bounded by gitSyncInterval.
|
|
55
68
|
*
|
|
56
69
|
* A dedicated, explicit and LOUD step rather than a side effect of the rebase
|
|
57
70
|
* loop, whose skip paths (a dirty tree, a missing .git) are silent: a wedged
|
|
58
71
|
* base clone otherwise leaves no diagnosable signal, and an editor forking a
|
|
59
|
-
* new branch "from base" silently gets a stale snapshot.
|
|
72
|
+
* new branch "from base" silently gets a stale snapshot. The returned outcome
|
|
73
|
+
* goes to worker-status.json for the same reason.
|
|
60
74
|
*
|
|
61
|
-
* ff-only on purpose: this clone must stay a linear mirror of
|
|
62
|
-
*
|
|
75
|
+
* ff-only on purpose: this clone must stay a linear mirror of remote.git's
|
|
76
|
+
* <baseBranch>, so a merge that isn't a fast-forward (diverged local
|
|
63
77
|
* history) is left untouched rather than force-resolved.
|
|
78
|
+
*
|
|
79
|
+
* Holds the provisioning lock and then, like the rebase loop, the [SYNC-C1]
|
|
80
|
+
* content-write lock, both try-only: the base branch is writable in dev, and
|
|
81
|
+
* a save racing the merge's working-tree update can be overwritten by it.
|
|
64
82
|
*/
|
|
65
|
-
export declare function refreshBaseBranchWorkspace(ctx: GitSyncContext): Promise<
|
|
83
|
+
export declare function refreshBaseBranchWorkspace(ctx: GitSyncContext): Promise<BaseRefreshReport>;
|