canopycms 0.0.65 → 0.0.66-int.82
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -3
- package/dist/ai/generate.d.ts +13 -0
- package/dist/ai/generate.js +9 -3
- package/dist/ai/to-plain-text.js +4 -0
- package/dist/ai/types.d.ts +20 -1
- package/dist/api/admin-branch-health.js +13 -12
- package/dist/api/branch.js +2 -2
- package/dist/branch-health.js +1 -1
- package/dist/branch-metadata-file.d.ts +58 -0
- package/dist/branch-metadata-file.js +65 -0
- package/dist/branch-metadata.d.ts +2 -26
- package/dist/branch-metadata.js +6 -33
- package/dist/branch-registry.js +4 -2
- package/dist/build/generate-ai-content.js +103 -0
- package/dist/cli/cli.js +432 -317
- package/dist/cli/generate-ai-content.js +264 -160
- package/dist/content-id-index.js +22 -0
- package/dist/content-listing.js +3 -0
- package/dist/content-reader.d.ts +25 -1
- package/dist/content-reader.js +15 -0
- package/dist/content-store.d.ts +50 -1
- package/dist/content-store.js +102 -4
- package/dist/context.d.ts +36 -4
- package/dist/context.js +15 -4
- package/dist/editor/admin/SystemHealthPanel.js +2 -2
- package/dist/entry-schema.d.ts +22 -0
- package/dist/git-manager.d.ts +9 -8
- package/dist/git-manager.js +35 -9
- package/dist/github-service.d.ts +1 -1
- package/dist/github-service.js +1 -1
- package/dist/paths/branch-name.d.ts +1 -1
- package/dist/paths/branch-name.js +1 -1
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/index.js +1 -1
- package/dist/schema/meta-loader.js +1 -1
- package/dist/services.d.ts +22 -0
- package/dist/types.d.ts +1 -1
- package/dist/url-exclusivity-fixtures.d.ts +76 -0
- package/dist/url-exclusivity-fixtures.js +119 -0
- package/dist/url-path-resolver.js +8 -4
- package/dist/utils/content-write-lock.d.ts +2 -1
- package/dist/utils/content-write-lock.js +2 -1
- package/dist/utils/error.d.ts +17 -0
- package/dist/utils/error.js +17 -0
- package/dist/utils/git.d.ts +1 -1
- package/dist/utils/git.js +1 -1
- package/dist/utils/occ-json-write.js +1 -1
- package/dist/worker/cms-worker.d.ts +22 -288
- package/dist/worker/cms-worker.js +118 -1995
- package/dist/worker/git-sync.d.ts +166 -0
- package/dist/worker/git-sync.js +554 -0
- package/dist/worker/history-rewrite.d.ts +129 -0
- package/dist/worker/history-rewrite.js +216 -0
- package/dist/worker/rebase.d.ts +171 -0
- package/dist/worker/rebase.js +859 -0
- package/dist/worker/task-runner.d.ts +93 -0
- package/dist/worker/task-runner.js +529 -0
- package/dist/worker/worker-context.d.ts +133 -0
- package/dist/worker/worker-context.js +1 -0
- package/package.json +1 -1
package/dist/cli/cli.js
CHANGED
|
@@ -1882,10 +1882,10 @@ import { z as z42 } from "zod";
|
|
|
1882
1882
|
import { z as z52 } from "zod";
|
|
1883
1883
|
import { z as z6 } from "zod";
|
|
1884
1884
|
import path9 from "node:path";
|
|
1885
|
-
import
|
|
1885
|
+
import path21 from "node:path";
|
|
1886
1886
|
import { createJiti as createJiti2 } from "jiti";
|
|
1887
|
-
import
|
|
1888
|
-
import
|
|
1887
|
+
import fs18 from "node:fs/promises";
|
|
1888
|
+
import path20 from "node:path";
|
|
1889
1889
|
import fs10 from "node:fs/promises";
|
|
1890
1890
|
import path11 from "node:path";
|
|
1891
1891
|
import matter2 from "gray-matter";
|
|
@@ -1920,21 +1920,23 @@ import path102 from "node:path";
|
|
|
1920
1920
|
import path12 from "node:path";
|
|
1921
1921
|
import fs11 from "node:fs/promises";
|
|
1922
1922
|
import { minimatch } from "minimatch";
|
|
1923
|
-
import
|
|
1924
|
-
import
|
|
1925
|
-
import
|
|
1926
|
-
import fs12 from "node:fs/promises";
|
|
1927
|
-
import path13 from "node:path";
|
|
1923
|
+
import path18 from "node:path";
|
|
1924
|
+
import fs15 from "node:fs/promises";
|
|
1925
|
+
import path16 from "node:path";
|
|
1928
1926
|
import fs13 from "node:fs/promises";
|
|
1929
1927
|
import path14 from "node:path";
|
|
1928
|
+
import fs12 from "node:fs/promises";
|
|
1929
|
+
import path13 from "node:path";
|
|
1930
|
+
import fs14 from "node:fs/promises";
|
|
1931
|
+
import path15 from "node:path";
|
|
1930
1932
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1931
1933
|
import lockfile2 from "proper-lockfile";
|
|
1932
|
-
import
|
|
1933
|
-
import
|
|
1934
|
+
import fs16 from "node:fs/promises";
|
|
1935
|
+
import path17 from "node:path";
|
|
1934
1936
|
import { simpleGit as simpleGit2 } from "simple-git";
|
|
1935
1937
|
import { simpleGit as simpleGit3 } from "simple-git";
|
|
1936
|
-
import
|
|
1937
|
-
import
|
|
1938
|
+
import fs17 from "node:fs/promises";
|
|
1939
|
+
import path19 from "node:path";
|
|
1938
1940
|
import matter3 from "gray-matter";
|
|
1939
1941
|
import { parse as yamlParse2 } from "yaml";
|
|
1940
1942
|
function canopyLogWarn2(...args) {
|
|
@@ -1943,11 +1945,11 @@ function canopyLogWarn2(...args) {
|
|
|
1943
1945
|
function canopyLogError(...args) {
|
|
1944
1946
|
active2.error(...args);
|
|
1945
1947
|
}
|
|
1946
|
-
function normalizeFilesystemPath(
|
|
1947
|
-
return
|
|
1948
|
+
function normalizeFilesystemPath(path222) {
|
|
1949
|
+
return path222.split(/[\\/]+/).filter(Boolean).join("/");
|
|
1948
1950
|
}
|
|
1949
|
-
function hasTraversalSequence(
|
|
1950
|
-
const normalized = normalizeFilesystemPath(
|
|
1951
|
+
function hasTraversalSequence(path222) {
|
|
1952
|
+
const normalized = normalizeFilesystemPath(path222);
|
|
1951
1953
|
return normalized.includes("..");
|
|
1952
1954
|
}
|
|
1953
1955
|
function createLogicalPath(...segments) {
|
|
@@ -1957,14 +1959,14 @@ function createLogicalPath(...segments) {
|
|
|
1957
1959
|
}
|
|
1958
1960
|
return normalized;
|
|
1959
1961
|
}
|
|
1960
|
-
function trimSlashes(
|
|
1962
|
+
function trimSlashes(path222) {
|
|
1961
1963
|
let start = 0;
|
|
1962
|
-
let end =
|
|
1963
|
-
while (start < end &&
|
|
1964
|
+
let end = path222.length;
|
|
1965
|
+
while (start < end && path222[start] === "/")
|
|
1964
1966
|
start++;
|
|
1965
|
-
while (end > start &&
|
|
1967
|
+
while (end > start && path222[end - 1] === "/")
|
|
1966
1968
|
end--;
|
|
1967
|
-
return
|
|
1969
|
+
return path222.slice(start, end);
|
|
1968
1970
|
}
|
|
1969
1971
|
function readModeEnv() {
|
|
1970
1972
|
if (typeof process === "undefined" || typeof process.env === "undefined")
|
|
@@ -2421,8 +2423,8 @@ function computeEntryUrl(collection, slug, contentRoot) {
|
|
|
2421
2423
|
if (slug && !isIndexSlug(slug)) {
|
|
2422
2424
|
segments.push(slug);
|
|
2423
2425
|
}
|
|
2424
|
-
const
|
|
2425
|
-
return
|
|
2426
|
+
const path222 = segments.length > 0 ? `/${segments.join("/")}` : "/";
|
|
2427
|
+
return path222.toLowerCase();
|
|
2426
2428
|
}
|
|
2427
2429
|
function isValidContentId(id) {
|
|
2428
2430
|
return CONTENT_ID_PATTERN.test(id);
|
|
@@ -2485,13 +2487,13 @@ function extractIdFromFilename(filename) {
|
|
|
2485
2487
|
return null;
|
|
2486
2488
|
}
|
|
2487
2489
|
async function resolveCollectionPath(root, logicalPath) {
|
|
2488
|
-
const
|
|
2489
|
-
const
|
|
2490
|
+
const fs192 = await import("node:fs/promises");
|
|
2491
|
+
const path222 = await import("node:path");
|
|
2490
2492
|
const segments = logicalPath.split("/").filter(Boolean);
|
|
2491
2493
|
let currentPath = root;
|
|
2492
2494
|
for (const segment of segments) {
|
|
2493
2495
|
try {
|
|
2494
|
-
const entries = await
|
|
2496
|
+
const entries = await fs192.readdir(currentPath, { withFileTypes: true });
|
|
2495
2497
|
const matchingDir = entries.find((entry) => {
|
|
2496
2498
|
if (!entry.isDirectory())
|
|
2497
2499
|
return false;
|
|
@@ -2499,7 +2501,7 @@ async function resolveCollectionPath(root, logicalPath) {
|
|
|
2499
2501
|
return logicalName === segment.toLowerCase();
|
|
2500
2502
|
});
|
|
2501
2503
|
if (matchingDir) {
|
|
2502
|
-
currentPath =
|
|
2504
|
+
currentPath = path222.join(currentPath, matchingDir.name);
|
|
2503
2505
|
} else {
|
|
2504
2506
|
return null;
|
|
2505
2507
|
}
|
|
@@ -3565,7 +3567,7 @@ function splitByCodeRegions(text3) {
|
|
|
3565
3567
|
return parts;
|
|
3566
3568
|
}
|
|
3567
3569
|
async function generateAIContent(options) {
|
|
3568
|
-
const { store, flatSchema, contentRoot, config, entryLinkUrl } = options;
|
|
3570
|
+
const { store, flatSchema, contentRoot, config, entryLinkUrl, generatedAt, buildId } = options;
|
|
3569
3571
|
const files = /* @__PURE__ */ new Map();
|
|
3570
3572
|
const idIndex = await store.idIndex();
|
|
3571
3573
|
const collections = flatSchema.filter((item) => item.type === "collection");
|
|
@@ -3616,7 +3618,8 @@ async function generateAIContent(options) {
|
|
|
3616
3618
|
}
|
|
3617
3619
|
}
|
|
3618
3620
|
const manifest = {
|
|
3619
|
-
|
|
3621
|
+
...buildId ? { buildId } : {},
|
|
3622
|
+
...generatedAt || !buildId ? { generated: generatedAt || (/* @__PURE__ */ new Date()).toISOString() } : {},
|
|
3620
3623
|
entries: rootEntries,
|
|
3621
3624
|
collections: manifestCollections,
|
|
3622
3625
|
bundles: manifestBundles
|
|
@@ -3828,35 +3831,50 @@ function matchesBundleFilter(entry, filter, contentRoot) {
|
|
|
3828
3831
|
}
|
|
3829
3832
|
return true;
|
|
3830
3833
|
}
|
|
3834
|
+
async function readBranchMetadataFile(branchRoot) {
|
|
3835
|
+
const resolvedRoot = path13.resolve(branchRoot);
|
|
3836
|
+
try {
|
|
3837
|
+
const raw = await fs12.readFile(branchMetadataFilePath(resolvedRoot), "utf8");
|
|
3838
|
+
return JSON.parse(raw);
|
|
3839
|
+
} catch (err) {
|
|
3840
|
+
if (isNotFoundError3(err)) {
|
|
3841
|
+
return null;
|
|
3842
|
+
}
|
|
3843
|
+
if (err instanceof SyntaxError) {
|
|
3844
|
+
throw new BranchMetadataCorruptError(resolvedRoot, err.message);
|
|
3845
|
+
}
|
|
3846
|
+
throw err;
|
|
3847
|
+
}
|
|
3848
|
+
}
|
|
3831
3849
|
async function writeOccJsonFile(filePath, payload, options) {
|
|
3832
3850
|
const { expectedVersion, settleMs = 50, trailingNewline = false } = options;
|
|
3833
3851
|
const newVersion = expectedVersion === null ? 1 : expectedVersion + 1;
|
|
3834
3852
|
const writeId = randomUUID2();
|
|
3835
3853
|
const data = { ...payload, version: newVersion, writeId };
|
|
3836
3854
|
const content = JSON.stringify(data, null, 2) + (trailingNewline ? "\n" : "");
|
|
3837
|
-
const dir =
|
|
3838
|
-
await
|
|
3855
|
+
const dir = path15.dirname(filePath);
|
|
3856
|
+
await fs14.mkdir(dir, { recursive: true });
|
|
3839
3857
|
const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
3840
|
-
await
|
|
3858
|
+
await fs14.writeFile(tempPath, content, "utf-8");
|
|
3841
3859
|
if (expectedVersion === null) {
|
|
3842
3860
|
try {
|
|
3843
|
-
await
|
|
3861
|
+
await fs14.link(tempPath, filePath);
|
|
3844
3862
|
} catch (err) {
|
|
3845
|
-
await
|
|
3863
|
+
await fs14.unlink(tempPath).catch(() => {
|
|
3846
3864
|
});
|
|
3847
3865
|
if (isFileExistsError2(err)) {
|
|
3848
3866
|
throw new OccWriteConflictError();
|
|
3849
3867
|
}
|
|
3850
3868
|
throw err;
|
|
3851
3869
|
}
|
|
3852
|
-
await
|
|
3870
|
+
await fs14.unlink(tempPath).catch(() => {
|
|
3853
3871
|
});
|
|
3854
3872
|
return { version: newVersion, writeId };
|
|
3855
3873
|
}
|
|
3856
3874
|
try {
|
|
3857
3875
|
let currentVersion = null;
|
|
3858
3876
|
try {
|
|
3859
|
-
const current = JSON.parse(await
|
|
3877
|
+
const current = JSON.parse(await fs14.readFile(filePath, "utf-8"));
|
|
3860
3878
|
currentVersion = current.version ?? 0;
|
|
3861
3879
|
} catch {
|
|
3862
3880
|
currentVersion = null;
|
|
@@ -3864,16 +3882,16 @@ async function writeOccJsonFile(filePath, payload, options) {
|
|
|
3864
3882
|
if (currentVersion !== expectedVersion) {
|
|
3865
3883
|
throw new OccWriteConflictError();
|
|
3866
3884
|
}
|
|
3867
|
-
await
|
|
3885
|
+
await fs14.rename(tempPath, filePath);
|
|
3868
3886
|
if (settleMs > 0) {
|
|
3869
3887
|
await new Promise((resolve2) => setTimeout(resolve2, settleMs));
|
|
3870
3888
|
}
|
|
3871
|
-
const afterWrite = JSON.parse(await
|
|
3889
|
+
const afterWrite = JSON.parse(await fs14.readFile(filePath, "utf-8"));
|
|
3872
3890
|
if (afterWrite.writeId !== writeId) {
|
|
3873
3891
|
throw new OccWriteConflictError();
|
|
3874
3892
|
}
|
|
3875
3893
|
} catch (err) {
|
|
3876
|
-
await
|
|
3894
|
+
await fs14.unlink(tempPath).catch(() => {
|
|
3877
3895
|
});
|
|
3878
3896
|
throw err;
|
|
3879
3897
|
}
|
|
@@ -3899,8 +3917,8 @@ async function withOccRetry(operation, options) {
|
|
|
3899
3917
|
throw new Error("Unreachable");
|
|
3900
3918
|
}
|
|
3901
3919
|
async function withOccFileLock(filePath, fn) {
|
|
3902
|
-
const dir =
|
|
3903
|
-
await
|
|
3920
|
+
const dir = path15.dirname(filePath);
|
|
3921
|
+
await fs14.mkdir(dir, { recursive: true });
|
|
3904
3922
|
const MAX_ATTEMPTS = 14;
|
|
3905
3923
|
let release = null;
|
|
3906
3924
|
for (let attempt = 1; release === null; attempt++) {
|
|
@@ -3984,11 +4002,11 @@ function gitChildEnv(overrides) {
|
|
|
3984
4002
|
return { ...env, ...FORCE_C_LOCALE, ...overrides };
|
|
3985
4003
|
}
|
|
3986
4004
|
async function ensureGitExcludePattern(repoPath, pattern) {
|
|
3987
|
-
const excludePath =
|
|
3988
|
-
await
|
|
4005
|
+
const excludePath = path17.join(repoPath, ".git", "info", "exclude");
|
|
4006
|
+
await fs16.mkdir(path17.dirname(excludePath), { recursive: true });
|
|
3989
4007
|
let content = "";
|
|
3990
4008
|
try {
|
|
3991
|
-
content = await
|
|
4009
|
+
content = await fs16.readFile(excludePath, "utf-8");
|
|
3992
4010
|
} catch (err) {
|
|
3993
4011
|
if (!isNotFoundError3(err))
|
|
3994
4012
|
throw err;
|
|
@@ -4000,7 +4018,7 @@ async function ensureGitExcludePattern(repoPath, pattern) {
|
|
|
4000
4018
|
}
|
|
4001
4019
|
const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
|
|
4002
4020
|
const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
|
|
4003
|
-
await
|
|
4021
|
+
await fs16.writeFile(excludePath, newContent, "utf-8");
|
|
4004
4022
|
log5.debug("git", "Added pattern to .git/info/exclude", { pattern });
|
|
4005
4023
|
}
|
|
4006
4024
|
async function loadOrCreateBranchContext(options) {
|
|
@@ -4156,30 +4174,30 @@ function isValidImageCropValue(value) {
|
|
|
4156
4174
|
}
|
|
4157
4175
|
return isValidCropRect(x, y, w, h);
|
|
4158
4176
|
}
|
|
4159
|
-
function validateImageValue(field, value,
|
|
4177
|
+
function validateImageValue(field, value, path222) {
|
|
4160
4178
|
if (!isPlainRecord3(value)) {
|
|
4161
|
-
return [{ fieldPath:
|
|
4179
|
+
return [{ fieldPath: path222, message: "Expected an image object with { src, alt }" }];
|
|
4162
4180
|
}
|
|
4163
4181
|
const errors = [];
|
|
4164
4182
|
if (typeof value.src !== "string" || value.src.trim() === "") {
|
|
4165
|
-
errors.push({ fieldPath: `${
|
|
4183
|
+
errors.push({ fieldPath: `${path222}.src`, message: "Image src is required" });
|
|
4166
4184
|
}
|
|
4167
4185
|
if (typeof value.alt !== "string") {
|
|
4168
|
-
errors.push({ fieldPath: `${
|
|
4186
|
+
errors.push({ fieldPath: `${path222}.alt`, message: "Image alt text is required" });
|
|
4169
4187
|
} else if (value.alt.trim() === "" && field.altOptional !== true) {
|
|
4170
|
-
errors.push({ fieldPath: `${
|
|
4188
|
+
errors.push({ fieldPath: `${path222}.alt`, message: "Image alt text is required" });
|
|
4171
4189
|
}
|
|
4172
4190
|
if (value.width !== void 0 && !isPositiveInt(value.width)) {
|
|
4173
|
-
errors.push({ fieldPath: `${
|
|
4191
|
+
errors.push({ fieldPath: `${path222}.width`, message: "Image width must be a positive integer" });
|
|
4174
4192
|
}
|
|
4175
4193
|
if (value.height !== void 0 && !isPositiveInt(value.height)) {
|
|
4176
4194
|
errors.push({
|
|
4177
|
-
fieldPath: `${
|
|
4195
|
+
fieldPath: `${path222}.height`,
|
|
4178
4196
|
message: "Image height must be a positive integer"
|
|
4179
4197
|
});
|
|
4180
4198
|
}
|
|
4181
4199
|
if (value.crop !== void 0 && !isValidImageCropValue(value.crop)) {
|
|
4182
|
-
errors.push({ fieldPath: `${
|
|
4200
|
+
errors.push({ fieldPath: `${path222}.crop`, message: "Invalid image crop rect" });
|
|
4183
4201
|
}
|
|
4184
4202
|
return errors;
|
|
4185
4203
|
}
|
|
@@ -4190,44 +4208,44 @@ function referenceValueId(value) {
|
|
|
4190
4208
|
return value.id;
|
|
4191
4209
|
return void 0;
|
|
4192
4210
|
}
|
|
4193
|
-
function validateScalar(field, value,
|
|
4211
|
+
function validateScalar(field, value, path222) {
|
|
4194
4212
|
switch (field.type) {
|
|
4195
4213
|
case "number":
|
|
4196
4214
|
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
4197
|
-
return [{ fieldPath:
|
|
4215
|
+
return [{ fieldPath: path222, message: "Expected a number" }];
|
|
4198
4216
|
}
|
|
4199
4217
|
return [];
|
|
4200
4218
|
case "boolean":
|
|
4201
4219
|
if (typeof value !== "boolean") {
|
|
4202
|
-
return [{ fieldPath:
|
|
4220
|
+
return [{ fieldPath: path222, message: "Expected true or false" }];
|
|
4203
4221
|
}
|
|
4204
4222
|
return [];
|
|
4205
4223
|
case "datetime":
|
|
4206
4224
|
if (typeof value !== "string" || value !== "" && Number.isNaN(Date.parse(value))) {
|
|
4207
|
-
return [{ fieldPath:
|
|
4225
|
+
return [{ fieldPath: path222, message: "Expected a valid date/time string" }];
|
|
4208
4226
|
}
|
|
4209
4227
|
return [];
|
|
4210
4228
|
case "select": {
|
|
4211
4229
|
if (typeof value !== "string") {
|
|
4212
|
-
return [{ fieldPath:
|
|
4230
|
+
return [{ fieldPath: path222, message: "Expected a selection" }];
|
|
4213
4231
|
}
|
|
4214
4232
|
const options = selectOptionValues(field);
|
|
4215
4233
|
if (value !== "" && !options.includes(value)) {
|
|
4216
|
-
return [{ fieldPath:
|
|
4234
|
+
return [{ fieldPath: path222, message: `Must be one of: ${options.join(", ")}` }];
|
|
4217
4235
|
}
|
|
4218
4236
|
return [];
|
|
4219
4237
|
}
|
|
4220
4238
|
case "reference":
|
|
4221
4239
|
if (referenceValueId(value) === void 0) {
|
|
4222
|
-
return [{ fieldPath:
|
|
4240
|
+
return [{ fieldPath: path222, message: "Expected a reference id" }];
|
|
4223
4241
|
}
|
|
4224
4242
|
return [];
|
|
4225
4243
|
case "image":
|
|
4226
|
-
return validateImageValue(field, value,
|
|
4244
|
+
return validateImageValue(field, value, path222);
|
|
4227
4245
|
default:
|
|
4228
4246
|
if (STRING_FIELD_TYPES.has(field.type)) {
|
|
4229
4247
|
if (typeof value !== "string") {
|
|
4230
|
-
return [{ fieldPath:
|
|
4248
|
+
return [{ fieldPath: path222, message: "Expected text" }];
|
|
4231
4249
|
}
|
|
4232
4250
|
}
|
|
4233
4251
|
return [];
|
|
@@ -4250,12 +4268,12 @@ function validateEntryData(fields, data, pathPrefix = "") {
|
|
|
4250
4268
|
errors.push(...validateEntryData(field.fields, data, pathPrefix));
|
|
4251
4269
|
continue;
|
|
4252
4270
|
}
|
|
4253
|
-
const
|
|
4271
|
+
const path222 = pathPrefix ? `${pathPrefix}.${field.name}` : field.name;
|
|
4254
4272
|
const value = data[field.name];
|
|
4255
4273
|
const isRequired = "required" in field && field.required === true;
|
|
4256
4274
|
if (isRequired && isEmptyForRequired(field, value)) {
|
|
4257
4275
|
const message = Array.isArray(value) ? "At least one item is required" : "This field is required";
|
|
4258
|
-
errors.push({ fieldPath:
|
|
4276
|
+
errors.push({ fieldPath: path222, message });
|
|
4259
4277
|
continue;
|
|
4260
4278
|
}
|
|
4261
4279
|
if (value === void 0 || value === null)
|
|
@@ -4263,11 +4281,11 @@ function validateEntryData(fields, data, pathPrefix = "") {
|
|
|
4263
4281
|
if (field.type === "block") {
|
|
4264
4282
|
const blockField = field;
|
|
4265
4283
|
if (!Array.isArray(value)) {
|
|
4266
|
-
errors.push({ fieldPath:
|
|
4284
|
+
errors.push({ fieldPath: path222, message: "Expected a list of blocks" });
|
|
4267
4285
|
continue;
|
|
4268
4286
|
}
|
|
4269
4287
|
value.forEach((item, index) => {
|
|
4270
|
-
const itemPath = `${
|
|
4288
|
+
const itemPath = `${path222}[${index}]`;
|
|
4271
4289
|
if (!isPlainRecord3(item)) {
|
|
4272
4290
|
errors.push({ fieldPath: itemPath, message: "Expected a block object" });
|
|
4273
4291
|
return;
|
|
@@ -4289,11 +4307,11 @@ function validateEntryData(fields, data, pathPrefix = "") {
|
|
|
4289
4307
|
const objectField = field;
|
|
4290
4308
|
if (objectField.list) {
|
|
4291
4309
|
if (!Array.isArray(value)) {
|
|
4292
|
-
errors.push({ fieldPath:
|
|
4310
|
+
errors.push({ fieldPath: path222, message: "Expected a list of items" });
|
|
4293
4311
|
continue;
|
|
4294
4312
|
}
|
|
4295
4313
|
value.forEach((item, index) => {
|
|
4296
|
-
const itemPath = `${
|
|
4314
|
+
const itemPath = `${path222}[${index}]`;
|
|
4297
4315
|
if (!isPlainRecord3(item)) {
|
|
4298
4316
|
errors.push({ fieldPath: itemPath, message: "Expected an object" });
|
|
4299
4317
|
return;
|
|
@@ -4303,27 +4321,27 @@ function validateEntryData(fields, data, pathPrefix = "") {
|
|
|
4303
4321
|
}
|
|
4304
4322
|
});
|
|
4305
4323
|
} else if (!isPlainRecord3(value)) {
|
|
4306
|
-
errors.push({ fieldPath:
|
|
4324
|
+
errors.push({ fieldPath: path222, message: "Expected an object" });
|
|
4307
4325
|
} else if (objectField.fields) {
|
|
4308
|
-
errors.push(...validateEntryData(objectField.fields, value,
|
|
4326
|
+
errors.push(...validateEntryData(objectField.fields, value, path222));
|
|
4309
4327
|
}
|
|
4310
4328
|
continue;
|
|
4311
4329
|
}
|
|
4312
4330
|
if ("list" in field && field.list) {
|
|
4313
4331
|
if (!Array.isArray(value)) {
|
|
4314
|
-
errors.push({ fieldPath:
|
|
4332
|
+
errors.push({ fieldPath: path222, message: "Expected a list of values" });
|
|
4315
4333
|
continue;
|
|
4316
4334
|
}
|
|
4317
4335
|
value.forEach((item, index) => {
|
|
4318
|
-
errors.push(...validateScalar(field, item, `${
|
|
4336
|
+
errors.push(...validateScalar(field, item, `${path222}[${index}]`));
|
|
4319
4337
|
});
|
|
4320
4338
|
continue;
|
|
4321
4339
|
}
|
|
4322
4340
|
if (Array.isArray(value) && KNOWN_FIELD_TYPES.has(field.type)) {
|
|
4323
|
-
errors.push({ fieldPath:
|
|
4341
|
+
errors.push({ fieldPath: path222, message: "Expected a single value, not a list" });
|
|
4324
4342
|
continue;
|
|
4325
4343
|
}
|
|
4326
|
-
errors.push(...validateScalar(field, value,
|
|
4344
|
+
errors.push(...validateScalar(field, value, path222));
|
|
4327
4345
|
}
|
|
4328
4346
|
return errors;
|
|
4329
4347
|
}
|
|
@@ -4369,7 +4387,7 @@ These are likely abandoned create-scaffolds (empty entries left behind when a cr
|
|
|
4369
4387
|
}
|
|
4370
4388
|
async function readGeneratedRecord(outputDir) {
|
|
4371
4389
|
try {
|
|
4372
|
-
const raw = await
|
|
4390
|
+
const raw = await fs18.readFile(path20.join(outputDir, GENERATED_RECORD_FILENAME), "utf-8");
|
|
4373
4391
|
const parsed = JSON.parse(raw);
|
|
4374
4392
|
if (typeof parsed !== "object" || parsed === null || !("files" in parsed))
|
|
4375
4393
|
return [];
|
|
@@ -4383,20 +4401,54 @@ async function readGeneratedRecord(outputDir) {
|
|
|
4383
4401
|
}
|
|
4384
4402
|
async function writeGeneratedRecord(outputDir, files) {
|
|
4385
4403
|
const record = { files: [...files].sort() };
|
|
4386
|
-
await
|
|
4387
|
-
await
|
|
4404
|
+
await fs18.mkdir(outputDir, { recursive: true });
|
|
4405
|
+
await fs18.writeFile(path20.join(outputDir, GENERATED_RECORD_FILENAME), `${JSON.stringify(record, null, 2)}
|
|
4388
4406
|
`, "utf-8");
|
|
4389
4407
|
}
|
|
4390
4408
|
async function removeEmptyDirsUpward(startDir, stopAt) {
|
|
4391
4409
|
let current = startDir;
|
|
4392
4410
|
while (current.startsWith(stopAt) && current !== stopAt) {
|
|
4393
4411
|
try {
|
|
4394
|
-
await
|
|
4412
|
+
await fs18.rmdir(current);
|
|
4395
4413
|
} catch {
|
|
4396
4414
|
return;
|
|
4397
4415
|
}
|
|
4398
|
-
current =
|
|
4416
|
+
current = path20.dirname(current);
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
function isUsableBuildId(value) {
|
|
4420
|
+
return SAFE_BUILD_ID.test(value) && value !== "." && value !== "..";
|
|
4421
|
+
}
|
|
4422
|
+
function unpinnedConsequence(buildId) {
|
|
4423
|
+
return buildId ? "omitting `generated` from the manifest entirely (a build id is set, so no build clock is recorded)." : "recording the current time in `generated` instead.";
|
|
4424
|
+
}
|
|
4425
|
+
function resolveBuildStamp() {
|
|
4426
|
+
const rawBuildId = process.env.CANOPY_BUILD_ID;
|
|
4427
|
+
const trimmedBuildId = rawBuildId?.trim();
|
|
4428
|
+
let buildId;
|
|
4429
|
+
if (rawBuildId === void 0) {
|
|
4430
|
+
buildId = void 0;
|
|
4431
|
+
} else if (!trimmedBuildId) {
|
|
4432
|
+
console.warn("CanopyCMS: CANOPY_BUILD_ID is set but blank \u2014 recording no buildId in the manifest.");
|
|
4433
|
+
} else if (!isUsableBuildId(trimmedBuildId)) {
|
|
4434
|
+
console.warn(`CanopyCMS: ignoring CANOPY_BUILD_ID="${trimmedBuildId}": it ${BUILD_ID_RULE}, so that the manifest's buildId can match the build id Next stores for the same artifact.`);
|
|
4435
|
+
} else {
|
|
4436
|
+
buildId = trimmedBuildId;
|
|
4437
|
+
}
|
|
4438
|
+
const rawEpoch = process.env.SOURCE_DATE_EPOCH;
|
|
4439
|
+
const trimmedEpoch = rawEpoch?.trim();
|
|
4440
|
+
if (rawEpoch !== void 0 && !trimmedEpoch) {
|
|
4441
|
+
console.warn(`CanopyCMS: SOURCE_DATE_EPOCH is set but blank \u2014 ${unpinnedConsequence(buildId)}`);
|
|
4442
|
+
}
|
|
4443
|
+
if (!trimmedEpoch)
|
|
4444
|
+
return { buildId };
|
|
4445
|
+
const seconds = /^\d+$/.test(trimmedEpoch) ? Number(trimmedEpoch) : Number.NaN;
|
|
4446
|
+
const date = new Date(seconds * 1e3);
|
|
4447
|
+
if (Number.isNaN(date.getTime())) {
|
|
4448
|
+
console.warn(`CanopyCMS: ignoring SOURCE_DATE_EPOCH="${trimmedEpoch}" (expected decimal seconds since the Unix epoch) \u2014 ${unpinnedConsequence(buildId)}`);
|
|
4449
|
+
return { buildId };
|
|
4399
4450
|
}
|
|
4451
|
+
return { generatedAt: date.toISOString(), buildId };
|
|
4400
4452
|
}
|
|
4401
4453
|
async function generateAIContentFiles(options) {
|
|
4402
4454
|
const { config, entrySchemaRegistry, outputDir, aiConfig: aiConfig2, _testFlatSchema } = options;
|
|
@@ -4418,11 +4470,12 @@ async function generateAIContentFiles(options) {
|
|
|
4418
4470
|
flatSchema,
|
|
4419
4471
|
contentRoot: contentRootName,
|
|
4420
4472
|
config: aiConfig2,
|
|
4421
|
-
entryLinkUrl: config.entryLinkUrl
|
|
4473
|
+
entryLinkUrl: config.entryLinkUrl,
|
|
4474
|
+
...resolveBuildStamp()
|
|
4422
4475
|
});
|
|
4423
|
-
const absoluteOutputDir =
|
|
4476
|
+
const absoluteOutputDir = path20.resolve(outputDir) + path20.sep;
|
|
4424
4477
|
const resolveOutputPath = (filePath) => {
|
|
4425
|
-
const absolutePath =
|
|
4478
|
+
const absolutePath = path20.resolve(path20.join(absoluteOutputDir, filePath));
|
|
4426
4479
|
if (!absolutePath.startsWith(absoluteOutputDir)) {
|
|
4427
4480
|
throw new Error(`Path traversal detected in AI content output: ${filePath}`);
|
|
4428
4481
|
}
|
|
@@ -4434,8 +4487,8 @@ async function generateAIContentFiles(options) {
|
|
|
4434
4487
|
let fileCount = 0;
|
|
4435
4488
|
for (const [filePath, content] of result.files) {
|
|
4436
4489
|
const absolutePath = resolveOutputPath(filePath);
|
|
4437
|
-
await
|
|
4438
|
-
await
|
|
4490
|
+
await fs18.mkdir(path20.dirname(absolutePath), { recursive: true });
|
|
4491
|
+
await fs18.writeFile(absolutePath, content, "utf-8");
|
|
4439
4492
|
fileCount++;
|
|
4440
4493
|
}
|
|
4441
4494
|
const currentSet = new Set(currentFiles);
|
|
@@ -4444,13 +4497,13 @@ async function generateAIContentFiles(options) {
|
|
|
4444
4497
|
for (const filePath of stale) {
|
|
4445
4498
|
const absolutePath = resolveOutputPath(filePath);
|
|
4446
4499
|
try {
|
|
4447
|
-
await
|
|
4500
|
+
await fs18.unlink(absolutePath);
|
|
4448
4501
|
removedCount++;
|
|
4449
4502
|
} catch (err) {
|
|
4450
4503
|
if (!isNodeError3(err) || err.code !== "ENOENT")
|
|
4451
4504
|
throw err;
|
|
4452
4505
|
}
|
|
4453
|
-
await removeEmptyDirsUpward(
|
|
4506
|
+
await removeEmptyDirsUpward(path20.dirname(absolutePath), absoluteOutputDir);
|
|
4454
4507
|
}
|
|
4455
4508
|
await writeGeneratedRecord(absoluteOutputDir, currentFiles);
|
|
4456
4509
|
return { fileCount, removedCount, outputDir: absoluteOutputDir };
|
|
@@ -4458,7 +4511,7 @@ async function generateAIContentFiles(options) {
|
|
|
4458
4511
|
async function generateAIContentCLI(options) {
|
|
4459
4512
|
const { projectDir, outputDir = "public/ai", configPath, appDir = "app" } = options;
|
|
4460
4513
|
console.log("\nCanopyCMS generate-ai-content\n");
|
|
4461
|
-
const canopyConfigPath =
|
|
4514
|
+
const canopyConfigPath = path21.join(projectDir, "canopycms.config.ts");
|
|
4462
4515
|
let canopyConfigModule;
|
|
4463
4516
|
try {
|
|
4464
4517
|
canopyConfigModule = await jiti.import(canopyConfigPath);
|
|
@@ -4469,7 +4522,7 @@ async function generateAIContentCLI(options) {
|
|
|
4469
4522
|
}
|
|
4470
4523
|
const configExport = canopyConfigModule.default ?? canopyConfigModule.config ?? canopyConfigModule;
|
|
4471
4524
|
const serverConfig = typeof configExport === "object" && configExport !== null && "server" in configExport ? configExport.server : configExport;
|
|
4472
|
-
const schemasPath =
|
|
4525
|
+
const schemasPath = path21.join(projectDir, appDir, "schemas.ts");
|
|
4473
4526
|
let entrySchemaRegistry = {};
|
|
4474
4527
|
try {
|
|
4475
4528
|
const schemasModule = await jiti.import(schemasPath);
|
|
@@ -4480,7 +4533,7 @@ async function generateAIContentCLI(options) {
|
|
|
4480
4533
|
let aiConfig2;
|
|
4481
4534
|
if (configPath) {
|
|
4482
4535
|
try {
|
|
4483
|
-
const aiConfigModule = await jiti.import(
|
|
4536
|
+
const aiConfigModule = await jiti.import(path21.resolve(configPath));
|
|
4484
4537
|
aiConfig2 = aiConfigModule.aiContentConfig ?? aiConfigModule.default ?? aiConfigModule.config;
|
|
4485
4538
|
} catch (err) {
|
|
4486
4539
|
console.error(`Could not load AI config from ${configPath}`);
|
|
@@ -4497,7 +4550,7 @@ async function generateAIContentCLI(options) {
|
|
|
4497
4550
|
console.error("Make sure canopycms.config.ts uses defineCanopyConfig().");
|
|
4498
4551
|
process.exit(1);
|
|
4499
4552
|
}
|
|
4500
|
-
const resolvedOutput =
|
|
4553
|
+
const resolvedOutput = path21.resolve(projectDir, outputDir);
|
|
4501
4554
|
console.log(` Output: ${resolvedOutput}`);
|
|
4502
4555
|
console.log(` Mode: ${serverConfig.mode ?? "dev"}`);
|
|
4503
4556
|
const result = await generateAIContentFiles({
|
|
@@ -4514,7 +4567,7 @@ async function generateAIContentCLI(options) {
|
|
|
4514
4567
|
console.log(` Output: ${result.outputDir}
|
|
4515
4568
|
`);
|
|
4516
4569
|
}
|
|
4517
|
-
var __defProp3, __getOwnPropNames3, __esm3, __export3, active2, init_logger2, 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, 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, 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, log22, BASE58_CHAR, ENTRY_LINK_PATTERN, log3, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, BranchRegistry, log4, OccWriteConflictError,
|
|
4570
|
+
var __defProp3, __getOwnPropNames3, __esm3, __export3, active2, init_logger2, 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, 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, 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, log22, BASE58_CHAR, ENTRY_LINK_PATTERN, BRANCH_META_DIR, BRANCH_META_FILE, BranchMetadataCorruptError, branchMetadataFilePath, log3, REGISTRY_FILE, REGISTRY_STALE_FILE, REGISTRY_TEMP_FILE, REGISTRY_VERSION, RESOURCE2, GET_MISS_REFRESH_MIN_INTERVAL_MS, BranchRegistry, log4, OccWriteConflictError, CURRENT_SCHEMA_VERSION, BranchMetadataConflictError, BranchMetadataFileManager, getBranchMetadataFileManager, loadBranchContext, NETWORK_SCHEME_PATTERN, TRANSPORT_HELPER_PATTERN, LEADING_OPTION_PATTERN, WINDOWS_DRIVE_PATTERN, COLON_BEFORE_SLASH_PATTERN, MISSING_REMOTE_REF_REASONS, log5, GIT_ENV_PASSTHROUGH, FORCE_C_LOCALE, remoteInitLocks, GITHUB_TRACKING_REF_PREFIX, GitConflictError, GitRemoteRefMissingError, GitManager, log6, workspaceInitLocks, BranchWorkspaceManager, log7, 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;
|
|
4518
4571
|
var init_generate_ai_content = __esm({
|
|
4519
4572
|
"dist/cli/generate-ai-content.js"() {
|
|
4520
4573
|
"use strict";
|
|
@@ -6256,14 +6309,62 @@ var init_generate_ai_content = __esm({
|
|
|
6256
6309
|
getSchemaItems() {
|
|
6257
6310
|
return this.schemaIndex.values();
|
|
6258
6311
|
}
|
|
6259
|
-
assertSchemaItem(
|
|
6260
|
-
const normalized = normalizeFilesystemPath(
|
|
6312
|
+
assertSchemaItem(path222) {
|
|
6313
|
+
const normalized = normalizeFilesystemPath(path222);
|
|
6261
6314
|
const item = this.schemaIndex.get(normalized);
|
|
6262
6315
|
if (!item) {
|
|
6263
|
-
throw new ContentStoreError(`Unknown schema item: ${
|
|
6316
|
+
throw new ContentStoreError(`Unknown schema item: ${path222}`, "NO_SCHEMA_ITEM");
|
|
6264
6317
|
}
|
|
6265
6318
|
return item;
|
|
6266
6319
|
}
|
|
6320
|
+
/**
|
|
6321
|
+
* Is this path a COLLECTION schema item?
|
|
6322
|
+
*
|
|
6323
|
+
* The non-throwing form of `assertCollection`, reading the same `schemaIndex` -- which is the
|
|
6324
|
+
* point. A caller that gates on this cannot disagree with what `buildPaths` will then do: the
|
|
6325
|
+
* Map is last-wins, so where a subcollection's path collides with a parent's entry-type name
|
|
6326
|
+
* both this and `buildPaths` see the collection. A `find` over the flat schema LIST is
|
|
6327
|
+
* first-wins and would not.
|
|
6328
|
+
*
|
|
6329
|
+
* Type-only, deliberately -- `resolvePath` additionally requires `entries`, but a collection
|
|
6330
|
+
* with subcollections and no entries of its own is legal, and mirroring that stricter test here
|
|
6331
|
+
* would reject something `buildPaths` accepts.
|
|
6332
|
+
*
|
|
6333
|
+
* Exists for `readByUrlPath`'s URL-addressability gate; see `ReadContentInput`'s
|
|
6334
|
+
* `urlAddressableOnly` and the note on `buildPaths`' entry-type branch below.
|
|
6335
|
+
*/
|
|
6336
|
+
isCollectionPath(collectionPath) {
|
|
6337
|
+
return this.schemaIndex.get(normalizeFilesystemPath(collectionPath))?.type === "collection";
|
|
6338
|
+
}
|
|
6339
|
+
/**
|
|
6340
|
+
* Does this collection declare `entryTypeName` in its `entries` config?
|
|
6341
|
+
*
|
|
6342
|
+
* Mirrors `parseTypedFilename(filename, collection.entries)`, which is how `listEntries` decides
|
|
6343
|
+
* whether a file on disk is one of the collection's entries at all. `buildPaths`' own directory
|
|
6344
|
+
* scan deliberately does NOT check this -- it matches on slug alone, so that an entry whose type
|
|
6345
|
+
* was renamed out of the schema stays findable and therefore still editable, renameable and
|
|
6346
|
+
* deletable. Only URL resolution consults this, so what enumeration hides is not served.
|
|
6347
|
+
*
|
|
6348
|
+
* Returns FALSE when the collection declares no `entries` at all, which is stricter than
|
|
6349
|
+
* `parseTypedFilename`'s own `if (entryTypes && ...)` guard and deliberately so: the enumerating
|
|
6350
|
+
* surface is not `parseTypedFilename`, it is `listCollectionEntries`, and that returns `[]`
|
|
6351
|
+
* outright for a collection with no `entries`. A collections-only container therefore publishes
|
|
6352
|
+
* nothing, so a URL read that resolved a file sitting in one would be answering where nothing is
|
|
6353
|
+
* advertised -- the exact disagreement this predicate exists to close. Such a file cannot have
|
|
6354
|
+
* been created by the CMS (there is no entry type to create it as); it arrived by hand, by merge
|
|
6355
|
+
* or by retrofit, and it is invisible to the sitemap and to static params either way.
|
|
6356
|
+
*
|
|
6357
|
+
* Returns true for a path that is not a collection at all, because that is rule 1's question,
|
|
6358
|
+
* not this one's -- and under `urlAddressableOnly` rule 1 has already rejected it.
|
|
6359
|
+
*/
|
|
6360
|
+
declaresEntryType(collectionPath, entryTypeName) {
|
|
6361
|
+
const item = this.schemaIndex.get(normalizeFilesystemPath(collectionPath));
|
|
6362
|
+
if (item?.type !== "collection")
|
|
6363
|
+
return true;
|
|
6364
|
+
if (!item.entries)
|
|
6365
|
+
return false;
|
|
6366
|
+
return item.entries.some((e) => e.name === entryTypeName);
|
|
6367
|
+
}
|
|
6267
6368
|
assertCollection(collectionPath) {
|
|
6268
6369
|
const item = this.assertSchemaItem(collectionPath);
|
|
6269
6370
|
if (item.type !== "collection") {
|
|
@@ -6520,7 +6621,16 @@ var init_generate_ai_content = __esm({
|
|
|
6520
6621
|
collection: schemaItem.logicalPath,
|
|
6521
6622
|
collectionName: schemaItem.name,
|
|
6522
6623
|
format,
|
|
6523
|
-
|
|
6624
|
+
// Copy, never the object gray-matter hands back: its cache is
|
|
6625
|
+
// process-global and keyed by file content, so every caller parsing the
|
|
6626
|
+
// same bytes gets the SAME `data` instance (the hazard content-listing.ts
|
|
6627
|
+
// documents at length). Assigning it straight into `doc.data` was safe
|
|
6628
|
+
// only by accident -- `resolveReferencesInData` spreads its input, so the
|
|
6629
|
+
// shared object got replaced a few lines down. But that only happens when
|
|
6630
|
+
// resolution runs: with `resolveReferences: false` the caller was handed
|
|
6631
|
+
// the shared instance itself, and one mutation would poison every later
|
|
6632
|
+
// parse of those bytes.
|
|
6633
|
+
data: { ...parsed.data ?? {} },
|
|
6524
6634
|
body: parsed.content,
|
|
6525
6635
|
bodyFieldName: findBodyFieldName(fields),
|
|
6526
6636
|
relativePath,
|
|
@@ -7186,6 +7296,17 @@ var init_generate_ai_content = __esm({
|
|
|
7186
7296
|
log22 = createDebugLogger2({ prefix: "EntryLinks" });
|
|
7187
7297
|
BASE58_CHAR = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]";
|
|
7188
7298
|
ENTRY_LINK_PATTERN = new RegExp(`entry:(${BASE58_CHAR}{12})(#[^\\s)>"']*)?`, "g");
|
|
7299
|
+
BRANCH_META_DIR = ".canopy-meta";
|
|
7300
|
+
BRANCH_META_FILE = "branch.json";
|
|
7301
|
+
BranchMetadataCorruptError = class extends Error {
|
|
7302
|
+
constructor(branchRoot, cause) {
|
|
7303
|
+
super(`Corrupt branch metadata in '${branchRoot}': ${cause}`);
|
|
7304
|
+
this.name = "BranchMetadataCorruptError";
|
|
7305
|
+
this.branchRoot = branchRoot;
|
|
7306
|
+
this.parseCause = cause;
|
|
7307
|
+
}
|
|
7308
|
+
};
|
|
7309
|
+
branchMetadataFilePath = (branchRoot) => path13.join(path13.resolve(branchRoot), BRANCH_META_DIR, BRANCH_META_FILE);
|
|
7189
7310
|
init_logger2();
|
|
7190
7311
|
log3 = createDebugLogger2({ prefix: "BranchRegistry" });
|
|
7191
7312
|
REGISTRY_FILE = "branches.json";
|
|
@@ -7198,10 +7319,10 @@ var init_generate_ai_content = __esm({
|
|
|
7198
7319
|
constructor(root) {
|
|
7199
7320
|
this.regenInFlight = null;
|
|
7200
7321
|
this.lastForcedRefreshMs = 0;
|
|
7201
|
-
this.root =
|
|
7202
|
-
this.registryPath =
|
|
7203
|
-
this.stalePath =
|
|
7204
|
-
this.tempPath =
|
|
7322
|
+
this.root = path14.resolve(root);
|
|
7323
|
+
this.registryPath = path14.join(this.root, REGISTRY_FILE);
|
|
7324
|
+
this.stalePath = path14.join(this.root, REGISTRY_STALE_FILE);
|
|
7325
|
+
this.tempPath = path14.join(this.root, REGISTRY_TEMP_FILE);
|
|
7205
7326
|
}
|
|
7206
7327
|
/**
|
|
7207
7328
|
* Returns all branches. Uses the cached snapshot if its embedded generation
|
|
@@ -7210,7 +7331,7 @@ var init_generate_ai_content = __esm({
|
|
|
7210
7331
|
async list() {
|
|
7211
7332
|
let parsed;
|
|
7212
7333
|
try {
|
|
7213
|
-
const raw = await
|
|
7334
|
+
const raw = await fs13.readFile(this.registryPath, "utf8");
|
|
7214
7335
|
parsed = JSON.parse(raw);
|
|
7215
7336
|
} catch (err) {
|
|
7216
7337
|
if (isNotFoundError3(err)) {
|
|
@@ -7299,7 +7420,7 @@ var init_generate_ai_content = __esm({
|
|
|
7299
7420
|
async performRegenerate() {
|
|
7300
7421
|
const read = await readResourceGeneration(this.root, RESOURCE2);
|
|
7301
7422
|
const branches = await this.scanBranchDirectories();
|
|
7302
|
-
await
|
|
7423
|
+
await fs13.unlink(this.stalePath).catch(() => {
|
|
7303
7424
|
});
|
|
7304
7425
|
if (!read.ok) {
|
|
7305
7426
|
return branches;
|
|
@@ -7310,12 +7431,12 @@ var init_generate_ai_content = __esm({
|
|
|
7310
7431
|
generation: read.token
|
|
7311
7432
|
};
|
|
7312
7433
|
const uniqueTempPath = `${this.tempPath}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
7313
|
-
await
|
|
7314
|
-
await
|
|
7434
|
+
await fs13.mkdir(this.root, { recursive: true });
|
|
7435
|
+
await fs13.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
|
|
7315
7436
|
try {
|
|
7316
|
-
await
|
|
7437
|
+
await fs13.rename(uniqueTempPath, this.registryPath);
|
|
7317
7438
|
} catch (err) {
|
|
7318
|
-
await
|
|
7439
|
+
await fs13.unlink(uniqueTempPath).catch(() => {
|
|
7319
7440
|
});
|
|
7320
7441
|
throw err;
|
|
7321
7442
|
}
|
|
@@ -7329,15 +7450,15 @@ var init_generate_ai_content = __esm({
|
|
|
7329
7450
|
async scanBranchDirectories() {
|
|
7330
7451
|
const branches = [];
|
|
7331
7452
|
try {
|
|
7332
|
-
const entries = await
|
|
7453
|
+
const entries = await fs13.readdir(this.root, { withFileTypes: true });
|
|
7333
7454
|
for (const entry of entries) {
|
|
7334
7455
|
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
7335
7456
|
continue;
|
|
7336
7457
|
}
|
|
7337
|
-
const branchRoot =
|
|
7458
|
+
const branchRoot = path14.join(this.root, entry.name);
|
|
7338
7459
|
let meta;
|
|
7339
7460
|
try {
|
|
7340
|
-
meta = await
|
|
7461
|
+
meta = await readBranchMetadataFile(branchRoot);
|
|
7341
7462
|
} catch (err) {
|
|
7342
7463
|
canopyLogWarn2(`CanopyCMS: Skipping branch directory '${entry.name}' during registry scan: ${getErrorMessage3(err)}`);
|
|
7343
7464
|
continue;
|
|
@@ -7366,8 +7487,6 @@ var init_generate_ai_content = __esm({
|
|
|
7366
7487
|
this.name = "OccWriteConflictError";
|
|
7367
7488
|
}
|
|
7368
7489
|
};
|
|
7369
|
-
BRANCH_META_DIR = ".canopy-meta";
|
|
7370
|
-
BRANCH_META_FILE = "branch.json";
|
|
7371
7490
|
CURRENT_SCHEMA_VERSION = 1;
|
|
7372
7491
|
BranchMetadataConflictError = class extends Error {
|
|
7373
7492
|
constructor(message = "Concurrent modification detected in branch metadata") {
|
|
@@ -7375,18 +7494,10 @@ var init_generate_ai_content = __esm({
|
|
|
7375
7494
|
this.name = "BranchMetadataConflictError";
|
|
7376
7495
|
}
|
|
7377
7496
|
};
|
|
7378
|
-
BranchMetadataCorruptError = class extends Error {
|
|
7379
|
-
constructor(branchRoot, cause) {
|
|
7380
|
-
super(`Corrupt branch metadata in '${branchRoot}': ${cause}`);
|
|
7381
|
-
this.name = "BranchMetadataCorruptError";
|
|
7382
|
-
this.branchRoot = branchRoot;
|
|
7383
|
-
this.parseCause = cause;
|
|
7384
|
-
}
|
|
7385
|
-
};
|
|
7386
7497
|
BranchMetadataFileManager = class _BranchMetadataFileManager {
|
|
7387
7498
|
constructor(branchRoot, baseRoot, options) {
|
|
7388
|
-
this.branchRoot =
|
|
7389
|
-
this.filePath =
|
|
7499
|
+
this.branchRoot = path16.resolve(branchRoot);
|
|
7500
|
+
this.filePath = path16.join(this.branchRoot, BRANCH_META_DIR, BRANCH_META_FILE);
|
|
7390
7501
|
this.baseRoot = baseRoot;
|
|
7391
7502
|
this.settleMs = options?.settleMs;
|
|
7392
7503
|
}
|
|
@@ -7395,20 +7506,7 @@ var init_generate_ai_content = __esm({
|
|
|
7395
7506
|
* Use this for read-only access (e.g., in registry scanning or loadBranchContext).
|
|
7396
7507
|
*/
|
|
7397
7508
|
static async loadOnly(branchRoot) {
|
|
7398
|
-
|
|
7399
|
-
const filePath = path15.join(resolvedRoot, BRANCH_META_DIR, BRANCH_META_FILE);
|
|
7400
|
-
try {
|
|
7401
|
-
const raw = await fs14.readFile(filePath, "utf8");
|
|
7402
|
-
return JSON.parse(raw);
|
|
7403
|
-
} catch (err) {
|
|
7404
|
-
if (isNotFoundError3(err)) {
|
|
7405
|
-
return null;
|
|
7406
|
-
}
|
|
7407
|
-
if (err instanceof SyntaxError) {
|
|
7408
|
-
throw new BranchMetadataCorruptError(resolvedRoot, err.message);
|
|
7409
|
-
}
|
|
7410
|
-
throw err;
|
|
7411
|
-
}
|
|
7509
|
+
return readBranchMetadataFile(branchRoot);
|
|
7412
7510
|
}
|
|
7413
7511
|
/**
|
|
7414
7512
|
* Get a BranchMetadataFileManager instance configured for registry invalidation.
|
|
@@ -7419,7 +7517,7 @@ var init_generate_ai_content = __esm({
|
|
|
7419
7517
|
}
|
|
7420
7518
|
async load() {
|
|
7421
7519
|
try {
|
|
7422
|
-
const raw = await
|
|
7520
|
+
const raw = await fs15.readFile(this.filePath, "utf8");
|
|
7423
7521
|
const parsed = JSON.parse(raw);
|
|
7424
7522
|
const version = parsed.version ?? 0;
|
|
7425
7523
|
return { meta: parsed, version };
|
|
@@ -7481,7 +7579,7 @@ var init_generate_ai_content = __esm({
|
|
|
7481
7579
|
*/
|
|
7482
7580
|
async save(incoming) {
|
|
7483
7581
|
try {
|
|
7484
|
-
await
|
|
7582
|
+
await fs15.stat(this.branchRoot);
|
|
7485
7583
|
} catch (err) {
|
|
7486
7584
|
if (isNotFoundError3(err)) {
|
|
7487
7585
|
throw new BranchMetadataConflictError("Branch no longer exists");
|
|
@@ -7594,12 +7692,12 @@ var init_generate_ai_content = __esm({
|
|
|
7594
7692
|
};
|
|
7595
7693
|
GitManager = class _GitManager {
|
|
7596
7694
|
constructor(options, gitOptions) {
|
|
7597
|
-
this.repoPath =
|
|
7695
|
+
this.repoPath = path17.resolve(options.repoPath);
|
|
7598
7696
|
this.baseBranch = options.baseBranch ?? "main";
|
|
7599
7697
|
this.remote = options.remote ?? "origin";
|
|
7600
7698
|
this.skipIndexMarker = options.skipIndexMarker ?? false;
|
|
7601
7699
|
this.git = simpleGit2({ baseDir: this.repoPath, ...gitOptions });
|
|
7602
|
-
this.git.env(gitChildEnv({ GIT_CEILING_DIRECTORIES:
|
|
7700
|
+
this.git.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path17.dirname(this.repoPath) }));
|
|
7603
7701
|
}
|
|
7604
7702
|
static async cloneRepo(remoteUrl, targetPath, baseBranch = "main") {
|
|
7605
7703
|
log5.debug("git", "Cloning repository", {
|
|
@@ -7641,11 +7739,11 @@ var init_generate_ai_content = __esm({
|
|
|
7641
7739
|
remotePath: options.remotePath,
|
|
7642
7740
|
baseBranch: options.baseBranch
|
|
7643
7741
|
});
|
|
7644
|
-
const remoteParent =
|
|
7742
|
+
const remoteParent = path17.dirname(options.remotePath);
|
|
7645
7743
|
releaseLock = await acquireProvisioningLock(remoteParent, ".remote-init.lock");
|
|
7646
7744
|
let remoteExists = false;
|
|
7647
7745
|
try {
|
|
7648
|
-
const stat = await
|
|
7746
|
+
const stat = await fs16.stat(options.remotePath);
|
|
7649
7747
|
remoteExists = stat.isDirectory();
|
|
7650
7748
|
} catch (err) {
|
|
7651
7749
|
if (!isNotFoundError3(err))
|
|
@@ -7730,7 +7828,7 @@ var init_generate_ai_content = __esm({
|
|
|
7730
7828
|
* guard, which also needs the tracking namespace: `syncGit()` fetches
|
|
7731
7829
|
* GitHub into `GITHUB_TRACKING_REF_PREFIX*` rather than `refs/heads/*`
|
|
7732
7830
|
* directly (see that constant's doc comment above), and
|
|
7733
|
-
* `reconcileTrackedBranches()` (worker/
|
|
7831
|
+
* `reconcileTrackedBranches()` (worker/git-sync.ts) only
|
|
7734
7832
|
* non-destructively brings `refs/heads/*` toward what's tracked there — so
|
|
7735
7833
|
* a branch that another CanopyCMS deployment sharing this repo (or a
|
|
7736
7834
|
* direct push to GitHub) just created can sit in the tracking namespace
|
|
@@ -7805,7 +7903,7 @@ var init_generate_ai_content = __esm({
|
|
|
7805
7903
|
* "absent" deterministic instead of parsing update-ref's locale-dependent
|
|
7806
7904
|
* failure text, and its captured SHA is passed to `update-ref -d` as the
|
|
7807
7905
|
* expected old value -- same pattern as reconcileTrackedBranches'
|
|
7808
|
-
* guarded updates (worker/
|
|
7906
|
+
* guarded updates (worker/git-sync.ts): a concurrent Lambda push
|
|
7809
7907
|
* re-creating/moving this branch between the read and the delete makes
|
|
7810
7908
|
* update-ref throw (surfaced as the caller's best-effort warning) instead
|
|
7811
7909
|
* of silently deleting a commit that was just pushed. No
|
|
@@ -7897,7 +7995,7 @@ var init_generate_ai_content = __esm({
|
|
|
7897
7995
|
*/
|
|
7898
7996
|
static async validateGitRepoExists(repoPath) {
|
|
7899
7997
|
try {
|
|
7900
|
-
const stat = await
|
|
7998
|
+
const stat = await fs16.stat(path17.join(repoPath, ".git"));
|
|
7901
7999
|
if (!stat.isDirectory()) {
|
|
7902
8000
|
throw new Error(`Expected git repo at ${repoPath}`);
|
|
7903
8001
|
}
|
|
@@ -7975,7 +8073,7 @@ var init_generate_ai_content = __esm({
|
|
|
7975
8073
|
}
|
|
7976
8074
|
if (config.autoDetectRemotePath) {
|
|
7977
8075
|
try {
|
|
7978
|
-
const stat = await
|
|
8076
|
+
const stat = await fs16.stat(config.autoDetectRemotePath);
|
|
7979
8077
|
if (stat.isDirectory()) {
|
|
7980
8078
|
log5.debug("git", "Auto-detected local remote", {
|
|
7981
8079
|
path: config.autoDetectRemotePath
|
|
@@ -7988,8 +8086,8 @@ var init_generate_ai_content = __esm({
|
|
|
7988
8086
|
if (config.shouldAutoInitLocal) {
|
|
7989
8087
|
const gitRoot = await this.findGitRoot();
|
|
7990
8088
|
const sourceRoot = options.sourceRoot;
|
|
7991
|
-
const sourcePath = sourceRoot ?
|
|
7992
|
-
const localRemotePath =
|
|
8089
|
+
const sourcePath = sourceRoot ? path17.resolve(gitRoot, sourceRoot) : gitRoot;
|
|
8090
|
+
const localRemotePath = path17.join(sourcePath, config.defaultRemotePath);
|
|
7993
8091
|
await this.ensureLocalSimulatedRemote({
|
|
7994
8092
|
remotePath: localRemotePath,
|
|
7995
8093
|
sourcePath: gitRoot,
|
|
@@ -8017,7 +8115,7 @@ var init_generate_ai_content = __esm({
|
|
|
8017
8115
|
static async repoExistsAt(workspacePath) {
|
|
8018
8116
|
try {
|
|
8019
8117
|
const checkGit = simpleGit2({ baseDir: workspacePath });
|
|
8020
|
-
checkGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES:
|
|
8118
|
+
checkGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path17.dirname(workspacePath) }));
|
|
8021
8119
|
await checkGit.raw(["rev-parse", "--git-dir"]);
|
|
8022
8120
|
return true;
|
|
8023
8121
|
} catch {
|
|
@@ -8039,19 +8137,19 @@ var init_generate_ai_content = __esm({
|
|
|
8039
8137
|
const baseBranch = await resolveBaseBranch({
|
|
8040
8138
|
defaultBaseBranch: options.baseBranch,
|
|
8041
8139
|
mode: options.mode,
|
|
8042
|
-
detectFrom: options.sourceRoot ?
|
|
8140
|
+
detectFrom: options.sourceRoot ? path17.resolve(process.cwd(), options.sourceRoot) : process.cwd()
|
|
8043
8141
|
});
|
|
8044
8142
|
const remoteName = options.remoteName ?? "origin";
|
|
8045
8143
|
const repoExists = await _GitManager.repoExistsAt(options.workspacePath);
|
|
8046
8144
|
if (!repoExists) {
|
|
8047
|
-
const gitPath =
|
|
8145
|
+
const gitPath = path17.join(options.workspacePath, ".git");
|
|
8048
8146
|
try {
|
|
8049
|
-
const stat = await
|
|
8147
|
+
const stat = await fs16.stat(gitPath);
|
|
8050
8148
|
if (stat.isDirectory()) {
|
|
8051
8149
|
log5.debug("git", "Removing corrupt .git directory", {
|
|
8052
8150
|
workspacePath: options.workspacePath
|
|
8053
8151
|
});
|
|
8054
|
-
await
|
|
8152
|
+
await fs16.rm(gitPath, { recursive: true });
|
|
8055
8153
|
}
|
|
8056
8154
|
} catch (cleanupErr) {
|
|
8057
8155
|
if (!isNotFoundError3(cleanupErr))
|
|
@@ -8078,7 +8176,7 @@ var init_generate_ai_content = __esm({
|
|
|
8078
8176
|
}
|
|
8079
8177
|
justCloned = true;
|
|
8080
8178
|
const freshGit = simpleGit2({ baseDir: options.workspacePath });
|
|
8081
|
-
freshGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES:
|
|
8179
|
+
freshGit.env(gitChildEnv({ GIT_CEILING_DIRECTORIES: path17.dirname(options.workspacePath) }));
|
|
8082
8180
|
await freshGit.addConfig("canopycms.managed", "true");
|
|
8083
8181
|
await freshGit.addConfig("user.name", options.gitBotAuthorName);
|
|
8084
8182
|
await freshGit.addConfig("user.email", options.gitBotAuthorEmail);
|
|
@@ -8429,9 +8527,9 @@ var init_generate_ai_content = __esm({
|
|
|
8429
8527
|
} catch {
|
|
8430
8528
|
}
|
|
8431
8529
|
for (const [filePath, content] of Object.entries(initialFiles)) {
|
|
8432
|
-
const absolutePath =
|
|
8433
|
-
await
|
|
8434
|
-
await
|
|
8530
|
+
const absolutePath = path17.join(this.repoPath, filePath);
|
|
8531
|
+
await fs16.mkdir(path17.dirname(absolutePath), { recursive: true });
|
|
8532
|
+
await fs16.writeFile(absolutePath, content, "utf-8");
|
|
8435
8533
|
await this.git.add(filePath);
|
|
8436
8534
|
}
|
|
8437
8535
|
await this.git.commit("Initialize settings branch", ["--allow-empty"]);
|
|
@@ -8458,7 +8556,7 @@ var init_generate_ai_content = __esm({
|
|
|
8458
8556
|
branchName: options.branchName,
|
|
8459
8557
|
mode: options.mode
|
|
8460
8558
|
});
|
|
8461
|
-
releaseLock = await acquireProvisioningLock(
|
|
8559
|
+
releaseLock = await acquireProvisioningLock(path18.dirname(options.branchRoot), `.${path18.basename(options.branchRoot)}.init.lock`);
|
|
8462
8560
|
await GitManager.initializeWorkspace({
|
|
8463
8561
|
workspacePath: options.branchRoot,
|
|
8464
8562
|
branchName: options.branchName,
|
|
@@ -8499,7 +8597,7 @@ var init_generate_ai_content = __esm({
|
|
|
8499
8597
|
const baseBranch = await resolveBaseBranch({
|
|
8500
8598
|
defaultBaseBranch: this.config.defaultBaseBranch,
|
|
8501
8599
|
mode,
|
|
8502
|
-
detectFrom: this.config.sourceRoot ?
|
|
8600
|
+
detectFrom: this.config.sourceRoot ? path18.resolve(process.cwd(), this.config.sourceRoot) : void 0
|
|
8503
8601
|
});
|
|
8504
8602
|
await this.ensureGitWorkspace({
|
|
8505
8603
|
branchRoot,
|
|
@@ -8536,7 +8634,7 @@ var init_generate_ai_content = __esm({
|
|
|
8536
8634
|
};
|
|
8537
8635
|
readEntryData = async (filePath, format, bodyFieldName = "body") => {
|
|
8538
8636
|
try {
|
|
8539
|
-
const raw = await
|
|
8637
|
+
const raw = await fs17.readFile(filePath, "utf8");
|
|
8540
8638
|
if (format === "json") {
|
|
8541
8639
|
return asRecord(JSON.parse(raw));
|
|
8542
8640
|
}
|
|
@@ -8597,7 +8695,7 @@ var init_generate_ai_content = __esm({
|
|
|
8597
8695
|
normalizePath(root, collectionRoot);
|
|
8598
8696
|
let dirents;
|
|
8599
8697
|
try {
|
|
8600
|
-
dirents = await
|
|
8698
|
+
dirents = await fs17.readdir(collectionRoot, { withFileTypes: true });
|
|
8601
8699
|
} catch (err) {
|
|
8602
8700
|
if (isNotFoundError3(err))
|
|
8603
8701
|
return [];
|
|
@@ -8605,7 +8703,7 @@ var init_generate_ai_content = __esm({
|
|
|
8605
8703
|
}
|
|
8606
8704
|
const files = dirents.filter((d) => d.isFile() && validExts.some((ext) => d.name.endsWith(ext)) && d.name !== ".collection.json").sort((a, b) => a.name.localeCompare(b.name));
|
|
8607
8705
|
const entries = await Promise.all(files.map(async (file) => {
|
|
8608
|
-
const absolutePath =
|
|
8706
|
+
const absolutePath = path19.join(collectionRoot, file.name);
|
|
8609
8707
|
const relativePath = normalizePath(root, absolutePath);
|
|
8610
8708
|
const parsed = parseTypedFilename(file.name, entryTypes);
|
|
8611
8709
|
if (!parsed) {
|
|
@@ -8620,7 +8718,7 @@ var init_generate_ai_content = __esm({
|
|
|
8620
8718
|
const format = entryType?.format || "json";
|
|
8621
8719
|
const bodyField = entryType?.schema ? findBodyFieldName(entryType.schema) : "body";
|
|
8622
8720
|
const [stats, data] = await Promise.all([
|
|
8623
|
-
|
|
8721
|
+
fs17.stat(absolutePath),
|
|
8624
8722
|
readEntryData(absolutePath, format, bodyField)
|
|
8625
8723
|
]);
|
|
8626
8724
|
const item = {
|
|
@@ -8645,22 +8743,24 @@ var init_generate_ai_content = __esm({
|
|
|
8645
8743
|
STRING_FIELD_TYPES = /* @__PURE__ */ new Set(["string", "markdown", "mdx", "code"]);
|
|
8646
8744
|
selectOptionValues = (field) => (field.options ?? []).map((opt) => typeof opt === "string" ? opt : opt.value);
|
|
8647
8745
|
GENERATED_RECORD_FILENAME = ".canopy-generated.json";
|
|
8746
|
+
SAFE_BUILD_ID = /^[A-Za-z0-9._-]{1,255}$/;
|
|
8747
|
+
BUILD_ID_RULE = 'must be 1-255 characters of [A-Za-z0-9._-] and not "." or ".."';
|
|
8648
8748
|
jiti = createJiti2(import.meta.url);
|
|
8649
8749
|
}
|
|
8650
8750
|
});
|
|
8651
8751
|
|
|
8652
8752
|
// dist/utils/atomic-write.js
|
|
8653
|
-
import
|
|
8654
|
-
import
|
|
8753
|
+
import fs19 from "node:fs/promises";
|
|
8754
|
+
import path23 from "node:path";
|
|
8655
8755
|
async function atomicWriteFile3(filePath, content) {
|
|
8656
|
-
const dir =
|
|
8657
|
-
await
|
|
8756
|
+
const dir = path23.dirname(filePath);
|
|
8757
|
+
await fs19.mkdir(dir, { recursive: true });
|
|
8658
8758
|
const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
8659
|
-
await
|
|
8759
|
+
await fs19.writeFile(tempPath, content, typeof content === "string" ? "utf-8" : void 0);
|
|
8660
8760
|
try {
|
|
8661
|
-
await
|
|
8761
|
+
await fs19.rename(tempPath, filePath);
|
|
8662
8762
|
} catch (err) {
|
|
8663
|
-
await
|
|
8763
|
+
await fs19.unlink(tempPath).catch(() => {
|
|
8664
8764
|
});
|
|
8665
8765
|
throw err;
|
|
8666
8766
|
}
|
|
@@ -8780,11 +8880,11 @@ var init_debug2 = __esm({
|
|
|
8780
8880
|
});
|
|
8781
8881
|
|
|
8782
8882
|
// dist/resource-generation.js
|
|
8783
|
-
import
|
|
8784
|
-
import
|
|
8883
|
+
import fs20 from "node:fs/promises";
|
|
8884
|
+
import path24 from "node:path";
|
|
8785
8885
|
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
8786
8886
|
function resourceGenerationPath2(root, resource) {
|
|
8787
|
-
return
|
|
8887
|
+
return path24.resolve(root, META_DIR3, `${resource}.generation`);
|
|
8788
8888
|
}
|
|
8789
8889
|
async function bumpResourceGeneration2(root, resource, options) {
|
|
8790
8890
|
const token = randomUUID3();
|
|
@@ -8802,7 +8902,7 @@ async function bumpResourceGeneration2(root, resource, options) {
|
|
|
8802
8902
|
}
|
|
8803
8903
|
async function readResourceGeneration2(root, resource) {
|
|
8804
8904
|
try {
|
|
8805
|
-
const token = await
|
|
8905
|
+
const token = await fs20.readFile(resourceGenerationPath2(root, resource), "utf-8");
|
|
8806
8906
|
return { ok: true, token };
|
|
8807
8907
|
} catch (err) {
|
|
8808
8908
|
if (isNodeError(err) && err.code === "ENOENT")
|
|
@@ -8829,10 +8929,10 @@ var init_resource_generation = __esm({
|
|
|
8829
8929
|
});
|
|
8830
8930
|
|
|
8831
8931
|
// dist/content-index-registry.js
|
|
8832
|
-
import
|
|
8932
|
+
import path25 from "node:path";
|
|
8833
8933
|
function invalidateContentIndexesForRoot2(root) {
|
|
8834
|
-
const rootKey =
|
|
8835
|
-
const prefix = rootKey +
|
|
8934
|
+
const rootKey = path25.resolve(root);
|
|
8935
|
+
const prefix = rootKey + path25.sep;
|
|
8836
8936
|
for (const [storeRoot, refs] of registry2) {
|
|
8837
8937
|
if (storeRoot !== rootKey && !storeRoot.startsWith(prefix))
|
|
8838
8938
|
continue;
|
|
@@ -9000,7 +9100,7 @@ var init_content_id_index = __esm({
|
|
|
9000
9100
|
});
|
|
9001
9101
|
|
|
9002
9102
|
// dist/schema/meta-loader.js
|
|
9003
|
-
import { promises as
|
|
9103
|
+
import { promises as fs21 } from "fs";
|
|
9004
9104
|
import { join as join4 } from "pathe";
|
|
9005
9105
|
import { z as z8 } from "zod";
|
|
9006
9106
|
import chokidar2 from "chokidar";
|
|
@@ -9010,7 +9110,7 @@ function stripEmbeddedIdFromName2(name) {
|
|
|
9010
9110
|
async function scanForCollectionMeta2(baseDir, relativePath = "") {
|
|
9011
9111
|
const collections = [];
|
|
9012
9112
|
try {
|
|
9013
|
-
const entries = await
|
|
9113
|
+
const entries = await fs21.readdir(baseDir, { withFileTypes: true });
|
|
9014
9114
|
for (const entry of entries) {
|
|
9015
9115
|
if (!entry.isDirectory())
|
|
9016
9116
|
continue;
|
|
@@ -9021,8 +9121,8 @@ async function scanForCollectionMeta2(baseDir, relativePath = "") {
|
|
|
9021
9121
|
const absolutePath = join4(baseDir, folderName);
|
|
9022
9122
|
const metaPath = join4(absolutePath, ".collection.json");
|
|
9023
9123
|
try {
|
|
9024
|
-
await
|
|
9025
|
-
const content = await
|
|
9124
|
+
await fs21.access(metaPath);
|
|
9125
|
+
const content = await fs21.readFile(metaPath, "utf-8");
|
|
9026
9126
|
const parsed = JSON.parse(content);
|
|
9027
9127
|
const meta = collectionMetaSchema2.parse(parsed);
|
|
9028
9128
|
collections.push({
|
|
@@ -9054,7 +9154,7 @@ async function loadCollectionMetaFiles2(contentRoot) {
|
|
|
9054
9154
|
let root = null;
|
|
9055
9155
|
const rootMetaPath = join4(contentRoot, ".collection.json");
|
|
9056
9156
|
try {
|
|
9057
|
-
await
|
|
9157
|
+
await fs21.access(rootMetaPath);
|
|
9058
9158
|
} catch (err) {
|
|
9059
9159
|
if (err.code === "ENOENT") {
|
|
9060
9160
|
} else {
|
|
@@ -9062,7 +9162,7 @@ async function loadCollectionMetaFiles2(contentRoot) {
|
|
|
9062
9162
|
}
|
|
9063
9163
|
}
|
|
9064
9164
|
try {
|
|
9065
|
-
const content = await
|
|
9165
|
+
const content = await fs21.readFile(rootMetaPath, "utf-8");
|
|
9066
9166
|
const parsed = JSON.parse(content);
|
|
9067
9167
|
root = rootCollectionMetaSchema2.parse(parsed);
|
|
9068
9168
|
} catch (err) {
|
|
@@ -9500,13 +9600,13 @@ var init_content_index_generation = __esm({
|
|
|
9500
9600
|
});
|
|
9501
9601
|
|
|
9502
9602
|
// dist/sync-core.js
|
|
9503
|
-
import
|
|
9504
|
-
import
|
|
9603
|
+
import fs24 from "node:fs/promises";
|
|
9604
|
+
import path26 from "node:path";
|
|
9505
9605
|
import { simpleGit as simpleGit4 } from "simple-git";
|
|
9506
9606
|
function assertWithinDir(resolved, parent, label) {
|
|
9507
|
-
const normalizedResolved =
|
|
9508
|
-
const normalizedParent =
|
|
9509
|
-
if (!normalizedResolved.startsWith(normalizedParent +
|
|
9607
|
+
const normalizedResolved = path26.resolve(resolved);
|
|
9608
|
+
const normalizedParent = path26.resolve(parent);
|
|
9609
|
+
if (!normalizedResolved.startsWith(normalizedParent + path26.sep) && normalizedResolved !== normalizedParent) {
|
|
9510
9610
|
throw new Error(`${label} escapes the expected directory: ${resolved}`);
|
|
9511
9611
|
}
|
|
9512
9612
|
}
|
|
@@ -9514,19 +9614,19 @@ async function safeReplaceDir(oldDir, newDir) {
|
|
|
9514
9614
|
const backupDir = `${oldDir}.sync-backup-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
9515
9615
|
const oldExists = await filePathExists(oldDir);
|
|
9516
9616
|
if (oldExists) {
|
|
9517
|
-
await
|
|
9617
|
+
await fs24.rename(oldDir, backupDir);
|
|
9518
9618
|
}
|
|
9519
9619
|
try {
|
|
9520
|
-
await
|
|
9620
|
+
await fs24.rename(newDir, oldDir);
|
|
9521
9621
|
} catch (err) {
|
|
9522
9622
|
if (oldExists) {
|
|
9523
|
-
await
|
|
9623
|
+
await fs24.rename(backupDir, oldDir).catch(() => {
|
|
9524
9624
|
});
|
|
9525
9625
|
}
|
|
9526
9626
|
throw err;
|
|
9527
9627
|
}
|
|
9528
9628
|
if (oldExists) {
|
|
9529
|
-
await
|
|
9629
|
+
await fs24.rm(backupDir, { recursive: true, force: true }).catch(() => {
|
|
9530
9630
|
});
|
|
9531
9631
|
}
|
|
9532
9632
|
}
|
|
@@ -9534,7 +9634,7 @@ async function listFilesRecursive(dir, prefix = "") {
|
|
|
9534
9634
|
const results = [];
|
|
9535
9635
|
let entries;
|
|
9536
9636
|
try {
|
|
9537
|
-
entries = await
|
|
9637
|
+
entries = await fs24.readdir(dir, { withFileTypes: true });
|
|
9538
9638
|
} catch {
|
|
9539
9639
|
return results;
|
|
9540
9640
|
}
|
|
@@ -9545,7 +9645,7 @@ async function listFilesRecursive(dir, prefix = "") {
|
|
|
9545
9645
|
continue;
|
|
9546
9646
|
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
9547
9647
|
if (entry.isDirectory()) {
|
|
9548
|
-
results.push(...await listFilesRecursive(
|
|
9648
|
+
results.push(...await listFilesRecursive(path26.join(dir, entry.name), rel));
|
|
9549
9649
|
} else {
|
|
9550
9650
|
results.push(rel);
|
|
9551
9651
|
}
|
|
@@ -9553,19 +9653,19 @@ async function listFilesRecursive(dir, prefix = "") {
|
|
|
9553
9653
|
return results;
|
|
9554
9654
|
}
|
|
9555
9655
|
async function copyDir(src, dest) {
|
|
9556
|
-
await
|
|
9557
|
-
const entries = await
|
|
9656
|
+
await fs24.mkdir(dest, { recursive: true });
|
|
9657
|
+
const entries = await fs24.readdir(src, { withFileTypes: true });
|
|
9558
9658
|
for (const entry of entries) {
|
|
9559
9659
|
if (entry.name === ".git")
|
|
9560
9660
|
continue;
|
|
9561
9661
|
if (entry.isSymbolicLink())
|
|
9562
9662
|
continue;
|
|
9563
|
-
const srcPath =
|
|
9564
|
-
const destPath =
|
|
9663
|
+
const srcPath = path26.join(src, entry.name);
|
|
9664
|
+
const destPath = path26.join(dest, entry.name);
|
|
9565
9665
|
if (entry.isDirectory()) {
|
|
9566
9666
|
await copyDir(srcPath, destPath);
|
|
9567
9667
|
} else {
|
|
9568
|
-
await
|
|
9668
|
+
await fs24.copyFile(srcPath, destPath);
|
|
9569
9669
|
}
|
|
9570
9670
|
}
|
|
9571
9671
|
}
|
|
@@ -9574,14 +9674,14 @@ async function pushContentToWorkspace(options) {
|
|
|
9574
9674
|
if (!await filePathExists(srcContentDir)) {
|
|
9575
9675
|
return { fileCount: 0 };
|
|
9576
9676
|
}
|
|
9577
|
-
const wsContentDir =
|
|
9677
|
+
const wsContentDir = path26.join(branchPath, contentRoot);
|
|
9578
9678
|
assertWithinDir(wsContentDir, branchPath, "content-root");
|
|
9579
9679
|
const tmpDir = `${wsContentDir}.sync-tmp-${Date.now()}`;
|
|
9580
9680
|
try {
|
|
9581
9681
|
await copyDir(srcContentDir, tmpDir);
|
|
9582
9682
|
await safeReplaceDir(wsContentDir, tmpDir);
|
|
9583
9683
|
} catch (err) {
|
|
9584
|
-
await
|
|
9684
|
+
await fs24.rm(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
9585
9685
|
});
|
|
9586
9686
|
throw err;
|
|
9587
9687
|
}
|
|
@@ -9631,15 +9731,15 @@ var init_provisioning_lock = __esm({
|
|
|
9631
9731
|
});
|
|
9632
9732
|
|
|
9633
9733
|
// dist/git-manager.js
|
|
9634
|
-
import
|
|
9635
|
-
import
|
|
9734
|
+
import fs25 from "node:fs/promises";
|
|
9735
|
+
import path27 from "node:path";
|
|
9636
9736
|
import { simpleGit as simpleGit6 } from "simple-git";
|
|
9637
9737
|
async function ensureGitExcludePattern2(repoPath, pattern) {
|
|
9638
|
-
const excludePath =
|
|
9639
|
-
await
|
|
9738
|
+
const excludePath = path27.join(repoPath, ".git", "info", "exclude");
|
|
9739
|
+
await fs25.mkdir(path27.dirname(excludePath), { recursive: true });
|
|
9640
9740
|
let content = "";
|
|
9641
9741
|
try {
|
|
9642
|
-
content = await
|
|
9742
|
+
content = await fs25.readFile(excludePath, "utf-8");
|
|
9643
9743
|
} catch (err) {
|
|
9644
9744
|
if (!isNotFoundError(err))
|
|
9645
9745
|
throw err;
|
|
@@ -9651,7 +9751,7 @@ async function ensureGitExcludePattern2(repoPath, pattern) {
|
|
|
9651
9751
|
}
|
|
9652
9752
|
const needsLeadingNewline = content.length > 0 && !content.endsWith("\n");
|
|
9653
9753
|
const newContent = content + (needsLeadingNewline ? "\n" : "") + pattern + "\n";
|
|
9654
|
-
await
|
|
9754
|
+
await fs25.writeFile(excludePath, newContent, "utf-8");
|
|
9655
9755
|
log9.debug("git", "Added pattern to .git/info/exclude", { pattern });
|
|
9656
9756
|
}
|
|
9657
9757
|
var log9;
|
|
@@ -9808,7 +9908,7 @@ var init_config5 = __esm({
|
|
|
9808
9908
|
});
|
|
9809
9909
|
|
|
9810
9910
|
// dist/operating-mode/client-unsafe-strategy.js
|
|
9811
|
-
import
|
|
9911
|
+
import path28 from "node:path";
|
|
9812
9912
|
function operatingStrategy3(mode) {
|
|
9813
9913
|
const cached = strategyCache3.get(mode);
|
|
9814
9914
|
if (cached)
|
|
@@ -9843,32 +9943,32 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
9843
9943
|
// - shouldCommit(), shouldPush()
|
|
9844
9944
|
// Add client-unsafe methods (use Node.js APIs)
|
|
9845
9945
|
getWorkspaceRoot(_sourceRoot) {
|
|
9846
|
-
return
|
|
9946
|
+
return path28.resolve(process.env.CANOPYCMS_WORKSPACE_ROOT ?? DEFAULT_PROD_WORKSPACE3);
|
|
9847
9947
|
}
|
|
9848
9948
|
getContentRoot(contentRoot, sourceRoot) {
|
|
9849
|
-
return
|
|
9949
|
+
return path28.resolve(sourceRoot ?? process.cwd(), contentRoot);
|
|
9850
9950
|
}
|
|
9851
9951
|
getContentBranchesRoot(sourceRoot) {
|
|
9852
|
-
return
|
|
9952
|
+
return path28.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
9853
9953
|
}
|
|
9854
9954
|
getContentBranchRoot(branchName, sourceRoot) {
|
|
9855
|
-
return
|
|
9955
|
+
return path28.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
9856
9956
|
}
|
|
9857
9957
|
getGitExcludePattern() {
|
|
9858
9958
|
return ".canopy-meta/";
|
|
9859
9959
|
}
|
|
9860
9960
|
getPermissionsFilePath(root) {
|
|
9861
|
-
return
|
|
9961
|
+
return path28.join(root, this.getPermissionsFileName());
|
|
9862
9962
|
}
|
|
9863
9963
|
getGroupsFilePath(root) {
|
|
9864
|
-
return
|
|
9964
|
+
return path28.join(root, this.getGroupsFileName());
|
|
9865
9965
|
}
|
|
9866
9966
|
getRemoteUrlConfig() {
|
|
9867
9967
|
return {
|
|
9868
9968
|
shouldAutoInitLocal: false,
|
|
9869
9969
|
defaultRemotePath: "",
|
|
9870
9970
|
envVarName: "CANOPYCMS_REMOTE_URL",
|
|
9871
|
-
autoDetectRemotePath:
|
|
9971
|
+
autoDetectRemotePath: path28.join(this.getWorkspaceRoot(), "remote.git")
|
|
9872
9972
|
};
|
|
9873
9973
|
}
|
|
9874
9974
|
requiresExistingRepo() {
|
|
@@ -9880,7 +9980,7 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
9880
9980
|
return `canopycms-settings-${resolveDeploymentName3(config, "prod")}`;
|
|
9881
9981
|
}
|
|
9882
9982
|
getSettingsRoot(sourceRoot) {
|
|
9883
|
-
return
|
|
9983
|
+
return path28.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
9884
9984
|
}
|
|
9885
9985
|
usesSeparateSettingsBranch() {
|
|
9886
9986
|
return true;
|
|
@@ -9897,25 +9997,25 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
9897
9997
|
DevStrategy3 = class extends DevClientSafeStrategy3 {
|
|
9898
9998
|
// Inherits client-safe methods from DevClientSafeStrategy
|
|
9899
9999
|
getWorkspaceRoot(sourceRoot) {
|
|
9900
|
-
return
|
|
10000
|
+
return path28.resolve(sourceRoot ?? process.cwd(), ".canopy-dev");
|
|
9901
10001
|
}
|
|
9902
10002
|
getContentRoot(contentRoot, sourceRoot) {
|
|
9903
|
-
return
|
|
10003
|
+
return path28.resolve(sourceRoot ?? process.cwd(), contentRoot);
|
|
9904
10004
|
}
|
|
9905
10005
|
getContentBranchesRoot(sourceRoot) {
|
|
9906
|
-
return
|
|
10006
|
+
return path28.join(this.getWorkspaceRoot(sourceRoot), "content-branches");
|
|
9907
10007
|
}
|
|
9908
10008
|
getContentBranchRoot(branchName, sourceRoot) {
|
|
9909
|
-
return
|
|
10009
|
+
return path28.resolve(this.getContentBranchesRoot(sourceRoot), branchName);
|
|
9910
10010
|
}
|
|
9911
10011
|
getGitExcludePattern() {
|
|
9912
10012
|
return ".canopy-meta/";
|
|
9913
10013
|
}
|
|
9914
10014
|
getPermissionsFilePath(root) {
|
|
9915
|
-
return
|
|
10015
|
+
return path28.join(root, this.getPermissionsFileName());
|
|
9916
10016
|
}
|
|
9917
10017
|
getGroupsFilePath(root) {
|
|
9918
|
-
return
|
|
10018
|
+
return path28.join(root, this.getGroupsFileName());
|
|
9919
10019
|
}
|
|
9920
10020
|
getRemoteUrlConfig() {
|
|
9921
10021
|
return {
|
|
@@ -9933,7 +10033,7 @@ var init_client_unsafe_strategy2 = __esm({
|
|
|
9933
10033
|
return `canopycms-settings-${resolveDeploymentName3(config, "local")}`;
|
|
9934
10034
|
}
|
|
9935
10035
|
getSettingsRoot(sourceRoot) {
|
|
9936
|
-
return
|
|
10036
|
+
return path28.join(this.getWorkspaceRoot(sourceRoot), "settings");
|
|
9937
10037
|
}
|
|
9938
10038
|
usesSeparateSettingsBranch() {
|
|
9939
10039
|
return true;
|
|
@@ -9965,8 +10065,8 @@ __export(sync_exports, {
|
|
|
9965
10065
|
SyncError: () => SyncError,
|
|
9966
10066
|
sync: () => sync
|
|
9967
10067
|
});
|
|
9968
|
-
import
|
|
9969
|
-
import
|
|
10068
|
+
import fs26 from "node:fs/promises";
|
|
10069
|
+
import path29 from "node:path";
|
|
9970
10070
|
import { simpleGit as simpleGit7 } from "simple-git";
|
|
9971
10071
|
import * as p2 from "@clack/prompts";
|
|
9972
10072
|
async function detectCurrentBranch(projectDir) {
|
|
@@ -9979,7 +10079,7 @@ async function detectCurrentBranch(projectDir) {
|
|
|
9979
10079
|
async function selectBranch(options, branchesDir) {
|
|
9980
10080
|
let branches = [];
|
|
9981
10081
|
if (await filePathExists(branchesDir)) {
|
|
9982
|
-
const entries = await
|
|
10082
|
+
const entries = await fs26.readdir(branchesDir, { withFileTypes: true });
|
|
9983
10083
|
branches = entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
9984
10084
|
}
|
|
9985
10085
|
let branchName = options.branch;
|
|
@@ -10007,11 +10107,11 @@ async function selectBranch(options, branchesDir) {
|
|
|
10007
10107
|
branchName = result;
|
|
10008
10108
|
}
|
|
10009
10109
|
}
|
|
10010
|
-
const branchPath =
|
|
10110
|
+
const branchPath = path29.join(branchesDir, branchName);
|
|
10011
10111
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
10012
10112
|
if (!branches.includes(branchName)) {
|
|
10013
10113
|
if (options.autoCreate) {
|
|
10014
|
-
await
|
|
10114
|
+
await fs26.mkdir(branchPath, { recursive: true });
|
|
10015
10115
|
const wsGit = simpleGit7({ baseDir: branchPath });
|
|
10016
10116
|
await wsGit.init();
|
|
10017
10117
|
await wsGit.checkoutLocalBranch(branchName);
|
|
@@ -10028,8 +10128,8 @@ async function selectBranch(options, branchesDir) {
|
|
|
10028
10128
|
async function syncPush(options) {
|
|
10029
10129
|
const { projectDir } = options;
|
|
10030
10130
|
const contentRoot = options.contentRoot || "content";
|
|
10031
|
-
const branchesDir =
|
|
10032
|
-
const srcContentDir =
|
|
10131
|
+
const branchesDir = path29.join(projectDir, ".canopy-dev", "content-branches");
|
|
10132
|
+
const srcContentDir = path29.join(projectDir, contentRoot);
|
|
10033
10133
|
assertWithinDir(srcContentDir, projectDir, "--content-root");
|
|
10034
10134
|
if (!await filePathExists(srcContentDir)) {
|
|
10035
10135
|
throw new SyncError(`Content directory not found: ${contentRoot}/ (expected at ${srcContentDir})`);
|
|
@@ -10037,7 +10137,7 @@ async function syncPush(options) {
|
|
|
10037
10137
|
const branchName = await selectBranch({ ...options, projectDir, autoCreate: true }, branchesDir);
|
|
10038
10138
|
if (!branchName)
|
|
10039
10139
|
return { fileCount: 0 };
|
|
10040
|
-
const branchPath =
|
|
10140
|
+
const branchPath = path29.join(branchesDir, branchName);
|
|
10041
10141
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
10042
10142
|
const wsGit = simpleGit7({ baseDir: branchPath });
|
|
10043
10143
|
const status = await wsGit.status();
|
|
@@ -10095,19 +10195,19 @@ async function syncPush(options) {
|
|
|
10095
10195
|
async function syncPull(options) {
|
|
10096
10196
|
const { projectDir } = options;
|
|
10097
10197
|
const contentRoot = options.contentRoot || "content";
|
|
10098
|
-
const branchesDir =
|
|
10198
|
+
const branchesDir = path29.join(projectDir, ".canopy-dev", "content-branches");
|
|
10099
10199
|
const branchName = await selectBranch({ ...options, projectDir }, branchesDir);
|
|
10100
10200
|
if (!branchName)
|
|
10101
10201
|
return { fileCount: 0 };
|
|
10102
|
-
const branchPath =
|
|
10202
|
+
const branchPath = path29.join(branchesDir, branchName);
|
|
10103
10203
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
10104
|
-
const branchContentDir =
|
|
10204
|
+
const branchContentDir = path29.join(branchPath, contentRoot);
|
|
10105
10205
|
assertWithinDir(branchContentDir, branchPath, "--content-root");
|
|
10106
10206
|
if (!await filePathExists(branchContentDir)) {
|
|
10107
10207
|
throw new SyncError(`Content directory not found in branch workspace: ${branchName}/${contentRoot}`);
|
|
10108
10208
|
}
|
|
10109
10209
|
p2.log.step(`Pulling content from branch: ${branchName}`);
|
|
10110
|
-
const destContentDir =
|
|
10210
|
+
const destContentDir = path29.join(projectDir, contentRoot);
|
|
10111
10211
|
assertWithinDir(destContentDir, projectDir, "--content-root");
|
|
10112
10212
|
const sourceGit = simpleGit7({ baseDir: projectDir });
|
|
10113
10213
|
const status = await sourceGit.status();
|
|
@@ -10159,7 +10259,7 @@ async function syncPull(options) {
|
|
|
10159
10259
|
await copyDir(branchContentDir, tmpDestDir);
|
|
10160
10260
|
await safeReplaceDir(destContentDir, tmpDestDir);
|
|
10161
10261
|
} catch (err) {
|
|
10162
|
-
await
|
|
10262
|
+
await fs26.rm(tmpDestDir, { recursive: true, force: true }).catch(() => {
|
|
10163
10263
|
});
|
|
10164
10264
|
throw err;
|
|
10165
10265
|
}
|
|
@@ -10183,8 +10283,8 @@ async function syncPull(options) {
|
|
|
10183
10283
|
async function syncBoth(options) {
|
|
10184
10284
|
const { projectDir } = options;
|
|
10185
10285
|
const contentRoot = options.contentRoot || "content";
|
|
10186
|
-
const branchesDir =
|
|
10187
|
-
const srcContentDir =
|
|
10286
|
+
const branchesDir = path29.join(projectDir, ".canopy-dev", "content-branches");
|
|
10287
|
+
const srcContentDir = path29.join(projectDir, contentRoot);
|
|
10188
10288
|
assertWithinDir(srcContentDir, projectDir, "--content-root");
|
|
10189
10289
|
if (!await filePathExists(srcContentDir)) {
|
|
10190
10290
|
throw new SyncError(`Content directory not found: ${contentRoot}/ (expected at ${srcContentDir})`);
|
|
@@ -10192,7 +10292,7 @@ async function syncBoth(options) {
|
|
|
10192
10292
|
const branchName = await selectBranch({ ...options, projectDir, autoCreate: true }, branchesDir);
|
|
10193
10293
|
if (!branchName)
|
|
10194
10294
|
return { pushed: 0, pulled: 0 };
|
|
10195
|
-
const branchPath =
|
|
10295
|
+
const branchPath = path29.join(branchesDir, branchName);
|
|
10196
10296
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
10197
10297
|
const wsGit = simpleGit7({ baseDir: branchPath });
|
|
10198
10298
|
const status = await wsGit.status();
|
|
@@ -10227,14 +10327,14 @@ async function syncBoth(options) {
|
|
|
10227
10327
|
try {
|
|
10228
10328
|
const incomingBranch = `sync-incoming-${Date.now()}`;
|
|
10229
10329
|
await wsGit.raw(["checkout", "-b", incomingBranch, baseRef]);
|
|
10230
|
-
const wsContentDir =
|
|
10330
|
+
const wsContentDir = path29.join(branchPath, contentRoot);
|
|
10231
10331
|
assertWithinDir(wsContentDir, branchPath, "--content-root");
|
|
10232
10332
|
const tmpDir = `${wsContentDir}.sync-tmp-${Date.now()}`;
|
|
10233
10333
|
try {
|
|
10234
10334
|
await copyDir(srcContentDir, tmpDir);
|
|
10235
10335
|
await safeReplaceDir(wsContentDir, tmpDir);
|
|
10236
10336
|
} catch (err) {
|
|
10237
|
-
await
|
|
10337
|
+
await fs26.rm(tmpDir, { recursive: true, force: true }).catch(() => {
|
|
10238
10338
|
});
|
|
10239
10339
|
await wsGit.checkout(currentBranch);
|
|
10240
10340
|
await wsGit.raw(["branch", "-D", incomingBranch]).catch(() => {
|
|
@@ -10291,11 +10391,11 @@ async function syncBoth(options) {
|
|
|
10291
10391
|
}
|
|
10292
10392
|
async function syncAbort(options) {
|
|
10293
10393
|
const { projectDir } = options;
|
|
10294
|
-
const branchesDir =
|
|
10394
|
+
const branchesDir = path29.join(projectDir, ".canopy-dev", "content-branches");
|
|
10295
10395
|
const branchName = await selectBranch({ ...options, projectDir }, branchesDir);
|
|
10296
10396
|
if (!branchName)
|
|
10297
10397
|
return;
|
|
10298
|
-
const branchPath =
|
|
10398
|
+
const branchPath = path29.join(branchesDir, branchName);
|
|
10299
10399
|
assertWithinDir(branchPath, branchesDir, "--branch");
|
|
10300
10400
|
const wsGit = simpleGit7({ baseDir: branchPath });
|
|
10301
10401
|
const status = await wsGit.status();
|
|
@@ -10423,8 +10523,8 @@ var init_async_mutex = __esm({
|
|
|
10423
10523
|
});
|
|
10424
10524
|
|
|
10425
10525
|
// dist/utils/occ-json-write.js
|
|
10426
|
-
import
|
|
10427
|
-
import
|
|
10526
|
+
import fs27 from "node:fs/promises";
|
|
10527
|
+
import path30 from "node:path";
|
|
10428
10528
|
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
10429
10529
|
import lockfile4 from "proper-lockfile";
|
|
10430
10530
|
async function writeOccJsonFile2(filePath, payload, options) {
|
|
@@ -10433,29 +10533,29 @@ async function writeOccJsonFile2(filePath, payload, options) {
|
|
|
10433
10533
|
const writeId = randomUUID4();
|
|
10434
10534
|
const data = { ...payload, version: newVersion, writeId };
|
|
10435
10535
|
const content = JSON.stringify(data, null, 2) + (trailingNewline ? "\n" : "");
|
|
10436
|
-
const dir =
|
|
10437
|
-
await
|
|
10536
|
+
const dir = path30.dirname(filePath);
|
|
10537
|
+
await fs27.mkdir(dir, { recursive: true });
|
|
10438
10538
|
const tempPath = `${filePath}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
10439
|
-
await
|
|
10539
|
+
await fs27.writeFile(tempPath, content, "utf-8");
|
|
10440
10540
|
if (expectedVersion === null) {
|
|
10441
10541
|
try {
|
|
10442
|
-
await
|
|
10542
|
+
await fs27.link(tempPath, filePath);
|
|
10443
10543
|
} catch (err) {
|
|
10444
|
-
await
|
|
10544
|
+
await fs27.unlink(tempPath).catch(() => {
|
|
10445
10545
|
});
|
|
10446
10546
|
if (isFileExistsError(err)) {
|
|
10447
10547
|
throw new OccWriteConflictError2();
|
|
10448
10548
|
}
|
|
10449
10549
|
throw err;
|
|
10450
10550
|
}
|
|
10451
|
-
await
|
|
10551
|
+
await fs27.unlink(tempPath).catch(() => {
|
|
10452
10552
|
});
|
|
10453
10553
|
return { version: newVersion, writeId };
|
|
10454
10554
|
}
|
|
10455
10555
|
try {
|
|
10456
10556
|
let currentVersion = null;
|
|
10457
10557
|
try {
|
|
10458
|
-
const current = JSON.parse(await
|
|
10558
|
+
const current = JSON.parse(await fs27.readFile(filePath, "utf-8"));
|
|
10459
10559
|
currentVersion = current.version ?? 0;
|
|
10460
10560
|
} catch {
|
|
10461
10561
|
currentVersion = null;
|
|
@@ -10463,16 +10563,16 @@ async function writeOccJsonFile2(filePath, payload, options) {
|
|
|
10463
10563
|
if (currentVersion !== expectedVersion) {
|
|
10464
10564
|
throw new OccWriteConflictError2();
|
|
10465
10565
|
}
|
|
10466
|
-
await
|
|
10566
|
+
await fs27.rename(tempPath, filePath);
|
|
10467
10567
|
if (settleMs > 0) {
|
|
10468
10568
|
await new Promise((resolve2) => setTimeout(resolve2, settleMs));
|
|
10469
10569
|
}
|
|
10470
|
-
const afterWrite = JSON.parse(await
|
|
10570
|
+
const afterWrite = JSON.parse(await fs27.readFile(filePath, "utf-8"));
|
|
10471
10571
|
if (afterWrite.writeId !== writeId) {
|
|
10472
10572
|
throw new OccWriteConflictError2();
|
|
10473
10573
|
}
|
|
10474
10574
|
} catch (err) {
|
|
10475
|
-
await
|
|
10575
|
+
await fs27.unlink(tempPath).catch(() => {
|
|
10476
10576
|
});
|
|
10477
10577
|
throw err;
|
|
10478
10578
|
}
|
|
@@ -10498,8 +10598,8 @@ async function withOccRetry2(operation, options) {
|
|
|
10498
10598
|
throw new Error("Unreachable");
|
|
10499
10599
|
}
|
|
10500
10600
|
async function withOccFileLock2(filePath, fn) {
|
|
10501
|
-
const dir =
|
|
10502
|
-
await
|
|
10601
|
+
const dir = path30.dirname(filePath);
|
|
10602
|
+
await fs27.mkdir(dir, { recursive: true });
|
|
10503
10603
|
const MAX_ATTEMPTS = 14;
|
|
10504
10604
|
let release = null;
|
|
10505
10605
|
for (let attempt = 1; release === null; attempt++) {
|
|
@@ -10557,14 +10657,51 @@ var init_occ_json_write = __esm({
|
|
|
10557
10657
|
}
|
|
10558
10658
|
});
|
|
10559
10659
|
|
|
10660
|
+
// dist/branch-metadata-file.js
|
|
10661
|
+
import fs28 from "node:fs/promises";
|
|
10662
|
+
import path31 from "node:path";
|
|
10663
|
+
async function readBranchMetadataFile2(branchRoot) {
|
|
10664
|
+
const resolvedRoot = path31.resolve(branchRoot);
|
|
10665
|
+
try {
|
|
10666
|
+
const raw = await fs28.readFile(branchMetadataFilePath2(resolvedRoot), "utf8");
|
|
10667
|
+
return JSON.parse(raw);
|
|
10668
|
+
} catch (err) {
|
|
10669
|
+
if (isNotFoundError(err)) {
|
|
10670
|
+
return null;
|
|
10671
|
+
}
|
|
10672
|
+
if (err instanceof SyntaxError) {
|
|
10673
|
+
throw new BranchMetadataCorruptError2(resolvedRoot, err.message);
|
|
10674
|
+
}
|
|
10675
|
+
throw err;
|
|
10676
|
+
}
|
|
10677
|
+
}
|
|
10678
|
+
var BRANCH_META_DIR2, BRANCH_META_FILE2, BranchMetadataCorruptError2, branchMetadataFilePath2;
|
|
10679
|
+
var init_branch_metadata_file = __esm({
|
|
10680
|
+
"dist/branch-metadata-file.js"() {
|
|
10681
|
+
"use strict";
|
|
10682
|
+
init_error();
|
|
10683
|
+
BRANCH_META_DIR2 = ".canopy-meta";
|
|
10684
|
+
BRANCH_META_FILE2 = "branch.json";
|
|
10685
|
+
BranchMetadataCorruptError2 = class extends Error {
|
|
10686
|
+
constructor(branchRoot, cause) {
|
|
10687
|
+
super(`Corrupt branch metadata in '${branchRoot}': ${cause}`);
|
|
10688
|
+
this.name = "BranchMetadataCorruptError";
|
|
10689
|
+
this.branchRoot = branchRoot;
|
|
10690
|
+
this.parseCause = cause;
|
|
10691
|
+
}
|
|
10692
|
+
};
|
|
10693
|
+
branchMetadataFilePath2 = (branchRoot) => path31.join(path31.resolve(branchRoot), BRANCH_META_DIR2, BRANCH_META_FILE2);
|
|
10694
|
+
}
|
|
10695
|
+
});
|
|
10696
|
+
|
|
10560
10697
|
// dist/branch-registry.js
|
|
10561
|
-
import
|
|
10562
|
-
import
|
|
10698
|
+
import fs29 from "node:fs/promises";
|
|
10699
|
+
import path33 from "node:path";
|
|
10563
10700
|
var log12, REGISTRY_FILE2, REGISTRY_STALE_FILE2, REGISTRY_TEMP_FILE2, REGISTRY_VERSION2, RESOURCE4, GET_MISS_REFRESH_MIN_INTERVAL_MS2, BranchRegistry2;
|
|
10564
10701
|
var init_branch_registry = __esm({
|
|
10565
10702
|
"dist/branch-registry.js"() {
|
|
10566
10703
|
"use strict";
|
|
10567
|
-
|
|
10704
|
+
init_branch_metadata_file();
|
|
10568
10705
|
init_error();
|
|
10569
10706
|
init_debug2();
|
|
10570
10707
|
init_logger3();
|
|
@@ -10580,10 +10717,10 @@ var init_branch_registry = __esm({
|
|
|
10580
10717
|
constructor(root) {
|
|
10581
10718
|
this.regenInFlight = null;
|
|
10582
10719
|
this.lastForcedRefreshMs = 0;
|
|
10583
|
-
this.root =
|
|
10584
|
-
this.registryPath =
|
|
10585
|
-
this.stalePath =
|
|
10586
|
-
this.tempPath =
|
|
10720
|
+
this.root = path33.resolve(root);
|
|
10721
|
+
this.registryPath = path33.join(this.root, REGISTRY_FILE2);
|
|
10722
|
+
this.stalePath = path33.join(this.root, REGISTRY_STALE_FILE2);
|
|
10723
|
+
this.tempPath = path33.join(this.root, REGISTRY_TEMP_FILE2);
|
|
10587
10724
|
}
|
|
10588
10725
|
/**
|
|
10589
10726
|
* Returns all branches. Uses the cached snapshot if its embedded generation
|
|
@@ -10592,7 +10729,7 @@ var init_branch_registry = __esm({
|
|
|
10592
10729
|
async list() {
|
|
10593
10730
|
let parsed;
|
|
10594
10731
|
try {
|
|
10595
|
-
const raw = await
|
|
10732
|
+
const raw = await fs29.readFile(this.registryPath, "utf8");
|
|
10596
10733
|
parsed = JSON.parse(raw);
|
|
10597
10734
|
} catch (err) {
|
|
10598
10735
|
if (isNotFoundError(err)) {
|
|
@@ -10681,7 +10818,7 @@ var init_branch_registry = __esm({
|
|
|
10681
10818
|
async performRegenerate() {
|
|
10682
10819
|
const read = await readResourceGeneration2(this.root, RESOURCE4);
|
|
10683
10820
|
const branches = await this.scanBranchDirectories();
|
|
10684
|
-
await
|
|
10821
|
+
await fs29.unlink(this.stalePath).catch(() => {
|
|
10685
10822
|
});
|
|
10686
10823
|
if (!read.ok) {
|
|
10687
10824
|
return branches;
|
|
@@ -10692,12 +10829,12 @@ var init_branch_registry = __esm({
|
|
|
10692
10829
|
generation: read.token
|
|
10693
10830
|
};
|
|
10694
10831
|
const uniqueTempPath = `${this.tempPath}.${Date.now()}.${Math.random().toString(36).slice(2)}`;
|
|
10695
|
-
await
|
|
10696
|
-
await
|
|
10832
|
+
await fs29.mkdir(this.root, { recursive: true });
|
|
10833
|
+
await fs29.writeFile(uniqueTempPath, JSON.stringify(snapshot, null, 2) + "\n", "utf8");
|
|
10697
10834
|
try {
|
|
10698
|
-
await
|
|
10835
|
+
await fs29.rename(uniqueTempPath, this.registryPath);
|
|
10699
10836
|
} catch (err) {
|
|
10700
|
-
await
|
|
10837
|
+
await fs29.unlink(uniqueTempPath).catch(() => {
|
|
10701
10838
|
});
|
|
10702
10839
|
throw err;
|
|
10703
10840
|
}
|
|
@@ -10711,15 +10848,15 @@ var init_branch_registry = __esm({
|
|
|
10711
10848
|
async scanBranchDirectories() {
|
|
10712
10849
|
const branches = [];
|
|
10713
10850
|
try {
|
|
10714
|
-
const entries = await
|
|
10851
|
+
const entries = await fs29.readdir(this.root, { withFileTypes: true });
|
|
10715
10852
|
for (const entry of entries) {
|
|
10716
10853
|
if (!entry.isDirectory() || entry.name.startsWith(".")) {
|
|
10717
10854
|
continue;
|
|
10718
10855
|
}
|
|
10719
|
-
const branchRoot =
|
|
10856
|
+
const branchRoot = path33.join(this.root, entry.name);
|
|
10720
10857
|
let meta;
|
|
10721
10858
|
try {
|
|
10722
|
-
meta = await
|
|
10859
|
+
meta = await readBranchMetadataFile2(branchRoot);
|
|
10723
10860
|
} catch (err) {
|
|
10724
10861
|
canopyLogWarn3(`CanopyCMS: Skipping branch directory '${entry.name}' during registry scan: ${getErrorMessage(err)}`);
|
|
10725
10862
|
continue;
|
|
@@ -10745,19 +10882,18 @@ var init_branch_registry = __esm({
|
|
|
10745
10882
|
});
|
|
10746
10883
|
|
|
10747
10884
|
// dist/branch-metadata.js
|
|
10748
|
-
import
|
|
10749
|
-
import
|
|
10750
|
-
var
|
|
10885
|
+
import fs30 from "node:fs/promises";
|
|
10886
|
+
import path34 from "node:path";
|
|
10887
|
+
var CURRENT_SCHEMA_VERSION2, BranchMetadataConflictError2, BranchMetadataFileManager2;
|
|
10751
10888
|
var init_branch_metadata = __esm({
|
|
10752
10889
|
"dist/branch-metadata.js"() {
|
|
10753
10890
|
"use strict";
|
|
10754
10891
|
init_branch_registry();
|
|
10892
|
+
init_branch_metadata_file();
|
|
10755
10893
|
init_paths();
|
|
10756
10894
|
init_error();
|
|
10757
10895
|
init_async_mutex();
|
|
10758
10896
|
init_occ_json_write();
|
|
10759
|
-
BRANCH_META_DIR2 = ".canopy-meta";
|
|
10760
|
-
BRANCH_META_FILE2 = "branch.json";
|
|
10761
10897
|
CURRENT_SCHEMA_VERSION2 = 1;
|
|
10762
10898
|
BranchMetadataConflictError2 = class extends Error {
|
|
10763
10899
|
constructor(message = "Concurrent modification detected in branch metadata") {
|
|
@@ -10765,18 +10901,10 @@ var init_branch_metadata = __esm({
|
|
|
10765
10901
|
this.name = "BranchMetadataConflictError";
|
|
10766
10902
|
}
|
|
10767
10903
|
};
|
|
10768
|
-
BranchMetadataCorruptError2 = class extends Error {
|
|
10769
|
-
constructor(branchRoot, cause) {
|
|
10770
|
-
super(`Corrupt branch metadata in '${branchRoot}': ${cause}`);
|
|
10771
|
-
this.name = "BranchMetadataCorruptError";
|
|
10772
|
-
this.branchRoot = branchRoot;
|
|
10773
|
-
this.parseCause = cause;
|
|
10774
|
-
}
|
|
10775
|
-
};
|
|
10776
10904
|
BranchMetadataFileManager2 = class _BranchMetadataFileManager2 {
|
|
10777
10905
|
constructor(branchRoot, baseRoot, options) {
|
|
10778
|
-
this.branchRoot =
|
|
10779
|
-
this.filePath =
|
|
10906
|
+
this.branchRoot = path34.resolve(branchRoot);
|
|
10907
|
+
this.filePath = path34.join(this.branchRoot, BRANCH_META_DIR2, BRANCH_META_FILE2);
|
|
10780
10908
|
this.baseRoot = baseRoot;
|
|
10781
10909
|
this.settleMs = options?.settleMs;
|
|
10782
10910
|
}
|
|
@@ -10785,20 +10913,7 @@ var init_branch_metadata = __esm({
|
|
|
10785
10913
|
* Use this for read-only access (e.g., in registry scanning or loadBranchContext).
|
|
10786
10914
|
*/
|
|
10787
10915
|
static async loadOnly(branchRoot) {
|
|
10788
|
-
|
|
10789
|
-
const filePath = path31.join(resolvedRoot, BRANCH_META_DIR2, BRANCH_META_FILE2);
|
|
10790
|
-
try {
|
|
10791
|
-
const raw = await fs28.readFile(filePath, "utf8");
|
|
10792
|
-
return JSON.parse(raw);
|
|
10793
|
-
} catch (err) {
|
|
10794
|
-
if (isNotFoundError(err)) {
|
|
10795
|
-
return null;
|
|
10796
|
-
}
|
|
10797
|
-
if (err instanceof SyntaxError) {
|
|
10798
|
-
throw new BranchMetadataCorruptError2(resolvedRoot, err.message);
|
|
10799
|
-
}
|
|
10800
|
-
throw err;
|
|
10801
|
-
}
|
|
10916
|
+
return readBranchMetadataFile2(branchRoot);
|
|
10802
10917
|
}
|
|
10803
10918
|
/**
|
|
10804
10919
|
* Get a BranchMetadataFileManager instance configured for registry invalidation.
|
|
@@ -10809,7 +10924,7 @@ var init_branch_metadata = __esm({
|
|
|
10809
10924
|
}
|
|
10810
10925
|
async load() {
|
|
10811
10926
|
try {
|
|
10812
|
-
const raw = await
|
|
10927
|
+
const raw = await fs30.readFile(this.filePath, "utf8");
|
|
10813
10928
|
const parsed = JSON.parse(raw);
|
|
10814
10929
|
const version = parsed.version ?? 0;
|
|
10815
10930
|
return { meta: parsed, version };
|
|
@@ -10871,7 +10986,7 @@ var init_branch_metadata = __esm({
|
|
|
10871
10986
|
*/
|
|
10872
10987
|
async save(incoming) {
|
|
10873
10988
|
try {
|
|
10874
|
-
await
|
|
10989
|
+
await fs30.stat(this.branchRoot);
|
|
10875
10990
|
} catch (err) {
|
|
10876
10991
|
if (isNotFoundError(err)) {
|
|
10877
10992
|
throw new BranchMetadataConflictError2("Branch no longer exists");
|
|
@@ -10946,8 +11061,8 @@ __export(migrate_exports, {
|
|
|
10946
11061
|
migrate: () => migrate,
|
|
10947
11062
|
slugifyName: () => slugifyName
|
|
10948
11063
|
});
|
|
10949
|
-
import
|
|
10950
|
-
import
|
|
11064
|
+
import fs31 from "node:fs/promises";
|
|
11065
|
+
import path35 from "node:path";
|
|
10951
11066
|
import * as p3 from "@clack/prompts";
|
|
10952
11067
|
function slugifyName(name) {
|
|
10953
11068
|
const slug = name.toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -10972,7 +11087,7 @@ function stripDirIdSuffix(dirName) {
|
|
|
10972
11087
|
return dirName;
|
|
10973
11088
|
}
|
|
10974
11089
|
async function planDirectory(dirPath, isRoot, ctx) {
|
|
10975
|
-
const entries = await
|
|
11090
|
+
const entries = await fs31.readdir(dirPath, { withFileTypes: true });
|
|
10976
11091
|
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
10977
11092
|
const ops = [];
|
|
10978
11093
|
let hasOwnFiles = false;
|
|
@@ -10981,7 +11096,7 @@ async function planDirectory(dirPath, isRoot, ctx) {
|
|
|
10981
11096
|
if (entry.name.startsWith("."))
|
|
10982
11097
|
continue;
|
|
10983
11098
|
if (entry.isDirectory()) {
|
|
10984
|
-
const subPath =
|
|
11099
|
+
const subPath = path35.join(dirPath, entry.name);
|
|
10985
11100
|
const sub = await planDirectory(subPath, false, ctx);
|
|
10986
11101
|
if (!sub.hasContent)
|
|
10987
11102
|
continue;
|
|
@@ -10989,10 +11104,10 @@ async function planDirectory(dirPath, isRoot, ctx) {
|
|
|
10989
11104
|
ops.push(...sub.ops);
|
|
10990
11105
|
if (!extractIdFromFilename2(entry.name)) {
|
|
10991
11106
|
const newName = `${toValidSlug(entry.name)}.${generateId2()}`;
|
|
10992
|
-
ops.push({ kind: "rename", from: subPath, to:
|
|
11107
|
+
ops.push({ kind: "rename", from: subPath, to: path35.join(dirPath, newName) });
|
|
10993
11108
|
}
|
|
10994
11109
|
} else if (entry.isFile()) {
|
|
10995
|
-
const ext =
|
|
11110
|
+
const ext = path35.extname(entry.name).slice(1);
|
|
10996
11111
|
const matchesFormat = ext === ctx.format || ctx.format === "yaml" && ext === "yml";
|
|
10997
11112
|
if (!matchesFormat)
|
|
10998
11113
|
continue;
|
|
@@ -11003,17 +11118,17 @@ async function planDirectory(dirPath, isRoot, ctx) {
|
|
|
11003
11118
|
const newName = `${ctx.entryType}.${toValidSlug(base)}.${generateId2()}.${ext}`;
|
|
11004
11119
|
ops.push({
|
|
11005
11120
|
kind: "rename",
|
|
11006
|
-
from:
|
|
11007
|
-
to:
|
|
11121
|
+
from: path35.join(dirPath, entry.name),
|
|
11122
|
+
to: path35.join(dirPath, newName)
|
|
11008
11123
|
});
|
|
11009
11124
|
}
|
|
11010
11125
|
}
|
|
11011
11126
|
}
|
|
11012
11127
|
const needsMeta = isRoot ? hasOwnFiles : hasContent;
|
|
11013
|
-
const metaPath =
|
|
11128
|
+
const metaPath = path35.join(dirPath, ".collection.json");
|
|
11014
11129
|
if (needsMeta && !await filePathExists(metaPath)) {
|
|
11015
11130
|
const entryTypeMeta = { name: ctx.entryType, format: ctx.format, schema: ctx.schema };
|
|
11016
|
-
const meta = isRoot ? { entries: [entryTypeMeta] } : { name: toValidSlug(stripDirIdSuffix(
|
|
11131
|
+
const meta = isRoot ? { entries: [entryTypeMeta] } : { name: toValidSlug(stripDirIdSuffix(path35.basename(dirPath))), entries: [entryTypeMeta] };
|
|
11017
11132
|
ops.push({ kind: "write", filePath: metaPath, content: JSON.stringify(meta, null, 2) + "\n" });
|
|
11018
11133
|
}
|
|
11019
11134
|
return { ops, hasContent };
|
|
@@ -11022,7 +11137,7 @@ async function migrate(options) {
|
|
|
11022
11137
|
const { projectDir } = options;
|
|
11023
11138
|
const contentRoot = options.contentRoot || "content";
|
|
11024
11139
|
p3.intro("CanopyCMS migrate");
|
|
11025
|
-
const contentDir =
|
|
11140
|
+
const contentDir = path35.join(projectDir, contentRoot);
|
|
11026
11141
|
if (!await filePathExists(contentDir)) {
|
|
11027
11142
|
throw new MigrateError(`Content directory not found: ${contentRoot}/ (expected at ${contentDir})`);
|
|
11028
11143
|
}
|
|
@@ -11099,14 +11214,14 @@ async function migrate(options) {
|
|
|
11099
11214
|
const applyOps = async () => {
|
|
11100
11215
|
for (const op of ops) {
|
|
11101
11216
|
if (op.kind === "rename") {
|
|
11102
|
-
await
|
|
11217
|
+
await fs31.rename(op.from, op.to);
|
|
11103
11218
|
} else {
|
|
11104
|
-
await
|
|
11219
|
+
await fs31.writeFile(op.filePath, op.content, "utf-8");
|
|
11105
11220
|
}
|
|
11106
11221
|
}
|
|
11107
11222
|
};
|
|
11108
11223
|
if (isBranchClone) {
|
|
11109
|
-
const lockPath =
|
|
11224
|
+
const lockPath = path35.join(projectDir, ".canopy-meta", "schema");
|
|
11110
11225
|
try {
|
|
11111
11226
|
await withLock2(lockPath, () => withOccFileLock2(lockPath, applyOps));
|
|
11112
11227
|
} catch (err) {
|
|
@@ -11154,7 +11269,7 @@ var init_migrate = __esm({
|
|
|
11154
11269
|
this.name = "MigrateError";
|
|
11155
11270
|
}
|
|
11156
11271
|
};
|
|
11157
|
-
describeOp = (op, baseDir) => op.kind === "rename" ? `rename ${
|
|
11272
|
+
describeOp = (op, baseDir) => op.kind === "rename" ? `rename ${path35.relative(baseDir, op.from)} \u2192 ${path35.basename(op.to)}` : `create ${path35.relative(baseDir, op.filePath)}`;
|
|
11158
11273
|
}
|
|
11159
11274
|
});
|
|
11160
11275
|
|