canopycms 0.8.0 → 0.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -47
- package/dist/api/admin-branch-health.d.ts +11 -0
- package/dist/api/admin-branch-health.js +36 -0
- package/dist/api/admin.d.ts +8 -1
- package/dist/api/branch-merge.js +1 -1
- package/dist/api/branch-review.js +20 -3
- package/dist/api/branch-status.js +30 -19
- package/dist/api/branch.d.ts +8 -3
- package/dist/api/branch.js +32 -3
- package/dist/api/client.d.ts +3 -1
- package/dist/api/client.js +4 -0
- package/dist/api/github-sync.d.ts +2 -0
- package/dist/api/github-sync.js +6 -1
- package/dist/api/pr-comment-sync.js +3 -1
- package/dist/branch-metadata-file.d.ts +66 -0
- package/dist/branch-metadata-file.js +14 -0
- package/dist/branch-metadata.d.ts +4 -2
- package/dist/branch-metadata.js +10 -2
- package/dist/branch-schema-cache.d.ts +22 -2
- package/dist/branch-schema-cache.js +88 -12
- package/dist/cli/cli.js +91 -18
- package/dist/cli/generate-ai-content.js +72 -11
- package/dist/cli/init.js +2 -2
- package/dist/content-reader.d.ts +24 -10
- package/dist/content-reader.js +43 -6
- package/dist/context.js +43 -41
- package/dist/editor/BranchManager.d.ts +8 -4
- package/dist/editor/BranchManager.js +43 -18
- package/dist/editor/Editor.js +3 -2
- package/dist/editor/admin/SystemHealthPanel.js +65 -31
- package/dist/editor/admin/UnpublishedAtMergeAlert.d.ts +16 -0
- package/dist/editor/admin/UnpublishedAtMergeAlert.js +50 -0
- package/dist/editor/admin/useSystemHealth.d.ts +2 -0
- package/dist/editor/admin/useSystemHealth.js +21 -0
- package/dist/editor/branch-lock.d.ts +2 -0
- package/dist/editor/branch-lock.js +5 -3
- package/dist/editor/branch-status.d.ts +25 -1
- package/dist/editor/branch-status.js +75 -0
- package/dist/editor/components/EditorHeader.d.ts +6 -2
- package/dist/editor/components/EditorHeader.js +51 -30
- package/dist/editor/components/EntryHeader.d.ts +3 -1
- package/dist/editor/components/EntryHeader.js +2 -1
- package/dist/editor/components/TooltipDetail.d.ts +14 -0
- package/dist/editor/components/TooltipDetail.js +18 -0
- package/dist/editor/copy.d.ts +7 -0
- package/dist/editor/copy.js +9 -0
- package/dist/editor/hooks/useBranchManager.d.ts +8 -3
- package/dist/editor/hooks/useBranchManager.js +48 -5
- package/dist/pr-comments.d.ts +2 -0
- package/dist/pr-comments.js +20 -14
- package/dist/resource-generation.js +2 -3
- package/dist/server.d.ts +6 -1
- package/dist/server.js +6 -1
- package/dist/submission-attribution.d.ts +1 -1
- package/dist/submission-attribution.js +20 -2
- package/dist/task-queue/task-queue.d.ts +5 -1
- package/dist/task-queue/task-queue.js +4 -2
- package/dist/types.d.ts +61 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worker/branch-merge.d.ts +68 -0
- package/dist/worker/branch-merge.js +228 -0
- package/dist/worker/canopy-state.d.ts +2 -0
- package/dist/worker/canopy-state.js +1 -1
- package/dist/worker/cms-worker.d.ts +1 -0
- package/dist/worker/cms-worker.js +5 -3
- package/dist/worker/git-sync.js +15 -8
- package/dist/worker/github-commits.d.ts +106 -0
- package/dist/worker/github-commits.js +485 -0
- package/dist/worker/github-gateway-http.js +3 -2
- package/dist/worker/github-gateway-wire.d.ts +24 -15
- package/dist/worker/github-gateway-wire.js +4 -1
- package/dist/worker/github-gateway.d.ts +16 -5
- package/dist/worker/github-gateway.js +22 -5
- package/dist/worker/github-mirror.d.ts +8 -0
- package/dist/worker/github-mirror.js +12 -1
- package/dist/worker/github-transfer.d.ts +3 -1
- package/dist/worker/github-transfer.js +3 -2
- package/dist/worker/history-rewrite.d.ts +13 -1
- package/dist/worker/history-rewrite.js +27 -3
- package/dist/worker/rebase.d.ts +13 -7
- package/dist/worker/rebase.js +351 -95
- package/dist/worker/task-runner.d.ts +1 -1
- package/dist/worker/task-runner.js +172 -34
- package/dist/worker/worker-context.d.ts +2 -0
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'node:fs/promises';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
+
import { LRUCache } from 'lru-cache';
|
|
3
4
|
import { resolveSchema, isValidSchema } from './schema/resolver.js';
|
|
4
5
|
import { registryFingerprint } from './schema/registry-fingerprint.js';
|
|
5
6
|
import { flattenSchema } from './config/flatten.js';
|
|
@@ -13,6 +14,8 @@ import { canopyLogWarn } from './utils/logger.js';
|
|
|
13
14
|
const SCHEMA_CACHE_VERSION = 4;
|
|
14
15
|
/** Minimum interval between mtime staleness checks (ms) */
|
|
15
16
|
const MTIME_CHECK_DEBOUNCE_MS = 1000;
|
|
17
|
+
/** Cap on the parsed snapshots one process keeps, counted in bytes of their files. */
|
|
18
|
+
const SNAPSHOT_MEMO_MAX_BYTES = 64 * 1024 * 1024;
|
|
16
19
|
/** File name of the schema cache inside {@link schemaCacheDir}. */
|
|
17
20
|
export const SCHEMA_CACHE_FILE = 'schema-cache.json';
|
|
18
21
|
/** resource-generation.ts resource key for the schema cache's marker. */
|
|
@@ -65,6 +68,13 @@ async function isStaleByMtime(dir, cachedAt) {
|
|
|
65
68
|
}
|
|
66
69
|
return false;
|
|
67
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Names one version of a file. Every write of the schema cache renames a new temp file into
|
|
73
|
+
* place, so a rewrite always changes the inode, whatever token or content it carries.
|
|
74
|
+
*/
|
|
75
|
+
function fileIdentity(stat) {
|
|
76
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}`;
|
|
77
|
+
}
|
|
68
78
|
/**
|
|
69
79
|
* Directory holding a branch's schema cache: `{branchRoot}/.git/canopycms` when
|
|
70
80
|
* the branch root is a full clone (every canopycms branch workspace is), else
|
|
@@ -80,8 +90,15 @@ async function schemaCacheDir(branchRoot) {
|
|
|
80
90
|
return isClone ? path.join(gitDir, 'canopycms') : path.join(branchRoot, CANOPY_META_DIR);
|
|
81
91
|
}
|
|
82
92
|
/**
|
|
83
|
-
* Per-branch schema cache: one file in {@link schemaCacheDir}
|
|
84
|
-
*
|
|
93
|
+
* Per-branch schema cache: one file in {@link schemaCacheDir}, the only state
|
|
94
|
+
* shared across processes. Each process keeps the entries it parsed or wrote,
|
|
95
|
+
* keyed by the file's identity, so it parses an unchanged file once while the
|
|
96
|
+
* entry stays in its size-bounded memo. Every call still opens the file and
|
|
97
|
+
* runs every freshness check below on the entry, and the file is only ever
|
|
98
|
+
* replaced by rename (see {@link fileIdentity}), so a process is as fresh as if
|
|
99
|
+
* it re-read the file. The identity is fstat'ed on an open handle because NFS
|
|
100
|
+
* revalidates attributes on open (close-to-open), while a bare stat() may
|
|
101
|
+
* answer from its attribute cache.
|
|
85
102
|
*
|
|
86
103
|
* Freshness follows the generation-marker protocol owned by
|
|
87
104
|
* resource-generation.ts, and this is one of that protocol's durable-snapshot
|
|
@@ -106,6 +123,19 @@ export class BranchSchemaCache {
|
|
|
106
123
|
constructor(mode = 'prod') {
|
|
107
124
|
/** Tracks when we last checked mtimes per contentRoot, to debounce rapid requests */
|
|
108
125
|
this.lastMtimeCheck = new Map();
|
|
126
|
+
/** Parsed cache files, by absolute path; see the class doc. */
|
|
127
|
+
this.snapshots = new LRUCache({
|
|
128
|
+
maxSize: SNAPSHOT_MEMO_MAX_BYTES,
|
|
129
|
+
sizeCalculation: (snapshot) => Math.max(1, snapshot.bytes),
|
|
130
|
+
});
|
|
131
|
+
/**
|
|
132
|
+
* In-flight resolves of the checkout during a build, by content root and registry. A build
|
|
133
|
+
* reads a checkout that does not change under it, so those two are its whole input and the
|
|
134
|
+
* result needs no revalidation. Only {@link isBuildMode}: `CANOPY_BUILD_MODE` is a build and
|
|
135
|
+
* script switch, and a long-lived process setting it would see a schema edit only on
|
|
136
|
+
* restart. A rejected resolve is dropped, so a strict-mode failure fails every read.
|
|
137
|
+
*/
|
|
138
|
+
this.buildResolves = new Map();
|
|
109
139
|
this.devMode = mode === 'dev';
|
|
110
140
|
}
|
|
111
141
|
/**
|
|
@@ -135,17 +165,12 @@ export class BranchSchemaCache {
|
|
|
135
165
|
async loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
136
166
|
const contentRoot = path.join(branchRoot, contentRootName);
|
|
137
167
|
const fromCheckout = this.readsCheckout(branchRoot);
|
|
168
|
+
if (fromCheckout && isBuildMode()) {
|
|
169
|
+
return this.resolveForBuild(branchRoot, entrySchemaRegistry, contentRootName);
|
|
170
|
+
}
|
|
138
171
|
if (!fromCheckout) {
|
|
139
172
|
const cachePath = path.join(await schemaCacheDir(branchRoot), SCHEMA_CACHE_FILE);
|
|
140
|
-
let cacheData =
|
|
141
|
-
try {
|
|
142
|
-
const cacheContent = await fs.readFile(cachePath, 'utf-8');
|
|
143
|
-
cacheData = JSON.parse(cacheContent);
|
|
144
|
-
}
|
|
145
|
-
catch {
|
|
146
|
-
// Cache doesn't exist or can't be read
|
|
147
|
-
cacheData = null;
|
|
148
|
-
}
|
|
173
|
+
let cacheData = await this.readSnapshot(cachePath);
|
|
149
174
|
// Strict version check, not truthiness: a snapshot from an older version
|
|
150
175
|
// left on EFS by a rolling deploy has no `generation` field, and an
|
|
151
176
|
// `undefined` token breaks the freshness comparison below.
|
|
@@ -187,6 +212,44 @@ export class BranchSchemaCache {
|
|
|
187
212
|
fromCheckout,
|
|
188
213
|
});
|
|
189
214
|
}
|
|
215
|
+
resolveForBuild(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
216
|
+
const key = `${path.resolve(branchRoot, contentRootName)}\0${registryFingerprint(entrySchemaRegistry)}`;
|
|
217
|
+
let pending = this.buildResolves.get(key);
|
|
218
|
+
if (!pending) {
|
|
219
|
+
pending = this.resolveFreshAndPersist(branchRoot, entrySchemaRegistry, contentRootName, {
|
|
220
|
+
fromCheckout: true,
|
|
221
|
+
});
|
|
222
|
+
this.buildResolves.set(key, pending);
|
|
223
|
+
pending.catch(() => this.buildResolves.delete(key));
|
|
224
|
+
}
|
|
225
|
+
return pending;
|
|
226
|
+
}
|
|
227
|
+
/** The parsed cache file, or null when it is absent or unreadable. */
|
|
228
|
+
async readSnapshot(cachePath) {
|
|
229
|
+
let handle;
|
|
230
|
+
try {
|
|
231
|
+
handle = await fs.open(cachePath, 'r');
|
|
232
|
+
}
|
|
233
|
+
catch {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const stat = await handle.stat({ bigint: true });
|
|
238
|
+
const identity = fileIdentity(stat);
|
|
239
|
+
const memo = this.snapshots.get(cachePath);
|
|
240
|
+
if (memo?.identity === identity)
|
|
241
|
+
return memo.entry;
|
|
242
|
+
const entry = JSON.parse(await fs.readFile(handle, 'utf-8'));
|
|
243
|
+
this.snapshots.set(cachePath, { identity, entry, bytes: Number(stat.size) });
|
|
244
|
+
return entry;
|
|
245
|
+
}
|
|
246
|
+
catch {
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
finally {
|
|
250
|
+
await handle.close().catch(() => { });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
190
253
|
/**
|
|
191
254
|
* Resolve the schema fresh from disk and persist it (subject to the
|
|
192
255
|
* skip-persist rule below), UNCONDITIONALLY -- never through the cache-read
|
|
@@ -241,16 +304,29 @@ export class BranchSchemaCache {
|
|
|
241
304
|
};
|
|
242
305
|
// Temp file then rename, with the temp file unlinked on a failed
|
|
243
306
|
// rename so a transient error leaves no stray `.tmp` beside the cache.
|
|
307
|
+
// The memo is seeded from the temp file's own identity, which the
|
|
308
|
+
// rename keeps: stat'ing cachePath afterwards could see a concurrent
|
|
309
|
+
// writer's file and pair it with this entry. It holds the parsed body,
|
|
310
|
+
// as every reader does, not the resolver's objects, which alias the
|
|
311
|
+
// registry and keep values JSON changes or drops.
|
|
244
312
|
await fs.mkdir(cacheDir, { recursive: true });
|
|
245
313
|
const tmpPath = path.join(cacheDir, `schema-cache.tmp.${Date.now()}.${Math.random()}.json`);
|
|
246
|
-
|
|
314
|
+
const body = JSON.stringify(newCache);
|
|
315
|
+
let identity;
|
|
247
316
|
try {
|
|
317
|
+
await fs.writeFile(tmpPath, body, 'utf-8');
|
|
318
|
+
identity = fileIdentity(await fs.stat(tmpPath, { bigint: true }));
|
|
248
319
|
await fs.rename(tmpPath, cachePath);
|
|
249
320
|
}
|
|
250
321
|
catch (err) {
|
|
251
322
|
await fs.unlink(tmpPath).catch(() => { });
|
|
252
323
|
throw err;
|
|
253
324
|
}
|
|
325
|
+
this.snapshots.set(cachePath, {
|
|
326
|
+
identity,
|
|
327
|
+
entry: JSON.parse(body),
|
|
328
|
+
bytes: Buffer.byteLength(body),
|
|
329
|
+
});
|
|
254
330
|
}
|
|
255
331
|
// else: the marker read failed for a reason other than "never bumped",
|
|
256
332
|
// so no token can be attributed to this resolve, and a snapshot stamped
|
package/dist/cli/cli.js
CHANGED
|
@@ -562,7 +562,7 @@ async function failTask(taskDir, taskId, error, logger = nullLogger) {
|
|
|
562
562
|
});
|
|
563
563
|
logger.debug("Failed task", { id: taskId, error });
|
|
564
564
|
}
|
|
565
|
-
async function retryTask(taskDir, taskId, error, logger = nullLogger) {
|
|
565
|
+
async function retryTask(taskDir, taskId, error, logger = nullLogger, options = {}) {
|
|
566
566
|
const processingPath = path6.join(taskDir, "processing", `${taskId}.json`);
|
|
567
567
|
const pendingDir = path6.join(taskDir, "pending");
|
|
568
568
|
const pendingPath = path6.join(pendingDir, `${taskId}.json`);
|
|
@@ -583,7 +583,7 @@ async function retryTask(taskDir, taskId, error, logger = nullLogger) {
|
|
|
583
583
|
throw err2;
|
|
584
584
|
}
|
|
585
585
|
const retryCount = (task.retryCount ?? 0) + 1;
|
|
586
|
-
const backoffMs = Math.min(5e3 * Math.pow(2, retryCount - 1), 6e4);
|
|
586
|
+
const backoffMs = options.delayMs ?? Math.min(5e3 * Math.pow(2, retryCount - 1), 6e4);
|
|
587
587
|
task.status = "pending";
|
|
588
588
|
task.retryCount = retryCount;
|
|
589
589
|
task.retryAfter = new Date(Date.now() + backoffMs).toISOString();
|
|
@@ -3405,6 +3405,7 @@ import path72 from "node:path";
|
|
|
3405
3405
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
3406
3406
|
import fs72 from "node:fs/promises";
|
|
3407
3407
|
import path82 from "node:path";
|
|
3408
|
+
import { LRUCache } from "lru-cache";
|
|
3408
3409
|
import { promises as fs62 } from "fs";
|
|
3409
3410
|
import { join } from "pathe";
|
|
3410
3411
|
import { z as z7 } from "zod";
|
|
@@ -5356,9 +5357,7 @@ async function bumpResourceGeneration(root, resource, options) {
|
|
|
5356
5357
|
await atomicWriteFile2(resourceGenerationPath(root, resource), token);
|
|
5357
5358
|
return token;
|
|
5358
5359
|
} catch (err2) {
|
|
5359
|
-
|
|
5360
|
-
error: getErrorMessage3(err2)
|
|
5361
|
-
});
|
|
5360
|
+
canopyLogWarn2(`CanopyCMS: failed to bump ${resource} generation for ${root}:`, getErrorMessage3(err2));
|
|
5362
5361
|
if (options?.mustSucceed)
|
|
5363
5362
|
throw err2;
|
|
5364
5363
|
return null;
|
|
@@ -5706,6 +5705,9 @@ async function isStaleByMtime(dir, cachedAt) {
|
|
|
5706
5705
|
}
|
|
5707
5706
|
return false;
|
|
5708
5707
|
}
|
|
5708
|
+
function fileIdentity(stat) {
|
|
5709
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}`;
|
|
5710
|
+
}
|
|
5709
5711
|
async function schemaCacheDir(branchRoot) {
|
|
5710
5712
|
const gitDir = path82.join(branchRoot, ".git");
|
|
5711
5713
|
const isClone = await fs72.stat(gitDir).then((stat) => stat.isDirectory(), () => false);
|
|
@@ -8432,7 +8434,7 @@ async function generateAIContentCLI(options) {
|
|
|
8432
8434
|
console.log(` Output: ${result.outputDir}
|
|
8433
8435
|
`);
|
|
8434
8436
|
}
|
|
8435
|
-
var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, RESERVED_SETTINGS_BRANCH_PREFIX2, init_branch_name, BASE58_PATTERN2, CONTENT_ID_PATTERN2, PHYSICAL_SEGMENT_PATTERN2, MAX_BRANCH_NAME_LENGTH2, init_validation, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceSortFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, editorUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, seoFieldNameSchema2, seoSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned22, init_mode_env, forEachReferenceField, init_validation2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, warned4, DEPLOYMENT_PLACEHOLDER2, DEFAULT_BRANCH_NAMESPACE_TEMPLATE2, BRANCH_NAMESPACE_ENV2, init_branch_namespace, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, envFlag2, LOG_LEVELS2, DebugLogger2, testLogger2, MARKDOWN_EXPORT_OPTIONS, BULLETS, DELIMITERS, EMPHASIS, STRONG, RULE, WORD_CHAR, GAP, Restyler, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, heldRoots, getFormatExtension, isDataOnlyFormat, asRecord, DEFAULT_SEO_FIELD_NAMES, asRecord2, asText, asFlag, asImageUrl, asOgType, asTwitterCard, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned5, SchemaUnavailableError, isValidId, parseTypedFilename, EMPTY_LOGICAL_PATH, ENTRY_EXTENSIONS, ContentIdIndex, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, INLINE_LINK_TARGET, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, GitProcessError, STDERR_CAP_BYTES, KILL_GRACE_MS, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, HASH_INACTIVITY_TIMEOUT_MS, MAX_HASH_RUNS, LS_REMOTE_INACTIVITY_TIMEOUT_MS, symlinkBlobSha, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, BaseBranchMissingError, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, parseEntryParts, parseEntryData, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
|
|
8437
|
+
var __defProp3, __getOwnPropNames3, __esm3, __export3, init_error3, init_atomic_write2, init_field_traversal, ITEM_KEY, ITEM_KEY_PATTERN, isItemKey, BASE58, isListField, init_item_keys, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize, RESERVED_SETTINGS_BRANCH_PREFIX2, init_branch_name, BASE58_PATTERN2, CONTENT_ID_PATTERN2, PHYSICAL_SEGMENT_PATTERN2, MAX_BRANCH_NAME_LENGTH2, init_validation, ROOT_COLLECTION_ID, init_types2, normalizePathValue, flattenSchema, init_flatten, scalarFieldTypes2, markdownFieldTypes2, primitiveFieldTypes2, fieldTypes2, init_types22, SAFE_HTML_TAGS2, COMPONENT_NAME2, MDX_BINDINGS2, FORBIDDEN_ATTRIBUTES2, init_mdx_allowlist2, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, mdxPropAllowSchema2, mdxAllowlistSchema2, markdownFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceSortFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, previewPrefixSchema2, editorUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, seoFieldNameSchema2, seoSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned22, init_mode_env, forEachReferenceField, init_validation2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned3, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, warned4, DEPLOYMENT_PLACEHOLDER2, DEFAULT_BRANCH_NAMESPACE_TEMPLATE2, BRANCH_NAMESPACE_ENV2, init_branch_namespace, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, task_queue_config_exports2, init_task_queue_config2, worker_status_exports, WORKER_STATUS_FILE, init_worker_status, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, envFlag2, LOG_LEVELS2, DebugLogger2, testLogger2, MARKDOWN_EXPORT_OPTIONS, BULLETS, DELIMITERS, EMPHASIS, STRONG, RULE, WORD_CHAR, GAP, Restyler, END, FRESH, LINE_WIDTH, MIN_LINE_WIDTH, SourceSplicer, log2, MAX_LCS_CELLS, Splicer, log22, EVIDENCE_MAX_DEPTH, log3, locks, PROVISIONING_LOCK_STALE_MS, sleep, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep2, CONTENT_WRITE_LOCK_STALE_MS, heldRoots, getFormatExtension, isDataOnlyFormat, asRecord, DEFAULT_SEO_FIELD_NAMES, asRecord2, asText, asFlag, asImageUrl, asOgType, asTwitterCard, RESTRICTED_REFERENCE_MARKER, SEO_GROUP_FIELDS, MAX_WARNED, warned5, SchemaUnavailableError, isValidId, parseTypedFilename, EMPTY_LOGICAL_PATH, ENTRY_EXTENSIONS, ContentIdIndex, CONTENT_EXTENSIONS, registry, finalization, log4, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, fingerprints, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log5, BaseBranchUnresolvedError, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, CANOPY_META_DIR, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SNAPSHOT_MEMO_MAX_BYTES, SCHEMA_CACHE_FILE, SCHEMA_GENERATION_RESOURCE, reportedIssues, BranchSchemaCache, RESOURCE, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, EntryGoneError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, isEmptyFieldValue, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, isMissingReference, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, DEFAULT_REFERENCE_RENDERING, FLAT_CELL_TYPES, log6, BASE58_CHAR, ENTRY_LINK_PATTERN, INLINE_LINK_TARGET, BranchMetadataCorruptError, BRANCH_META_DIR, BRANCH_META_FILE, branchMetadataFilePath, UNKNOWN_TIME, enumOf, branchMetadataShape, branchMetadataSchema, branchMetadataFileSchema, log7, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, snapshotSchema, reportedUnreadable, BranchRegistry, log8, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, loadBranchContext, GitProcessError, STDERR_CAP_BYTES, KILL_GRACE_MS, log9, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, GIT_NETWORK_ENV_PASSTHROUGH, NO_AUTO_GC_CONFIG, HASH_INACTIVITY_TIMEOUT_MS, MAX_HASH_RUNS, LS_REMOTE_INACTIVITY_TIMEOUT_MS, symlinkBlobSha, REMOTE_GIT_CONFIG, REMOTE_GIT_MAX_LOOSE_OBJECTS, REMOTE_GIT_MAX_PACKS, PARALLEL_CHECKOUT_ARGS, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, BaseBranchMissingError, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, SettingsRemoteUnreadableError, SettingsBranchDivergedError, SETTINGS_BRANCH_FILES, SettingsBranchHasContentHistoryError, GitManager, PUBLISH_LOCK_WAIT_MS, CREATE_RESIDUE_MIN_QUIET_MS, STAGING_MAX_AGE_MS, REPAIR_MAX_AGE_MS, DELETING_MAX_AGE_MS, SETTINGS_TRASH_MAX_AGE_MS, BranchProvisioningBusyError, BranchDirOccupiedError, STAGING_PREFIX, REPAIR_PREFIX, TRASH_PREFIX, stagingDirName, NO_GIT, RENAME_BLOCKED, SPARSE_CONE_RECORD, defaultSink, sink, ProvisionLog, testHooks, provisionsInFlight, BranchWorkspaceManager, log10, normalizePath, parseEntryParts, parseEntryData, readEntryData, createReferenceResolver, withoutUnavailableEntries, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, CROP_PRECISION, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
|
|
8436
8438
|
var init_generate_ai_content = __esm({
|
|
8437
8439
|
"dist/cli/generate-ai-content.js"() {
|
|
8438
8440
|
"use strict";
|
|
@@ -10742,6 +10744,7 @@ var init_generate_ai_content = __esm({
|
|
|
10742
10744
|
init_field_traversal();
|
|
10743
10745
|
init_atomic_write2();
|
|
10744
10746
|
init_error3();
|
|
10747
|
+
init_logger2();
|
|
10745
10748
|
log4 = createDebugLogger2({ prefix: "ResourceGeneration" });
|
|
10746
10749
|
META_DIR2 = ".canopy-meta";
|
|
10747
10750
|
init_logger2();
|
|
@@ -10878,12 +10881,18 @@ var init_generate_ai_content = __esm({
|
|
|
10878
10881
|
init_logger2();
|
|
10879
10882
|
SCHEMA_CACHE_VERSION = 4;
|
|
10880
10883
|
MTIME_CHECK_DEBOUNCE_MS = 1e3;
|
|
10884
|
+
SNAPSHOT_MEMO_MAX_BYTES = 64 * 1024 * 1024;
|
|
10881
10885
|
SCHEMA_CACHE_FILE = "schema-cache.json";
|
|
10882
10886
|
SCHEMA_GENERATION_RESOURCE = "schema";
|
|
10883
10887
|
reportedIssues = /* @__PURE__ */ new Set();
|
|
10884
10888
|
BranchSchemaCache = class {
|
|
10885
10889
|
constructor(mode = "prod") {
|
|
10886
10890
|
this.lastMtimeCheck = /* @__PURE__ */ new Map();
|
|
10891
|
+
this.snapshots = new LRUCache({
|
|
10892
|
+
maxSize: SNAPSHOT_MEMO_MAX_BYTES,
|
|
10893
|
+
sizeCalculation: (snapshot) => Math.max(1, snapshot.bytes)
|
|
10894
|
+
});
|
|
10895
|
+
this.buildResolves = /* @__PURE__ */ new Map();
|
|
10887
10896
|
this.devMode = mode === "dev";
|
|
10888
10897
|
}
|
|
10889
10898
|
/**
|
|
@@ -10913,15 +10922,12 @@ var init_generate_ai_content = __esm({
|
|
|
10913
10922
|
async loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
10914
10923
|
const contentRoot = path82.join(branchRoot, contentRootName);
|
|
10915
10924
|
const fromCheckout = this.readsCheckout(branchRoot);
|
|
10925
|
+
if (fromCheckout && isBuildMode()) {
|
|
10926
|
+
return this.resolveForBuild(branchRoot, entrySchemaRegistry, contentRootName);
|
|
10927
|
+
}
|
|
10916
10928
|
if (!fromCheckout) {
|
|
10917
10929
|
const cachePath = path82.join(await schemaCacheDir(branchRoot), SCHEMA_CACHE_FILE);
|
|
10918
|
-
let cacheData =
|
|
10919
|
-
try {
|
|
10920
|
-
const cacheContent = await fs72.readFile(cachePath, "utf-8");
|
|
10921
|
-
cacheData = JSON.parse(cacheContent);
|
|
10922
|
-
} catch {
|
|
10923
|
-
cacheData = null;
|
|
10924
|
-
}
|
|
10930
|
+
let cacheData = await this.readSnapshot(cachePath);
|
|
10925
10931
|
if (cacheData && cacheData.version === SCHEMA_CACHE_VERSION && cacheData.registryFingerprint === registryFingerprint(entrySchemaRegistry)) {
|
|
10926
10932
|
const read = await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
|
|
10927
10933
|
if (isGenerationCurrent(cacheData.generation, read)) {
|
|
@@ -10951,6 +10957,42 @@ var init_generate_ai_content = __esm({
|
|
|
10951
10957
|
fromCheckout
|
|
10952
10958
|
});
|
|
10953
10959
|
}
|
|
10960
|
+
resolveForBuild(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
10961
|
+
const key = `${path82.resolve(branchRoot, contentRootName)}\0${registryFingerprint(entrySchemaRegistry)}`;
|
|
10962
|
+
let pending = this.buildResolves.get(key);
|
|
10963
|
+
if (!pending) {
|
|
10964
|
+
pending = this.resolveFreshAndPersist(branchRoot, entrySchemaRegistry, contentRootName, {
|
|
10965
|
+
fromCheckout: true
|
|
10966
|
+
});
|
|
10967
|
+
this.buildResolves.set(key, pending);
|
|
10968
|
+
pending.catch(() => this.buildResolves.delete(key));
|
|
10969
|
+
}
|
|
10970
|
+
return pending;
|
|
10971
|
+
}
|
|
10972
|
+
/** The parsed cache file, or null when it is absent or unreadable. */
|
|
10973
|
+
async readSnapshot(cachePath) {
|
|
10974
|
+
let handle;
|
|
10975
|
+
try {
|
|
10976
|
+
handle = await fs72.open(cachePath, "r");
|
|
10977
|
+
} catch {
|
|
10978
|
+
return null;
|
|
10979
|
+
}
|
|
10980
|
+
try {
|
|
10981
|
+
const stat = await handle.stat({ bigint: true });
|
|
10982
|
+
const identity = fileIdentity(stat);
|
|
10983
|
+
const memo = this.snapshots.get(cachePath);
|
|
10984
|
+
if (memo?.identity === identity)
|
|
10985
|
+
return memo.entry;
|
|
10986
|
+
const entry = JSON.parse(await fs72.readFile(handle, "utf-8"));
|
|
10987
|
+
this.snapshots.set(cachePath, { identity, entry, bytes: Number(stat.size) });
|
|
10988
|
+
return entry;
|
|
10989
|
+
} catch {
|
|
10990
|
+
return null;
|
|
10991
|
+
} finally {
|
|
10992
|
+
await handle.close().catch(() => {
|
|
10993
|
+
});
|
|
10994
|
+
}
|
|
10995
|
+
}
|
|
10954
10996
|
/**
|
|
10955
10997
|
* Resolve the schema fresh from disk and persist it (subject to the
|
|
10956
10998
|
* skip-persist rule below), UNCONDITIONALLY -- never through the cache-read
|
|
@@ -10992,14 +11034,22 @@ var init_generate_ai_content = __esm({
|
|
|
10992
11034
|
};
|
|
10993
11035
|
await fs72.mkdir(cacheDir, { recursive: true });
|
|
10994
11036
|
const tmpPath = path82.join(cacheDir, `schema-cache.tmp.${Date.now()}.${Math.random()}.json`);
|
|
10995
|
-
|
|
11037
|
+
const body = JSON.stringify(newCache);
|
|
11038
|
+
let identity;
|
|
10996
11039
|
try {
|
|
11040
|
+
await fs72.writeFile(tmpPath, body, "utf-8");
|
|
11041
|
+
identity = fileIdentity(await fs72.stat(tmpPath, { bigint: true }));
|
|
10997
11042
|
await fs72.rename(tmpPath, cachePath);
|
|
10998
11043
|
} catch (err2) {
|
|
10999
11044
|
await fs72.unlink(tmpPath).catch(() => {
|
|
11000
11045
|
});
|
|
11001
11046
|
throw err2;
|
|
11002
11047
|
}
|
|
11048
|
+
this.snapshots.set(cachePath, {
|
|
11049
|
+
identity,
|
|
11050
|
+
entry: JSON.parse(body),
|
|
11051
|
+
bytes: Buffer.byteLength(body)
|
|
11052
|
+
});
|
|
11003
11053
|
}
|
|
11004
11054
|
}
|
|
11005
11055
|
reportSchemaIssues(issues);
|
|
@@ -12343,9 +12393,20 @@ var init_generate_ai_content = __esm({
|
|
|
12343
12393
|
mergedAt: z72.string().optional(),
|
|
12344
12394
|
rebaseFailure: z72.object({ message: z72.string(), firstAt: z72.string(), lastAt: z72.string() }).optional(),
|
|
12345
12395
|
historyRewrittenFrom: z72.string().optional(),
|
|
12396
|
+
lastGitHubTip: z72.string().optional(),
|
|
12397
|
+
keptOverGitHub: z72.array(z72.string()).optional(),
|
|
12398
|
+
rewrittenOnGitHub: z72.string().optional(),
|
|
12399
|
+
submitUnfinished: z72.boolean().optional(),
|
|
12400
|
+
submitNotQueued: z72.boolean().optional(),
|
|
12401
|
+
unpublishedAtMerge: z72.boolean().optional(),
|
|
12346
12402
|
syncFailureReason: z72.string().optional(),
|
|
12347
12403
|
editors: z72.array(z72.string()).optional(),
|
|
12348
12404
|
uncommittedEditors: z72.array(z72.string()).optional(),
|
|
12405
|
+
// Dropped when malformed: it only decorates the branch card, so it never fails a read.
|
|
12406
|
+
pullRequestHold: z72.discriminatedUnion("kind", [
|
|
12407
|
+
z72.object({ kind: z72.literal("conflicts") }),
|
|
12408
|
+
z72.object({ kind: z72.literal("retargeted"), base: z72.string() })
|
|
12409
|
+
]).optional().catch(void 0),
|
|
12349
12410
|
// Dropped when malformed: it only decorates the PR body, so it never fails a read.
|
|
12350
12411
|
review: z72.object({
|
|
12351
12412
|
state: z72.enum(["approved", "changes-requested"]),
|
|
@@ -17797,9 +17858,7 @@ async function bumpResourceGeneration2(root, resource, options) {
|
|
|
17797
17858
|
await atomicWriteFile3(resourceGenerationPath2(root, resource), token);
|
|
17798
17859
|
return token;
|
|
17799
17860
|
} catch (err2) {
|
|
17800
|
-
|
|
17801
|
-
error: getErrorMessage(err2)
|
|
17802
|
-
});
|
|
17861
|
+
canopyLogWarn3(`CanopyCMS: failed to bump ${resource} generation for ${root}:`, getErrorMessage(err2));
|
|
17803
17862
|
if (options?.mustSucceed)
|
|
17804
17863
|
throw err2;
|
|
17805
17864
|
return null;
|
|
@@ -17828,6 +17887,7 @@ var init_resource_generation = __esm({
|
|
|
17828
17887
|
init_atomic_write3();
|
|
17829
17888
|
init_error();
|
|
17830
17889
|
init_debug2();
|
|
17890
|
+
init_logger3();
|
|
17831
17891
|
log11 = createDebugLogger3({ prefix: "ResourceGeneration" });
|
|
17832
17892
|
META_DIR3 = ".canopy-meta";
|
|
17833
17893
|
}
|
|
@@ -18064,10 +18124,12 @@ var init_meta_loader = __esm({
|
|
|
18064
18124
|
});
|
|
18065
18125
|
|
|
18066
18126
|
// dist/branch-schema-cache.js
|
|
18067
|
-
|
|
18127
|
+
import { LRUCache as LRUCache2 } from "lru-cache";
|
|
18128
|
+
var SNAPSHOT_MEMO_MAX_BYTES2, SCHEMA_GENERATION_RESOURCE2;
|
|
18068
18129
|
var init_branch_schema_cache = __esm({
|
|
18069
18130
|
"dist/branch-schema-cache.js"() {
|
|
18070
18131
|
"use strict";
|
|
18132
|
+
SNAPSHOT_MEMO_MAX_BYTES2 = 64 * 1024 * 1024;
|
|
18071
18133
|
SCHEMA_GENERATION_RESOURCE2 = "schema";
|
|
18072
18134
|
}
|
|
18073
18135
|
});
|
|
@@ -19153,9 +19215,20 @@ var init_branch_metadata_file = __esm({
|
|
|
19153
19215
|
mergedAt: z18.string().optional(),
|
|
19154
19216
|
rebaseFailure: z18.object({ message: z18.string(), firstAt: z18.string(), lastAt: z18.string() }).optional(),
|
|
19155
19217
|
historyRewrittenFrom: z18.string().optional(),
|
|
19218
|
+
lastGitHubTip: z18.string().optional(),
|
|
19219
|
+
keptOverGitHub: z18.array(z18.string()).optional(),
|
|
19220
|
+
rewrittenOnGitHub: z18.string().optional(),
|
|
19221
|
+
submitUnfinished: z18.boolean().optional(),
|
|
19222
|
+
submitNotQueued: z18.boolean().optional(),
|
|
19223
|
+
unpublishedAtMerge: z18.boolean().optional(),
|
|
19156
19224
|
syncFailureReason: z18.string().optional(),
|
|
19157
19225
|
editors: z18.array(z18.string()).optional(),
|
|
19158
19226
|
uncommittedEditors: z18.array(z18.string()).optional(),
|
|
19227
|
+
// Dropped when malformed: it only decorates the branch card, so it never fails a read.
|
|
19228
|
+
pullRequestHold: z18.discriminatedUnion("kind", [
|
|
19229
|
+
z18.object({ kind: z18.literal("conflicts") }),
|
|
19230
|
+
z18.object({ kind: z18.literal("retargeted"), base: z18.string() })
|
|
19231
|
+
]).optional().catch(void 0),
|
|
19159
19232
|
// Dropped when malformed: it only decorates the PR body, so it never fails a read.
|
|
19160
19233
|
review: z18.object({
|
|
19161
19234
|
state: z18.enum(["approved", "changes-requested"]),
|
|
@@ -4404,6 +4404,7 @@ init_error();
|
|
|
4404
4404
|
import fs5 from "node:fs/promises";
|
|
4405
4405
|
import path7 from "node:path";
|
|
4406
4406
|
import { randomUUID } from "node:crypto";
|
|
4407
|
+
init_logger();
|
|
4407
4408
|
var log4 = createDebugLogger({ prefix: "ResourceGeneration" });
|
|
4408
4409
|
var META_DIR2 = ".canopy-meta";
|
|
4409
4410
|
function resourceGenerationPath(root, resource) {
|
|
@@ -4415,9 +4416,7 @@ async function bumpResourceGeneration(root, resource, options) {
|
|
|
4415
4416
|
await atomicWriteFile(resourceGenerationPath(root, resource), token);
|
|
4416
4417
|
return token;
|
|
4417
4418
|
} catch (err) {
|
|
4418
|
-
|
|
4419
|
-
error: getErrorMessage(err)
|
|
4420
|
-
});
|
|
4419
|
+
canopyLogWarn(`CanopyCMS: failed to bump ${resource} generation for ${root}:`, getErrorMessage(err));
|
|
4421
4420
|
if (options?.mustSucceed)
|
|
4422
4421
|
throw err;
|
|
4423
4422
|
return null;
|
|
@@ -4443,6 +4442,7 @@ function isGenerationCurrent(snapshotToken, read) {
|
|
|
4443
4442
|
// dist/branch-schema-cache.js
|
|
4444
4443
|
import fs7 from "node:fs/promises";
|
|
4445
4444
|
import path8 from "node:path";
|
|
4445
|
+
import { LRUCache } from "lru-cache";
|
|
4446
4446
|
|
|
4447
4447
|
// dist/schema/meta-loader.js
|
|
4448
4448
|
import { promises as fs6 } from "fs";
|
|
@@ -4902,6 +4902,7 @@ async function resolveBaseBranch(options) {
|
|
|
4902
4902
|
init_logger();
|
|
4903
4903
|
var SCHEMA_CACHE_VERSION = 4;
|
|
4904
4904
|
var MTIME_CHECK_DEBOUNCE_MS = 1e3;
|
|
4905
|
+
var SNAPSHOT_MEMO_MAX_BYTES = 64 * 1024 * 1024;
|
|
4905
4906
|
var SCHEMA_CACHE_FILE = "schema-cache.json";
|
|
4906
4907
|
var SCHEMA_GENERATION_RESOURCE = "schema";
|
|
4907
4908
|
var reportedIssues = /* @__PURE__ */ new Set();
|
|
@@ -4935,6 +4936,9 @@ async function isStaleByMtime(dir, cachedAt) {
|
|
|
4935
4936
|
}
|
|
4936
4937
|
return false;
|
|
4937
4938
|
}
|
|
4939
|
+
function fileIdentity(stat) {
|
|
4940
|
+
return `${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}`;
|
|
4941
|
+
}
|
|
4938
4942
|
async function schemaCacheDir(branchRoot) {
|
|
4939
4943
|
const gitDir = path8.join(branchRoot, ".git");
|
|
4940
4944
|
const isClone = await fs7.stat(gitDir).then((stat) => stat.isDirectory(), () => false);
|
|
@@ -4943,6 +4947,11 @@ async function schemaCacheDir(branchRoot) {
|
|
|
4943
4947
|
var BranchSchemaCache = class {
|
|
4944
4948
|
constructor(mode = "prod") {
|
|
4945
4949
|
this.lastMtimeCheck = /* @__PURE__ */ new Map();
|
|
4950
|
+
this.snapshots = new LRUCache({
|
|
4951
|
+
maxSize: SNAPSHOT_MEMO_MAX_BYTES,
|
|
4952
|
+
sizeCalculation: (snapshot) => Math.max(1, snapshot.bytes)
|
|
4953
|
+
});
|
|
4954
|
+
this.buildResolves = /* @__PURE__ */ new Map();
|
|
4946
4955
|
this.devMode = mode === "dev";
|
|
4947
4956
|
}
|
|
4948
4957
|
/**
|
|
@@ -4972,15 +4981,12 @@ var BranchSchemaCache = class {
|
|
|
4972
4981
|
async loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
4973
4982
|
const contentRoot = path8.join(branchRoot, contentRootName);
|
|
4974
4983
|
const fromCheckout = this.readsCheckout(branchRoot);
|
|
4984
|
+
if (fromCheckout && isBuildMode()) {
|
|
4985
|
+
return this.resolveForBuild(branchRoot, entrySchemaRegistry, contentRootName);
|
|
4986
|
+
}
|
|
4975
4987
|
if (!fromCheckout) {
|
|
4976
4988
|
const cachePath = path8.join(await schemaCacheDir(branchRoot), SCHEMA_CACHE_FILE);
|
|
4977
|
-
let cacheData =
|
|
4978
|
-
try {
|
|
4979
|
-
const cacheContent = await fs7.readFile(cachePath, "utf-8");
|
|
4980
|
-
cacheData = JSON.parse(cacheContent);
|
|
4981
|
-
} catch {
|
|
4982
|
-
cacheData = null;
|
|
4983
|
-
}
|
|
4989
|
+
let cacheData = await this.readSnapshot(cachePath);
|
|
4984
4990
|
if (cacheData && cacheData.version === SCHEMA_CACHE_VERSION && cacheData.registryFingerprint === registryFingerprint(entrySchemaRegistry)) {
|
|
4985
4991
|
const read = await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
|
|
4986
4992
|
if (isGenerationCurrent(cacheData.generation, read)) {
|
|
@@ -5010,6 +5016,42 @@ var BranchSchemaCache = class {
|
|
|
5010
5016
|
fromCheckout
|
|
5011
5017
|
});
|
|
5012
5018
|
}
|
|
5019
|
+
resolveForBuild(branchRoot, entrySchemaRegistry, contentRootName) {
|
|
5020
|
+
const key = `${path8.resolve(branchRoot, contentRootName)}\0${registryFingerprint(entrySchemaRegistry)}`;
|
|
5021
|
+
let pending = this.buildResolves.get(key);
|
|
5022
|
+
if (!pending) {
|
|
5023
|
+
pending = this.resolveFreshAndPersist(branchRoot, entrySchemaRegistry, contentRootName, {
|
|
5024
|
+
fromCheckout: true
|
|
5025
|
+
});
|
|
5026
|
+
this.buildResolves.set(key, pending);
|
|
5027
|
+
pending.catch(() => this.buildResolves.delete(key));
|
|
5028
|
+
}
|
|
5029
|
+
return pending;
|
|
5030
|
+
}
|
|
5031
|
+
/** The parsed cache file, or null when it is absent or unreadable. */
|
|
5032
|
+
async readSnapshot(cachePath) {
|
|
5033
|
+
let handle;
|
|
5034
|
+
try {
|
|
5035
|
+
handle = await fs7.open(cachePath, "r");
|
|
5036
|
+
} catch {
|
|
5037
|
+
return null;
|
|
5038
|
+
}
|
|
5039
|
+
try {
|
|
5040
|
+
const stat = await handle.stat({ bigint: true });
|
|
5041
|
+
const identity = fileIdentity(stat);
|
|
5042
|
+
const memo = this.snapshots.get(cachePath);
|
|
5043
|
+
if (memo?.identity === identity)
|
|
5044
|
+
return memo.entry;
|
|
5045
|
+
const entry = JSON.parse(await fs7.readFile(handle, "utf-8"));
|
|
5046
|
+
this.snapshots.set(cachePath, { identity, entry, bytes: Number(stat.size) });
|
|
5047
|
+
return entry;
|
|
5048
|
+
} catch {
|
|
5049
|
+
return null;
|
|
5050
|
+
} finally {
|
|
5051
|
+
await handle.close().catch(() => {
|
|
5052
|
+
});
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5013
5055
|
/**
|
|
5014
5056
|
* Resolve the schema fresh from disk and persist it (subject to the
|
|
5015
5057
|
* skip-persist rule below), UNCONDITIONALLY -- never through the cache-read
|
|
@@ -5051,14 +5093,22 @@ var BranchSchemaCache = class {
|
|
|
5051
5093
|
};
|
|
5052
5094
|
await fs7.mkdir(cacheDir, { recursive: true });
|
|
5053
5095
|
const tmpPath = path8.join(cacheDir, `schema-cache.tmp.${Date.now()}.${Math.random()}.json`);
|
|
5054
|
-
|
|
5096
|
+
const body = JSON.stringify(newCache);
|
|
5097
|
+
let identity;
|
|
5055
5098
|
try {
|
|
5099
|
+
await fs7.writeFile(tmpPath, body, "utf-8");
|
|
5100
|
+
identity = fileIdentity(await fs7.stat(tmpPath, { bigint: true }));
|
|
5056
5101
|
await fs7.rename(tmpPath, cachePath);
|
|
5057
5102
|
} catch (err) {
|
|
5058
5103
|
await fs7.unlink(tmpPath).catch(() => {
|
|
5059
5104
|
});
|
|
5060
5105
|
throw err;
|
|
5061
5106
|
}
|
|
5107
|
+
this.snapshots.set(cachePath, {
|
|
5108
|
+
identity,
|
|
5109
|
+
entry: JSON.parse(body),
|
|
5110
|
+
bytes: Buffer.byteLength(body)
|
|
5111
|
+
});
|
|
5062
5112
|
}
|
|
5063
5113
|
}
|
|
5064
5114
|
reportSchemaIssues(issues);
|
|
@@ -7797,9 +7847,20 @@ var branchMetadataShape = {
|
|
|
7797
7847
|
mergedAt: z7.string().optional(),
|
|
7798
7848
|
rebaseFailure: z7.object({ message: z7.string(), firstAt: z7.string(), lastAt: z7.string() }).optional(),
|
|
7799
7849
|
historyRewrittenFrom: z7.string().optional(),
|
|
7850
|
+
lastGitHubTip: z7.string().optional(),
|
|
7851
|
+
keptOverGitHub: z7.array(z7.string()).optional(),
|
|
7852
|
+
rewrittenOnGitHub: z7.string().optional(),
|
|
7853
|
+
submitUnfinished: z7.boolean().optional(),
|
|
7854
|
+
submitNotQueued: z7.boolean().optional(),
|
|
7855
|
+
unpublishedAtMerge: z7.boolean().optional(),
|
|
7800
7856
|
syncFailureReason: z7.string().optional(),
|
|
7801
7857
|
editors: z7.array(z7.string()).optional(),
|
|
7802
7858
|
uncommittedEditors: z7.array(z7.string()).optional(),
|
|
7859
|
+
// Dropped when malformed: it only decorates the branch card, so it never fails a read.
|
|
7860
|
+
pullRequestHold: z7.discriminatedUnion("kind", [
|
|
7861
|
+
z7.object({ kind: z7.literal("conflicts") }),
|
|
7862
|
+
z7.object({ kind: z7.literal("retargeted"), base: z7.string() })
|
|
7863
|
+
]).optional().catch(void 0),
|
|
7803
7864
|
// Dropped when malformed: it only decorates the PR body, so it never fails a read.
|
|
7804
7865
|
review: z7.object({
|
|
7805
7866
|
state: z7.enum(["approved", "changes-requested"]),
|
package/dist/cli/init.js
CHANGED
|
@@ -1207,7 +1207,7 @@ async function failTask(taskDir, taskId, error, logger = nullLogger) {
|
|
|
1207
1207
|
});
|
|
1208
1208
|
logger.debug("Failed task", { id: taskId, error });
|
|
1209
1209
|
}
|
|
1210
|
-
async function retryTask(taskDir, taskId, error, logger = nullLogger) {
|
|
1210
|
+
async function retryTask(taskDir, taskId, error, logger = nullLogger, options = {}) {
|
|
1211
1211
|
const processingPath = path6.join(taskDir, "processing", `${taskId}.json`);
|
|
1212
1212
|
const pendingDir = path6.join(taskDir, "pending");
|
|
1213
1213
|
const pendingPath = path6.join(pendingDir, `${taskId}.json`);
|
|
@@ -1228,7 +1228,7 @@ async function retryTask(taskDir, taskId, error, logger = nullLogger) {
|
|
|
1228
1228
|
throw err;
|
|
1229
1229
|
}
|
|
1230
1230
|
const retryCount = (task.retryCount ?? 0) + 1;
|
|
1231
|
-
const backoffMs = Math.min(5e3 * Math.pow(2, retryCount - 1), 6e4);
|
|
1231
|
+
const backoffMs = options.delayMs ?? Math.min(5e3 * Math.pow(2, retryCount - 1), 6e4);
|
|
1232
1232
|
task.status = "pending";
|
|
1233
1233
|
task.retryCount = retryCount;
|
|
1234
1234
|
task.retryAfter = new Date(Date.now() + backoffMs).toISOString();
|
package/dist/content-reader.d.ts
CHANGED
|
@@ -89,20 +89,34 @@ export interface ContentReadMeta {
|
|
|
89
89
|
/** The entry's URL path with no query, by the rule `listEntries` uses. */
|
|
90
90
|
urlPath: string;
|
|
91
91
|
}
|
|
92
|
+
/** What a content read resolves to. */
|
|
93
|
+
export interface ContentReadResult<T = unknown> {
|
|
94
|
+
data: T;
|
|
95
|
+
/**
|
|
96
|
+
* The requested `entryPath` and slug as a percent-encoded URL path, plus `?branch=<name>`.
|
|
97
|
+
* Through an entry-type `entryPath` it is not the entry's URL. Compare entries by
|
|
98
|
+
* `meta.urlPath`.
|
|
99
|
+
*/
|
|
100
|
+
path: string;
|
|
101
|
+
meta: ContentReadMeta;
|
|
102
|
+
}
|
|
92
103
|
export interface ContentReader {
|
|
93
|
-
read: <T = unknown>(input: ReadContentInput, message?: string) => Promise<
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
read: <T = unknown>(input: ReadContentInput, message?: string) => Promise<ContentReadResult<T>>;
|
|
105
|
+
}
|
|
106
|
+
/** @internal The reader `CanopyContext` (context.ts) builds on. */
|
|
107
|
+
export interface ContentReaderInternal extends ContentReader {
|
|
108
|
+
/**
|
|
109
|
+
* Read the first of `inputs` that resolves, against one store resolved once for their branch,
|
|
110
|
+
* so a lookup trying several paths resolves the branch and its schema once. Every input must
|
|
111
|
+
* name the same branch and user. An error `isMiss` accepts moves on to the next input; null
|
|
112
|
+
* when every input misses, or when resolving the branch itself throws a miss.
|
|
113
|
+
*/
|
|
114
|
+
readFirst: <T = unknown>(inputs: readonly ReadContentInput[], isMiss: (err: unknown, input: ReadContentInput) => boolean) => Promise<ContentReadResult<T> | null>;
|
|
103
115
|
}
|
|
104
116
|
/**
|
|
105
117
|
* Server-side helper to read content directly from a branch workspace. A branch with no workspace
|
|
106
118
|
* reads as NOT_FOUND unless `allowCreateBranch` or a `getBranchContext` resolver says otherwise.
|
|
107
119
|
*/
|
|
108
120
|
export declare const createContentReader: (options: ContentReaderOptions) => ContentReader;
|
|
121
|
+
/** @internal {@link createContentReader} plus `readFirst`, for context.ts. */
|
|
122
|
+
export declare const createContentReaderInternal: (options: ContentReaderOptions) => ContentReaderInternal;
|