@lupinum/ginko 0.4.0 → 0.6.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 +161 -0
- package/README.md +2 -2
- package/dist/basics.d.mts +492 -0
- package/dist/basics.d.ts +492 -0
- package/dist/basics.mjs +416 -0
- package/dist/cli/index.mjs +59 -56
- package/dist/index.d.mts +5 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.mjs +3 -3
- package/dist/internal/search.mjs +6 -3
- package/dist/internal.d.mts +387 -41
- package/dist/internal.d.ts +387 -41
- package/dist/internal.mjs +267 -101
- package/dist/nuxt/module.d.mts +2 -2
- package/dist/nuxt/module.d.ts +2 -2
- package/dist/nuxt/module.mjs +108 -15
- package/dist/nuxt/runtime/GinkoImage.d.mts +1 -1
- package/dist/nuxt/runtime/GinkoMarkdown.mjs +2 -2
- package/dist/nuxt/runtime/composables.d.mts +4 -3
- package/dist/nuxt/runtime/composables.mjs +19 -15
- package/dist/nuxt/runtime/fetch.d.mts +3 -1
- package/dist/nuxt/runtime/fetch.mjs +12 -6
- package/dist/nuxt/runtime/markdown.d.mts +3 -0
- package/dist/nuxt/runtime/markdown.mjs +15 -3
- package/dist/nuxt/runtime/plugin.mjs +6 -35
- package/dist/nuxt/runtime/preview-listener.d.mts +13 -0
- package/dist/nuxt/runtime/preview-listener.mjs +37 -0
- package/dist/nuxt/runtime/preview.vue +3 -1
- package/dist/nuxt/runtime/schema.d.ts +6 -0
- package/dist/nuxt/runtime/server/bundles.d.ts +4 -0
- package/dist/nuxt/runtime/server/files-md.d.ts +2 -0
- package/dist/nuxt/runtime/server/files.d.ts +2 -0
- package/dist/nuxt/runtime/server/headers.d.ts +2 -0
- package/dist/nuxt/runtime/server/items.d.mts +12 -0
- package/dist/nuxt/runtime/server/items.d.ts +12 -0
- package/dist/nuxt/runtime/server/items.mjs +17 -0
- package/dist/nuxt/runtime/server/llms-full.d.ts +2 -0
- package/dist/nuxt/runtime/server/llms.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown-trees.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown.d.ts +2 -0
- package/dist/nuxt/runtime/server/markdown.mjs +1 -1
- package/dist/nuxt/runtime/server/platform-md.d.mts +3 -0
- package/dist/nuxt/runtime/server/platform-md.d.ts +3 -0
- package/dist/nuxt/runtime/server/platform-md.mjs +71 -0
- package/dist/nuxt/runtime/server/redirects.d.ts +2 -0
- package/dist/nuxt/runtime/server/robots.d.ts +2 -0
- package/dist/nuxt/runtime/server/search.d.ts +2 -0
- package/dist/nuxt/runtime/server/site-config.d.ts +14 -0
- package/dist/nuxt/runtime/server/sitemap-source.d.ts +2 -0
- package/dist/nuxt/runtime/server/sitemap-xml.d.ts +24 -0
- package/dist/nuxt/runtime/server/sitemap.d.ts +2 -0
- package/dist/nuxt/runtime/shared.d.mts +10 -3
- package/dist/nuxt/runtime/shared.mjs +2 -2
- package/dist/shared/{ginko.BBkZW2nW.d.ts → ginko.6cI8KXZP.d.mts} +2 -2
- package/dist/shared/{ginko.CrNRBkS5.mjs → ginko.B1-DoOea.mjs} +253 -309
- package/dist/shared/ginko.BNVC-g9n.mjs +10389 -0
- package/dist/shared/{ginko.Bwan3BB-.d.mts → ginko.B_pACpVU.d.ts} +2 -2
- package/dist/shared/ginko.BaFweQcB.d.mts +91 -0
- package/dist/shared/ginko.BiDXgjLy.d.ts +91 -0
- package/dist/shared/ginko.C-oz8wwI.mjs +95 -0
- package/dist/shared/{ginko.BDF3bW0C.d.ts → ginko.CLzVaDi4.d.ts} +56 -5
- package/dist/shared/ginko.C_nq6vk7.d.mts +1622 -0
- package/dist/shared/ginko.CjWB8N7f.d.ts +128 -0
- package/dist/shared/ginko.D1PeQ3ds.mjs +36 -0
- package/dist/shared/ginko.D2oht0Kx.d.ts +1622 -0
- package/dist/shared/ginko.DCHGkQ7P.mjs +116 -0
- package/dist/shared/ginko.DIl7l-75.mjs +26 -0
- package/dist/shared/{ginko.akHewCGt.d.mts → ginko.DkIc0ZZk.d.mts} +56 -5
- package/dist/shared/{ginko.D-fNojn9.d.mts → ginko.DrkUeYFP.d.mts} +824 -10
- package/dist/shared/{ginko.D-fNojn9.d.ts → ginko.DrkUeYFP.d.ts} +824 -10
- package/dist/shared/ginko.WAfoMhv1.mjs +601 -0
- package/dist/shared/ginko.o1yIKlgj.d.mts +128 -0
- package/dist/shared/{ginko.Dht0xxE5.mjs → ginko.raCzOPYC.mjs} +1 -2
- package/dist/shared/{ginko.C90fPpMP.mjs → ginko.t-gbwzxh.mjs} +128 -101
- package/dist/shared/ginko.yKMIjJ68.mjs +84 -0
- package/dist/vue.d.mts +5 -4
- package/dist/vue.d.ts +5 -4
- package/dist/vue.mjs +1 -1
- package/i18n/ar.json +212 -0
- package/i18n/de.json +9 -1
- package/i18n/en.json +9 -1
- package/i18n/es.json +12 -4
- package/i18n/fr.json +212 -0
- package/i18n/it.json +212 -0
- package/package.json +7 -3
- package/dist/shared/ginko.BTUfZCfa.d.mts +0 -172
- package/dist/shared/ginko.C-QjDn3O.mjs +0 -4944
- package/dist/shared/ginko.CQvxhyez.mjs +0 -40
- package/dist/shared/ginko.Dc0oNcDv.d.ts +0 -172
- package/dist/shared/ginko.Mn30dmlm.mjs +0 -20
|
@@ -1,33 +1,10 @@
|
|
|
1
|
-
import { m as msg,
|
|
2
|
-
import {
|
|
1
|
+
import { m as msg, u as labelOf, i as isTranslatable, q as effectiveAlt, A as ALT_MAX, v as ALT_INVISIBLE, g as effectiveMax, x as isShown, a as parseMarkdown, y as effectiveProfile, j as bare, z as fileRefUses, D as DEFAULT_LOCALE, C as isLocale, n as sha256, M as MAX_LOCALES, E as isCurrency, F as coreMessage, G as componentProblems, H as labelProblem, I as MAX_CENTS, J as MARKDOWN_PROFILES, l as effectiveMin, K as profileCovers } from './ginko.BNVC-g9n.mjs';
|
|
2
|
+
import { r as roundedMb, C as CALENDAR_NAMES, h as holidaysOf, S as STORED_HOLIDAYS, l as limitOf, f as formatMb } from './ginko.D1PeQ3ds.mjs';
|
|
3
|
+
import { f as firstDayShown, w as workWeekOf, a as startOfWeek, D as DEFAULT_WEEK, e as weekInfoOf } from './ginko.C-oz8wwI.mjs';
|
|
3
4
|
|
|
4
5
|
const PREFILL_TOKENS = ["@today", "@workWeek", "@nextWorkWeek"];
|
|
5
6
|
const isPrefillToken = (value) => PREFILL_TOKENS.includes(value);
|
|
6
7
|
|
|
7
|
-
const MB = 1024 * 1024;
|
|
8
|
-
const UPLOAD_TYPES = {
|
|
9
|
-
"image/jpeg": { label: "JPEG", kind: "image", limit: 20 * MB },
|
|
10
|
-
"image/png": { label: "PNG", kind: "image", limit: 20 * MB },
|
|
11
|
-
"image/webp": { label: "WebP", kind: "image", limit: 20 * MB },
|
|
12
|
-
"image/heic": { label: "HEIC", kind: "image", limit: 20 * MB },
|
|
13
|
-
"application/pdf": { label: "PDF", kind: "file", limit: 10 * MB }
|
|
14
|
-
};
|
|
15
|
-
function acceptOf(kind) {
|
|
16
|
-
return typesOf(kind).join(",");
|
|
17
|
-
}
|
|
18
|
-
function limitOf(kind) {
|
|
19
|
-
return Math.max(...typesOf(kind).map((type) => UPLOAD_TYPES[type].limit));
|
|
20
|
-
}
|
|
21
|
-
function formatMb(bytes) {
|
|
22
|
-
return `${roundedMb(bytes)}`.replace(".", ",");
|
|
23
|
-
}
|
|
24
|
-
function roundedMb(bytes) {
|
|
25
|
-
return Math.round(bytes / MB * 10) / 10;
|
|
26
|
-
}
|
|
27
|
-
function typesOf(kind) {
|
|
28
|
-
return Object.keys(UPLOAD_TYPES).filter((type) => UPLOAD_TYPES[type].kind === kind);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
8
|
function staleOf(brick, data, today, week, planned = []) {
|
|
32
9
|
const weekly = weeklyTask(brick);
|
|
33
10
|
if (!weekly) return void 0;
|
|
@@ -43,6 +20,18 @@ function weeklyTask(brick) {
|
|
|
43
20
|
}
|
|
44
21
|
return void 0;
|
|
45
22
|
}
|
|
23
|
+
function weekStartField(brick) {
|
|
24
|
+
if (brick.plans) return brick.plans.field;
|
|
25
|
+
const weekly = weeklyTask(brick);
|
|
26
|
+
if (weekly) return weekly.field;
|
|
27
|
+
for (const task of brick.tasks ?? []) {
|
|
28
|
+
if (task.kind === "remove") continue;
|
|
29
|
+
for (const [field, token] of Object.entries(task.prefill ?? {})) {
|
|
30
|
+
if ((token === "@workWeek" || token === "@nextWorkWeek") && brick.fields[field]?.type === "date") return field;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return void 0;
|
|
34
|
+
}
|
|
46
35
|
function plannedWeeks(schema, singleKey, entriesOf) {
|
|
47
36
|
const weeks = [];
|
|
48
37
|
for (const [key, brick] of Object.entries(schema.bricks)) {
|
|
@@ -60,6 +49,32 @@ function plansThisWeek(schema, singleKey, task, entryBrick, data, thisWeek) {
|
|
|
60
49
|
if (single?.kind !== "single" || plan?.kind !== "collection" || plan.retired || plan.plans?.brick !== singleKey) return false;
|
|
61
50
|
return weeklyTask(single)?.task.id === task.id && data[plan.plans.field] === thisWeek;
|
|
62
51
|
}
|
|
52
|
+
function duePlan(brick, data, today) {
|
|
53
|
+
const plan = brick.planned;
|
|
54
|
+
if (!plan) return void 0;
|
|
55
|
+
const next = data[plan.next];
|
|
56
|
+
const from = data[plan.from];
|
|
57
|
+
return next !== void 0 && next !== null && typeof from === "string" && from <= today ? next : void 0;
|
|
58
|
+
}
|
|
59
|
+
function createAsks(brick, key, task) {
|
|
60
|
+
const field = Object.hasOwn(brick.fields, key) ? brick.fields[key] : void 0;
|
|
61
|
+
if (task?.fields && !task.fields.includes(key)) return false;
|
|
62
|
+
return !!field && field.type !== "slug" && key !== brick.planned?.next && key !== brick.planned?.from;
|
|
63
|
+
}
|
|
64
|
+
function liveFrom(schema, brickKey, data, changed, today) {
|
|
65
|
+
const brick = Object.hasOwn(schema.bricks, brickKey) ? schema.bricks[brickKey] : void 0;
|
|
66
|
+
if (!brick) return void 0;
|
|
67
|
+
if (brick.kind === "collection" && brick.plans) {
|
|
68
|
+
const start = data[brick.plans.field];
|
|
69
|
+
const first = typeof start === "string" ? firstDayShown(start, schema.week) : void 0;
|
|
70
|
+
return first && first > today ? first : void 0;
|
|
71
|
+
}
|
|
72
|
+
const plan = brick.planned;
|
|
73
|
+
if (!plan || !changed.length || !changed.every((root) => root === plan.next || root === plan.from)) return void 0;
|
|
74
|
+
const from = data[plan.from];
|
|
75
|
+
const next = data[plan.next];
|
|
76
|
+
return next !== void 0 && next !== null && typeof from === "string" && from > today ? from : void 0;
|
|
77
|
+
}
|
|
63
78
|
|
|
64
79
|
const replacements = { \u00E4: "ae", \u00F6: "oe", \u00FC: "ue", \u00DF: "ss", \u00C4: "ae", \u00D6: "oe", \u00DC: "ue" };
|
|
65
80
|
function slugify(input) {
|
|
@@ -67,6 +82,13 @@ function slugify(input) {
|
|
|
67
82
|
return slug || "eintrag";
|
|
68
83
|
}
|
|
69
84
|
const SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
85
|
+
function slugBases(locales, texts) {
|
|
86
|
+
const main = locales[0];
|
|
87
|
+
return Object.fromEntries(locales.map((locale) => {
|
|
88
|
+
const own = texts[locale] ?? "";
|
|
89
|
+
return [locale, locale === main ? slugify(own) : own ? slugify(own) : void 0];
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
70
92
|
function uniqueSlug(base, taken) {
|
|
71
93
|
if (!taken(base)) return base;
|
|
72
94
|
for (let n = 2; ; n++) {
|
|
@@ -75,6 +97,7 @@ function uniqueSlug(base, taken) {
|
|
|
75
97
|
}
|
|
76
98
|
}
|
|
77
99
|
|
|
100
|
+
const SHAPE_CODES = /* @__PURE__ */ new Set(["TYPE", "NOT_OBJECT", "NOT_LOCALIZED", "UNKNOWN_FIELD", "UNKNOWN_LOCALE", "UNKNOWN_BRICK"]);
|
|
78
101
|
const datePattern = /^\d{4}-\d{2}-\d{2}$/;
|
|
79
102
|
const timePattern = /^([01]\d|2[0-3]):[0-5]\d$/;
|
|
80
103
|
const rowIdPattern = /^[A-Za-z0-9_-]{10}$/;
|
|
@@ -83,11 +106,18 @@ async function validate(schema, brickKey, data, opts) {
|
|
|
83
106
|
if (!brick) return { errors: [issue("", "UNKNOWN_BRICK", "unknownBrick", { brick: brickKey })], warnings: [] };
|
|
84
107
|
const texts = [];
|
|
85
108
|
const issues = checkFields(brick.fields, data, "", { locales: schema.locales, main: schema.locales[0], texts, refs: opts.refs, fileAlt: opts.fileAlt });
|
|
86
|
-
for (const { field, text, path } of texts) {
|
|
87
|
-
const { problemMessages: problems } = await parseMarkdown(text, { profile: effectiveProfile(field), components: schema.components });
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
109
|
+
for (const { field, text, path, locale } of texts) {
|
|
110
|
+
const { problemMessages: problems, value } = await parseMarkdown(text, { profile: effectiveProfile(field), components: schema.components });
|
|
111
|
+
const label = shown(field, schema.locales[0]);
|
|
112
|
+
for (const problem of problems) {
|
|
113
|
+
issues.push(problem.line === void 0 ? issue(path, "MARKDOWN", "markdown", { label, problem: bare(problem) }) : issue(path, "MARKDOWN", "markdownAt", { label, line: problem.line, problem: bare(problem) }));
|
|
114
|
+
}
|
|
115
|
+
if (opts.uploads) issues.push(...fileRefIssues(value.nodes, path, label, locale, schema, opts.uploads, opts.fileAlt));
|
|
116
|
+
}
|
|
117
|
+
const rules = ruleIssues(schema, brick, data);
|
|
118
|
+
if (opts.mode === "restore") return { errors: issues.filter((i) => SHAPE_CODES.has(i.code)), warnings: [...issues.filter((i) => !SHAPE_CODES.has(i.code)), ...rules.map((r) => r.issue)] };
|
|
119
|
+
if (opts.mode === "release") return { errors: issues, warnings: rules.map((r) => r.issue) };
|
|
120
|
+
if (opts.mode === "create") return { errors: [...issues, ...rules.map((r) => r.issue)], warnings: [] };
|
|
91
121
|
const changed = opts.changed ?? /* @__PURE__ */ new Set();
|
|
92
122
|
const errors = [];
|
|
93
123
|
const warnings = [];
|
|
@@ -96,10 +126,59 @@ async function validate(schema, brickKey, data, opts) {
|
|
|
96
126
|
if (related || issue2.code === "UNKNOWN_FIELD") errors.push(issue2);
|
|
97
127
|
else warnings.push(issue2);
|
|
98
128
|
}
|
|
129
|
+
for (const { issue: issue2, touches } of rules) {
|
|
130
|
+
if (touches.some((field) => [...changed].some((c) => c === field || c.startsWith(`${field}.`)))) errors.push(issue2);
|
|
131
|
+
else warnings.push(issue2);
|
|
132
|
+
}
|
|
99
133
|
return { errors, warnings };
|
|
100
134
|
}
|
|
135
|
+
function ruleIssues(schema, brick, data) {
|
|
136
|
+
if (!isRecord$1(data)) return [];
|
|
137
|
+
const out = [];
|
|
138
|
+
const main = schema.locales[0];
|
|
139
|
+
const plan = brick.planned;
|
|
140
|
+
if (plan) {
|
|
141
|
+
const set = (key) => data[key] !== void 0 && data[key] !== null;
|
|
142
|
+
const missing = set(plan.next) && !set(plan.from) ? plan.from : set(plan.from) && !set(plan.next) ? plan.next : void 0;
|
|
143
|
+
const field = missing && Object.hasOwn(brick.fields, missing) ? brick.fields[missing] : void 0;
|
|
144
|
+
if (missing && field) out.push({ issue: issue(missing, "REQUIRED", "fieldMissing", { label: shown(field, main) }), touches: [plan.next, plan.from] });
|
|
145
|
+
}
|
|
146
|
+
for (const [name, field] of Object.entries(brick.fields)) {
|
|
147
|
+
if (field.type !== "time" || !field.after) continue;
|
|
148
|
+
const [start2, end] = [data[field.after], data[name]];
|
|
149
|
+
if (typeof start2 === "string" && typeof end === "string" && timePattern.test(start2) && timePattern.test(end) && end <= start2) {
|
|
150
|
+
out.push({ issue: issue(name, "TIME_ORDER", "timeOrder", { start: shown(brick.fields[field.after], main), end: shown(field, main) }), touches: [field.after, name] });
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const week = weekStartField(brick);
|
|
154
|
+
const start = week ? data[week] : void 0;
|
|
155
|
+
if (week && isDate(start) && startOfWeek(start, schema.week ?? DEFAULT_WEEK) !== start) {
|
|
156
|
+
out.push({ issue: issue(week, "NOT_WEEK_START", "notWeekStart", { label: shown(brick.fields[week], main) }), touches: [week] });
|
|
157
|
+
}
|
|
158
|
+
return out;
|
|
159
|
+
}
|
|
160
|
+
function fileRefIssues(nodes, path, label, locale, schema, uploads, fileAlt) {
|
|
161
|
+
const out = [];
|
|
162
|
+
const refs = fileRefUses(nodes, schema.components);
|
|
163
|
+
if (refs.some((ref) => !uploads(ref.id))) out.push(issue(path, "FILE_REF_MISSING", "fileRefMissing", { label }));
|
|
164
|
+
if (refs.some((ref) => ref.kind !== "link" && uploads(ref.id) === "file")) out.push(issue(path, "FILE_REF_NOT_IMAGE", "fileRefPdf", { label }));
|
|
165
|
+
const library = new Map(refs.filter((ref) => ref.kind === "img").map((ref) => [ref.node, fileAlt?.(ref.id)]));
|
|
166
|
+
let described = true;
|
|
167
|
+
eachNode(nodes, (node) => {
|
|
168
|
+
if (node[0] === "img" && !effectiveAlt({ [locale]: node[1].alt }, library.get(node), locale)) described = false;
|
|
169
|
+
});
|
|
170
|
+
if (!described) out.push(issue(path, "ALT_REQUIRED", "markdownImageAltMissing", { label }));
|
|
171
|
+
return out;
|
|
172
|
+
}
|
|
173
|
+
function eachNode(nodes, fn) {
|
|
174
|
+
for (const node of nodes) {
|
|
175
|
+
if (!Array.isArray(node) || typeof node[0] !== "string" || !isRecord$1(node[1])) continue;
|
|
176
|
+
fn(node);
|
|
177
|
+
eachNode(node.slice(2), fn);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
101
180
|
function checkFields(fields, data, path, ctx) {
|
|
102
|
-
if (!isRecord$
|
|
181
|
+
if (!isRecord$1(data)) return [issue(path, "NOT_OBJECT", "notObject")];
|
|
103
182
|
const issues = [];
|
|
104
183
|
for (const key of Object.keys(data)) {
|
|
105
184
|
if (key === "_id" && path) continue;
|
|
@@ -110,7 +189,7 @@ function checkFields(fields, data, path, ctx) {
|
|
|
110
189
|
const at = join(path, key);
|
|
111
190
|
const value = data[key];
|
|
112
191
|
if (value === void 0 || value === null) {
|
|
113
|
-
if (!field.optional) issues.push(issue(at, "REQUIRED", "fieldMissing", { label: shown(field, ctx.main) }));
|
|
192
|
+
if (!field.optional && isShown(field, data)) issues.push(issue(at, "REQUIRED", "fieldMissing", { label: shown(field, ctx.main) }));
|
|
114
193
|
continue;
|
|
115
194
|
}
|
|
116
195
|
issues.push(...checkField$1(field, value, at, ctx));
|
|
@@ -119,7 +198,7 @@ function checkFields(fields, data, path, ctx) {
|
|
|
119
198
|
}
|
|
120
199
|
function checkField$1(field, value, path, ctx) {
|
|
121
200
|
if (isTranslatable(field)) {
|
|
122
|
-
if (!isRecord$
|
|
201
|
+
if (!isRecord$1(value)) return [issue(path, "NOT_LOCALIZED", "notLocalized", { label: shown(field, ctx.main) })];
|
|
123
202
|
const out = [];
|
|
124
203
|
for (const locale of Object.keys(value)) {
|
|
125
204
|
if (!ctx.locales.includes(locale)) out.push(issue(join(path, locale), "UNKNOWN_LOCALE", "unknownLocale", { locale }));
|
|
@@ -131,14 +210,15 @@ function checkField$1(field, value, path, ctx) {
|
|
|
131
210
|
for (const locale of ctx.locales) {
|
|
132
211
|
const v = value[locale];
|
|
133
212
|
if (v === void 0 || v === null || v === "") continue;
|
|
134
|
-
out.push(...checkScalar(field, v, join(path, locale), locale
|
|
213
|
+
out.push(...checkScalar(field, v, join(path, locale), locale, ctx));
|
|
135
214
|
}
|
|
136
215
|
return out;
|
|
137
216
|
}
|
|
138
|
-
return checkScalar(field, value, path,
|
|
217
|
+
return checkScalar(field, value, path, void 0, ctx);
|
|
139
218
|
}
|
|
140
219
|
const CONTROL = /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/;
|
|
141
|
-
function checkScalar(field, value, path,
|
|
220
|
+
function checkScalar(field, value, path, locale, ctx) {
|
|
221
|
+
const isMain = locale === void 0 || locale === ctx?.main;
|
|
142
222
|
const label = shown(field, ctx?.main);
|
|
143
223
|
const fail = (code, key, params) => [issue(path, code, key, { label, ...params })];
|
|
144
224
|
switch (field.type) {
|
|
@@ -150,7 +230,7 @@ function checkScalar(field, value, path, isMain, ctx) {
|
|
|
150
230
|
if (!field.optional && isMain && value.trim() === "") return fail("REQUIRED", "fieldEmpty");
|
|
151
231
|
if (value.length > max) return fail("TOO_LONG", "tooLong", { max });
|
|
152
232
|
if (CONTROL.test(value)) return fail("FORMAT", "controlChars");
|
|
153
|
-
if (field.type === "markdown") ctx?.texts?.push({ field, text: value, path });
|
|
233
|
+
if (field.type === "markdown") ctx?.texts?.push({ field, text: value, path, locale: locale ?? ctx.main });
|
|
154
234
|
if (field.type === "text" && field.format && value !== "") {
|
|
155
235
|
const ok = field.format === "url" ? /^https?:\/\/[^\s]+$/i.test(value) : field.format === "email" ? /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) : /^\+?[0-9 ()/-]{5,30}$/.test(value) && (value.match(/[0-9]/g)?.length ?? 0) >= 5;
|
|
156
236
|
if (!ok) return fail("FORMAT", "badFormat");
|
|
@@ -166,7 +246,7 @@ function checkScalar(field, value, path, isMain, ctx) {
|
|
|
166
246
|
if (Math.abs(Math.round((value - field.min) / field.step) * field.step - (value - field.min)) > 1e-9) return fail("STEP", "badStep", { step: field.step });
|
|
167
247
|
return [];
|
|
168
248
|
case "money": {
|
|
169
|
-
if (!isRecord$
|
|
249
|
+
if (!isRecord$1(value) || !Number.isSafeInteger(value.amount) || Object.keys(value).some((k) => k !== "amount")) return fail("TYPE", "notMoney");
|
|
170
250
|
const cents = value.amount;
|
|
171
251
|
if (cents < (field.min ?? 0) || cents > effectiveMax(field)) return fail("RANGE", "moneyOutOfRange");
|
|
172
252
|
return [];
|
|
@@ -178,7 +258,7 @@ function checkScalar(field, value, path, isMain, ctx) {
|
|
|
178
258
|
case "time":
|
|
179
259
|
return typeof value === "string" && timePattern.test(value) ? [] : fail("TIME", "badTime");
|
|
180
260
|
case "daterange":
|
|
181
|
-
if (!isRecord$
|
|
261
|
+
if (!isRecord$1(value) || !isDate(value.from) || !isDate(value.to) || Object.keys(value).length !== 2) return fail("DATE", "badDateRange");
|
|
182
262
|
return value.from <= value.to ? [] : fail("DATE_ORDER", "dateRangeOrder");
|
|
183
263
|
case "choice":
|
|
184
264
|
return typeof value === "string" && field.options.some((o) => o.value === value) ? [] : fail("CHOICE", "badChoice");
|
|
@@ -205,7 +285,7 @@ function checkScalar(field, value, path, isMain, ctx) {
|
|
|
205
285
|
const key = field.unique;
|
|
206
286
|
const seen = /* @__PURE__ */ new Set();
|
|
207
287
|
for (const row of value) {
|
|
208
|
-
const v = isRecord$
|
|
288
|
+
const v = isRecord$1(row) ? row[key] : void 0;
|
|
209
289
|
if (typeof v !== "string") continue;
|
|
210
290
|
if (seen.has(v)) {
|
|
211
291
|
out.push(issue(path, "DUPLICATE", "duplicateRowValue", { label, value: v }));
|
|
@@ -216,7 +296,7 @@ function checkScalar(field, value, path, isMain, ctx) {
|
|
|
216
296
|
}
|
|
217
297
|
const ids = /* @__PURE__ */ new Set();
|
|
218
298
|
value.forEach((row, index) => {
|
|
219
|
-
const id = isRecord$
|
|
299
|
+
const id = isRecord$1(row) && typeof row._id === "string" ? row._id : void 0;
|
|
220
300
|
if (!id || !rowIdPattern.test(id)) {
|
|
221
301
|
out.push(issue(join(path, String(index)), "ROW_ID", "badRowId", { label, row: index + 1 }));
|
|
222
302
|
return;
|
|
@@ -231,12 +311,12 @@ function checkScalar(field, value, path, isMain, ctx) {
|
|
|
231
311
|
return out;
|
|
232
312
|
}
|
|
233
313
|
case "image":
|
|
234
|
-
if (!isRecord$
|
|
314
|
+
if (!isRecord$1(value) || typeof value.file !== "string" || !isRecord$1(value.alt) || Object.keys(value).some((k) => k !== "file" && k !== "alt")) return fail("TYPE", "notImage");
|
|
235
315
|
for (const l of Object.keys(value.alt)) if (!ctx.locales.includes(l)) return [issue(join(path, `alt.${l}`), "UNKNOWN_LOCALE", "unknownLocale", { locale: l })];
|
|
236
316
|
if (effectiveAlt(value.alt, ctx.fileAlt?.(value.file), ctx.main) === "") return [issue(join(path, "alt"), "ALT_REQUIRED", "altMissing", { label })];
|
|
237
|
-
return Object.entries(value.alt).
|
|
317
|
+
return Object.entries(value.alt).flatMap(([l, a]) => typeof a !== "string" || a.length > ALT_MAX ? [issue(join(path, `alt.${l}`), "TOO_LONG", "altTooLong", { label })] : ALT_INVISIBLE.test(a) ? [issue(join(path, `alt.${l}`), "FORMAT", "controlChars", { label })] : []);
|
|
238
318
|
case "file":
|
|
239
|
-
return isRecord$
|
|
319
|
+
return isRecord$1(value) && typeof value.file === "string" && Object.keys(value).length === 1 ? [] : fail("TYPE", "notFile");
|
|
240
320
|
}
|
|
241
321
|
}
|
|
242
322
|
function checkValue(schema, field, value, path) {
|
|
@@ -259,7 +339,7 @@ function fileProblem(field, file) {
|
|
|
259
339
|
return void 0;
|
|
260
340
|
}
|
|
261
341
|
const shown = (field, main) => labelOf(field.label, DEFAULT_LOCALE, main ?? DEFAULT_LOCALE);
|
|
262
|
-
function isRecord$
|
|
342
|
+
function isRecord$1(v) {
|
|
263
343
|
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
264
344
|
}
|
|
265
345
|
function join(path, key) {
|
|
@@ -271,12 +351,12 @@ function issue(path, code, key, params) {
|
|
|
271
351
|
}
|
|
272
352
|
|
|
273
353
|
const RESERVED_KEYS = /* @__PURE__ */ new Set([...Object.getOwnPropertyNames(Object.prototype), "prototype"]);
|
|
354
|
+
const LINK_HEADS = /* @__PURE__ */ new Set(["page", "file"]);
|
|
274
355
|
const UNSAFE_PATH = /\.\.|\/\/|[?#]/;
|
|
275
|
-
const SCHEMA_KEYS = /* @__PURE__ */ new Set(["site", "name", "url", "timeZone", "locales", "currency", "week", "pages", "bricks", "migrations", "retired", "components", "agents"]);
|
|
356
|
+
const SCHEMA_KEYS = /* @__PURE__ */ new Set(["site", "name", "url", "timeZone", "locales", "currency", "week", "holidays", "pages", "bricks", "migrations", "retired", "components", "agents"]);
|
|
276
357
|
const COMPONENT_KEYS = /* @__PURE__ */ new Set(["kind", "label", "describe", "props", "slots", "children", "parents", "editor"]);
|
|
277
|
-
const BRICK_KEYS = /* @__PURE__ */ new Set(["pos", "kind", "source", "label", "describe", "fields", "layout", "tasks", "max", "order", "route", "visible", "untranslated", "plans", "drafts", "retired"]);
|
|
278
|
-
const FIELD_KEYS = /* @__PURE__ */ new Set(["pos", "type", "label", "hint", "unit", "optional", "default", "translate", "retired", "max", "min", "step", "format", "currency", "from", "accept", "maxBytes", "options", "of", "itemLabel", "unique", "profile", "to", "multiple", "showWhen", "group"]);
|
|
279
|
-
const SITE_BUDGET = { siteBytes: 4 * 1024 * 1024, entries: 2e3, entryBytes: 256 * 1024 };
|
|
358
|
+
const BRICK_KEYS = /* @__PURE__ */ new Set(["pos", "kind", "source", "label", "describe", "fields", "layout", "tasks", "max", "order", "route", "visible", "untranslated", "plans", "planned", "drafts", "status", "retired"]);
|
|
359
|
+
const FIELD_KEYS = /* @__PURE__ */ new Set(["pos", "type", "label", "hint", "unit", "optional", "default", "translate", "retired", "max", "min", "step", "format", "currency", "from", "accept", "maxBytes", "options", "of", "itemLabel", "unique", "profile", "to", "multiple", "showWhen", "askAgainWhen", "group", "after"]);
|
|
280
360
|
const SCHEMA_LIMITS = { bytes: 256 * 1024, bricks: 30, fieldsPerBrick: 60 };
|
|
281
361
|
function serializeSchema(site) {
|
|
282
362
|
const retired = site.retired ?? {};
|
|
@@ -299,6 +379,8 @@ function serializeSchema(site) {
|
|
|
299
379
|
currency: site.currency,
|
|
300
380
|
// Computed once here; the server, the Studio and the site all read `schema.week`.
|
|
301
381
|
week: weekInfoOf(site.languages?.[site.locales[0]] ?? site.locales[0], site.firstDayOfWeek),
|
|
382
|
+
// Like `week`: resolved once, so the server and the Studio never run the site's code to know its holidays.
|
|
383
|
+
...withHolidays(site),
|
|
302
384
|
pages: Object.fromEntries(Object.entries(site.pages).map(([key, page]) => [key, typeof page === "string" ? { [site.locales[0]]: page } : page])),
|
|
303
385
|
bricks,
|
|
304
386
|
migrations,
|
|
@@ -307,6 +389,10 @@ function serializeSchema(site) {
|
|
|
307
389
|
...site.agents?.review !== void 0 ? { agents: { review: site.agents.review } } : {}
|
|
308
390
|
}, [], []);
|
|
309
391
|
}
|
|
392
|
+
function withHolidays(site) {
|
|
393
|
+
const holidays = holidaysOf(site);
|
|
394
|
+
return holidays === void 0 ? {} : { holidays };
|
|
395
|
+
}
|
|
310
396
|
function storedField(field, locales) {
|
|
311
397
|
return Object.fromEntries(Object.entries(field).flatMap(([key, value]) => {
|
|
312
398
|
if (key === "of" && field.type === "list") return [[key, Object.fromEntries(Object.entries(field.of).map(([name, column]) => [name, storedField(column, locales)]))]];
|
|
@@ -334,14 +420,14 @@ function storedPrefill(prefill, fields, locales) {
|
|
|
334
420
|
function storedValue(field, value, locales) {
|
|
335
421
|
if (isTranslatable(field)) {
|
|
336
422
|
if (typeof value === "string") return { [locales[0]]: value };
|
|
337
|
-
if (!isRecord
|
|
423
|
+
if (!isRecord(value) || Object.values(value).some((text) => typeof text !== "string")) return value;
|
|
338
424
|
const kept = Object.fromEntries(Object.entries(value).filter(([locale]) => locales.includes(locale)));
|
|
339
425
|
return Object.hasOwn(kept, locales[0]) ? kept : void 0;
|
|
340
426
|
}
|
|
341
427
|
if (field.type !== "list" || !Array.isArray(value)) return value;
|
|
342
428
|
const rows = [];
|
|
343
429
|
for (const row of value) {
|
|
344
|
-
if (!isRecord
|
|
430
|
+
if (!isRecord(row)) {
|
|
345
431
|
rows.push(row);
|
|
346
432
|
continue;
|
|
347
433
|
}
|
|
@@ -356,7 +442,7 @@ function storedValue(field, value, locales) {
|
|
|
356
442
|
}
|
|
357
443
|
return rows;
|
|
358
444
|
}
|
|
359
|
-
const isRecord
|
|
445
|
+
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
360
446
|
const LOST = "$lost";
|
|
361
447
|
function plain(value, path, ancestors) {
|
|
362
448
|
switch (typeof value) {
|
|
@@ -405,6 +491,10 @@ function platformSchema(schema) {
|
|
|
405
491
|
function ordered(record) {
|
|
406
492
|
return Object.entries(record).sort(([a, x], [b, y]) => (x.pos ?? Number.MAX_SAFE_INTEGER) - (y.pos ?? Number.MAX_SAFE_INTEGER) || a.localeCompare(b));
|
|
407
493
|
}
|
|
494
|
+
function grantGroups(schema, grants) {
|
|
495
|
+
const held = new Set(grants);
|
|
496
|
+
return ordered(schema.bricks).filter(([, brick]) => !brick.retired).map(([key, brick]) => ({ brick: key, label: brick.label, tasks: (brick.tasks ?? []).filter((task) => held.has(`${key}.${task.id}`)).map((task) => task.label) })).filter((group) => group.tasks.length > 0);
|
|
497
|
+
}
|
|
408
498
|
function migrationFingerprint(brick, fills, compute) {
|
|
409
499
|
return sha256(`${brick}
|
|
410
500
|
${fills}
|
|
@@ -430,7 +520,9 @@ const SITE_KEYS = {
|
|
|
430
520
|
agents: true,
|
|
431
521
|
components: true,
|
|
432
522
|
migrations: true,
|
|
433
|
-
retired: true
|
|
523
|
+
retired: true,
|
|
524
|
+
country: true,
|
|
525
|
+
calendar: true
|
|
434
526
|
};
|
|
435
527
|
function checkSite(site, schema = serializeSchema(site)) {
|
|
436
528
|
const problems = Object.keys(site).filter((key) => !Object.hasOwn(SITE_KEYS, key)).map((key) => `Unknown key \u201C${key}\u201D. Known keys: ${Object.keys(SITE_KEYS).join(", ")}.`);
|
|
@@ -438,6 +530,10 @@ function checkSite(site, schema = serializeSchema(site)) {
|
|
|
438
530
|
if (!site.locales.includes(locale)) problems.push(`languages: locale \u201C${locale}\u201D is not in locales.`);
|
|
439
531
|
if (!isLocale(language)) problems.push(`languages.${locale}: invalid language \u201C${String(language)}\u201D (expected a canonical language tag such as \u201Cde-AT\u201D or \u201Cen-US\u201D).`);
|
|
440
532
|
}
|
|
533
|
+
if (site.country !== void 0 && !/^[A-Z]{2}$/.test(String(site.country))) problems.push(`country: invalid country \u201C${String(site.country)}\u201D (expected an ISO 3166-1 code such as \u201CAT\u201D or \u201CDE\u201D).`);
|
|
534
|
+
if (site.calendar !== void 0 && typeof site.calendar !== "function" && !CALENDAR_NAMES.includes(site.calendar)) {
|
|
535
|
+
problems.push(`calendar: unknown calendar \u201C${String(site.calendar)}\u201D (expected AT, DE, SG, none or a function (year) => holidays).`);
|
|
536
|
+
}
|
|
441
537
|
const color = site.seo?.color;
|
|
442
538
|
if (color !== void 0 && !/^#(?:[\da-f]{3}|[\da-f]{6})$/i.test(color)) problems.push(`seo.color: invalid colour \u201C${String(color)}\u201D (expected \u201C#rrggbb\u201D or \u201C#rgb\u201D).`);
|
|
443
539
|
return [...problems, ...checkSchema(schema)];
|
|
@@ -471,6 +567,7 @@ function schemaProblems(schema) {
|
|
|
471
567
|
if (!isSiteUrl(schema.url)) problems.push(`Invalid site url \u201C${String(schema.url)}\u201D (expected an absolute http(s) address such as \u201Chttps://linde.example\u201D).`);
|
|
472
568
|
if (!isTimeZone(schema.timeZone)) problems.push(`Invalid time zone \u201C${String(schema.timeZone)}\u201D (expected for example \u201CEurope/Vienna\u201D).`);
|
|
473
569
|
if (!isWeek(schema.week)) problems.push("week must be { firstDay: 1 to 7, weekend: [days 1 to 7] } (1 = Monday, 7 = Sunday).");
|
|
570
|
+
if (schema.holidays !== void 0 && !STORED_HOLIDAYS.includes(schema.holidays)) problems.push(`holidays must be one of ${STORED_HOLIDAYS.join(", ")}.`);
|
|
474
571
|
if (!isCurrency(schema.currency)) problems.push(`Invalid currency \u201C${String(schema.currency)}\u201D (expected an ISO 4217 code such as \u201CEUR\u201D or \u201CJPY\u201D).`);
|
|
475
572
|
const pageOwner = /* @__PURE__ */ new Map();
|
|
476
573
|
for (const [key, paths] of Object.entries(schema.pages)) {
|
|
@@ -496,6 +593,7 @@ function schemaProblems(schema) {
|
|
|
496
593
|
const routes = /* @__PURE__ */ new Map();
|
|
497
594
|
for (const [key, brick] of Object.entries(schema.bricks)) {
|
|
498
595
|
if (!/^[a-z][a-zA-Z0-9]{0,40}$/.test(key) || RESERVED_KEYS.has(key)) problems.push(`Invalid brick name \u201C${key}\u201D.`);
|
|
596
|
+
else if (LINK_HEADS.has(key)) problems.push(`The brick name \u201C${key}\u201D is reserved: \`$${key}.\u2026\` in a Markdown text means ${key === "page" ? "a page" : "a file of the Files library"}.`);
|
|
499
597
|
unknown(key, brick, BRICK_KEYS);
|
|
500
598
|
pushLabel(`${key}.label`, brick.label);
|
|
501
599
|
if (brick.describe !== void 0) pushLabel(`${key}.describe`, brick.describe);
|
|
@@ -521,8 +619,11 @@ function schemaProblems(schema) {
|
|
|
521
619
|
else if (!token) for (const p of checkValue(schema, field, value, path)) problems.push(`${p.path}: ${coreMessage("en", p.key, p.params)}`);
|
|
522
620
|
else if (field.type !== "date") problems.push(`${path}: \u201C${value}\u201D needs a date field.`);
|
|
523
621
|
}
|
|
622
|
+
if (task.kind === "create" && task.fields !== void 0) problems.push(...createFieldsProblems(`${key}.${task.id}.fields`, task, brick));
|
|
524
623
|
if (task.needsApproval !== void 0 && typeof task.needsApproval !== "boolean") problems.push(`${key}.${task.id}: needsApproval must be true or false.`);
|
|
525
624
|
}
|
|
625
|
+
if (brick.planned !== void 0) problems.push(...plannedProblems(key, brick));
|
|
626
|
+
if (brick.status !== void 0) problems.push(...statusProblems(key, brick, schema));
|
|
526
627
|
if (brick.kind === "single" && "drafts" in brick) problems.push(`${key}: drafts are only allowed for collections.`);
|
|
527
628
|
if (brick.kind === "single" && "plans" in brick) problems.push(`${key}: plans are only allowed for collections.`);
|
|
528
629
|
if (brick.kind === "collection") {
|
|
@@ -634,6 +735,25 @@ function isWeek(week) {
|
|
|
634
735
|
const { firstDay, weekend } = week;
|
|
635
736
|
return isIsoDay(firstDay) && Array.isArray(weekend) && weekend.length <= 6 && weekend.every(isIsoDay);
|
|
636
737
|
}
|
|
738
|
+
function createFieldsProblems(path, task, brick) {
|
|
739
|
+
if (!Array.isArray(task.fields)) return [`${path}: must be a list of field names.`];
|
|
740
|
+
const fields = brick.fields;
|
|
741
|
+
const listed = task.fields;
|
|
742
|
+
const problems = [];
|
|
743
|
+
for (const name of listed) {
|
|
744
|
+
const field = Object.hasOwn(fields, name) ? fields[name] : void 0;
|
|
745
|
+
if (!field) problems.push(`${path}: \u201C${name}\u201D is not a field of this brick.`);
|
|
746
|
+
else if (field.type === "slug") problems.push(`${path}: \u201C${name}\u201D is the address and is made from \u201C${field.from}\u201D, so the task cannot ask for it.`);
|
|
747
|
+
else if (brick.planned && (name === brick.planned.next || name === brick.planned.from)) problems.push(`${path}: \u201C${name}\u201D is the plan of a planned change, so the task cannot ask for it.`);
|
|
748
|
+
else if (field.retired) problems.push(`${path}: \u201C${name}\u201D is retired, so the task cannot ask for it.`);
|
|
749
|
+
}
|
|
750
|
+
const prefilled = Object.keys(task.prefill ?? {});
|
|
751
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
752
|
+
if (field.optional || field.default !== void 0 || field.type === "slug" || field.retired) continue;
|
|
753
|
+
if (!listed.includes(name) && !prefilled.includes(name)) problems.push(`${path}: \u201C${name}\u201D is required and has no default, so the task must ask for it.`);
|
|
754
|
+
}
|
|
755
|
+
return problems;
|
|
756
|
+
}
|
|
637
757
|
function plansProblems(key, plans, fields, schema) {
|
|
638
758
|
if (typeof plans !== "object" || plans === null || Array.isArray(plans)) return [`${key}: plans must be an object with brick and field.`];
|
|
639
759
|
const { brick, field } = plans;
|
|
@@ -650,6 +770,74 @@ function plansProblems(key, plans, fields, schema) {
|
|
|
650
770
|
}
|
|
651
771
|
return problems;
|
|
652
772
|
}
|
|
773
|
+
const HOURS_FIELDS = { week: "list", exceptions: "list", closedOn: "date", holidays: "choice" };
|
|
774
|
+
const HOUR_PLAN_FIELDS = { week: "list", from: "date", yearly: "boolean" };
|
|
775
|
+
function isHourPlans(brick) {
|
|
776
|
+
return brick.kind === "collection" && !brick.retired && Object.entries(HOUR_PLAN_FIELDS).every(([name, type]) => Object.hasOwn(brick.fields, name) && !brick.fields[name].retired && brick.fields[name].type === type);
|
|
777
|
+
}
|
|
778
|
+
function statusProblems(key, brick, schema) {
|
|
779
|
+
const status = brick.status;
|
|
780
|
+
const kinds = brick.kind === "collection" ? ["next", "count", "missing", "age"] : ["hours"];
|
|
781
|
+
const shape = brick.kind === "collection" ? "{ next }, { count: true }, { missing } or { age }" : "{ hours: true } or { hours: { plans } }";
|
|
782
|
+
if (typeof status !== "object" || status === null || Array.isArray(status)) return [`${key}: status must be ${shape}.`];
|
|
783
|
+
const entries = Object.entries(status);
|
|
784
|
+
if (entries.length !== 1 || !kinds.includes(entries[0][0])) return [`${key}: status must be ${shape}.`];
|
|
785
|
+
const [kind, value] = entries[0];
|
|
786
|
+
if (kind === "count") {
|
|
787
|
+
if (value !== true) return [`${key}: status.count must be true.`];
|
|
788
|
+
const vis = brick.visible;
|
|
789
|
+
return vis?.mode === "until" ? [] : [`${key}: status { count: true } needs visible: { mode: 'until' }.`];
|
|
790
|
+
}
|
|
791
|
+
if (kind === "hours") {
|
|
792
|
+
const problems = [];
|
|
793
|
+
if (value !== true) {
|
|
794
|
+
const plans = typeof value === "object" && value !== null && !Array.isArray(value) && Object.keys(value).length === 1 ? value.plans : void 0;
|
|
795
|
+
if (typeof plans !== "string") return [`${key}: status.hours must be true or { plans: '<key of the hour plans>' }.`];
|
|
796
|
+
const target = Object.hasOwn(schema.bricks, plans) ? schema.bricks[plans] : void 0;
|
|
797
|
+
if (!target || target.retired) problems.push(`${key}: status.hours.plans names \u201C${plans}\u201D, which is not a live brick of the site.`);
|
|
798
|
+
else if (!isHourPlans(target)) problems.push(`${key}: status.hours.plans names \u201C${plans}\u201D, which is not a collection of hour plans (hourPlans(): a list \u201Cweek\u201D, a date \u201Cfrom\u201D and a switch \u201Cyearly\u201D).`);
|
|
799
|
+
}
|
|
800
|
+
for (const [name, type] of Object.entries(HOURS_FIELDS)) {
|
|
801
|
+
const field2 = Object.hasOwn(brick.fields, name) ? brick.fields[name] : void 0;
|
|
802
|
+
if (!field2 || field2.retired) problems.push(`${key}: status { hours } needs the live field \u201C${name}\u201D.`);
|
|
803
|
+
else if (field2.type !== type) problems.push(`${key}: status { hours } needs \u201C${name}\u201D to be a ${type} field.`);
|
|
804
|
+
}
|
|
805
|
+
return problems;
|
|
806
|
+
}
|
|
807
|
+
const field = typeof value === "string" && Object.hasOwn(brick.fields, value) ? brick.fields[value] : void 0;
|
|
808
|
+
if (!field) return [`${key}: status.${kind} names \u201C${String(value)}\u201D, which is not a field of the brick.`];
|
|
809
|
+
if (field.retired) return [`${key}: status.${kind} names the retired field \u201C${value}\u201D; remove the status or name another field.`];
|
|
810
|
+
if (kind === "missing") return field.optional && field.default === void 0 ? [] : [`${key}: status.missing needs an optional field without a default, \u201C${value}\u201D is not.`];
|
|
811
|
+
return field.type === "date" || field.type === "daterange" ? [] : [`${key}: status.${kind} needs a date or daterange field, \u201C${value}\u201D is a ${field.type} field.`];
|
|
812
|
+
}
|
|
813
|
+
function plannedProblems(key, brick) {
|
|
814
|
+
const planned = brick.planned;
|
|
815
|
+
if (typeof planned !== "object" || planned === null || Array.isArray(planned)) return [`${key}: planned must be an object with field, next and from.`];
|
|
816
|
+
const p = planned;
|
|
817
|
+
const problems = [];
|
|
818
|
+
for (const k of Object.keys(p)) if (!["field", "next", "from"].includes(k)) problems.push(`${key}.planned: unknown property \u201C${k}\u201D.`);
|
|
819
|
+
const named = (role) => {
|
|
820
|
+
const name = p[role];
|
|
821
|
+
const field2 = typeof name === "string" && Object.hasOwn(brick.fields, name) ? brick.fields[name] : void 0;
|
|
822
|
+
if (!field2 || field2.retired) problems.push(`${key}: planned.${role} names \u201C${String(name)}\u201D, which is not a field of the brick.`);
|
|
823
|
+
return field2;
|
|
824
|
+
};
|
|
825
|
+
const field = named("field");
|
|
826
|
+
const next = named("next");
|
|
827
|
+
const from = named("from");
|
|
828
|
+
if (!field || !next || !from) return problems;
|
|
829
|
+
const [f, n, d] = [p.field, p.next, p.from];
|
|
830
|
+
if ((/* @__PURE__ */ new Set([f, n, d])).size < 3) problems.push(`${key}: planned needs three different fields.`);
|
|
831
|
+
if (next.type !== field.type) problems.push(`${key}: planned.next \u201C${n}\u201D needs the type of \u201C${f}\u201D (${field.type}).`);
|
|
832
|
+
if (from.type !== "date") problems.push(`${key}: planned.from \u201C${d}\u201D must be a date field.`);
|
|
833
|
+
if (!next.optional || !from.optional) problems.push(`${key}: planned.next \u201C${n}\u201D and planned.from \u201C${d}\u201D must be optional.`);
|
|
834
|
+
for (const task of brick.tasks ?? []) {
|
|
835
|
+
if (task.kind === "edit" && task.fields.includes(f) && !(task.fields.includes(n) && task.fields.includes(d))) {
|
|
836
|
+
problems.push(`${key}.${task.id}: the task changes \u201C${f}\u201D, so it must also show its plan \u201C${n}\u201D and \u201C${d}\u201D.`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
return problems;
|
|
840
|
+
}
|
|
653
841
|
function mirrorProblem(target, source, siteCurrency) {
|
|
654
842
|
if (source.type !== target.type) return `type ${source.type} instead of ${target.type}`;
|
|
655
843
|
if (isTranslatable(source) !== isTranslatable(target)) return "different translatability";
|
|
@@ -734,6 +922,11 @@ function checkField(path, field, siblings, topLevel, schema) {
|
|
|
734
922
|
if (keys !== "field,is" && keys !== "field,not") problems.push(`${path}.showWhen: needs \`field\` and one of \`is\` or \`not\`.`);
|
|
735
923
|
else if (typeof when.field !== "string" || !Object.hasOwn(siblings, when.field) || when.field === path.split(".").pop()) problems.push(`${path}.showWhen: \u201C${String(when.field)}\u201D is not another field next to this one.`);
|
|
736
924
|
}
|
|
925
|
+
if (field.type === "choices" && field.askAgainWhen !== void 0) {
|
|
926
|
+
const target = field.askAgainWhen;
|
|
927
|
+
const sibling = typeof target === "string" && Object.hasOwn(siblings, target) ? siblings[target] : void 0;
|
|
928
|
+
if (target === path.split(".").pop() || sibling?.type !== "text" && sibling?.type !== "longtext") problems.push(`${path}.askAgainWhen: must name a text field next to it.`);
|
|
929
|
+
}
|
|
737
930
|
let numbersOk = true;
|
|
738
931
|
for (const key of NUMBER_OPTIONS) {
|
|
739
932
|
const value = field[key];
|
|
@@ -808,6 +1001,11 @@ function checkField(path, field, siblings, topLevel, schema) {
|
|
|
808
1001
|
case "file":
|
|
809
1002
|
if (!(field.maxBytes > 0 && field.maxBytes <= limitOf("file"))) problems.push(`${path}: maxBytes at most ${formatMb(limitOf("file"))} MB.`);
|
|
810
1003
|
break;
|
|
1004
|
+
case "time":
|
|
1005
|
+
if (field.after !== void 0 && (!topLevel || siblings[field.after]?.type !== "time" || field.after === path.split(".").pop())) {
|
|
1006
|
+
problems.push(`${path}.after: \u201C${String(field.after)}\u201D is not another time field of the brick.`);
|
|
1007
|
+
}
|
|
1008
|
+
break;
|
|
811
1009
|
case "slug": {
|
|
812
1010
|
const source = siblings[field.from];
|
|
813
1011
|
if (!source || !["text", "longtext"].includes(source.type)) problems.push(`${path}: from must be a text field.`);
|
|
@@ -818,258 +1016,4 @@ function checkField(path, field, siblings, topLevel, schema) {
|
|
|
818
1016
|
return problems;
|
|
819
1017
|
}
|
|
820
1018
|
|
|
821
|
-
|
|
822
|
-
return path.length > 1 ? path.replace(/\/+$/, "") : path;
|
|
823
|
-
}
|
|
824
|
-
function escapeRegex(text) {
|
|
825
|
-
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
826
|
-
}
|
|
827
|
-
function compile(rule) {
|
|
828
|
-
const wildcard = rule.from.endsWith("*");
|
|
829
|
-
const base = trimSlash(wildcard ? rule.from.slice(0, -1).replace(/\/$/, "") : rule.from);
|
|
830
|
-
const params = [];
|
|
831
|
-
const source = base.split("/").map((segment) => {
|
|
832
|
-
const param = /^:([A-Za-z_]\w*)$/.exec(segment);
|
|
833
|
-
if (!param) return escapeRegex(segment);
|
|
834
|
-
params.push(param[1]);
|
|
835
|
-
return "([^/]+)";
|
|
836
|
-
}).join("/");
|
|
837
|
-
const tail = wildcard ? "(?:/(.*))?" : "";
|
|
838
|
-
return { rule, pattern: new RegExp(`^${source}${tail}$`), params, wildcard };
|
|
839
|
-
}
|
|
840
|
-
const compiledRules = /* @__PURE__ */ new WeakMap();
|
|
841
|
-
const entryRules = /* @__PURE__ */ new WeakMap();
|
|
842
|
-
function compiledFor(rules) {
|
|
843
|
-
let compiled = compiledRules.get(rules);
|
|
844
|
-
if (!compiled) compiledRules.set(rules, compiled = rules.map(compile));
|
|
845
|
-
return compiled;
|
|
846
|
-
}
|
|
847
|
-
function matchRedirect(rules, path) {
|
|
848
|
-
const queryStart = path.indexOf("?");
|
|
849
|
-
const pathPart = trimSlash(queryStart === -1 ? path : path.slice(0, queryStart));
|
|
850
|
-
const query = queryStart === -1 ? "" : path.slice(queryStart + 1);
|
|
851
|
-
for (const compiled of compiledFor(rules)) {
|
|
852
|
-
const match = compiled.pattern.exec(pathPart);
|
|
853
|
-
if (!match) continue;
|
|
854
|
-
const values = {};
|
|
855
|
-
compiled.params.forEach((name, index) => {
|
|
856
|
-
values[name] = match[index + 1] ?? "";
|
|
857
|
-
});
|
|
858
|
-
values.splat = compiled.wildcard ? match[match.length - 1] ?? "" : "";
|
|
859
|
-
let to = compiled.rule.to.replace(/:([A-Za-z_]\w*)/g, (token, name) => values[name] ?? token);
|
|
860
|
-
if (/^[/\\]/.test(to)) to = "/" + to.replace(/^[/\\]+/, "");
|
|
861
|
-
if (query) to += (to.includes("?") ? "&" : "?") + query;
|
|
862
|
-
return { to, status: compiled.rule.status ?? 301 };
|
|
863
|
-
}
|
|
864
|
-
return void 0;
|
|
865
|
-
}
|
|
866
|
-
function findRedirect(rules, bundle, path) {
|
|
867
|
-
if (!bundle) return matchRedirect(rules, path);
|
|
868
|
-
let fromEntries = entryRules.get(bundle);
|
|
869
|
-
if (!fromEntries) entryRules.set(bundle, fromEntries = entryRedirects(bundle));
|
|
870
|
-
return matchRedirect(rules, path) ?? matchRedirect(fromEntries, path);
|
|
871
|
-
}
|
|
872
|
-
function entryRedirects(bundle) {
|
|
873
|
-
const rules = [];
|
|
874
|
-
for (const brick of Object.values(bundle.bricks)) {
|
|
875
|
-
if (brick.kind !== "collection") continue;
|
|
876
|
-
for (const item of brick.items) {
|
|
877
|
-
const value = item.data.redirectFrom;
|
|
878
|
-
const oldPaths = Array.isArray(value) ? value.filter((path) => typeof path === "string") : typeof value === "string" ? [value] : [];
|
|
879
|
-
const url = item.urls?.[bundle.locale];
|
|
880
|
-
if (!url) continue;
|
|
881
|
-
for (const from of oldPaths) rules.push({ from, to: url, status: 301 });
|
|
882
|
-
}
|
|
883
|
-
}
|
|
884
|
-
return rules;
|
|
885
|
-
}
|
|
886
|
-
function redirectProblems(rules) {
|
|
887
|
-
const problems = [];
|
|
888
|
-
const seen = /* @__PURE__ */ new Set();
|
|
889
|
-
for (const { from, to } of rules) {
|
|
890
|
-
if (!from.startsWith("/")) problems.push(`"${from}": path must start with "/".`);
|
|
891
|
-
if (trimSlash(from) === trimSlash(to)) problems.push(`"${from}": redirect points to itself.`);
|
|
892
|
-
if (/^[/\\]?:/.test(to)) problems.push(`"${from}": "to" starts with a parameter (${to}). Start it with a fixed path such as "/new/:splat" so a visitor cannot choose the target host.`);
|
|
893
|
-
if (seen.has(from)) problems.push(`"${from}": defined more than once.`);
|
|
894
|
-
seen.add(from);
|
|
895
|
-
}
|
|
896
|
-
return problems;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
const isRecord = (v) => typeof v === "object" && v !== null && !Array.isArray(v);
|
|
900
|
-
const isTree = (v) => isRecord(v) && Array.isArray(v.nodes);
|
|
901
|
-
const isNode = (n) => Array.isArray(n) && typeof n[0] === "string" && isRecord(n[1]);
|
|
902
|
-
function mapTrees(bundle, fn) {
|
|
903
|
-
const walk = (value, at, field) => {
|
|
904
|
-
if (isTree(value)) return fn(value, { ...at, field });
|
|
905
|
-
if (Array.isArray(value)) return value.map((v, i) => walk(v, at, `${field}.${i}`));
|
|
906
|
-
if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, walk(v, at, field ? `${field}.${k}` : k)]));
|
|
907
|
-
return value;
|
|
908
|
-
};
|
|
909
|
-
const data = (d, brick, id) => walk(d, { brick, id }, "");
|
|
910
|
-
const bricks = Object.fromEntries(Object.entries(bundle.bricks).map(([key, b]) => [key, b.kind === "single" ? { ...b, data: data(b.data, key, key) } : { ...b, items: b.items.map((item) => ({ ...item, data: data(item.data, key, item.id) })) }]));
|
|
911
|
-
return { ...bundle, bricks };
|
|
912
|
-
}
|
|
913
|
-
function find(bundle, brick, ref) {
|
|
914
|
-
const b = Object.hasOwn(bundle.bricks, brick) ? bundle.bricks[brick] : void 0;
|
|
915
|
-
if (b?.kind !== "collection") return void 0;
|
|
916
|
-
return b.items.find((i) => i.id === `${brick}/${ref}` || i.id === ref || i.data.slug === ref);
|
|
917
|
-
}
|
|
918
|
-
function routeTarget(bundle, schema, ref) {
|
|
919
|
-
const dot = ref.indexOf(".");
|
|
920
|
-
if (dot < 1) return void 0;
|
|
921
|
-
const head = ref.slice(0, dot);
|
|
922
|
-
const rest = ref.slice(dot + 1);
|
|
923
|
-
if (head === "page") return Object.hasOwn(schema.pages, rest) ? schema.pages[rest][bundle.locale] : void 0;
|
|
924
|
-
return find(bundle, head, rest)?.urls?.[bundle.locale];
|
|
925
|
-
}
|
|
926
|
-
function resolveRouteLinks(bundle, schema) {
|
|
927
|
-
const hasSnippets = Object.hasOwn(schema.bricks, "snippets") && schema.bricks.snippets.kind === "collection";
|
|
928
|
-
const snippetNodes = (name) => {
|
|
929
|
-
const body = hasSnippets ? find(bundle, "snippets", name)?.data.body : void 0;
|
|
930
|
-
return isTree(body) ? body.nodes : void 0;
|
|
931
|
-
};
|
|
932
|
-
const resolve = (nodes, nested) => nodes.flatMap((node) => {
|
|
933
|
-
if (!isNode(node)) return [node];
|
|
934
|
-
const [tag, props, ...children] = node;
|
|
935
|
-
if (tag === "snippet" && !nested) {
|
|
936
|
-
const inner = typeof props.name === "string" ? snippetNodes(props.name) : void 0;
|
|
937
|
-
return inner ? resolve(inner, true) : [[tag, { ...props, "data-broken": true }, ...children]];
|
|
938
|
-
}
|
|
939
|
-
let next = props;
|
|
940
|
-
const href = props.href;
|
|
941
|
-
if (tag === "a" && typeof href === "string" && href.startsWith("$")) {
|
|
942
|
-
const hash = href.indexOf("#");
|
|
943
|
-
const target = routeTarget(bundle, schema, hash === -1 ? href.slice(1) : href.slice(1, hash));
|
|
944
|
-
next = target === void 0 ? { ...props, "data-broken": true } : { ...props, href: target + (hash === -1 ? "" : href.slice(hash)) };
|
|
945
|
-
}
|
|
946
|
-
return [[tag, next, ...resolve(children, nested)]];
|
|
947
|
-
});
|
|
948
|
-
return mapTrees(bundle, (tree) => ({
|
|
949
|
-
...tree,
|
|
950
|
-
nodes: resolve(tree.nodes, false),
|
|
951
|
-
...tree.summary ? { summary: resolve(tree.summary, false) } : {}
|
|
952
|
-
}));
|
|
953
|
-
}
|
|
954
|
-
function eachNode(nodes, fn) {
|
|
955
|
-
for (const node of nodes) if (isNode(node)) {
|
|
956
|
-
fn(node);
|
|
957
|
-
eachNode(node.slice(2), fn);
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
const normal = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
|
|
961
|
-
function checkLinks(bundles, schema, options = {}) {
|
|
962
|
-
const config = options.linkCheck ?? {};
|
|
963
|
-
const levels = { broken: config.broken ?? "error", anchors: config.anchors ?? "warn", redirected: config.redirected ?? "warn" };
|
|
964
|
-
const rules = [...options.redirects ?? [], ...bundles.flatMap(entryRedirects)];
|
|
965
|
-
const known = /* @__PURE__ */ new Set();
|
|
966
|
-
const trees = /* @__PURE__ */ new Map();
|
|
967
|
-
for (const bundle of bundles) {
|
|
968
|
-
for (const pages of Object.values(schema.pages)) {
|
|
969
|
-
const p = pages[bundle.locale];
|
|
970
|
-
if (p) known.add(normal(p));
|
|
971
|
-
}
|
|
972
|
-
for (const b of Object.values(bundle.bricks)) {
|
|
973
|
-
if (b.kind !== "collection") continue;
|
|
974
|
-
for (const item of b.items) {
|
|
975
|
-
const url = item.urls?.[bundle.locale];
|
|
976
|
-
if (!url) continue;
|
|
977
|
-
known.add(normal(url));
|
|
978
|
-
const own = [];
|
|
979
|
-
mapTrees({ ...bundle, bricks: { x: { kind: "single", data: item.data, fallback: [] } } }, (t) => {
|
|
980
|
-
own.push(t);
|
|
981
|
-
return t;
|
|
982
|
-
});
|
|
983
|
-
trees.set(normal(url), own);
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
}
|
|
987
|
-
const hasId = (list, id) => list.some((t) => {
|
|
988
|
-
let found = false;
|
|
989
|
-
eachNode(t.nodes, (n) => {
|
|
990
|
-
if (/^h[1-6]$/.test(n[0]) && n[1].id === id) found = true;
|
|
991
|
-
});
|
|
992
|
-
return found;
|
|
993
|
-
});
|
|
994
|
-
const issues = [];
|
|
995
|
-
for (const bundle of bundles) {
|
|
996
|
-
mapTrees(bundle, (tree, at) => {
|
|
997
|
-
const add = (severity, href, key, params) => issues.push({ severity, locale: bundle.locale, brick: at.brick, id: at.id, field: at.field, href, ...msg(`links.${key}`, params) });
|
|
998
|
-
const broken = (href, key, params) => {
|
|
999
|
-
if (levels.broken !== "off") add(levels.broken, href, key, params);
|
|
1000
|
-
};
|
|
1001
|
-
eachNode([...tree.nodes, ...tree.summary ?? []], (node) => {
|
|
1002
|
-
const [tag, props] = node;
|
|
1003
|
-
if (tag === "snippet" && props["data-broken"]) return broken(`snippet:${String(props.name)}`, "snippetMissing", { name: String(props.name) });
|
|
1004
|
-
const href = props.href;
|
|
1005
|
-
if (tag !== "a" || typeof href !== "string") return;
|
|
1006
|
-
if (props["data-broken"]) {
|
|
1007
|
-
const head = href.slice(1).split(".")[0];
|
|
1008
|
-
if (href.startsWith("$") && head !== "page" && Object.hasOwn(schema.bricks, head) && !Object.hasOwn(bundle.bricks, head)) return;
|
|
1009
|
-
return broken(href, "linkBroken", { href });
|
|
1010
|
-
}
|
|
1011
|
-
const hash = href.indexOf("#");
|
|
1012
|
-
const path = hash === -1 ? href : href.slice(0, hash);
|
|
1013
|
-
const anchor = hash === -1 ? "" : href.slice(hash + 1);
|
|
1014
|
-
let target;
|
|
1015
|
-
if (path === "") target = [tree];
|
|
1016
|
-
else if (path.startsWith("/") && !path.startsWith("//")) {
|
|
1017
|
-
const clean = normal(path.split("?")[0]);
|
|
1018
|
-
const moved = levels.redirected === "off" ? void 0 : matchRedirect(rules, clean);
|
|
1019
|
-
if (moved) {
|
|
1020
|
-
add(levels.redirected, href, "linkRedirected", { href, to: moved.to });
|
|
1021
|
-
return;
|
|
1022
|
-
}
|
|
1023
|
-
if (!known.has(clean)) {
|
|
1024
|
-
if (options.files && !options.files(clean)) broken(href, "pathUnknown", { path: clean });
|
|
1025
|
-
return;
|
|
1026
|
-
}
|
|
1027
|
-
target = trees.get(clean);
|
|
1028
|
-
} else return;
|
|
1029
|
-
if (anchor && levels.anchors !== "off" && target && !hasId(target, anchor)) add(levels.anchors, href, "anchorMissing", { anchor });
|
|
1030
|
-
});
|
|
1031
|
-
return tree;
|
|
1032
|
-
});
|
|
1033
|
-
}
|
|
1034
|
-
return issues;
|
|
1035
|
-
}
|
|
1036
|
-
function externalLinks(bundles) {
|
|
1037
|
-
const out = [];
|
|
1038
|
-
for (const bundle of bundles) {
|
|
1039
|
-
mapTrees(bundle, (tree, at) => {
|
|
1040
|
-
eachNode([...tree.nodes, ...tree.summary ?? []], ([tag, props]) => {
|
|
1041
|
-
const href = props.href;
|
|
1042
|
-
if (tag === "a" && typeof href === "string" && /^https?:\/\//.test(href)) out.push({ href, locale: bundle.locale, ...at });
|
|
1043
|
-
});
|
|
1044
|
-
return tree;
|
|
1045
|
-
});
|
|
1046
|
-
}
|
|
1047
|
-
return out;
|
|
1048
|
-
}
|
|
1049
|
-
const MD_INLINE = /\]\(\s*<?([^)\s>]+)/g;
|
|
1050
|
-
const MD_DEFINITION = /^[ \t]{0,3}\[[^\]]+\]:[ \t]*<?(\S+?)>?(?:[ \t]+.*)?$/gm;
|
|
1051
|
-
const MD_SNIPPET = /::snippet\{[^}]*\bname="([^"]*)"/g;
|
|
1052
|
-
function linkTargets(fields, data) {
|
|
1053
|
-
const out = /* @__PURE__ */ new Set();
|
|
1054
|
-
const text = (value) => {
|
|
1055
|
-
for (const markdown of typeof value === "string" ? [value] : isRecord(value) ? Object.values(value) : []) {
|
|
1056
|
-
if (typeof markdown !== "string") continue;
|
|
1057
|
-
for (const m of markdown.matchAll(MD_INLINE)) out.add(m[1]);
|
|
1058
|
-
for (const m of markdown.matchAll(MD_DEFINITION)) out.add(m[1]);
|
|
1059
|
-
for (const m of markdown.matchAll(MD_SNIPPET)) out.add(`snippet:${m[1]}`);
|
|
1060
|
-
}
|
|
1061
|
-
};
|
|
1062
|
-
const walk = (inner, row) => {
|
|
1063
|
-
for (const [name, field] of Object.entries(inner)) {
|
|
1064
|
-
const value = row[name];
|
|
1065
|
-
if (field.type === "markdown") text(value);
|
|
1066
|
-
else if (field.type === "list" && Array.isArray(value)) {
|
|
1067
|
-
for (const r of value) if (isRecord(r)) walk(field.of, r);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
};
|
|
1071
|
-
walk(fields, data);
|
|
1072
|
-
return out;
|
|
1073
|
-
}
|
|
1074
|
-
|
|
1075
|
-
export { platformSchema as A, BRICK_KEYS as B, slugify as C, staleOf as D, uniqueSlug as E, FIELD_KEYS as F, validate as G, weeklyTask as H, PREFILL_TOKENS as P, SCHEMA_KEYS as S, UPLOAD_TYPES as U, checkSite as a, redirectProblems as b, checkLinks as c, entryRedirects as d, externalLinks as e, SCHEMA_LIMITS as f, SITE_BUDGET as g, SLUG_PATTERN as h, acceptOf as i, checkSchema as j, fileProblem as k, findRedirect as l, formatMb as m, isDate as n, isPrefillToken as o, isTimeZone as p, limitOf as q, resolveRouteLinks as r, serializeSchema as s, linkTargets as t, mainLocale as u, matchRedirect as v, migrationFingerprint as w, ordered as x, plannedWeeks as y, plansThisWeek as z };
|
|
1019
|
+
export { weekStartField as A, BRICK_KEYS as B, weeklyTask as C, FIELD_KEYS as F, PREFILL_TOKENS as P, SCHEMA_KEYS as S, checkSchema as a, createAsks as b, checkSite as c, SCHEMA_LIMITS as d, SHAPE_CODES as e, SLUG_PATTERN as f, duePlan as g, fileProblem as h, grantGroups as i, isDate as j, isHourPlans as k, isPrefillToken as l, isTimeZone as m, liveFrom as n, ordered as o, platformSchema as p, mainLocale as q, migrationFingerprint as r, serializeSchema as s, plannedWeeks as t, plansThisWeek as u, validate as v, slugBases as w, slugify as x, staleOf as y, uniqueSlug as z };
|