canopycms 0.0.67 → 0.0.68-int.93
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/dist/ai/handler.js +8 -0
- package/dist/api/admin-branch-health.js +12 -7
- package/dist/api/admin.d.ts +8 -8
- package/dist/api/branch-status.js +6 -2
- package/dist/api/client.d.ts +12 -0
- package/dist/api/client.js +35 -11
- package/dist/api/content.d.ts +6 -5
- package/dist/api/content.js +38 -39
- package/dist/api/entries.js +6 -8
- package/dist/api/github-sync.d.ts +10 -1
- package/dist/api/github-sync.js +20 -10
- package/dist/api/reference-options.js +1 -1
- package/dist/api/request-url.d.ts +8 -0
- package/dist/api/request-url.js +15 -0
- package/dist/api/resolve-references.js +2 -2
- package/dist/api/settings-helpers.d.ts +1 -1
- package/dist/api/settings-helpers.js +1 -4
- 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.js +3 -3
- package/dist/branch-schema-cache.d.ts +5 -3
- package/dist/branch-schema-cache.js +29 -11
- package/dist/branch-workspace.js +2 -2
- package/dist/cli/cli.js +285 -168
- package/dist/cli/generate-ai-content.js +190 -109
- package/dist/cli/github-app-manifest.d.ts +4 -3
- package/dist/cli/github-app-manifest.js +4 -3
- package/dist/cli/init.js +15 -10
- package/dist/cli/sync.js +15 -13
- package/dist/config/schemas/config.d.ts +6 -3
- package/dist/config/schemas/config.js +3 -1
- package/dist/config/schemas/field.js +1 -0
- package/dist/config/types.d.ts +11 -2
- package/dist/config/validation.d.ts +6 -0
- package/dist/config/validation.js +37 -0
- package/dist/content-listing.d.ts +2 -2
- package/dist/content-listing.js +3 -3
- package/dist/content-reader.js +4 -4
- package/dist/content-store.d.ts +2 -0
- package/dist/content-store.js +2 -1
- package/dist/content-tree.js +1 -1
- package/dist/context.d.ts +16 -7
- package/dist/context.js +116 -67
- package/dist/editor/Editor.js +4 -4
- package/dist/editor/FormRenderer.js +20 -5
- package/dist/editor/admin/SystemHealthPanel.js +37 -6
- package/dist/editor/components/NoEditPermissionNotice.d.ts +10 -0
- package/dist/editor/components/NoEditPermissionNotice.js +17 -0
- package/dist/editor/hooks/useBranchesData.js +3 -1
- package/dist/editor/hooks/useDraftManager.d.ts +1 -1
- package/dist/editor/hooks/useDraftManager.js +2 -1
- package/dist/editor/hooks/useEntriesData.js +2 -2
- package/dist/editor/hooks/useEntryManager.js +17 -11
- package/dist/entry-schema-registry.js +2 -1
- package/dist/git-manager.d.ts +20 -0
- package/dist/git-manager.js +50 -4
- package/dist/github-service.d.ts +8 -0
- package/dist/github-service.js +5 -1
- package/dist/http/handler.js +24 -9
- 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/worker-not-ready.d.ts +9 -0
- package/dist/http/worker-not-ready.js +17 -0
- package/dist/operating-mode/client-unsafe-strategy.js +0 -6
- package/dist/operating-mode/types.d.ts +0 -3
- package/dist/paths/index.d.ts +1 -1
- package/dist/paths/index.js +1 -1
- package/dist/paths/normalize.d.ts +7 -0
- package/dist/paths/normalize.js +9 -0
- package/dist/reference-resolver.d.ts +3 -3
- package/dist/reference-resolver.js +3 -2
- package/dist/resolve-canopy-user.js +2 -1
- package/dist/services.d.ts +12 -5
- package/dist/services.js +56 -56
- package/dist/settings-workspace.js +36 -0
- 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.js +7 -8
- package/dist/types.d.ts +23 -2
- package/dist/utils/content-write-lock.d.ts +3 -7
- package/dist/utils/content-write-lock.js +6 -4
- package/dist/utils/debug.d.ts +8 -0
- package/dist/utils/debug.js +10 -2
- package/dist/utils/git.d.ts +28 -0
- package/dist/utils/git.js +38 -0
- package/dist/utils/provisioning-lock.d.ts +15 -5
- package/dist/utils/provisioning-lock.js +31 -11
- package/dist/utils/request-timing.d.ts +25 -0
- package/dist/utils/request-timing.js +101 -0
- package/dist/utils/url-prefix.d.ts +15 -0
- package/dist/utils/url-prefix.js +26 -0
- package/dist/worker/canopy-state.d.ts +45 -0
- package/dist/worker/canopy-state.js +75 -0
- package/dist/worker/git-sync.d.ts +4 -2
- package/dist/worker/git-sync.js +111 -24
- package/dist/worker/provisioned-workspace.d.ts +35 -0
- package/dist/worker/provisioned-workspace.js +50 -0
- package/dist/worker/rebase.js +75 -12
- package/dist/worker/task-runner.js +39 -4
- package/package.json +1 -1
package/dist/cli/cli.js
CHANGED
|
@@ -1285,7 +1285,8 @@ var init_init = __esm({
|
|
|
1285
1285
|
});
|
|
1286
1286
|
objectFieldSchema = fieldBaseSchema.extend({
|
|
1287
1287
|
type: z.literal("object"),
|
|
1288
|
-
fields: z.array(z.lazy(() => fieldHolder[0])).min(1)
|
|
1288
|
+
fields: z.array(z.lazy(() => fieldHolder[0])).min(1),
|
|
1289
|
+
itemTitleField: z.string().min(1).optional()
|
|
1289
1290
|
});
|
|
1290
1291
|
inlineGroupFieldSchema = z.object({
|
|
1291
1292
|
type: z.literal("group"),
|
|
@@ -1466,6 +1467,9 @@ var init_init = __esm({
|
|
|
1466
1467
|
defaultRemoteUrl: defaultRemoteUrlSchema.optional(),
|
|
1467
1468
|
gitBotAuthorName: gitBotAuthorNameSchema,
|
|
1468
1469
|
gitBotAuthorEmail: gitBotAuthorEmailSchema,
|
|
1470
|
+
// Unset reads as on for Edited-by and off for Co-authored-by (services.ts submitBranch).
|
|
1471
|
+
gitEditedByTrailers: z5.boolean().optional(),
|
|
1472
|
+
gitCoAuthoredByTrailers: z5.boolean().optional(),
|
|
1469
1473
|
githubTokenEnvVar: githubTokenEnvVarSchema.optional(),
|
|
1470
1474
|
// Required by design (follow-up to SEC-C1): a prod deploy that omits `mode` must fail
|
|
1471
1475
|
// validation loudly rather than silently running header-trusting dev auth semantics.
|
|
@@ -1477,7 +1481,6 @@ var init_init = __esm({
|
|
|
1477
1481
|
// Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
|
|
1478
1482
|
allowNetworkRemoteInProd: z5.boolean().optional(),
|
|
1479
1483
|
settingsBranch: z5.string().optional(),
|
|
1480
|
-
autoCreateSettingsPR: z5.boolean().optional(),
|
|
1481
1484
|
// `.optional()`, NOT bare `deploymentNameSchema`: its own `.default('prod')` would make
|
|
1482
1485
|
// `parse(undefined)` resolve to 'prod' instead of staying `undefined`, collapsing the
|
|
1483
1486
|
// env > config > modeDefault precedence chain `resolveDeploymentName`
|
|
@@ -1734,6 +1737,10 @@ var init_init = __esm({
|
|
|
1734
1737
|
throw new Error(errorMsg);
|
|
1735
1738
|
}
|
|
1736
1739
|
}
|
|
1740
|
+
/**
|
|
1741
|
+
* Keyed by label alone, so two overlapping timers with one label overwrite each other;
|
|
1742
|
+
* anything that can run concurrently uses {@link timed}.
|
|
1743
|
+
*/
|
|
1737
1744
|
time(label) {
|
|
1738
1745
|
this.timers.set(label, Date.now());
|
|
1739
1746
|
}
|
|
@@ -1748,12 +1755,16 @@ var init_init = __esm({
|
|
|
1748
1755
|
this.debug(category, `${label} completed`, { durationMs: duration });
|
|
1749
1756
|
return duration;
|
|
1750
1757
|
}
|
|
1758
|
+
/**
|
|
1759
|
+
* Log `<label> completed {durationMs}` once `fn` settles. The start time is local to the
|
|
1760
|
+
* call, so concurrent spans with the same label on one logger never overwrite each other.
|
|
1761
|
+
*/
|
|
1751
1762
|
async timed(category, label, fn) {
|
|
1752
|
-
|
|
1763
|
+
const start = Date.now();
|
|
1753
1764
|
try {
|
|
1754
1765
|
return await fn();
|
|
1755
1766
|
} finally {
|
|
1756
|
-
this.
|
|
1767
|
+
this.debug(category, `${label} completed`, { durationMs: Date.now() - start });
|
|
1757
1768
|
}
|
|
1758
1769
|
}
|
|
1759
1770
|
};
|
|
@@ -1919,9 +1930,6 @@ var init_init = __esm({
|
|
|
1919
1930
|
throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
|
|
1920
1931
|
}
|
|
1921
1932
|
}
|
|
1922
|
-
shouldCreateSettingsPR(config) {
|
|
1923
|
-
return config.autoCreateSettingsPR ?? true;
|
|
1924
|
-
}
|
|
1925
1933
|
};
|
|
1926
1934
|
DevStrategy = class extends DevClientSafeStrategy {
|
|
1927
1935
|
getWorkspaceRoot(sourceRoot) {
|
|
@@ -1968,9 +1976,6 @@ var init_init = __esm({
|
|
|
1968
1976
|
}
|
|
1969
1977
|
validateConfig(_config) {
|
|
1970
1978
|
}
|
|
1971
|
-
shouldCreateSettingsPR(_config) {
|
|
1972
|
-
return false;
|
|
1973
|
-
}
|
|
1974
1979
|
};
|
|
1975
1980
|
strategyCache = /* @__PURE__ */ new Map();
|
|
1976
1981
|
init_mode_env();
|
|
@@ -3058,6 +3063,8 @@ import { promises as fs62 } from "fs";
|
|
|
3058
3063
|
import { join as join3 } from "pathe";
|
|
3059
3064
|
import { z as z8 } from "zod";
|
|
3060
3065
|
import chokidar from "chokidar";
|
|
3066
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
3067
|
+
import { simpleGit as simpleGit3 } from "simple-git";
|
|
3061
3068
|
import fs82 from "node:fs/promises";
|
|
3062
3069
|
import { sep, resolve, relative } from "node:path";
|
|
3063
3070
|
import fs9 from "node:fs/promises";
|
|
@@ -3079,7 +3086,6 @@ import lockfile2 from "proper-lockfile";
|
|
|
3079
3086
|
import fs16 from "node:fs/promises";
|
|
3080
3087
|
import path17 from "node:path";
|
|
3081
3088
|
import { simpleGit as simpleGit22 } from "simple-git";
|
|
3082
|
-
import { simpleGit as simpleGit3 } from "simple-git";
|
|
3083
3089
|
import fs17 from "node:fs/promises";
|
|
3084
3090
|
import path19 from "node:path";
|
|
3085
3091
|
import matter3 from "gray-matter";
|
|
@@ -3145,6 +3151,9 @@ function createLogicalPath(...segments) {
|
|
|
3145
3151
|
}
|
|
3146
3152
|
return normalized;
|
|
3147
3153
|
}
|
|
3154
|
+
function entryLogicalPath(collectionLogicalPath, slug) {
|
|
3155
|
+
return collectionLogicalPath ? `${collectionLogicalPath}/${slug}` : slug;
|
|
3156
|
+
}
|
|
3148
3157
|
function trimSlashes(path222) {
|
|
3149
3158
|
let start = 0;
|
|
3150
3159
|
let end = path222.length;
|
|
@@ -3473,12 +3482,14 @@ async function withLock(key, fn) {
|
|
|
3473
3482
|
locks.delete(key);
|
|
3474
3483
|
}
|
|
3475
3484
|
}
|
|
3476
|
-
function provisioningLockOptions(lockPath, retries, onCompromised) {
|
|
3485
|
+
function provisioningLockOptions(lockPath, retries, onCompromised, staleMs) {
|
|
3477
3486
|
return {
|
|
3478
3487
|
lockfilePath: lockPath,
|
|
3479
3488
|
realpath: false,
|
|
3480
3489
|
retries,
|
|
3481
|
-
stale:
|
|
3490
|
+
stale: staleMs,
|
|
3491
|
+
// Fixed, not stale/2: every holder refreshes at 15s whatever threshold it judges others by.
|
|
3492
|
+
update: 15e3,
|
|
3482
3493
|
// proper-lockfile invokes this from inside its refresh timer, so ANY throw escaping here is
|
|
3483
3494
|
// an uncaught exception that kills the process. Call sites are told not to throw (see
|
|
3484
3495
|
// OnLockCompromised); this makes it structural rather than a convention, and also covers the
|
|
@@ -3514,23 +3525,26 @@ function releaseIgnoringAlreadyReleased(release, lockPath) {
|
|
|
3514
3525
|
}
|
|
3515
3526
|
};
|
|
3516
3527
|
}
|
|
3528
|
+
function branchProvisioningLockName(dirName) {
|
|
3529
|
+
return `.${dirName}.init.lock`;
|
|
3530
|
+
}
|
|
3517
3531
|
async function acquireProvisioningLock(lockTargetDir2, lockName, onCompromised) {
|
|
3518
3532
|
await fs23.mkdir(lockTargetDir2, { recursive: true });
|
|
3519
3533
|
const lockPath = path22.join(lockTargetDir2, lockName);
|
|
3520
|
-
const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, { retries: 600, factor: 1, minTimeout: 300, maxTimeout: 800, randomize: true }, onCompromised));
|
|
3534
|
+
const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, { retries: 600, factor: 1, minTimeout: 300, maxTimeout: 800, randomize: true }, onCompromised, PROVISIONING_LOCK_STALE_MS));
|
|
3521
3535
|
return releaseIgnoringAlreadyReleased(release, lockPath);
|
|
3522
3536
|
}
|
|
3523
|
-
async function tryAcquireProvisioningLock(lockTargetDir2, lockName, onCompromised) {
|
|
3537
|
+
async function tryAcquireProvisioningLock(lockTargetDir2, lockName, onCompromised, staleMs = PROVISIONING_LOCK_STALE_MS) {
|
|
3524
3538
|
await fs23.mkdir(lockTargetDir2, { recursive: true });
|
|
3525
3539
|
const lockPath = path22.join(lockTargetDir2, lockName);
|
|
3526
|
-
const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, 0, onCompromised));
|
|
3540
|
+
const release = await lockfile.lock(lockPath, provisioningLockOptions(lockPath, 0, onCompromised, staleMs));
|
|
3527
3541
|
return releaseIgnoringAlreadyReleased(release, lockPath);
|
|
3528
3542
|
}
|
|
3529
3543
|
function lockTargetDir(branchRoot) {
|
|
3530
3544
|
return path32.join(path32.resolve(branchRoot), META_DIR);
|
|
3531
3545
|
}
|
|
3532
3546
|
function tryAcquireContentWriteLock(branchRoot, onCompromised) {
|
|
3533
|
-
return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME, onCompromised);
|
|
3547
|
+
return tryAcquireProvisioningLock(lockTargetDir(branchRoot), CONTENT_WRITE_LOCK_NAME, onCompromised, CONTENT_WRITE_LOCK_STALE_MS);
|
|
3534
3548
|
}
|
|
3535
3549
|
async function acquireContentWriteLock(branchRoot, waitMs = DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, onCompromised) {
|
|
3536
3550
|
const startedAt = Date.now();
|
|
@@ -4006,6 +4020,61 @@ function isValidSchema(schema) {
|
|
|
4006
4020
|
const hasCollections = !!(schema.collections && schema.collections.length > 0);
|
|
4007
4021
|
return hasEntries || hasCollections;
|
|
4008
4022
|
}
|
|
4023
|
+
async function timeRequestPhase(phase, fn) {
|
|
4024
|
+
const parent = storage.getStore();
|
|
4025
|
+
if (!parent)
|
|
4026
|
+
return fn();
|
|
4027
|
+
const key = parent.path ? `${parent.path}>${phase}` : phase;
|
|
4028
|
+
let total = parent.phases.get(key);
|
|
4029
|
+
if (!total) {
|
|
4030
|
+
total = { count: 0, ms: 0 };
|
|
4031
|
+
parent.phases.set(key, total);
|
|
4032
|
+
}
|
|
4033
|
+
const start = performance.now();
|
|
4034
|
+
try {
|
|
4035
|
+
return await storage.run({ ...parent, path: key }, fn);
|
|
4036
|
+
} finally {
|
|
4037
|
+
total.count += 1;
|
|
4038
|
+
total.ms += performance.now() - start;
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
function isNetworkRemoteUrl(url) {
|
|
4042
|
+
if (/^file:\/\//i.test(url))
|
|
4043
|
+
return false;
|
|
4044
|
+
if (NETWORK_SCHEME_PATTERN.test(url))
|
|
4045
|
+
return true;
|
|
4046
|
+
if (TRANSPORT_HELPER_PATTERN.test(url))
|
|
4047
|
+
return true;
|
|
4048
|
+
if (LEADING_OPTION_PATTERN.test(url))
|
|
4049
|
+
return true;
|
|
4050
|
+
if (WINDOWS_DRIVE_PATTERN.test(url))
|
|
4051
|
+
return false;
|
|
4052
|
+
return COLON_BEFORE_SLASH_PATTERN.test(url);
|
|
4053
|
+
}
|
|
4054
|
+
function isMissingRemoteRefFailure(message) {
|
|
4055
|
+
return MISSING_REMOTE_REF_REASONS.some((reason) => message.includes(reason));
|
|
4056
|
+
}
|
|
4057
|
+
async function stageAllExceptCanopyState(git) {
|
|
4058
|
+
await git.add(["-A"]);
|
|
4059
|
+
await git.raw(["reset", "-q", "--", CANOPY_META_DIR]);
|
|
4060
|
+
}
|
|
4061
|
+
async function detectHeadBranch(repoRoot, fallback = "main") {
|
|
4062
|
+
try {
|
|
4063
|
+
const git = simpleGit3({ baseDir: repoRoot });
|
|
4064
|
+
const head = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
|
|
4065
|
+
return head && head !== "HEAD" ? head : fallback;
|
|
4066
|
+
} catch {
|
|
4067
|
+
return fallback;
|
|
4068
|
+
}
|
|
4069
|
+
}
|
|
4070
|
+
async function resolveBaseBranch(options) {
|
|
4071
|
+
if (options.defaultBaseBranch)
|
|
4072
|
+
return options.defaultBaseBranch;
|
|
4073
|
+
if (options.mode === "dev") {
|
|
4074
|
+
return detectHeadBranch(options.detectFrom ?? process.cwd());
|
|
4075
|
+
}
|
|
4076
|
+
return "main";
|
|
4077
|
+
}
|
|
4009
4078
|
async function isStaleByMtime(dir, cachedAt) {
|
|
4010
4079
|
let entries;
|
|
4011
4080
|
try {
|
|
@@ -4027,6 +4096,11 @@ async function isStaleByMtime(dir, cachedAt) {
|
|
|
4027
4096
|
}
|
|
4028
4097
|
return false;
|
|
4029
4098
|
}
|
|
4099
|
+
async function schemaCacheDir(branchRoot) {
|
|
4100
|
+
const gitDir = path82.join(branchRoot, ".git");
|
|
4101
|
+
const isClone = await fs72.stat(gitDir).then((stat) => stat.isDirectory(), () => false);
|
|
4102
|
+
return isClone ? path82.join(gitDir, "canopycms") : path82.join(branchRoot, CANOPY_META_DIR);
|
|
4103
|
+
}
|
|
4030
4104
|
async function bumpContentIndexGeneration(root) {
|
|
4031
4105
|
return bumpResourceGeneration(root, RESOURCE);
|
|
4032
4106
|
}
|
|
@@ -4663,7 +4737,7 @@ function resolveEntryLinksInText(text3, idIndex, contentRoot, customResolver) {
|
|
|
4663
4737
|
return part.text.replace(ENTRY_LINK_PATTERN, (_match, id, anchor) => {
|
|
4664
4738
|
const location = idIndex.findById(id);
|
|
4665
4739
|
if (!location || location.type !== "entry" || !location.collection || !location.slug) {
|
|
4666
|
-
|
|
4740
|
+
log3.warn("resolve", `Entry link target not found: entry:${id}`);
|
|
4667
4741
|
return anchor ?? "#";
|
|
4668
4742
|
}
|
|
4669
4743
|
let url;
|
|
@@ -5118,7 +5192,7 @@ async function withOccFileLock(filePath, fn) {
|
|
|
5118
5192
|
// Our critical sections are short and idempotent-on-conflict (OCC
|
|
5119
5193
|
// verify inside); log and let the section finish.
|
|
5120
5194
|
onCompromised: (err) => {
|
|
5121
|
-
|
|
5195
|
+
log5.warn("lock", `Lock compromised mid-hold for ${filePath}`, {
|
|
5122
5196
|
error: getErrorMessage3(err)
|
|
5123
5197
|
});
|
|
5124
5198
|
}
|
|
@@ -5140,43 +5214,10 @@ async function withOccFileLock(filePath, fn) {
|
|
|
5140
5214
|
await release().catch((err) => {
|
|
5141
5215
|
if (isNodeError3(err) && err.code === "ENOENT")
|
|
5142
5216
|
return;
|
|
5143
|
-
|
|
5217
|
+
log5.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage3(err) });
|
|
5144
5218
|
});
|
|
5145
5219
|
}
|
|
5146
5220
|
}
|
|
5147
|
-
function isNetworkRemoteUrl(url) {
|
|
5148
|
-
if (/^file:\/\//i.test(url))
|
|
5149
|
-
return false;
|
|
5150
|
-
if (NETWORK_SCHEME_PATTERN.test(url))
|
|
5151
|
-
return true;
|
|
5152
|
-
if (TRANSPORT_HELPER_PATTERN.test(url))
|
|
5153
|
-
return true;
|
|
5154
|
-
if (LEADING_OPTION_PATTERN.test(url))
|
|
5155
|
-
return true;
|
|
5156
|
-
if (WINDOWS_DRIVE_PATTERN.test(url))
|
|
5157
|
-
return false;
|
|
5158
|
-
return COLON_BEFORE_SLASH_PATTERN.test(url);
|
|
5159
|
-
}
|
|
5160
|
-
function isMissingRemoteRefFailure(message) {
|
|
5161
|
-
return MISSING_REMOTE_REF_REASONS.some((reason) => message.includes(reason));
|
|
5162
|
-
}
|
|
5163
|
-
async function detectHeadBranch(repoRoot, fallback = "main") {
|
|
5164
|
-
try {
|
|
5165
|
-
const git = simpleGit3({ baseDir: repoRoot });
|
|
5166
|
-
const head = (await git.revparse(["--abbrev-ref", "HEAD"])).trim();
|
|
5167
|
-
return head && head !== "HEAD" ? head : fallback;
|
|
5168
|
-
} catch {
|
|
5169
|
-
return fallback;
|
|
5170
|
-
}
|
|
5171
|
-
}
|
|
5172
|
-
async function resolveBaseBranch(options) {
|
|
5173
|
-
if (options.defaultBaseBranch)
|
|
5174
|
-
return options.defaultBaseBranch;
|
|
5175
|
-
if (options.mode === "dev") {
|
|
5176
|
-
return detectHeadBranch(options.detectFrom ?? process.cwd());
|
|
5177
|
-
}
|
|
5178
|
-
return "main";
|
|
5179
|
-
}
|
|
5180
5221
|
function gitChildEnv(overrides) {
|
|
5181
5222
|
const env = {};
|
|
5182
5223
|
for (const [key, value] of Object.entries(process.env)) {
|
|
@@ -5197,13 +5238,13 @@ async function ensureGitExcludePattern(repoPath, pattern) {
|
|
|
5197
5238
|
}
|
|
5198
5239
|
const lines = content.split("\n");
|
|
5199
5240
|
if (lines.some((line) => line.trim() === pattern)) {
|
|
5200
|
-
|
|
5241
|
+
log6.debug("git", "Pattern already in .git/info/exclude", { pattern });
|
|
5201
5242
|
return;
|
|
5202
5243
|
}
|
|
5203
5244
|
const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
|
|
5204
5245
|
const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
|
|
5205
5246
|
await fs16.writeFile(excludePath, newContent, "utf-8");
|
|
5206
|
-
|
|
5247
|
+
log6.debug("git", "Added pattern to .git/info/exclude", { pattern });
|
|
5207
5248
|
}
|
|
5208
5249
|
async function loadOrCreateBranchContext(options) {
|
|
5209
5250
|
if (readsFromCheckout(options.config)) {
|
|
@@ -5269,7 +5310,7 @@ async function listEntries(branchRoot, flatSchema, contentRootName, options, vis
|
|
|
5269
5310
|
const resolver = options?.resolveReferences ? createReferenceResolver(branchRoot, flatSchema, contentRootName) : null;
|
|
5270
5311
|
const collectionResults = await Promise.all(collections.map(async (collection) => {
|
|
5271
5312
|
const entries = await listCollectionEntries(branchRoot, collection, (file) => skippedFiles.push(file));
|
|
5272
|
-
const visible = shouldInclude ? entries.filter((e) => shouldInclude(e.
|
|
5313
|
+
const visible = shouldInclude ? entries.filter((e) => shouldInclude(e.logicalPath)) : entries;
|
|
5273
5314
|
const resolved = resolver ? await resolveCollectionItemReferences(visible, collection, resolver) : visible;
|
|
5274
5315
|
return resolved.map((entry) => ({ entry, collection }));
|
|
5275
5316
|
}));
|
|
@@ -5751,7 +5792,7 @@ async function generateAIContentCLI(options) {
|
|
|
5751
5792
|
console.log(` Output: ${result.outputDir}
|
|
5752
5793
|
`);
|
|
5753
5794
|
}
|
|
5754
|
-
var __defProp3, __getOwnPropNames3, __esm3, __export3, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize2, ROOT_COLLECTION_ID, init_types3, normalizePathValue, flattenSchema, init_flatten2, primitiveFieldTypes2, fieldTypes2, init_types22, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, assetMountUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env2, forEachReferenceField, init_validation2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned22, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, permissionTargetSchema2, pathPermissionSchema2, init_permissions2, init_helpers2, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, EVIDENCE_MAX_DEPTH, locks, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep, DEFAULT_SEO_FIELD_NAMES, SEO_GROUP_FIELDS, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, LOG_LEVELS2, DebugLogger2, testLogger2, log2, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, SCHEMA_CACHE_VERSION, MTIME_CHECK_DEBOUNCE_MS, SCHEMA_GENERATION_RESOURCE, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, FLAT_CELL_TYPES,
|
|
5795
|
+
var __defProp3, __getOwnPropNames3, __esm3, __export3, active2, init_logger2, SENTINEL_BASE2, DOT_SEGMENT2, init_sanitize_href2, init_normalize2, ROOT_COLLECTION_ID, init_types3, normalizePathValue, flattenSchema, init_flatten2, primitiveFieldTypes2, fieldTypes2, init_types22, fieldBaseSchema2, selectOptionSchema2, referenceOptionSchema2, primitiveFieldSchema2, ASPECT_RATIO_RE2, imageFieldSchema2, selectFieldSchema2, referenceFieldSchema2, fieldHolder2, blockSchema2, blockFieldSchema2, objectFieldSchema2, inlineGroupFieldSchema2, customFieldSchema2, knownFieldSchema2, fieldSchema2, init_field2, relativePathSchema2, entryTypeSchema2, collectionSchema2, init_collection2, uploadTargetUrlSchema2, assetMountUrlSchema2, init_url2, mediaSchema2, init_media2, defaultBranchAccessSchema2, defaultPathAccessLevelSchema2, defaultPathAccessLevelsSchema2, defaultPathAccessSchema2, defaultBaseBranchSchema2, defaultRemoteNameSchema2, defaultRemoteUrlSchema2, gitBotAuthorNameSchema2, gitBotAuthorEmailSchema2, githubTokenEnvVarSchema2, operatingModeSchema2, deployedAsSchema2, contentRootSchema2, sourceRootSchema2, basePathSchema2, deploymentNameSchema2, devContentSyncSchema2, devConfigSchema2, editorConfigSchema2, CanopyConfigSchema2, DEFAULT_PROD_WORKSPACE2, init_config3, SERVER_MODE_ENV_VAR, BROWSER_MODE_ENV_VAR, warned2, init_mode_env2, forEachReferenceField, init_validation2, ProdClientSafeStrategy2, DevClientSafeStrategy2, clientStrategyCache, init_client_safe_strategy, warned22, VALID_DEPLOYMENT_NAME2, isValidDeploymentName2, init_deployment_name, permissionTargetSchema2, pathPermissionSchema2, init_permissions2, init_helpers2, init_config22, ProdStrategy2, DevStrategy2, strategyCache2, init_client_unsafe_strategy, operating_mode_exports, init_operating_mode, BLOCK_DISCRIMINATOR_KEYS, BLOCK_STRUCTURAL_KEYS, EVIDENCE_MAX_DEPTH, locks, PROVISIONING_LOCK_STALE_MS, META_DIR, CONTENT_WRITE_LOCK_NAME, DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS, ContentWriteLockBusyError, sleep, CONTENT_WRITE_LOCK_STALE_MS, DEFAULT_SEO_FIELD_NAMES, SEO_GROUP_FIELDS, BASE58_PATTERN, CONTENT_ID_PATTERN, PHYSICAL_SEGMENT_PATTERN, isValidId, EMPTY_LOGICAL_PATH, ContentIdIndex, parseTypedFilename, CONTENT_EXTENSIONS, registry, finalization, LOG_LEVELS2, DebugLogger2, testLogger2, log2, META_DIR2, entryTypeMetaSchema, collectionMetaSchema, rootCollectionMetaSchema, editDistance, closestName, forEachEntryType, collectEntryTypeNames, validateReferenceEntryTypes, isDeployedStatic, isBuildMode, readsFromCheckout, STATIC_DEPLOY_USER, storage, log22, 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, BranchSchemaCache, RESOURCE, getFormatExtension, isDataOnlyFormat, asRecord, BranchPathError, resolveContentBranchesRoot, ContentStoreError, ContentConflictError, BranchSyncingError, DuplicateContentIdError, UrlPathConflictError, DEFAULT_INDEX_FRESHNESS_INTERVAL_MS, FORCED_REFRESH_MIN_INTERVAL_MS, STALE_LOOKUP, createReferenceResolveCache, ContentStore, ATTR_CONTENT, BLOCK_PREFIX, BLOCK_SUFFIX, BLOCK_RESTORE_RE, INLINE_PREFIX, INLINE_SUFFIX, INLINE_RESTORE_RE, FLAT_CELL_TYPES, log3, BASE58_CHAR, ENTRY_LINK_PATTERN, BRANCH_META_DIR, BRANCH_META_FILE, BranchMetadataCorruptError, branchMetadataFilePath, log4, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, BranchRegistry, log5, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, getBranchMetadataFileManager, loadBranchContext, log6, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, RemoteNotReadyError, GitManager, log7, workspaceInitLocks, BranchWorkspaceManager, log8, normalizePath, readEntryData, createReferenceResolver, resolveCollectionItemReferences, looksLikeMalformedEntry, listCollectionEntries, MAX_INPUT_PIXELS, KNOWN_FIELD_TYPES, STRING_FIELD_TYPES, selectOptionValues, GENERATED_RECORD_FILENAME, SAFE_BUILD_ID, BUILD_ID_RULE, jiti;
|
|
5755
5796
|
var init_generate_ai_content = __esm({
|
|
5756
5797
|
"dist/cli/generate-ai-content.js"() {
|
|
5757
5798
|
"use strict";
|
|
@@ -5975,7 +6016,8 @@ var init_generate_ai_content = __esm({
|
|
|
5975
6016
|
});
|
|
5976
6017
|
objectFieldSchema2 = fieldBaseSchema2.extend({
|
|
5977
6018
|
type: z22.literal("object"),
|
|
5978
|
-
fields: z22.array(z22.lazy(() => fieldHolder2[0])).min(1)
|
|
6019
|
+
fields: z22.array(z22.lazy(() => fieldHolder2[0])).min(1),
|
|
6020
|
+
itemTitleField: z22.string().min(1).optional()
|
|
5979
6021
|
});
|
|
5980
6022
|
inlineGroupFieldSchema2 = z22.object({
|
|
5981
6023
|
type: z22.literal("group"),
|
|
@@ -6149,6 +6191,9 @@ var init_generate_ai_content = __esm({
|
|
|
6149
6191
|
defaultRemoteUrl: defaultRemoteUrlSchema2.optional(),
|
|
6150
6192
|
gitBotAuthorName: gitBotAuthorNameSchema2,
|
|
6151
6193
|
gitBotAuthorEmail: gitBotAuthorEmailSchema2,
|
|
6194
|
+
// Unset reads as on for Edited-by and off for Co-authored-by (services.ts submitBranch).
|
|
6195
|
+
gitEditedByTrailers: z62.boolean().optional(),
|
|
6196
|
+
gitCoAuthoredByTrailers: z62.boolean().optional(),
|
|
6152
6197
|
githubTokenEnvVar: githubTokenEnvVarSchema2.optional(),
|
|
6153
6198
|
// Required by design (follow-up to SEC-C1): a prod deploy that omits `mode` must fail
|
|
6154
6199
|
// validation loudly rather than silently running header-trusting dev auth semantics.
|
|
@@ -6160,7 +6205,6 @@ var init_generate_ai_content = __esm({
|
|
|
6160
6205
|
// Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
|
|
6161
6206
|
allowNetworkRemoteInProd: z62.boolean().optional(),
|
|
6162
6207
|
settingsBranch: z62.string().optional(),
|
|
6163
|
-
autoCreateSettingsPR: z62.boolean().optional(),
|
|
6164
6208
|
// `.optional()`, NOT bare `deploymentNameSchema`: its own `.default('prod')` would make
|
|
6165
6209
|
// `parse(undefined)` resolve to 'prod' instead of staying `undefined`, collapsing the
|
|
6166
6210
|
// env > config > modeDefault precedence chain `resolveDeploymentName`
|
|
@@ -6377,9 +6421,6 @@ var init_generate_ai_content = __esm({
|
|
|
6377
6421
|
throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
|
|
6378
6422
|
}
|
|
6379
6423
|
}
|
|
6380
|
-
shouldCreateSettingsPR(config) {
|
|
6381
|
-
return config.autoCreateSettingsPR ?? true;
|
|
6382
|
-
}
|
|
6383
6424
|
};
|
|
6384
6425
|
DevStrategy2 = class extends DevClientSafeStrategy2 {
|
|
6385
6426
|
getWorkspaceRoot(sourceRoot) {
|
|
@@ -6426,9 +6467,6 @@ var init_generate_ai_content = __esm({
|
|
|
6426
6467
|
}
|
|
6427
6468
|
validateConfig(_config) {
|
|
6428
6469
|
}
|
|
6429
|
-
shouldCreateSettingsPR(_config) {
|
|
6430
|
-
return false;
|
|
6431
|
-
}
|
|
6432
6470
|
};
|
|
6433
6471
|
strategyCache2 = /* @__PURE__ */ new Map();
|
|
6434
6472
|
}
|
|
@@ -6458,6 +6496,7 @@ var init_generate_ai_content = __esm({
|
|
|
6458
6496
|
locks = /* @__PURE__ */ new Map();
|
|
6459
6497
|
init_logger2();
|
|
6460
6498
|
init_logger2();
|
|
6499
|
+
PROVISIONING_LOCK_STALE_MS = 9e4;
|
|
6461
6500
|
META_DIR = ".canopy-meta";
|
|
6462
6501
|
CONTENT_WRITE_LOCK_NAME = "content-write.lock";
|
|
6463
6502
|
DEFAULT_CONTENT_WRITE_LOCK_WAIT_MS = 2e3;
|
|
@@ -6470,6 +6509,7 @@ var init_generate_ai_content = __esm({
|
|
|
6470
6509
|
sleep = (ms) => new Promise((resolve2) => {
|
|
6471
6510
|
setTimeout(resolve2, ms);
|
|
6472
6511
|
});
|
|
6512
|
+
CONTENT_WRITE_LOCK_STALE_MS = 3e4;
|
|
6473
6513
|
init_sanitize_href2();
|
|
6474
6514
|
DEFAULT_SEO_FIELD_NAMES = {
|
|
6475
6515
|
title: "metaTitle",
|
|
@@ -6915,6 +6955,10 @@ var init_generate_ai_content = __esm({
|
|
|
6915
6955
|
throw new Error(errorMsg);
|
|
6916
6956
|
}
|
|
6917
6957
|
}
|
|
6958
|
+
/**
|
|
6959
|
+
* Keyed by label alone, so two overlapping timers with one label overwrite each other;
|
|
6960
|
+
* anything that can run concurrently uses {@link timed}.
|
|
6961
|
+
*/
|
|
6918
6962
|
time(label) {
|
|
6919
6963
|
this.timers.set(label, Date.now());
|
|
6920
6964
|
}
|
|
@@ -6929,12 +6973,16 @@ var init_generate_ai_content = __esm({
|
|
|
6929
6973
|
this.debug(category, `${label} completed`, { durationMs: duration });
|
|
6930
6974
|
return duration;
|
|
6931
6975
|
}
|
|
6976
|
+
/**
|
|
6977
|
+
* Log `<label> completed {durationMs}` once `fn` settles. The start time is local to the
|
|
6978
|
+
* call, so concurrent spans with the same label on one logger never overwrite each other.
|
|
6979
|
+
*/
|
|
6932
6980
|
async timed(category, label, fn) {
|
|
6933
|
-
|
|
6981
|
+
const start = Date.now();
|
|
6934
6982
|
try {
|
|
6935
6983
|
return await fn();
|
|
6936
6984
|
} finally {
|
|
6937
|
-
this.
|
|
6985
|
+
this.debug(category, `${label} completed`, { durationMs: Date.now() - start });
|
|
6938
6986
|
}
|
|
6939
6987
|
}
|
|
6940
6988
|
};
|
|
@@ -7059,8 +7107,18 @@ var init_generate_ai_content = __esm({
|
|
|
7059
7107
|
email: "static-deploy@canopycms",
|
|
7060
7108
|
name: "Static Deploy"
|
|
7061
7109
|
});
|
|
7110
|
+
storage = new AsyncLocalStorage();
|
|
7111
|
+
log22 = createDebugLogger2({ prefix: "CanopyCMS" });
|
|
7112
|
+
NETWORK_SCHEME_PATTERN = /^(https?|ssh|git):\/\//i;
|
|
7113
|
+
TRANSPORT_HELPER_PATTERN = /^[a-z][a-z0-9+.-]*::/i;
|
|
7114
|
+
LEADING_OPTION_PATTERN = /^-/;
|
|
7115
|
+
WINDOWS_DRIVE_PATTERN = /^[a-zA-Z]:[\\/]/;
|
|
7116
|
+
COLON_BEFORE_SLASH_PATTERN = /^[^/\\\s]+:/;
|
|
7117
|
+
MISSING_REMOTE_REF_REASONS = ["couldn't find remote ref", "Couldn't find remote ref"];
|
|
7118
|
+
CANOPY_META_DIR = ".canopy-meta";
|
|
7062
7119
|
SCHEMA_CACHE_VERSION = 3;
|
|
7063
7120
|
MTIME_CHECK_DEBOUNCE_MS = 1e3;
|
|
7121
|
+
SCHEMA_CACHE_FILE = "schema-cache.json";
|
|
7064
7122
|
SCHEMA_GENERATION_RESOURCE = "schema";
|
|
7065
7123
|
BranchSchemaCache = class {
|
|
7066
7124
|
constructor(mode = "prod") {
|
|
@@ -7070,7 +7128,7 @@ var init_generate_ai_content = __esm({
|
|
|
7070
7128
|
/**
|
|
7071
7129
|
* Whether to skip the on-disk cache for this branchRoot.
|
|
7072
7130
|
*
|
|
7073
|
-
* Never write
|
|
7131
|
+
* Never write a schema cache at the project root, whichever entrypoint
|
|
7074
7132
|
* produced the cwd branchRoot. branchRoot equals process.cwd() only in the
|
|
7075
7133
|
* synthetic contexts static deployments and build phases use; a real branch
|
|
7076
7134
|
* root is always nested under the workspace.
|
|
@@ -7080,7 +7138,7 @@ var init_generate_ai_content = __esm({
|
|
|
7080
7138
|
}
|
|
7081
7139
|
/** Get schema for a branch, from the cache when fresh, else resolved fresh. */
|
|
7082
7140
|
async getSchema(branchRoot, entrySchemaRegistry, contentRootName = "content") {
|
|
7083
|
-
return this.loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName);
|
|
7141
|
+
return timeRequestPhase("schema", () => this.loadFromCacheOrResolve(branchRoot, entrySchemaRegistry, contentRootName));
|
|
7084
7142
|
}
|
|
7085
7143
|
/**
|
|
7086
7144
|
* Resolve the schema from disk. Protected rather than a direct resolveSchema
|
|
@@ -7094,8 +7152,7 @@ var init_generate_ai_content = __esm({
|
|
|
7094
7152
|
const contentRoot = path82.join(branchRoot, contentRootName);
|
|
7095
7153
|
const skipDiskCache = this.skipDiskCache(branchRoot);
|
|
7096
7154
|
if (!skipDiskCache) {
|
|
7097
|
-
const
|
|
7098
|
-
const cachePath = path82.join(cacheDir, "schema-cache.json");
|
|
7155
|
+
const cachePath = path82.join(await schemaCacheDir(branchRoot), SCHEMA_CACHE_FILE);
|
|
7099
7156
|
let cacheData = null;
|
|
7100
7157
|
try {
|
|
7101
7158
|
const cacheContent = await fs72.readFile(cachePath, "utf-8");
|
|
@@ -7137,7 +7194,7 @@ var init_generate_ai_content = __esm({
|
|
|
7137
7194
|
const { skipDiskCache } = options;
|
|
7138
7195
|
const contentRoot = path82.join(branchRoot, contentRootName);
|
|
7139
7196
|
const read = skipDiskCache ? null : await readResourceGeneration(branchRoot, SCHEMA_GENERATION_RESOURCE);
|
|
7140
|
-
const result = await this.resolveFresh(contentRoot, entrySchemaRegistry);
|
|
7197
|
+
const result = await timeRequestPhase("resolve", () => this.resolveFresh(contentRoot, entrySchemaRegistry));
|
|
7141
7198
|
if (!isValidSchema(result.schema)) {
|
|
7142
7199
|
throw new Error(`No schema found in ${contentRoot}. Create .collection.json files with references to field schemas defined in your entry schema registry.`);
|
|
7143
7200
|
}
|
|
@@ -7148,9 +7205,9 @@ var init_generate_ai_content = __esm({
|
|
|
7148
7205
|
}
|
|
7149
7206
|
const flatSchema = flattenSchema(result.schema, contentRootName);
|
|
7150
7207
|
if (!skipDiskCache) {
|
|
7151
|
-
const cacheDir =
|
|
7152
|
-
const cachePath = path82.join(cacheDir,
|
|
7153
|
-
await fs72.unlink(path82.join(
|
|
7208
|
+
const cacheDir = await schemaCacheDir(branchRoot);
|
|
7209
|
+
const cachePath = path82.join(cacheDir, SCHEMA_CACHE_FILE);
|
|
7210
|
+
await fs72.unlink(path82.join(branchRoot, CANOPY_META_DIR, "schema-cache.stale")).catch(() => {
|
|
7154
7211
|
});
|
|
7155
7212
|
if (read && read.ok) {
|
|
7156
7213
|
const newCache = {
|
|
@@ -7643,6 +7700,7 @@ var init_generate_ai_content = __esm({
|
|
|
7643
7700
|
return {
|
|
7644
7701
|
absolutePath: resolved,
|
|
7645
7702
|
relativePath: path11.relative(this.root, resolved),
|
|
7703
|
+
logicalPath: entryLogicalPath(schemaItem.logicalPath, safeSlug),
|
|
7646
7704
|
id,
|
|
7647
7705
|
entryTypeName: finalEntryTypeName,
|
|
7648
7706
|
existed
|
|
@@ -8337,7 +8395,7 @@ var init_generate_ai_content = __esm({
|
|
|
8337
8395
|
"reference",
|
|
8338
8396
|
"image"
|
|
8339
8397
|
]);
|
|
8340
|
-
|
|
8398
|
+
log3 = createDebugLogger2({ prefix: "EntryLinks" });
|
|
8341
8399
|
BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
8342
8400
|
ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
|
|
8343
8401
|
BRANCH_META_DIR = ".canopy-meta";
|
|
@@ -8352,7 +8410,7 @@ var init_generate_ai_content = __esm({
|
|
|
8352
8410
|
};
|
|
8353
8411
|
branchMetadataFilePath = (branchRoot) => path13.join(path13.resolve(branchRoot), BRANCH_META_DIR, BRANCH_META_FILE);
|
|
8354
8412
|
init_logger2();
|
|
8355
|
-
|
|
8413
|
+
log4 = createDebugLogger2({ prefix: "BranchRegistry" });
|
|
8356
8414
|
REGISTRY_FILE = "branches.json";
|
|
8357
8415
|
REGISTRY_STALE_FILE = "branches.stale.json";
|
|
8358
8416
|
REGISTRY_TEMP_FILE = "branches.tmp.json";
|
|
@@ -8432,7 +8490,7 @@ var init_generate_ai_content = __esm({
|
|
|
8432
8490
|
});
|
|
8433
8491
|
await this.regenerate();
|
|
8434
8492
|
} catch (err) {
|
|
8435
|
-
|
|
8493
|
+
log4.warn("invalidate", "Eager regeneration after invalidate() failed", {
|
|
8436
8494
|
error: getErrorMessage3(err)
|
|
8437
8495
|
});
|
|
8438
8496
|
}
|
|
@@ -8517,7 +8575,7 @@ var init_generate_ai_content = __esm({
|
|
|
8517
8575
|
return branches;
|
|
8518
8576
|
}
|
|
8519
8577
|
};
|
|
8520
|
-
|
|
8578
|
+
log5 = createDebugLogger2({ prefix: "OccJsonWrite" });
|
|
8521
8579
|
OccWriteConflictError = class extends Error {
|
|
8522
8580
|
constructor(message = "Concurrent modification detected") {
|
|
8523
8581
|
super(message);
|
|
@@ -8687,13 +8745,7 @@ var init_generate_ai_content = __esm({
|
|
|
8687
8745
|
};
|
|
8688
8746
|
};
|
|
8689
8747
|
init_operating_mode();
|
|
8690
|
-
|
|
8691
|
-
TRANSPORT_HELPER_PATTERN = /^[a-z][a-z0-9+.-]*::/i;
|
|
8692
|
-
LEADING_OPTION_PATTERN = /^-/;
|
|
8693
|
-
WINDOWS_DRIVE_PATTERN = /^[a-zA-Z]:[\\/]/;
|
|
8694
|
-
COLON_BEFORE_SLASH_PATTERN = /^[^/\\\s]+:/;
|
|
8695
|
-
MISSING_REMOTE_REF_REASONS = ["couldn't find remote ref", "Couldn't find remote ref"];
|
|
8696
|
-
log5 = createDebugLogger2({ prefix: "GitManager" });
|
|
8748
|
+
log6 = createDebugLogger2({ prefix: "GitManager" });
|
|
8697
8749
|
GIT_ENV_PASSTHROUGH = /^(PATH|HOME|USER|LANG|LC_[A-Z]+|TZ|TMPDIR|GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL|DATE)|GIT_TERMINAL_PROMPT|GIT_TRACE[0-9A-Z_]*)$/;
|
|
8698
8750
|
FORCE_C_LOCALE = { LC_ALL: "C", LANG: "C" };
|
|
8699
8751
|
remoteInitLocks = /* @__PURE__ */ new Map();
|
|
@@ -8714,6 +8766,13 @@ var init_generate_ai_content = __esm({
|
|
|
8714
8766
|
this.name = "GitRemoteRefMissingError";
|
|
8715
8767
|
}
|
|
8716
8768
|
};
|
|
8769
|
+
RemoteNotReadyError = class extends Error {
|
|
8770
|
+
constructor(expectedRemotePath) {
|
|
8771
|
+
super(`CanopyCMS: no git remote is available yet. defaultRemoteUrl (or CANOPYCMS_REMOTE_URL) is not set and the CMS worker has not created ${expectedRemotePath}; the worker may still be starting.`);
|
|
8772
|
+
this.expectedRemotePath = expectedRemotePath;
|
|
8773
|
+
this.name = "RemoteNotReadyError";
|
|
8774
|
+
}
|
|
8775
|
+
};
|
|
8717
8776
|
GitManager = class _GitManager {
|
|
8718
8777
|
constructor(options, gitOptions) {
|
|
8719
8778
|
this.repoPath = path17.resolve(options.repoPath);
|
|
@@ -8724,14 +8783,14 @@ var init_generate_ai_content = __esm({
|
|
|
8724
8783
|
this.git.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path17.dirname(this.repoPath) }));
|
|
8725
8784
|
}
|
|
8726
8785
|
static async cloneRepo(remoteUrl, targetPath, baseBranch = "main") {
|
|
8727
|
-
|
|
8786
|
+
log6.debug("git", "Cloning repository", {
|
|
8728
8787
|
remoteUrl,
|
|
8729
8788
|
targetPath,
|
|
8730
8789
|
baseBranch
|
|
8731
8790
|
});
|
|
8732
8791
|
const git = simpleGit22();
|
|
8733
8792
|
await git.clone(remoteUrl, targetPath, ["--branch", baseBranch, "--single-branch"]);
|
|
8734
|
-
|
|
8793
|
+
log6.debug("git", "Clone complete");
|
|
8735
8794
|
}
|
|
8736
8795
|
/**
|
|
8737
8796
|
* Initializes a local bare git repository to simulate a remote for dev mode,
|
|
@@ -8745,15 +8804,15 @@ var init_generate_ai_content = __esm({
|
|
|
8745
8804
|
static async ensureLocalSimulatedRemote(options) {
|
|
8746
8805
|
const existingLock = remoteInitLocks.get(options.remotePath);
|
|
8747
8806
|
if (existingLock) {
|
|
8748
|
-
|
|
8807
|
+
log6.debug("git", "Waiting for existing remote initialization", {
|
|
8749
8808
|
remotePath: options.remotePath
|
|
8750
8809
|
});
|
|
8751
8810
|
await existingLock;
|
|
8752
8811
|
}
|
|
8753
|
-
const lockPromise =
|
|
8812
|
+
const lockPromise = log6.timed("git", "ensureLocalSimulatedRemote", async () => {
|
|
8754
8813
|
let releaseLock;
|
|
8755
8814
|
try {
|
|
8756
|
-
|
|
8815
|
+
log6.debug("git", "Initializing local simulated remote", {
|
|
8757
8816
|
remotePath: options.remotePath,
|
|
8758
8817
|
baseBranch: options.baseBranch
|
|
8759
8818
|
});
|
|
@@ -8768,7 +8827,7 @@ var init_generate_ai_content = __esm({
|
|
|
8768
8827
|
throw err;
|
|
8769
8828
|
}
|
|
8770
8829
|
if (remoteExists && await _GitManager.bareRemoteHasBranch(options.remotePath, options.baseBranch)) {
|
|
8771
|
-
|
|
8830
|
+
log6.debug("git", "Remote already has base branch, skipping");
|
|
8772
8831
|
return;
|
|
8773
8832
|
}
|
|
8774
8833
|
let gitRoot = options.sourcePath;
|
|
@@ -8787,8 +8846,8 @@ var init_generate_ai_content = __esm({
|
|
|
8787
8846
|
}
|
|
8788
8847
|
let hasCommits = false;
|
|
8789
8848
|
try {
|
|
8790
|
-
const
|
|
8791
|
-
hasCommits =
|
|
8849
|
+
const log92 = await sourceGit.log(["-1"]);
|
|
8850
|
+
hasCommits = log92.total > 0;
|
|
8792
8851
|
} catch {
|
|
8793
8852
|
hasCommits = false;
|
|
8794
8853
|
}
|
|
@@ -8800,12 +8859,12 @@ var init_generate_ai_content = __esm({
|
|
|
8800
8859
|
throw new Error(`Cannot initialize local simulated remote: base branch '${options.baseBranch}' does not exist locally. Please checkout '${options.baseBranch}' first or provide an explicit remoteUrl.`);
|
|
8801
8860
|
}
|
|
8802
8861
|
if (remoteExists) {
|
|
8803
|
-
|
|
8862
|
+
log6.debug("git", "Existing remote is missing base branch \u2014 pushing it from source", {
|
|
8804
8863
|
remotePath: options.remotePath,
|
|
8805
8864
|
baseBranch: options.baseBranch
|
|
8806
8865
|
});
|
|
8807
8866
|
} else {
|
|
8808
|
-
|
|
8867
|
+
log6.debug("git", "Creating bare remote repository");
|
|
8809
8868
|
await simpleGit22().raw([
|
|
8810
8869
|
"init",
|
|
8811
8870
|
"--bare",
|
|
@@ -8819,13 +8878,13 @@ var init_generate_ai_content = __esm({
|
|
|
8819
8878
|
baseBranch: options.baseBranch,
|
|
8820
8879
|
subdirectory: options.subdirectory
|
|
8821
8880
|
});
|
|
8822
|
-
|
|
8881
|
+
log6.debug("git", "Remote initialization complete");
|
|
8823
8882
|
} finally {
|
|
8824
8883
|
if (releaseLock) {
|
|
8825
8884
|
try {
|
|
8826
8885
|
await releaseLock();
|
|
8827
8886
|
} catch (err) {
|
|
8828
|
-
|
|
8887
|
+
log6.debug("git", "Failed to release remote-init lock", { err });
|
|
8829
8888
|
}
|
|
8830
8889
|
}
|
|
8831
8890
|
remoteInitLocks.delete(options.remotePath);
|
|
@@ -9060,7 +9119,7 @@ var init_generate_ai_content = __esm({
|
|
|
9060
9119
|
try {
|
|
9061
9120
|
const stat = await fs16.stat(config.autoDetectRemotePath);
|
|
9062
9121
|
if (stat.isDirectory()) {
|
|
9063
|
-
|
|
9122
|
+
log6.debug("git", "Auto-detected local remote", {
|
|
9064
9123
|
path: config.autoDetectRemotePath
|
|
9065
9124
|
});
|
|
9066
9125
|
return config.autoDetectRemotePath;
|
|
@@ -9124,7 +9183,7 @@ var init_generate_ai_content = __esm({
|
|
|
9124
9183
|
try {
|
|
9125
9184
|
const stat = await fs16.stat(gitPath);
|
|
9126
9185
|
if (stat.isDirectory()) {
|
|
9127
|
-
|
|
9186
|
+
log6.debug("git", "Removing corrupt .git directory", {
|
|
9128
9187
|
workspacePath: options.workspacePath
|
|
9129
9188
|
});
|
|
9130
9189
|
await fs16.rm(gitPath, { recursive: true });
|
|
@@ -9145,6 +9204,10 @@ var init_generate_ai_content = __esm({
|
|
|
9145
9204
|
allowNetworkRemoteInProd: options.allowNetworkRemoteInProd
|
|
9146
9205
|
});
|
|
9147
9206
|
if (!remoteUrl) {
|
|
9207
|
+
const { operatingStrategy: operatingStrategy22 } = await Promise.resolve().then(() => (init_operating_mode(), operating_mode_exports));
|
|
9208
|
+
const { autoDetectRemotePath } = operatingStrategy22(options.mode).getRemoteUrlConfig();
|
|
9209
|
+
if (autoDetectRemotePath)
|
|
9210
|
+
throw new RemoteNotReadyError(autoDetectRemotePath);
|
|
9148
9211
|
throw new Error("CanopyCMS: defaultRemoteUrl (or CANOPYCMS_REMOTE_URL) is required to initialize workspace");
|
|
9149
9212
|
}
|
|
9150
9213
|
try {
|
|
@@ -9168,7 +9231,7 @@ var init_generate_ai_content = __esm({
|
|
|
9168
9231
|
await git.git.addConfig("canopycms.managed", "true");
|
|
9169
9232
|
await git.git.addConfig("user.name", options.gitBotAuthorName);
|
|
9170
9233
|
await git.git.addConfig("user.email", options.gitBotAuthorEmail);
|
|
9171
|
-
|
|
9234
|
+
log6.debug("git", "Marked workspace as CanopyCMS-managed", {
|
|
9172
9235
|
workspacePath: options.workspacePath
|
|
9173
9236
|
});
|
|
9174
9237
|
if (!justCloned) {
|
|
@@ -9351,6 +9414,10 @@ var init_generate_ai_content = __esm({
|
|
|
9351
9414
|
const fileArray = Array.isArray(files) ? files : [files];
|
|
9352
9415
|
await this.git.add(fileArray);
|
|
9353
9416
|
}
|
|
9417
|
+
/** Stage every working-tree change except canopycms's own state. See {@link stageAllExceptCanopyState}. */
|
|
9418
|
+
async addAllExceptCanopyState() {
|
|
9419
|
+
await stageAllExceptCanopyState(this.git);
|
|
9420
|
+
}
|
|
9354
9421
|
async commit(message) {
|
|
9355
9422
|
await this.git.commit(message);
|
|
9356
9423
|
}
|
|
@@ -9382,12 +9449,12 @@ var init_generate_ai_content = __esm({
|
|
|
9382
9449
|
* ("couldn't find remote ref"); that counts as "ahead", not an error.
|
|
9383
9450
|
*/
|
|
9384
9451
|
async hasUnpushedCommits(branch) {
|
|
9385
|
-
const target = branch ?? await this.git.revparse(["--abbrev-ref", "--end-of-options", "HEAD"]);
|
|
9386
|
-
const localSha = (await this.git.revparse(["--end-of-options", target])).trim();
|
|
9452
|
+
const target = branch ?? await this.git.revparse(["--verify", "--abbrev-ref", "--end-of-options", "HEAD"]);
|
|
9453
|
+
const localSha = (await this.git.revparse(["--verify", "--end-of-options", target])).trim();
|
|
9387
9454
|
let fetchedTip;
|
|
9388
9455
|
try {
|
|
9389
9456
|
await this.git.raw(["fetch", "--end-of-options", this.remote, target]);
|
|
9390
|
-
fetchedTip = (await this.git.revparse(["--end-of-options", "FETCH_HEAD"])).trim();
|
|
9457
|
+
fetchedTip = (await this.git.revparse(["--verify", "--end-of-options", "FETCH_HEAD"])).trim();
|
|
9391
9458
|
} catch {
|
|
9392
9459
|
return true;
|
|
9393
9460
|
}
|
|
@@ -9396,6 +9463,25 @@ var init_generate_ai_content = __esm({
|
|
|
9396
9463
|
const aheadCount = (await this.git.raw(["rev-list", "--count", `${fetchedTip}..${localSha}`])).trim();
|
|
9397
9464
|
return aheadCount !== "0";
|
|
9398
9465
|
}
|
|
9466
|
+
/**
|
|
9467
|
+
* Repo-relative paths that differ between HEAD and its merge base with the
|
|
9468
|
+
* base branch: everything the branch changes, across all of its commits.
|
|
9469
|
+
* Diffs against the just-fetched base tip pinned to a SHA — the pullBaseInner
|
|
9470
|
+
* constraint.
|
|
9471
|
+
*/
|
|
9472
|
+
async listChangedPathsSinceBase() {
|
|
9473
|
+
await this.git.raw(["fetch", "--end-of-options", this.remote, this.baseBranch]);
|
|
9474
|
+
const baseTip = (await this.git.revparse(["--verify", "--end-of-options", "FETCH_HEAD"])).trim();
|
|
9475
|
+
const output = await this.git.raw([
|
|
9476
|
+
"diff",
|
|
9477
|
+
"--name-only",
|
|
9478
|
+
"-z",
|
|
9479
|
+
"--no-renames",
|
|
9480
|
+
`${baseTip}...HEAD`,
|
|
9481
|
+
"--"
|
|
9482
|
+
]);
|
|
9483
|
+
return output.split("\0").filter((p5) => p5.length > 0);
|
|
9484
|
+
}
|
|
9399
9485
|
async ensureAuthor(author) {
|
|
9400
9486
|
const config = await this.git.listConfig();
|
|
9401
9487
|
const isManaged = config.all["canopycms.managed"] === "true";
|
|
@@ -9462,10 +9548,10 @@ var init_generate_ai_content = __esm({
|
|
|
9462
9548
|
}
|
|
9463
9549
|
}
|
|
9464
9550
|
async createOrphanSettingsBranchInner(branchName, initialFiles) {
|
|
9465
|
-
|
|
9551
|
+
log6.debug("git", "Creating orphan settings branch", { branchName });
|
|
9466
9552
|
const branches = await this.git.branch();
|
|
9467
9553
|
if (branches.all.includes(branchName)) {
|
|
9468
|
-
|
|
9554
|
+
log6.debug("git", "Orphan branch already exists", { branchName });
|
|
9469
9555
|
await this.git.checkout(branchName);
|
|
9470
9556
|
return;
|
|
9471
9557
|
}
|
|
@@ -9481,17 +9567,17 @@ var init_generate_ai_content = __esm({
|
|
|
9481
9567
|
await this.git.add(filePath);
|
|
9482
9568
|
}
|
|
9483
9569
|
await this.git.commit("Initialize settings branch", ["--allow-empty"]);
|
|
9484
|
-
|
|
9570
|
+
log6.debug("git", "Orphan settings branch created", { branchName });
|
|
9485
9571
|
}
|
|
9486
9572
|
};
|
|
9487
|
-
|
|
9573
|
+
log7 = createDebugLogger2({ prefix: "BranchWorkspace" });
|
|
9488
9574
|
workspaceInitLocks = /* @__PURE__ */ new Map();
|
|
9489
9575
|
BranchWorkspaceManager = class {
|
|
9490
9576
|
constructor(config) {
|
|
9491
9577
|
this.config = config;
|
|
9492
9578
|
}
|
|
9493
9579
|
async ensureGitWorkspace(options) {
|
|
9494
|
-
return
|
|
9580
|
+
return log7.timed("workspace", "ensureGitWorkspace", async () => {
|
|
9495
9581
|
const existingLock = workspaceInitLocks.get(options.branchRoot);
|
|
9496
9582
|
if (existingLock) {
|
|
9497
9583
|
await existingLock;
|
|
@@ -9500,11 +9586,11 @@ var init_generate_ai_content = __esm({
|
|
|
9500
9586
|
const lockPromise = (async () => {
|
|
9501
9587
|
let releaseLock;
|
|
9502
9588
|
try {
|
|
9503
|
-
|
|
9589
|
+
log7.debug("workspace", "Ensuring git workspace", {
|
|
9504
9590
|
branchName: options.branchName,
|
|
9505
9591
|
mode: options.mode
|
|
9506
9592
|
});
|
|
9507
|
-
releaseLock = await acquireProvisioningLock(path18.dirname(options.branchRoot),
|
|
9593
|
+
releaseLock = await acquireProvisioningLock(path18.dirname(options.branchRoot), branchProvisioningLockName(path18.basename(options.branchRoot)));
|
|
9508
9594
|
await GitManager.initializeWorkspace({
|
|
9509
9595
|
workspacePath: options.branchRoot,
|
|
9510
9596
|
branchName: options.branchName,
|
|
@@ -9525,7 +9611,7 @@ var init_generate_ai_content = __esm({
|
|
|
9525
9611
|
try {
|
|
9526
9612
|
await releaseLock();
|
|
9527
9613
|
} catch (err) {
|
|
9528
|
-
|
|
9614
|
+
log7.debug("workspace", "Failed to release workspace-init lock", { err });
|
|
9529
9615
|
}
|
|
9530
9616
|
}
|
|
9531
9617
|
workspaceInitLocks.delete(options.branchRoot);
|
|
@@ -9572,7 +9658,7 @@ var init_generate_ai_content = __esm({
|
|
|
9572
9658
|
};
|
|
9573
9659
|
}
|
|
9574
9660
|
};
|
|
9575
|
-
|
|
9661
|
+
log8 = createDebugLogger2({ prefix: "ContentListing" });
|
|
9576
9662
|
normalizePath = (root, target) => {
|
|
9577
9663
|
const result = validateAndNormalizePath(root, target);
|
|
9578
9664
|
if (!result.valid) {
|
|
@@ -9598,7 +9684,7 @@ var init_generate_ai_content = __esm({
|
|
|
9598
9684
|
} catch (err) {
|
|
9599
9685
|
if (isNotFoundError3(err))
|
|
9600
9686
|
return {};
|
|
9601
|
-
|
|
9687
|
+
log8.warn("readEntryData", `Failed to read entry data from ${filePath}: ${getErrorMessage3(err)}`);
|
|
9602
9688
|
return {};
|
|
9603
9689
|
}
|
|
9604
9690
|
};
|
|
@@ -9655,7 +9741,7 @@ var init_generate_ai_content = __esm({
|
|
|
9655
9741
|
const relativePath = normalizePath(root, absolutePath);
|
|
9656
9742
|
const parsed = parseTypedFilename(file.name, entryTypes);
|
|
9657
9743
|
if (!parsed) {
|
|
9658
|
-
|
|
9744
|
+
log8.warn("listCollectionEntries", `Skipping file with unrecognized filename format: ${file.name} (expected {type}.{slug}.{id}.{ext} with a known entry type and valid 12-char Base58 ID)`);
|
|
9659
9745
|
if (looksLikeMalformedEntry(file.name, entryTypes)) {
|
|
9660
9746
|
onSkip?.({ filename: file.name, collectionPath: collection.logicalPath });
|
|
9661
9747
|
}
|
|
@@ -9670,7 +9756,7 @@ var init_generate_ai_content = __esm({
|
|
|
9670
9756
|
readEntryData(absolutePath, format, bodyField)
|
|
9671
9757
|
]);
|
|
9672
9758
|
const item = {
|
|
9673
|
-
logicalPath:
|
|
9759
|
+
logicalPath: entryLogicalPath(collection.logicalPath, slug),
|
|
9674
9760
|
contentId,
|
|
9675
9761
|
slug,
|
|
9676
9762
|
collectionPath: collection.logicalPath,
|
|
@@ -9697,6 +9783,23 @@ var init_generate_ai_content = __esm({
|
|
|
9697
9783
|
}
|
|
9698
9784
|
});
|
|
9699
9785
|
|
|
9786
|
+
// dist/utils/git.js
|
|
9787
|
+
import { simpleGit as simpleGit4 } from "simple-git";
|
|
9788
|
+
function isCanopyInternalPath(repoRelativePath) {
|
|
9789
|
+
return repoRelativePath === CANOPY_META_DIR2 || repoRelativePath.startsWith(`${CANOPY_META_DIR2}/`);
|
|
9790
|
+
}
|
|
9791
|
+
async function stageAllExceptCanopyState2(git) {
|
|
9792
|
+
await git.add(["-A"]);
|
|
9793
|
+
await git.raw(["reset", "-q", "--", CANOPY_META_DIR2]);
|
|
9794
|
+
}
|
|
9795
|
+
var CANOPY_META_DIR2;
|
|
9796
|
+
var init_git = __esm({
|
|
9797
|
+
"dist/utils/git.js"() {
|
|
9798
|
+
"use strict";
|
|
9799
|
+
CANOPY_META_DIR2 = ".canopy-meta";
|
|
9800
|
+
}
|
|
9801
|
+
});
|
|
9802
|
+
|
|
9700
9803
|
// dist/utils/atomic-write.js
|
|
9701
9804
|
import fs19 from "node:fs/promises";
|
|
9702
9805
|
import path23 from "node:path";
|
|
@@ -9783,6 +9886,10 @@ var init_debug2 = __esm({
|
|
|
9783
9886
|
throw new Error(errorMsg);
|
|
9784
9887
|
}
|
|
9785
9888
|
}
|
|
9889
|
+
/**
|
|
9890
|
+
* Keyed by label alone, so two overlapping timers with one label overwrite each other;
|
|
9891
|
+
* anything that can run concurrently uses {@link timed}.
|
|
9892
|
+
*/
|
|
9786
9893
|
time(label) {
|
|
9787
9894
|
this.timers.set(label, Date.now());
|
|
9788
9895
|
}
|
|
@@ -9797,12 +9904,16 @@ var init_debug2 = __esm({
|
|
|
9797
9904
|
this.debug(category, `${label} completed`, { durationMs: duration });
|
|
9798
9905
|
return duration;
|
|
9799
9906
|
}
|
|
9907
|
+
/**
|
|
9908
|
+
* Log `<label> completed {durationMs}` once `fn` settles. The start time is local to the
|
|
9909
|
+
* call, so concurrent spans with the same label on one logger never overwrite each other.
|
|
9910
|
+
*/
|
|
9800
9911
|
async timed(category, label, fn) {
|
|
9801
|
-
|
|
9912
|
+
const start = Date.now();
|
|
9802
9913
|
try {
|
|
9803
9914
|
return await fn();
|
|
9804
9915
|
} finally {
|
|
9805
|
-
this.
|
|
9916
|
+
this.debug(category, `${label} completed`, { durationMs: Date.now() - start });
|
|
9806
9917
|
}
|
|
9807
9918
|
}
|
|
9808
9919
|
};
|
|
@@ -9827,7 +9938,7 @@ async function bumpResourceGeneration2(root, resource, options) {
|
|
|
9827
9938
|
await atomicWriteFile3(resourceGenerationPath2(root, resource), token);
|
|
9828
9939
|
return token;
|
|
9829
9940
|
} catch (err) {
|
|
9830
|
-
|
|
9941
|
+
log9.warn("generation", `Failed to bump ${resource} generation for ${root}`, {
|
|
9831
9942
|
error: getErrorMessage(err)
|
|
9832
9943
|
});
|
|
9833
9944
|
if (options?.mustSucceed)
|
|
@@ -9842,7 +9953,7 @@ async function readResourceGeneration2(root, resource) {
|
|
|
9842
9953
|
} catch (err) {
|
|
9843
9954
|
if (isNodeError(err) && err.code === "ENOENT")
|
|
9844
9955
|
return { ok: true, token: null };
|
|
9845
|
-
|
|
9956
|
+
log9.warn("generation", `Failed to read ${resource} generation for ${root}`, {
|
|
9846
9957
|
error: getErrorMessage(err)
|
|
9847
9958
|
});
|
|
9848
9959
|
return { ok: false };
|
|
@@ -9851,14 +9962,14 @@ async function readResourceGeneration2(root, resource) {
|
|
|
9851
9962
|
function isGenerationCurrent2(snapshotToken, read) {
|
|
9852
9963
|
return read.ok && read.token === snapshotToken;
|
|
9853
9964
|
}
|
|
9854
|
-
var
|
|
9965
|
+
var log9, META_DIR3;
|
|
9855
9966
|
var init_resource_generation = __esm({
|
|
9856
9967
|
"dist/resource-generation.js"() {
|
|
9857
9968
|
"use strict";
|
|
9858
9969
|
init_atomic_write2();
|
|
9859
9970
|
init_error();
|
|
9860
9971
|
init_debug2();
|
|
9861
|
-
|
|
9972
|
+
log9 = createDebugLogger3({ prefix: "ResourceGeneration" });
|
|
9862
9973
|
META_DIR3 = ".canopy-meta";
|
|
9863
9974
|
}
|
|
9864
9975
|
});
|
|
@@ -10263,7 +10374,8 @@ var init_field3 = __esm({
|
|
|
10263
10374
|
});
|
|
10264
10375
|
objectFieldSchema3 = fieldBaseSchema3.extend({
|
|
10265
10376
|
type: z10.literal("object"),
|
|
10266
|
-
fields: z10.array(z10.lazy(() => fieldHolder3[0])).min(1)
|
|
10377
|
+
fields: z10.array(z10.lazy(() => fieldHolder3[0])).min(1),
|
|
10378
|
+
itemTitleField: z10.string().min(1).optional()
|
|
10267
10379
|
});
|
|
10268
10380
|
inlineGroupFieldSchema3 = z10.object({
|
|
10269
10381
|
type: z10.literal("group"),
|
|
@@ -10505,6 +10617,9 @@ var init_config4 = __esm({
|
|
|
10505
10617
|
defaultRemoteUrl: defaultRemoteUrlSchema3.optional(),
|
|
10506
10618
|
gitBotAuthorName: gitBotAuthorNameSchema3,
|
|
10507
10619
|
gitBotAuthorEmail: gitBotAuthorEmailSchema3,
|
|
10620
|
+
// Unset reads as on for Edited-by and off for Co-authored-by (services.ts submitBranch).
|
|
10621
|
+
gitEditedByTrailers: z14.boolean().optional(),
|
|
10622
|
+
gitCoAuthoredByTrailers: z14.boolean().optional(),
|
|
10508
10623
|
githubTokenEnvVar: githubTokenEnvVarSchema3.optional(),
|
|
10509
10624
|
// Required by design (follow-up to SEC-C1): a prod deploy that omits `mode` must fail
|
|
10510
10625
|
// validation loudly rather than silently running header-trusting dev auth semantics.
|
|
@@ -10516,7 +10631,6 @@ var init_config4 = __esm({
|
|
|
10516
10631
|
// Default false/unset — the standard AWS Lambda+worker topology must leave this unset.
|
|
10517
10632
|
allowNetworkRemoteInProd: z14.boolean().optional(),
|
|
10518
10633
|
settingsBranch: z14.string().optional(),
|
|
10519
|
-
autoCreateSettingsPR: z14.boolean().optional(),
|
|
10520
10634
|
// `.optional()`, NOT bare `deploymentNameSchema`: its own `.default('prod')` would make
|
|
10521
10635
|
// `parse(undefined)` resolve to 'prod' instead of staying `undefined`, collapsing the
|
|
10522
10636
|
// env > config > modeDefault precedence chain `resolveDeploymentName`
|
|
@@ -10577,6 +10691,18 @@ var init_build_mode = __esm({
|
|
|
10577
10691
|
}
|
|
10578
10692
|
});
|
|
10579
10693
|
|
|
10694
|
+
// dist/utils/request-timing.js
|
|
10695
|
+
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
|
|
10696
|
+
var storage2, log10;
|
|
10697
|
+
var init_request_timing = __esm({
|
|
10698
|
+
"dist/utils/request-timing.js"() {
|
|
10699
|
+
"use strict";
|
|
10700
|
+
init_debug2();
|
|
10701
|
+
storage2 = new AsyncLocalStorage2();
|
|
10702
|
+
log10 = createDebugLogger3({ prefix: "CanopyCMS" });
|
|
10703
|
+
}
|
|
10704
|
+
});
|
|
10705
|
+
|
|
10580
10706
|
// dist/branch-schema-cache.js
|
|
10581
10707
|
var SCHEMA_GENERATION_RESOURCE2;
|
|
10582
10708
|
var init_branch_schema_cache = __esm({
|
|
@@ -10587,6 +10713,8 @@ var init_branch_schema_cache = __esm({
|
|
|
10587
10713
|
init_entry_type_reference_validator();
|
|
10588
10714
|
init_build_mode();
|
|
10589
10715
|
init_resource_generation();
|
|
10716
|
+
init_request_timing();
|
|
10717
|
+
init_git();
|
|
10590
10718
|
SCHEMA_GENERATION_RESOURCE2 = "schema";
|
|
10591
10719
|
}
|
|
10592
10720
|
});
|
|
@@ -10614,7 +10742,7 @@ var init_content_index_generation = __esm({
|
|
|
10614
10742
|
// dist/sync-core.js
|
|
10615
10743
|
import fs24 from "node:fs/promises";
|
|
10616
10744
|
import path26 from "node:path";
|
|
10617
|
-
import { simpleGit as
|
|
10745
|
+
import { simpleGit as simpleGit5 } from "simple-git";
|
|
10618
10746
|
function assertWithinDir(resolved, parent, label) {
|
|
10619
10747
|
const normalizedResolved = path26.resolve(resolved);
|
|
10620
10748
|
const normalizedParent = path26.resolve(parent);
|
|
@@ -10698,10 +10826,10 @@ async function pushContentToWorkspace(options) {
|
|
|
10698
10826
|
throw err;
|
|
10699
10827
|
}
|
|
10700
10828
|
try {
|
|
10701
|
-
const wsGit =
|
|
10702
|
-
await wsGit
|
|
10703
|
-
const
|
|
10704
|
-
if (
|
|
10829
|
+
const wsGit = simpleGit5({ baseDir: branchPath });
|
|
10830
|
+
await stageAllExceptCanopyState2(wsGit);
|
|
10831
|
+
const staged = (await wsGit.status()).files.filter((f) => !isCanopyInternalPath(f.path));
|
|
10832
|
+
if (staged.length === 0) {
|
|
10705
10833
|
if (baseTag)
|
|
10706
10834
|
await wsGit.tag(["-f", baseTag]);
|
|
10707
10835
|
return { fileCount: 0 };
|
|
@@ -10709,7 +10837,7 @@ async function pushContentToWorkspace(options) {
|
|
|
10709
10837
|
await wsGit.commit(commitMessage ?? "sync: update content from working tree");
|
|
10710
10838
|
if (baseTag)
|
|
10711
10839
|
await wsGit.tag(["-f", baseTag]);
|
|
10712
|
-
return { fileCount:
|
|
10840
|
+
return { fileCount: staged.length };
|
|
10713
10841
|
} finally {
|
|
10714
10842
|
await invalidateBranchContentCaches2(branchPath);
|
|
10715
10843
|
}
|
|
@@ -10720,18 +10848,11 @@ var init_sync_core = __esm({
|
|
|
10720
10848
|
"use strict";
|
|
10721
10849
|
init_content_index_generation();
|
|
10722
10850
|
init_fs();
|
|
10851
|
+
init_git();
|
|
10723
10852
|
SYNC_BASE_TAG = "canopycms-sync-base";
|
|
10724
10853
|
}
|
|
10725
10854
|
});
|
|
10726
10855
|
|
|
10727
|
-
// dist/utils/git.js
|
|
10728
|
-
import { simpleGit as simpleGit5 } from "simple-git";
|
|
10729
|
-
var init_git = __esm({
|
|
10730
|
-
"dist/utils/git.js"() {
|
|
10731
|
-
"use strict";
|
|
10732
|
-
}
|
|
10733
|
-
});
|
|
10734
|
-
|
|
10735
10856
|
// dist/utils/provisioning-lock.js
|
|
10736
10857
|
import lockfile3 from "proper-lockfile";
|
|
10737
10858
|
var init_provisioning_lock = __esm({
|
|
@@ -10758,15 +10879,15 @@ async function ensureGitExcludePattern2(repoPath, pattern) {
|
|
|
10758
10879
|
}
|
|
10759
10880
|
const lines = content.split("\n");
|
|
10760
10881
|
if (lines.some((line) => line.trim() === pattern)) {
|
|
10761
|
-
|
|
10882
|
+
log11.debug("git", "Pattern already in .git/info/exclude", { pattern });
|
|
10762
10883
|
return;
|
|
10763
10884
|
}
|
|
10764
10885
|
const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
|
|
10765
10886
|
const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
|
|
10766
10887
|
await fs25.writeFile(excludePath, newContent, "utf-8");
|
|
10767
|
-
|
|
10888
|
+
log11.debug("git", "Added pattern to .git/info/exclude", { pattern });
|
|
10768
10889
|
}
|
|
10769
|
-
var
|
|
10890
|
+
var log11;
|
|
10770
10891
|
var init_git_manager = __esm({
|
|
10771
10892
|
"dist/git-manager.js"() {
|
|
10772
10893
|
"use strict";
|
|
@@ -10776,7 +10897,7 @@ var init_git_manager = __esm({
|
|
|
10776
10897
|
init_error();
|
|
10777
10898
|
init_git();
|
|
10778
10899
|
init_provisioning_lock();
|
|
10779
|
-
|
|
10900
|
+
log11 = createDebugLogger3({ prefix: "GitManager" });
|
|
10780
10901
|
}
|
|
10781
10902
|
});
|
|
10782
10903
|
|
|
@@ -10993,9 +11114,6 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
10993
11114
|
throw new Error("gitBotAuthorName and gitBotAuthorEmail are required in prod mode");
|
|
10994
11115
|
}
|
|
10995
11116
|
}
|
|
10996
|
-
shouldCreateSettingsPR(config) {
|
|
10997
|
-
return config.autoCreateSettingsPR ?? true;
|
|
10998
|
-
}
|
|
10999
11117
|
};
|
|
11000
11118
|
DevStrategy3 = class extends DevClientSafeStrategy3 {
|
|
11001
11119
|
getWorkspaceRoot(sourceRoot) {
|
|
@@ -11042,9 +11160,6 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
11042
11160
|
}
|
|
11043
11161
|
validateConfig(_config) {
|
|
11044
11162
|
}
|
|
11045
|
-
shouldCreateSettingsPR(_config) {
|
|
11046
|
-
return false;
|
|
11047
|
-
}
|
|
11048
11163
|
};
|
|
11049
11164
|
strategyCache3 = /* @__PURE__ */ new Map();
|
|
11050
11165
|
}
|
|
@@ -11157,13 +11272,14 @@ async function syncPush(options) {
|
|
|
11157
11272
|
}
|
|
11158
11273
|
return { fileCount: 0 };
|
|
11159
11274
|
}
|
|
11160
|
-
|
|
11161
|
-
|
|
11162
|
-
|
|
11275
|
+
const editorChanges = status.files.filter((f) => !isCanopyInternalPath(f.path));
|
|
11276
|
+
if (editorChanges.length > 0 && !options.force) {
|
|
11277
|
+
p2.log.warn(`Branch workspace has ${editorChanges.length} uncommitted change(s) that will be committed to history then overwritten:`);
|
|
11278
|
+
for (const file of editorChanges.slice(0, 10)) {
|
|
11163
11279
|
p2.log.warn(` ${file.path}`);
|
|
11164
11280
|
}
|
|
11165
|
-
if (
|
|
11166
|
-
p2.log.warn(` ... and ${
|
|
11281
|
+
if (editorChanges.length > 10) {
|
|
11282
|
+
p2.log.warn(` ... and ${editorChanges.length - 10} more`);
|
|
11167
11283
|
}
|
|
11168
11284
|
const confirm5 = await p2.confirm({
|
|
11169
11285
|
message: "Continue? Editor changes will be preserved in git history.",
|
|
@@ -11174,8 +11290,8 @@ async function syncPush(options) {
|
|
|
11174
11290
|
return { fileCount: 0 };
|
|
11175
11291
|
}
|
|
11176
11292
|
}
|
|
11177
|
-
if (
|
|
11178
|
-
await wsGit
|
|
11293
|
+
if (editorChanges.length > 0) {
|
|
11294
|
+
await stageAllExceptCanopyState2(wsGit);
|
|
11179
11295
|
await wsGit.commit("sync: save editor state before push");
|
|
11180
11296
|
p2.log.info("Committed editor changes to history before push");
|
|
11181
11297
|
}
|
|
@@ -11312,8 +11428,8 @@ async function syncBoth(options) {
|
|
|
11312
11428
|
}
|
|
11313
11429
|
return { pushed: 0, pulled: 0 };
|
|
11314
11430
|
}
|
|
11315
|
-
if (status.files.
|
|
11316
|
-
await wsGit
|
|
11431
|
+
if (status.files.some((f) => !isCanopyInternalPath(f.path))) {
|
|
11432
|
+
await stageAllExceptCanopyState2(wsGit);
|
|
11317
11433
|
await wsGit.commit("sync: save editor state before merge");
|
|
11318
11434
|
p2.log.info("Committed editor changes before merge");
|
|
11319
11435
|
}
|
|
@@ -11342,9 +11458,9 @@ async function syncBoth(options) {
|
|
|
11342
11458
|
});
|
|
11343
11459
|
throw err;
|
|
11344
11460
|
}
|
|
11345
|
-
await wsGit
|
|
11346
|
-
const
|
|
11347
|
-
if (
|
|
11461
|
+
await stageAllExceptCanopyState2(wsGit);
|
|
11462
|
+
const incomingFiles = (await wsGit.status()).files.filter((f) => !isCanopyInternalPath(f.path));
|
|
11463
|
+
if (incomingFiles.length === 0) {
|
|
11348
11464
|
await wsGit.checkout(currentBranch);
|
|
11349
11465
|
await wsGit.raw(["branch", "-D", incomingBranch]);
|
|
11350
11466
|
p2.log.info("No working-tree changes to merge \u2014 pulling editor changes only");
|
|
@@ -11385,7 +11501,7 @@ async function syncBoth(options) {
|
|
|
11385
11501
|
await wsGit.tag(["-f", SYNC_BASE_TAG]);
|
|
11386
11502
|
p2.log.success("Merged working-tree changes with editor changes");
|
|
11387
11503
|
const pullResult = await syncPull({ ...options, branch: branchName, force: true });
|
|
11388
|
-
return { pushed:
|
|
11504
|
+
return { pushed: incomingFiles.length, pulled: pullResult.fileCount };
|
|
11389
11505
|
} finally {
|
|
11390
11506
|
await invalidateBranchContentCaches2(branchPath);
|
|
11391
11507
|
}
|
|
@@ -11433,6 +11549,7 @@ var init_sync = __esm({
|
|
|
11433
11549
|
"dist/cli/sync.js"() {
|
|
11434
11550
|
"use strict";
|
|
11435
11551
|
init_fs();
|
|
11552
|
+
init_git();
|
|
11436
11553
|
init_sync_core();
|
|
11437
11554
|
init_content_index_generation();
|
|
11438
11555
|
init_git_manager();
|
|
@@ -11616,7 +11733,7 @@ async function withOccFileLock2(filePath, fn) {
|
|
|
11616
11733
|
// Our critical sections are short and idempotent-on-conflict (OCC
|
|
11617
11734
|
// verify inside); log and let the section finish.
|
|
11618
11735
|
onCompromised: (err) => {
|
|
11619
|
-
|
|
11736
|
+
log13.warn("lock", `Lock compromised mid-hold for ${filePath}`, {
|
|
11620
11737
|
error: getErrorMessage(err)
|
|
11621
11738
|
});
|
|
11622
11739
|
}
|
|
@@ -11638,17 +11755,17 @@ async function withOccFileLock2(filePath, fn) {
|
|
|
11638
11755
|
await release().catch((err) => {
|
|
11639
11756
|
if (isNodeError(err) && err.code === "ENOENT")
|
|
11640
11757
|
return;
|
|
11641
|
-
|
|
11758
|
+
log13.warn("lock", `Failed to release lock for ${filePath}`, { error: getErrorMessage(err) });
|
|
11642
11759
|
});
|
|
11643
11760
|
}
|
|
11644
11761
|
}
|
|
11645
|
-
var
|
|
11762
|
+
var log13, OccWriteConflictError2;
|
|
11646
11763
|
var init_occ_json_write = __esm({
|
|
11647
11764
|
"dist/utils/occ-json-write.js"() {
|
|
11648
11765
|
"use strict";
|
|
11649
11766
|
init_error();
|
|
11650
11767
|
init_debug2();
|
|
11651
|
-
|
|
11768
|
+
log13 = createDebugLogger3({ prefix: "OccJsonWrite" });
|
|
11652
11769
|
OccWriteConflictError2 = class extends Error {
|
|
11653
11770
|
constructor(message = "Concurrent modification detected") {
|
|
11654
11771
|
super(message);
|
|
@@ -11698,7 +11815,7 @@ var init_branch_metadata_file = __esm({
|
|
|
11698
11815
|
// dist/branch-registry.js
|
|
11699
11816
|
import fs29 from "node:fs/promises";
|
|
11700
11817
|
import path33 from "node:path";
|
|
11701
|
-
var
|
|
11818
|
+
var log14, REGISTRY_FILE2, REGISTRY_STALE_FILE2, REGISTRY_TEMP_FILE2, REGISTRY_VERSION2, RESOURCE4, GET_MISS_REFRESH_MIN_INTERVAL_MS2, BranchRegistry2;
|
|
11702
11819
|
var init_branch_registry = __esm({
|
|
11703
11820
|
"dist/branch-registry.js"() {
|
|
11704
11821
|
"use strict";
|
|
@@ -11707,7 +11824,7 @@ var init_branch_registry = __esm({
|
|
|
11707
11824
|
init_debug2();
|
|
11708
11825
|
init_logger3();
|
|
11709
11826
|
init_resource_generation();
|
|
11710
|
-
|
|
11827
|
+
log14 = createDebugLogger3({ prefix: "BranchRegistry" });
|
|
11711
11828
|
REGISTRY_FILE2 = "branches.json";
|
|
11712
11829
|
REGISTRY_STALE_FILE2 = "branches.stale.json";
|
|
11713
11830
|
REGISTRY_TEMP_FILE2 = "branches.tmp.json";
|
|
@@ -11787,7 +11904,7 @@ var init_branch_registry = __esm({
|
|
|
11787
11904
|
});
|
|
11788
11905
|
await this.regenerate();
|
|
11789
11906
|
} catch (err) {
|
|
11790
|
-
|
|
11907
|
+
log14.warn("invalidate", "Eager regeneration after invalidate() failed", {
|
|
11791
11908
|
error: getErrorMessage(err)
|
|
11792
11909
|
});
|
|
11793
11910
|
}
|