canopycms 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -47
- package/dist/ai/json-to-markdown.js +2 -0
- package/dist/ai/resolve-branch.js +2 -1
- package/dist/api/branch-entry-changes.js +2 -4
- package/dist/authorization/branch.d.ts +10 -4
- package/dist/authorization/branch.js +24 -4
- package/dist/authorization/types.d.ts +1 -1
- package/dist/cli/cli.js +1984 -1630
- package/dist/cli/configured-asset-store.js +2 -8
- package/dist/cli/generate-ai-content.js +86 -16
- package/dist/cli/init.js +1 -1
- package/dist/cli/site-config.d.ts +9 -0
- package/dist/cli/site-config.js +17 -0
- package/dist/cli/sync.js +226 -74
- package/dist/config/types.d.ts +4 -4
- package/dist/config/types.js +1 -1
- package/dist/config/validation.d.ts +9 -3
- package/dist/config/validation.js +30 -5
- package/dist/content-listing.js +2 -3
- package/dist/content-store.js +5 -5
- package/dist/editor/FormRenderer.js +5 -0
- package/dist/editor/components/EntryChangesDrawer.js +3 -2
- package/dist/editor/entry-diff.js +7 -1
- package/dist/editor/fields/DateField.d.ts +19 -0
- package/dist/editor/fields/DateField.js +53 -0
- package/dist/editor/fields/DateTimeField.d.ts +10 -0
- package/dist/editor/fields/DateTimeField.js +5 -15
- package/dist/editor/fields/list-item-title.js +2 -1
- package/dist/editor/theme.js +12 -4
- package/dist/entry-schema-registry.js +2 -1
- package/dist/services.d.ts +3 -2
- package/dist/static/index.js +5 -2
- package/dist/system-users.d.ts +2 -0
- package/dist/system-users.js +6 -1
- package/dist/utils/calendar-day.d.ts +23 -0
- package/dist/utils/calendar-day.js +102 -0
- package/dist/utils/content-serialize.d.ts +4 -2
- package/dist/utils/content-serialize.js +8 -6
- package/dist/validation/entry-validator.js +7 -1
- package/dist/validation/field-traversal.js +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
* site's own dependencies, which a release job given `--bucket` does not have.
|
|
5
5
|
*/
|
|
6
6
|
import path from 'node:path';
|
|
7
|
-
import { createJiti } from 'jiti';
|
|
8
7
|
import { z } from 'zod';
|
|
9
8
|
import { createAssetStore } from '../assets/factory.js';
|
|
10
9
|
import { mediaSchema } from '../config/schemas/media.js';
|
|
11
10
|
import { operatingStrategy } from '../operating-mode/index.js';
|
|
11
|
+
import { importSiteServerConfig } from './site-config.js';
|
|
12
12
|
const serverConfigSchema = z.object({
|
|
13
13
|
mode: z.enum(['prod', 'dev']),
|
|
14
14
|
media: mediaSchema.optional(),
|
|
@@ -19,13 +19,7 @@ const serverConfigSchema = z.object({
|
|
|
19
19
|
* local store, prod never does.
|
|
20
20
|
*/
|
|
21
21
|
export async function loadConfiguredAssetStore(projectDir) {
|
|
22
|
-
const configPath =
|
|
23
|
-
const jiti = createJiti(import.meta.url);
|
|
24
|
-
const mod = (await jiti.import(configPath));
|
|
25
|
-
const configExport = mod.default ?? mod.config ?? mod;
|
|
26
|
-
const server = typeof configExport === 'object' && configExport !== null && 'server' in configExport
|
|
27
|
-
? configExport.server
|
|
28
|
-
: configExport;
|
|
22
|
+
const { configPath, server } = await importSiteServerConfig(projectDir);
|
|
29
23
|
const parsed = serverConfigSchema.safeParse(server);
|
|
30
24
|
if (!parsed.success) {
|
|
31
25
|
throw new Error(`Invalid CanopyCMS config at ${configPath}: ${parsed.error.message}`);
|
|
@@ -68,7 +68,7 @@ var init_atomic_write = __esm({
|
|
|
68
68
|
|
|
69
69
|
// dist/validation/field-traversal.js
|
|
70
70
|
function isPlainRecord(value) {
|
|
71
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
71
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && !(value instanceof Date);
|
|
72
72
|
}
|
|
73
73
|
function resolveBlockItem(blockField, item) {
|
|
74
74
|
const templateName = typeof item.template === "string" ? item.template : typeof item._type === "string" ? item._type : void 0;
|
|
@@ -573,7 +573,7 @@ var scalarFieldTypes, markdownFieldTypes, primitiveFieldTypes, fieldTypes;
|
|
|
573
573
|
var init_types2 = __esm({
|
|
574
574
|
"dist/config/types.js"() {
|
|
575
575
|
"use strict";
|
|
576
|
-
scalarFieldTypes = ["string", "number", "boolean", "datetime", "code"];
|
|
576
|
+
scalarFieldTypes = ["string", "number", "boolean", "date", "datetime", "code"];
|
|
577
577
|
markdownFieldTypes = ["markdown", "mdx"];
|
|
578
578
|
primitiveFieldTypes = [...scalarFieldTypes, ...markdownFieldTypes];
|
|
579
579
|
fieldTypes = [
|
|
@@ -1640,6 +1640,70 @@ function isBlockStructuralKey(key) {
|
|
|
1640
1640
|
// dist/utils/content-serialize.js
|
|
1641
1641
|
init_item_keys();
|
|
1642
1642
|
|
|
1643
|
+
// dist/utils/calendar-day.js
|
|
1644
|
+
init_item_keys();
|
|
1645
|
+
init_field_traversal();
|
|
1646
|
+
var DAY = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
1647
|
+
var MS_PER_DAY = 864e5;
|
|
1648
|
+
var isRealDay = (year, month, day) => {
|
|
1649
|
+
const lastOfMonth = /* @__PURE__ */ new Date(0);
|
|
1650
|
+
lastOfMonth.setUTCFullYear(year, month, 0);
|
|
1651
|
+
return month >= 1 && month <= 12 && day >= 1 && day <= lastOfMonth.getUTCDate();
|
|
1652
|
+
};
|
|
1653
|
+
var dayParts = (value) => {
|
|
1654
|
+
const match = typeof value === "string" ? DAY.exec(value) : null;
|
|
1655
|
+
if (!match)
|
|
1656
|
+
return void 0;
|
|
1657
|
+
const [year, month, day] = match.slice(1).map(Number);
|
|
1658
|
+
return isRealDay(year, month, day) ? [year, month, day] : void 0;
|
|
1659
|
+
};
|
|
1660
|
+
var isCalendarDay = (value) => dayParts(value) !== void 0;
|
|
1661
|
+
var dayOfBareDate = (value) => value.getTime() % MS_PER_DAY === 0 ? value.toISOString().slice(0, 10) : void 0;
|
|
1662
|
+
var mapItems = (items, mapItem) => {
|
|
1663
|
+
const next = items.map((item) => isPlainRecord(item) ? mapItem(item) : item);
|
|
1664
|
+
return next.every((item, i) => item === items[i]) ? items : next;
|
|
1665
|
+
};
|
|
1666
|
+
var blockItemWithDays = (field, item) => {
|
|
1667
|
+
const resolved = resolveBlockItem(field, item);
|
|
1668
|
+
if (!resolved)
|
|
1669
|
+
return item;
|
|
1670
|
+
const data = withCalendarDays(resolved.fields, resolved.data);
|
|
1671
|
+
if (data === resolved.data)
|
|
1672
|
+
return item;
|
|
1673
|
+
return resolved.data === item ? data : { ...item, value: data };
|
|
1674
|
+
};
|
|
1675
|
+
var valueWithDays = (field, value) => {
|
|
1676
|
+
if (field.type === "date") {
|
|
1677
|
+
return value instanceof Date ? dayOfBareDate(value) ?? value : value;
|
|
1678
|
+
}
|
|
1679
|
+
if (field.type === "object") {
|
|
1680
|
+
const { fields, list } = field;
|
|
1681
|
+
if (list) {
|
|
1682
|
+
return Array.isArray(value) ? mapItems(value, (item) => withCalendarDays(fields ?? [], item)) : value;
|
|
1683
|
+
}
|
|
1684
|
+
return isPlainRecord(value) ? withCalendarDays(fields ?? [], value) : value;
|
|
1685
|
+
}
|
|
1686
|
+
if (field.type === "block" && Array.isArray(value)) {
|
|
1687
|
+
return mapItems(value, (item) => blockItemWithDays(field, item));
|
|
1688
|
+
}
|
|
1689
|
+
return value;
|
|
1690
|
+
};
|
|
1691
|
+
function withCalendarDays(fields, data) {
|
|
1692
|
+
let out = data;
|
|
1693
|
+
for (const field of fields) {
|
|
1694
|
+
if (field.type === "group") {
|
|
1695
|
+
out = withCalendarDays(field.fields, out);
|
|
1696
|
+
continue;
|
|
1697
|
+
}
|
|
1698
|
+
const value = out[field.name];
|
|
1699
|
+
const next = valueWithDays(field, value);
|
|
1700
|
+
if (next !== value)
|
|
1701
|
+
out = { ...out, [field.name]: next };
|
|
1702
|
+
}
|
|
1703
|
+
return out;
|
|
1704
|
+
}
|
|
1705
|
+
var entryAsRead = (fields, data) => withItemKeys(fields, withCalendarDays(fields, data), "content");
|
|
1706
|
+
|
|
1643
1707
|
// dist/utils/debug.js
|
|
1644
1708
|
var envFlag = (name) => typeof process !== "undefined" && process.env[name] === "true";
|
|
1645
1709
|
var LOG_LEVELS = {
|
|
@@ -3235,9 +3299,9 @@ function asJson(value) {
|
|
|
3235
3299
|
const key = identityKey(value);
|
|
3236
3300
|
return key === void 0 || key === "undefined" ? void 0 : JSON.parse(key);
|
|
3237
3301
|
}
|
|
3238
|
-
function frontmatterAsRead(raw) {
|
|
3302
|
+
function frontmatterAsRead(raw, fields) {
|
|
3239
3303
|
try {
|
|
3240
|
-
return asJson(matter(raw, {}).data);
|
|
3304
|
+
return asJson(withCalendarDays(fields, matter(raw, {}).data));
|
|
3241
3305
|
} catch {
|
|
3242
3306
|
return void 0;
|
|
3243
3307
|
}
|
|
@@ -3305,7 +3369,7 @@ ${asKey ? `${text}: v` : `v: ${text}`}
|
|
|
3305
3369
|
return true;
|
|
3306
3370
|
}
|
|
3307
3371
|
}
|
|
3308
|
-
function serializeFrontmatter(editorBody, data, existingRaw, format) {
|
|
3372
|
+
function serializeFrontmatter(editorBody, data, existingRaw, format, fields = []) {
|
|
3309
3373
|
const file = { content: bodyToWrite(editorBody, data, format, existingRaw) };
|
|
3310
3374
|
if (existingRaw === void 0)
|
|
3311
3375
|
return matter.stringify(file, data);
|
|
@@ -3313,7 +3377,7 @@ function serializeFrontmatter(editorBody, data, existingRaw, format) {
|
|
|
3313
3377
|
if (existingFrontmatter === void 0)
|
|
3314
3378
|
return matter.stringify(file, data);
|
|
3315
3379
|
const onDisk = parseDocument2(existingFrontmatter);
|
|
3316
|
-
const toReconcile = onDisk.errors.length === 0 ? withUnchangedAsOnDisk(data, frontmatterAsRead(existingRaw), onDisk.toJS()) : data;
|
|
3380
|
+
const toReconcile = onDisk.errors.length === 0 ? withUnchangedAsOnDisk(data, frontmatterAsRead(existingRaw, fields), onDisk.toJS()) : data;
|
|
3317
3381
|
const reconciled = reconcileYamlSource(existingFrontmatter, isPlainRecord2(toReconcile) ? toReconcile : data, { rootAtColumnZero: true, quote: readsAsAnotherValue });
|
|
3318
3382
|
if (reconciled === void 0)
|
|
3319
3383
|
return matter.stringify(file, data);
|
|
@@ -3325,7 +3389,7 @@ function serializeFrontmatter(editorBody, data, existingRaw, format) {
|
|
|
3325
3389
|
}
|
|
3326
3390
|
}
|
|
3327
3391
|
});
|
|
3328
|
-
if (isDeepStrictEqual2(frontmatterAsRead(written), asJson(data)))
|
|
3392
|
+
if (isDeepStrictEqual2(frontmatterAsRead(written, fields), asJson(data)))
|
|
3329
3393
|
return written;
|
|
3330
3394
|
log2.debug("content-serialize", "reconciled frontmatter reads back differently; writing as gray-matter prints it");
|
|
3331
3395
|
return matter.stringify(file, data);
|
|
@@ -3639,9 +3703,6 @@ function linkOnlyReferenceData(data, format, fields) {
|
|
|
3639
3703
|
return rest;
|
|
3640
3704
|
}
|
|
3641
3705
|
|
|
3642
|
-
// dist/content-store.js
|
|
3643
|
-
init_item_keys();
|
|
3644
|
-
|
|
3645
3706
|
// dist/static/seo.js
|
|
3646
3707
|
var DEFAULT_SEO_FIELD_NAMES = {
|
|
3647
3708
|
title: "metaTitle",
|
|
@@ -5834,7 +5895,7 @@ var ContentStore = class {
|
|
|
5834
5895
|
absolutePath
|
|
5835
5896
|
};
|
|
5836
5897
|
}
|
|
5837
|
-
doc.data =
|
|
5898
|
+
doc.data = entryAsRead(fields, doc.data);
|
|
5838
5899
|
if (options.resolveReferences !== false) {
|
|
5839
5900
|
doc.data = await this.resolveReferencesInData(doc.data, fields, void 0, options.referenceAccess, {
|
|
5840
5901
|
entry: slug ? entryLogicalPath(schemaItem.logicalPath, slug) : schemaItem.logicalPath,
|
|
@@ -5938,7 +5999,7 @@ var ContentStore = class {
|
|
|
5938
5999
|
} else if (input.format === "yaml") {
|
|
5939
6000
|
content = serializeYaml(input.data ?? {}, existingRaw);
|
|
5940
6001
|
} else {
|
|
5941
|
-
content = serializeFrontmatter(input.body, input.data ?? {}, existingRaw, input.format);
|
|
6002
|
+
content = serializeFrontmatter(input.body, input.data ?? {}, existingRaw, input.format, fields);
|
|
5942
6003
|
}
|
|
5943
6004
|
await atomicWriteFile(absolutePath, content);
|
|
5944
6005
|
const liveIndex = this._idIndex;
|
|
@@ -6830,6 +6891,7 @@ _${field.description}_` : "";
|
|
|
6830
6891
|
switch (field.type) {
|
|
6831
6892
|
case "string":
|
|
6832
6893
|
case "number":
|
|
6894
|
+
case "date":
|
|
6833
6895
|
case "datetime":
|
|
6834
6896
|
return `${heading} ${label}${descriptionLine}
|
|
6835
6897
|
|
|
@@ -6908,6 +6970,7 @@ ${items.map((item) => `- ${item}`).join("\n")}`;
|
|
|
6908
6970
|
var FLAT_CELL_TYPES = /* @__PURE__ */ new Set([
|
|
6909
6971
|
"string",
|
|
6910
6972
|
"number",
|
|
6973
|
+
"date",
|
|
6911
6974
|
"datetime",
|
|
6912
6975
|
"boolean",
|
|
6913
6976
|
"select",
|
|
@@ -10937,6 +11000,9 @@ async function loadOrCreateBranchContext(options) {
|
|
|
10937
11000
|
});
|
|
10938
11001
|
}
|
|
10939
11002
|
|
|
11003
|
+
// dist/system-users.js
|
|
11004
|
+
var AI_USER_ID = "canopycms-ai";
|
|
11005
|
+
|
|
10940
11006
|
// dist/ai/resolve-branch.js
|
|
10941
11007
|
async function resolveBranchRoot(config) {
|
|
10942
11008
|
if (readsFromCheckout(config)) {
|
|
@@ -10955,7 +11021,7 @@ async function resolveBranchRoot(config) {
|
|
|
10955
11021
|
config: resolved,
|
|
10956
11022
|
branchName: activeBranch,
|
|
10957
11023
|
mode: config.mode,
|
|
10958
|
-
createdBy:
|
|
11024
|
+
createdBy: AI_USER_ID,
|
|
10959
11025
|
remoteUrl: config.defaultRemoteUrl
|
|
10960
11026
|
});
|
|
10961
11027
|
return context.branchRoot;
|
|
@@ -10967,7 +11033,6 @@ import path24 from "node:path";
|
|
|
10967
11033
|
import matter3 from "gray-matter";
|
|
10968
11034
|
import { parse as yamlParse2 } from "yaml";
|
|
10969
11035
|
init_error();
|
|
10970
|
-
init_item_keys();
|
|
10971
11036
|
var log10 = createDebugLogger({ prefix: "ContentListing" });
|
|
10972
11037
|
var normalizePath = (root, target) => {
|
|
10973
11038
|
const result = validateAndNormalizePath(root, target);
|
|
@@ -11138,7 +11203,7 @@ var listCollectionEntries = async (root, collection, onSkip, readsId) => {
|
|
|
11138
11203
|
fs20.stat(absolutePath),
|
|
11139
11204
|
readEntryData(absolutePath, format, bodyField)
|
|
11140
11205
|
]);
|
|
11141
|
-
const data =
|
|
11206
|
+
const data = entryAsRead(entryType?.schema ?? [], rawData);
|
|
11142
11207
|
const item = {
|
|
11143
11208
|
logicalPath: entryLogicalPath(collection.logicalPath, slug),
|
|
11144
11209
|
contentId,
|
|
@@ -11251,6 +11316,11 @@ function validateScalar(field, value, path27) {
|
|
|
11251
11316
|
return [{ fieldPath: path27, message: "Expected true or false" }];
|
|
11252
11317
|
}
|
|
11253
11318
|
return [];
|
|
11319
|
+
case "date":
|
|
11320
|
+
if (value !== "" && !isCalendarDay(value)) {
|
|
11321
|
+
return [{ fieldPath: path27, message: "Expected a real date, written YYYY-MM-DD" }];
|
|
11322
|
+
}
|
|
11323
|
+
return [];
|
|
11254
11324
|
case "datetime":
|
|
11255
11325
|
if (typeof value !== "string" || value !== "" && Number.isNaN(Date.parse(value))) {
|
|
11256
11326
|
return [{ fieldPath: path27, message: "Expected a valid date/time string" }];
|
|
@@ -11405,7 +11475,7 @@ function findInvalidEntries(items) {
|
|
|
11405
11475
|
for (const item of items) {
|
|
11406
11476
|
if (!item.schema)
|
|
11407
11477
|
continue;
|
|
11408
|
-
const data = normalizeDatesDeep(item.data);
|
|
11478
|
+
const data = normalizeDatesDeep(withCalendarDays(item.schema, item.data));
|
|
11409
11479
|
const errors = validateEntryData(item.schema, data);
|
|
11410
11480
|
if (errors.length > 0) {
|
|
11411
11481
|
invalid.push({ entryPath: item.entryPath, errors });
|
package/dist/cli/init.js
CHANGED
|
@@ -26,7 +26,7 @@ var scalarFieldTypes, markdownFieldTypes, primitiveFieldTypes, fieldTypes;
|
|
|
26
26
|
var init_types = __esm({
|
|
27
27
|
"dist/config/types.js"() {
|
|
28
28
|
"use strict";
|
|
29
|
-
scalarFieldTypes = ["string", "number", "boolean", "datetime", "code"];
|
|
29
|
+
scalarFieldTypes = ["string", "number", "boolean", "date", "datetime", "code"];
|
|
30
30
|
markdownFieldTypes = ["markdown", "mdx"];
|
|
31
31
|
primitiveFieldTypes = [...scalarFieldTypes, ...markdownFieldTypes];
|
|
32
32
|
fieldTypes = [
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports a site's `canopycms.config.ts` for CLI commands that need it (`sync`, and
|
|
3
|
+
* `configured-asset-store.ts`). Importing runs the site's config and loads its own dependencies.
|
|
4
|
+
*/
|
|
5
|
+
/** The site's server config export (`server` of a split config, else the export itself). */
|
|
6
|
+
export declare function importSiteServerConfig(projectDir: string): Promise<{
|
|
7
|
+
configPath: string;
|
|
8
|
+
server: unknown;
|
|
9
|
+
}>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports a site's `canopycms.config.ts` for CLI commands that need it (`sync`, and
|
|
3
|
+
* `configured-asset-store.ts`). Importing runs the site's config and loads its own dependencies.
|
|
4
|
+
*/
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { createJiti } from 'jiti';
|
|
7
|
+
/** The site's server config export (`server` of a split config, else the export itself). */
|
|
8
|
+
export async function importSiteServerConfig(projectDir) {
|
|
9
|
+
const configPath = path.join(projectDir, 'canopycms.config.ts');
|
|
10
|
+
const jiti = createJiti(import.meta.url);
|
|
11
|
+
const mod = (await jiti.import(configPath));
|
|
12
|
+
const configExport = mod.default ?? mod.config ?? mod;
|
|
13
|
+
const server = typeof configExport === 'object' && configExport !== null && 'server' in configExport
|
|
14
|
+
? configExport.server
|
|
15
|
+
: configExport;
|
|
16
|
+
return { configPath, server };
|
|
17
|
+
}
|