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
|
@@ -2,6 +2,8 @@ import type { RootCollectionConfig } from './config/index.js';
|
|
|
2
2
|
import type { FlatSchemaItem } from './config/types.js';
|
|
3
3
|
import type { OperatingMode } from './operating-mode/index.js';
|
|
4
4
|
import type { EntrySchemaRegistry, SchemaResolutionResult } from './schema/types.js';
|
|
5
|
+
/** File name of the schema cache inside {@link schemaCacheDir}. */
|
|
6
|
+
export declare const SCHEMA_CACHE_FILE = "schema-cache.json";
|
|
5
7
|
/** resource-generation.ts resource key for the schema cache's marker. */
|
|
6
8
|
export declare const SCHEMA_GENERATION_RESOURCE = "schema";
|
|
7
9
|
/** @internal Exported for tests. */
|
|
@@ -18,8 +20,8 @@ export interface BranchSchemaCacheEntry {
|
|
|
18
20
|
generation: string | null;
|
|
19
21
|
}
|
|
20
22
|
/**
|
|
21
|
-
* Per-branch schema cache:
|
|
22
|
-
*
|
|
23
|
+
* Per-branch schema cache: one file in {@link schemaCacheDir} with no in-memory
|
|
24
|
+
* layer, so it stays coherent across Lambda invocations.
|
|
23
25
|
*
|
|
24
26
|
* Freshness follows the generation-marker protocol owned by
|
|
25
27
|
* resource-generation.ts, and this is one of that protocol's durable-snapshot
|
|
@@ -39,7 +41,7 @@ export declare class BranchSchemaCache {
|
|
|
39
41
|
/**
|
|
40
42
|
* Whether to skip the on-disk cache for this branchRoot.
|
|
41
43
|
*
|
|
42
|
-
* Never write
|
|
44
|
+
* Never write a schema cache at the project root, whichever entrypoint
|
|
43
45
|
* produced the cwd branchRoot. branchRoot equals process.cwd() only in the
|
|
44
46
|
* synthetic contexts static deployments and build phases use; a real branch
|
|
45
47
|
* root is always nested under the workspace.
|
|
@@ -5,10 +5,14 @@ import { flattenSchema } from './config/flatten.js';
|
|
|
5
5
|
import { validateReferenceEntryTypes } from './validation/entry-type-reference-validator.js';
|
|
6
6
|
import { isBuildMode } from './build-mode.js';
|
|
7
7
|
import { bumpResourceGeneration, readResourceGeneration, isGenerationCurrent, } from './resource-generation.js';
|
|
8
|
+
import { timeRequestPhase } from './utils/request-timing.js';
|
|
9
|
+
import { CANOPY_META_DIR } from './utils/git.js';
|
|
8
10
|
/** Bump when BranchSchemaCacheEntry shape changes to auto-invalidate stale caches */
|
|
9
11
|
const SCHEMA_CACHE_VERSION = 3;
|
|
10
12
|
/** Minimum interval between mtime staleness checks (ms) */
|
|
11
13
|
const MTIME_CHECK_DEBOUNCE_MS = 1000;
|
|
14
|
+
/** File name of the schema cache inside {@link schemaCacheDir}. */
|
|
15
|
+
export const SCHEMA_CACHE_FILE = 'schema-cache.json';
|
|
12
16
|
/** resource-generation.ts resource key for the schema cache's marker. */
|
|
13
17
|
export const SCHEMA_GENERATION_RESOURCE = 'schema';
|
|
14
18
|
/**
|
|
@@ -43,8 +47,22 @@ async function isStaleByMtime(dir, cachedAt) {
|
|
|
43
47
|
return false;
|
|
44
48
|
}
|
|
45
49
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
50
|
+
* Directory holding a branch's schema cache: `{branchRoot}/.git/canopycms` when
|
|
51
|
+
* the branch root is a full clone (every canopycms branch workspace is), else
|
|
52
|
+
* `{branchRoot}/.canopy-meta`. Under `.git/` because nothing there is ever
|
|
53
|
+
* tracked or reported by `git status`, so the cache can neither dirty the
|
|
54
|
+
* workspace nor enter a commit, even where an adopter has committed
|
|
55
|
+
* `.canopy-meta/`. It also lives and dies with the clone, so a re-clone never
|
|
56
|
+
* reads its predecessor's snapshot.
|
|
57
|
+
*/
|
|
58
|
+
async function schemaCacheDir(branchRoot) {
|
|
59
|
+
const gitDir = path.join(branchRoot, '.git');
|
|
60
|
+
const isClone = await fs.stat(gitDir).then((stat) => stat.isDirectory(), () => false);
|
|
61
|
+
return isClone ? path.join(gitDir, 'canopycms') : path.join(branchRoot, CANOPY_META_DIR);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Per-branch schema cache: one file in {@link schemaCacheDir} with no in-memory
|
|
65
|
+
* layer, so it stays coherent across Lambda invocations.
|
|
48
66
|
*
|
|
49
67
|
* Freshness follows the generation-marker protocol owned by
|
|
50
68
|
* resource-generation.ts, and this is one of that protocol's durable-snapshot
|
|
@@ -65,7 +83,7 @@ export class BranchSchemaCache {
|
|
|
65
83
|
/**
|
|
66
84
|
* Whether to skip the on-disk cache for this branchRoot.
|
|
67
85
|
*
|
|
68
|
-
* Never write
|
|
86
|
+
* Never write a schema cache at the project root, whichever entrypoint
|
|
69
87
|
* produced the cwd branchRoot. branchRoot equals process.cwd() only in the
|
|
70
88
|
* synthetic contexts static deployments and build phases use; a real branch
|
|
71
89
|
* root is always nested under the workspace.
|
|
@@ -75,7 +93,7 @@ export class BranchSchemaCache {
|
|
|
75
93
|
}
|
|
76
94
|
/** Get schema for a branch, from the cache when fresh, else resolved fresh. */
|
|
77
95
|
async getSchema(branchRoot, entrySchemaRegistry, contentRootName = 'content') {
|
|
78
|
-
return this.loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName);
|
|
96
|
+
return timeRequestPhase('schema', () => this.loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName));
|
|
79
97
|
}
|
|
80
98
|
/**
|
|
81
99
|
* Resolve the schema from disk. Protected rather than a direct resolveSchema
|
|
@@ -89,8 +107,7 @@ export class BranchSchemaCache {
|
|
|
89
107
|
const contentRoot = path.join(branchRoot, contentRootName);
|
|
90
108
|
const skipDiskCache = this.skipDiskCache(branchRoot);
|
|
91
109
|
if (!skipDiskCache) {
|
|
92
|
-
const
|
|
93
|
-
const cachePath = path.join(cacheDir, 'schema-cache.json');
|
|
110
|
+
const cachePath = path.join(await schemaCacheDir(branchRoot), SCHEMA_CACHE_FILE);
|
|
94
111
|
let cacheData = null;
|
|
95
112
|
try {
|
|
96
113
|
const cacheContent = await fs.readFile(cachePath, 'utf-8');
|
|
@@ -149,7 +166,8 @@ export class BranchSchemaCache {
|
|
|
149
166
|
const read = skipDiskCache
|
|
150
167
|
? null
|
|
151
168
|
: await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
|
|
152
|
-
|
|
169
|
+
// Nested under `schema`, so a request summary names a cache miss (`…schema>resolve`).
|
|
170
|
+
const result = await timeRequestPhase('resolve', () => this.resolveFresh(contentRoot, entrySchemaRegistry));
|
|
153
171
|
// Validate schema has content
|
|
154
172
|
if (!isValidSchema(result.schema)) {
|
|
155
173
|
throw new Error(`No schema found in ${contentRoot}. Create .collection.json files ` +
|
|
@@ -166,11 +184,11 @@ export class BranchSchemaCache {
|
|
|
166
184
|
// Use configured contentRoot name as base path for logical paths
|
|
167
185
|
const flatSchema = flattenSchema(result.schema, contentRootName);
|
|
168
186
|
if (!skipDiskCache) {
|
|
169
|
-
const cacheDir =
|
|
170
|
-
const cachePath = path.join(cacheDir,
|
|
187
|
+
const cacheDir = await schemaCacheDir(branchRoot);
|
|
188
|
+
const cachePath = path.join(cacheDir, SCHEMA_CACHE_FILE);
|
|
171
189
|
// Opportunistic cleanup of the retired .stale marker file, which a
|
|
172
190
|
// mid-flight deploy can leave behind. Not load-bearing.
|
|
173
|
-
await fs.unlink(path.join(
|
|
191
|
+
await fs.unlink(path.join(branchRoot, CANOPY_META_DIR, 'schema-cache.stale')).catch(() => { });
|
|
174
192
|
if (read && read.ok) {
|
|
175
193
|
const newCache = {
|
|
176
194
|
version: SCHEMA_CACHE_VERSION,
|
|
@@ -180,7 +198,7 @@ export class BranchSchemaCache {
|
|
|
180
198
|
generation: read.token,
|
|
181
199
|
};
|
|
182
200
|
// Temp file then rename, with the temp file unlinked on a failed
|
|
183
|
-
// rename so a transient error leaves no stray `.tmp`
|
|
201
|
+
// rename so a transient error leaves no stray `.tmp` beside the cache.
|
|
184
202
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
185
203
|
const tmpPath = path.join(cacheDir, `schema-cache.tmp.${Date.now()}.${Math.random()}.json`);
|
|
186
204
|
await fs.writeFile(tmpPath, JSON.stringify(newCache, null, 2), 'utf-8');
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { CanopyConfig } from './config/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* The cone a content branch's clone checks out for `contentRoot`, sorted as
|
|
4
|
+
* `git sparse-checkout list` prints it, or null for a full clone when the content root is the
|
|
5
|
+
* repository root.
|
|
6
|
+
*/
|
|
7
|
+
export declare function sparseConeFor(contentRoot: string | undefined): string[] | null;
|
|
8
|
+
export declare function sameCone(a: readonly string[] | null, b: readonly string[] | null): boolean;
|
|
9
|
+
export interface RecordedSparseCone {
|
|
10
|
+
cone: string[] | null;
|
|
11
|
+
}
|
|
12
|
+
/** The recorded cone, or undefined when none is recorded or the record does not parse. */
|
|
13
|
+
export declare function readRecordedSparseCone(contentBranchesRoot: string): Promise<RecordedSparseCone | undefined>;
|
|
14
|
+
/**
|
|
15
|
+
* Record `cone` unless it already is, by temp file and rename. Does not create the branches
|
|
16
|
+
* root: a deployment without one has no clones to re-apply a cone to.
|
|
17
|
+
*/
|
|
18
|
+
export declare function recordSparseCone(contentBranchesRoot: string, cone: string[] | null): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Record the cone `config` asks for, once per process start, so a changed content root reaches
|
|
21
|
+
* existing clones without waiting for a new branch. Best-effort: a failure is logged.
|
|
22
|
+
*/
|
|
23
|
+
export declare function recordConfiguredSparseCone(config: CanopyConfig): Promise<void>;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sparse checkout for content-branch clones. An editor reads and writes only under the content
|
|
3
|
+
* root, and on EFS every checked-out file costs an NFS round trip, so a content branch's clone
|
|
4
|
+
* checks out the content root and `.canopy-meta` in cone mode, which always keeps the files at
|
|
5
|
+
* the repository root (a root permissions.json among them).
|
|
6
|
+
*
|
|
7
|
+
* A cone is set only by `stageBranchWorkspace` and, on clones already sparse, by the worker
|
|
8
|
+
* (worker/sparse-cone.ts), so settings workspaces are never sparse; their orphan init also
|
|
9
|
+
* empties the index with `--sparse`, so a sparse one could not leak base content either.
|
|
10
|
+
* Sparse-index stays off: git expands it for most commands anyway.
|
|
11
|
+
*
|
|
12
|
+
* `git add` and `git rm` refuse a path outside the cone unless given `--sparse` (git >= 2.34),
|
|
13
|
+
* so every call that can name one passes it.
|
|
14
|
+
*/
|
|
15
|
+
import fs from 'node:fs/promises';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { readsFromCheckout } from './build-mode.js';
|
|
18
|
+
import { operatingStrategy } from './operating-mode/index.js';
|
|
19
|
+
import { getErrorMessage, isNodeError } from './utils/error.js';
|
|
20
|
+
import { CANOPY_META_DIR } from './utils/git.js';
|
|
21
|
+
import { canopyLogWarn } from './utils/logger.js';
|
|
22
|
+
/**
|
|
23
|
+
* The cone a content branch's clone checks out for `contentRoot`, sorted as
|
|
24
|
+
* `git sparse-checkout list` prints it, or null for a full clone when the content root is the
|
|
25
|
+
* repository root.
|
|
26
|
+
*/
|
|
27
|
+
export function sparseConeFor(contentRoot) {
|
|
28
|
+
const root = (contentRoot ?? 'content')
|
|
29
|
+
.split('/')
|
|
30
|
+
.filter((segment) => segment && segment !== '.')
|
|
31
|
+
.join('/');
|
|
32
|
+
return root ? [...new Set([CANOPY_META_DIR, root])].sort() : null;
|
|
33
|
+
}
|
|
34
|
+
export function sameCone(a, b) {
|
|
35
|
+
if (a === null || b === null)
|
|
36
|
+
return a === b;
|
|
37
|
+
const [left, right] = [[...a].sort(), [...b].sort()];
|
|
38
|
+
return left.length === right.length && left.every((dir, i) => dir === right[i]);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Beside the branch directories: the cone this deployment's config asks for, written by the
|
|
42
|
+
* processes that hold the config and read by the worker, which does not, to re-apply a changed
|
|
43
|
+
* content root to existing clones.
|
|
44
|
+
*/
|
|
45
|
+
const SPARSE_CONE_RECORD = '.sparse-cone.json';
|
|
46
|
+
function isConeDir(dir) {
|
|
47
|
+
return (typeof dir === 'string' &&
|
|
48
|
+
dir.length > 0 &&
|
|
49
|
+
!path.isAbsolute(dir) &&
|
|
50
|
+
!dir.split('/').some((segment) => segment === '' || segment === '.' || segment === '..'));
|
|
51
|
+
}
|
|
52
|
+
/** The recorded cone, or undefined when none is recorded or the record does not parse. */
|
|
53
|
+
export async function readRecordedSparseCone(contentBranchesRoot) {
|
|
54
|
+
let raw;
|
|
55
|
+
try {
|
|
56
|
+
raw = await fs.readFile(path.join(contentBranchesRoot, SPARSE_CONE_RECORD), 'utf8');
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
if (isNodeError(err) && err.code === 'ENOENT')
|
|
60
|
+
return undefined;
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
let parsed;
|
|
64
|
+
try {
|
|
65
|
+
parsed = JSON.parse(raw);
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
if (typeof parsed !== 'object' || parsed === null || !('cone' in parsed))
|
|
71
|
+
return undefined;
|
|
72
|
+
const { cone } = parsed;
|
|
73
|
+
if (cone === null)
|
|
74
|
+
return { cone: null };
|
|
75
|
+
return Array.isArray(cone) && cone.length > 0 && cone.every(isConeDir)
|
|
76
|
+
? { cone: cone.filter(isConeDir) }
|
|
77
|
+
: undefined;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Record `cone` unless it already is, by temp file and rename. Does not create the branches
|
|
81
|
+
* root: a deployment without one has no clones to re-apply a cone to.
|
|
82
|
+
*/
|
|
83
|
+
export async function recordSparseCone(contentBranchesRoot, cone) {
|
|
84
|
+
const current = await readRecordedSparseCone(contentBranchesRoot);
|
|
85
|
+
if (current && sameCone(current.cone, cone))
|
|
86
|
+
return;
|
|
87
|
+
const target = path.join(contentBranchesRoot, SPARSE_CONE_RECORD);
|
|
88
|
+
const temp = `${target}.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
89
|
+
try {
|
|
90
|
+
await fs.writeFile(temp, `${JSON.stringify({ cone })}\n`, 'utf8');
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
if (isNodeError(err) && err.code === 'ENOENT')
|
|
94
|
+
return;
|
|
95
|
+
throw err;
|
|
96
|
+
}
|
|
97
|
+
try {
|
|
98
|
+
await fs.rename(temp, target);
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
await fs.unlink(temp).catch(() => { });
|
|
102
|
+
throw err;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Record the cone `config` asks for, once per process start, so a changed content root reaches
|
|
107
|
+
* existing clones without waiting for a new branch. Best-effort: a failure is logged.
|
|
108
|
+
*/
|
|
109
|
+
export async function recordConfiguredSparseCone(config) {
|
|
110
|
+
try {
|
|
111
|
+
if (readsFromCheckout(config))
|
|
112
|
+
return;
|
|
113
|
+
await recordSparseCone(operatingStrategy(config.mode).getContentBranchesRoot(config.sourceRoot), sparseConeFor(config.contentRoot));
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
canopyLogWarn(`[canopy] Could not record the sparse-checkout cone: ${getErrorMessage(err)}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { CanopyConfig } from './config/index.js';
|
|
2
|
+
import { type BlockingKind } from './branch-provisioning.js';
|
|
2
3
|
import type { BranchAccessControl, BranchContext, CanopyUserId } from './types.js';
|
|
3
4
|
import type { OperatingMode } from './operating-mode/index.js';
|
|
4
5
|
export interface OpenBranchOptions {
|
|
@@ -12,14 +13,52 @@ export interface OpenBranchOptions {
|
|
|
12
13
|
remoteUrl?: string;
|
|
13
14
|
}
|
|
14
15
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
16
|
+
* `created`: this call published the branch. `exists`: it was already there, or another request
|
|
17
|
+
* published it first; its metadata is returned as found, never merged with this call's.
|
|
18
|
+
*/
|
|
19
|
+
export interface ProvisionOutcome {
|
|
20
|
+
kind: 'created' | 'exists';
|
|
21
|
+
context: BranchContext;
|
|
22
|
+
}
|
|
23
|
+
/** @internal Test seams around the publish step. */
|
|
24
|
+
export interface ProvisioningTestHooks {
|
|
25
|
+
beforePublish?: (paths: {
|
|
26
|
+
stagingPath: string;
|
|
27
|
+
finalPath: string;
|
|
28
|
+
}) => Promise<void>;
|
|
29
|
+
inspectBlocked?: (finalPath: string) => Promise<BlockingKind>;
|
|
30
|
+
}
|
|
31
|
+
/** @internal Exported for tests; no argument clears them. */
|
|
32
|
+
export declare function setProvisioningTestHooks(hooks?: ProvisioningTestHooks): void;
|
|
33
|
+
/**
|
|
34
|
+
* Provisions per-branch workspaces under [PROV-1]: a branch's directory appears only by rename,
|
|
35
|
+
* complete with branch.json (branch-provisioning.ts).
|
|
17
36
|
*/
|
|
18
37
|
export declare class BranchWorkspaceManager {
|
|
19
38
|
private readonly config;
|
|
20
39
|
constructor(config: CanopyConfig);
|
|
21
|
-
|
|
40
|
+
/** The branch's context, provisioning it first when it does not exist. */
|
|
22
41
|
openOrCreateBranch(options: OpenBranchOptions): Promise<BranchContext>;
|
|
42
|
+
/**
|
|
43
|
+
* Provision a branch workspace, or report the one already there.
|
|
44
|
+
*
|
|
45
|
+
* @throws BranchProvisioningBusyError when another process is mid-way through this branch, or
|
|
46
|
+
* what is at its path is too fresh to judge (retriable)
|
|
47
|
+
* @throws BranchDirOccupiedError in prod, when its path holds a repository this deployment did
|
|
48
|
+
* not create
|
|
49
|
+
* @throws BranchMetadataCorruptError when its branch.json does not parse
|
|
50
|
+
*/
|
|
51
|
+
provisionBranch(options: OpenBranchOptions): Promise<ProvisionOutcome>;
|
|
52
|
+
private provisionUnshared;
|
|
53
|
+
private stageAndPublish;
|
|
54
|
+
/**
|
|
55
|
+
* The one exception to [PROV-1]: a dev workspace that already holds real work is kept where it
|
|
56
|
+
* is, given branch.json under the branch's lock.
|
|
57
|
+
*/
|
|
58
|
+
private adoptInPlace;
|
|
59
|
+
/** Whether residue at the branch path may still belong to a live process. */
|
|
60
|
+
private recentlyActive;
|
|
61
|
+
private existing;
|
|
23
62
|
}
|
|
24
63
|
export { loadBranchContext } from './branch-metadata.js';
|
|
25
64
|
/**
|