@v7-cms/core 0.0.0-alpha.10
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/LICENSE +21 -0
- package/dist/body-mode.d.ts +35 -0
- package/dist/body-mode.d.ts.map +1 -0
- package/dist/body-mode.js +116 -0
- package/dist/body-mode.js.map +1 -0
- package/dist/config-load.d.ts +106 -0
- package/dist/config-load.d.ts.map +1 -0
- package/dist/config-load.js +193 -0
- package/dist/config-load.js.map +1 -0
- package/dist/config.d.ts +148 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +2 -0
- package/dist/config.js.map +1 -0
- package/dist/fields-builtin.d.ts +21 -0
- package/dist/fields-builtin.d.ts.map +1 -0
- package/dist/fields-builtin.js +240 -0
- package/dist/fields-builtin.js.map +1 -0
- package/dist/fields.d.ts +84 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +10 -0
- package/dist/fields.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/serialize/entry.d.ts +64 -0
- package/dist/serialize/entry.d.ts.map +1 -0
- package/dist/serialize/entry.js +156 -0
- package/dist/serialize/entry.js.map +1 -0
- package/dist/serialize/index.d.ts +2 -0
- package/dist/serialize/index.d.ts.map +1 -0
- package/dist/serialize/index.js +2 -0
- package/dist/serialize/index.js.map +1 -0
- package/dist/storage/types.d.ts +97 -0
- package/dist/storage/types.d.ts.map +1 -0
- package/dist/storage/types.js +14 -0
- package/dist/storage/types.js.map +1 -0
- package/package.json +59 -0
- package/schema/config.json +171 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PurrNeko
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choosing how to edit a body.
|
|
3
|
+
*
|
|
4
|
+
* A rich-text editor normalises Markdown: it re-wraps emphasis, realigns tables, changes list
|
|
5
|
+
* markers and fence styles. That is fine for prose and fatal for MDX, which carries ESM imports
|
|
6
|
+
* and JSX that a Markdown editor does not understand at all. So a body is classified before it is
|
|
7
|
+
* opened, and anything the rich editor would not round-trip is edited as source instead.
|
|
8
|
+
*
|
|
9
|
+
* The bias is deliberate: when in doubt, choose source. A false positive costs the author a nicer
|
|
10
|
+
* editor; a false negative silently corrupts their file.
|
|
11
|
+
*/
|
|
12
|
+
export type BodyMode = 'rich' | 'source';
|
|
13
|
+
export interface BodyModeOptions {
|
|
14
|
+
/** File extension, since MDX is always source-edited. */
|
|
15
|
+
extension?: string;
|
|
16
|
+
/** Set by the config when a field is explicitly source-only. */
|
|
17
|
+
forceSource?: boolean;
|
|
18
|
+
/** Fence languages this consumer treats as structured, added to the built-in set. */
|
|
19
|
+
structuredFences?: string[];
|
|
20
|
+
}
|
|
21
|
+
export interface BodyModeResult {
|
|
22
|
+
mode: BodyMode;
|
|
23
|
+
/** Why source was chosen, so the UI can explain itself instead of just refusing. */
|
|
24
|
+
reason?: 'mdx' | 'imports' | 'jsx' | 'structured-fence' | 'html' | 'math' | 'indented-code' | 'configured';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Decide how to edit a body.
|
|
28
|
+
*
|
|
29
|
+
* Only clear, unambiguous signals force source mode. A stray `<` in prose, or a `$` in a price,
|
|
30
|
+
* must not send the author to the source editor for no reason.
|
|
31
|
+
*/
|
|
32
|
+
export declare function classifyBody(body: string, options?: BodyModeOptions): BodyModeResult;
|
|
33
|
+
/** Convenience for the UI: whether a body may be opened in the rich editor. */
|
|
34
|
+
export declare function canEditAsRichText(body: string, options?: BodyModeOptions): boolean;
|
|
35
|
+
//# sourceMappingURL=body-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-mode.d.ts","sourceRoot":"","sources":["../src/body-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,MAAM,MAAM,QAAQ,GAAG,MAAM,GAAG,QAAQ,CAAC;AAgBzC,MAAM,WAAW,eAAe;IAC9B,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qFAAqF;IACrF,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,QAAQ,CAAC;IACf,oFAAoF;IACpF,MAAM,CAAC,EACH,KAAK,GACL,SAAS,GACT,KAAK,GACL,kBAAkB,GAClB,MAAM,GACN,MAAM,GACN,eAAe,GACf,YAAY,CAAC;CAClB;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,cAAc,CA8ExF;AAED,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE,eAAoB,GAAG,OAAO,CAEtF"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Choosing how to edit a body.
|
|
3
|
+
*
|
|
4
|
+
* A rich-text editor normalises Markdown: it re-wraps emphasis, realigns tables, changes list
|
|
5
|
+
* markers and fence styles. That is fine for prose and fatal for MDX, which carries ESM imports
|
|
6
|
+
* and JSX that a Markdown editor does not understand at all. So a body is classified before it is
|
|
7
|
+
* opened, and anything the rich editor would not round-trip is edited as source instead.
|
|
8
|
+
*
|
|
9
|
+
* The bias is deliberate: when in doubt, choose source. A false positive costs the author a nicer
|
|
10
|
+
* editor; a false negative silently corrupts their file.
|
|
11
|
+
*/
|
|
12
|
+
/** Fence languages a Markdown editor would rewrite or mangle. */
|
|
13
|
+
const STRUCTURED_FENCES = new Set([
|
|
14
|
+
'mermaid',
|
|
15
|
+
'abc',
|
|
16
|
+
'math',
|
|
17
|
+
'katex',
|
|
18
|
+
'plantuml',
|
|
19
|
+
'graphviz',
|
|
20
|
+
'markmap',
|
|
21
|
+
'mindmap',
|
|
22
|
+
'echarts',
|
|
23
|
+
'wavedrom',
|
|
24
|
+
]);
|
|
25
|
+
/**
|
|
26
|
+
* Decide how to edit a body.
|
|
27
|
+
*
|
|
28
|
+
* Only clear, unambiguous signals force source mode. A stray `<` in prose, or a `$` in a price,
|
|
29
|
+
* must not send the author to the source editor for no reason.
|
|
30
|
+
*/
|
|
31
|
+
export function classifyBody(body, options = {}) {
|
|
32
|
+
// The extension is the most specific signal, so it is reported ahead of a blanket config flag:
|
|
33
|
+
// "this is MDX" tells the author more than "this field is source only".
|
|
34
|
+
if (options.extension === 'mdx')
|
|
35
|
+
return { mode: 'source', reason: 'mdx' };
|
|
36
|
+
if (options.forceSource)
|
|
37
|
+
return { mode: 'source', reason: 'configured' };
|
|
38
|
+
const structured = options.structuredFences
|
|
39
|
+
? new Set([...STRUCTURED_FENCES, ...options.structuredFences])
|
|
40
|
+
: STRUCTURED_FENCES;
|
|
41
|
+
const lines = body.split('\n');
|
|
42
|
+
let inFence = false;
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
const fence = /^\s{0,3}(?:`{3,}|~{3,})\s*([^\s`]*)/.exec(line);
|
|
45
|
+
if (fence) {
|
|
46
|
+
if (!inFence) {
|
|
47
|
+
const language = (fence[1] ?? '').toLowerCase();
|
|
48
|
+
if (language && structured.has(language)) {
|
|
49
|
+
return { mode: 'source', reason: 'structured-fence' };
|
|
50
|
+
}
|
|
51
|
+
inFence = true;
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
inFence = false;
|
|
55
|
+
}
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (inFence)
|
|
59
|
+
continue;
|
|
60
|
+
// ESM in MDX, at the start of a line where it is unambiguous.
|
|
61
|
+
if (/^\s*(import|export)\s/.test(line))
|
|
62
|
+
return { mode: 'source', reason: 'imports' };
|
|
63
|
+
// JSX or HTML component syntax: a capitalised tag, or an explicit block element. Prose rarely
|
|
64
|
+
// starts a line with one, so requiring line-start keeps false positives down.
|
|
65
|
+
if (/^\s*<\/?[A-Z][A-Za-z0-9.]*[\s/>]/.test(line))
|
|
66
|
+
return { mode: 'source', reason: 'jsx' };
|
|
67
|
+
if (/^\s*<(div|span|figure|details|summary|iframe|video|audio|table)\b/i.test(line)) {
|
|
68
|
+
return { mode: 'source', reason: 'html' };
|
|
69
|
+
}
|
|
70
|
+
// Display math. Inline `$…$` is common in prose and round-trips acceptably, but a `$$` block
|
|
71
|
+
// is a block-level construct and is treated as structured.
|
|
72
|
+
if (/^\s*\$\$/.test(line))
|
|
73
|
+
return { mode: 'source', reason: 'math' };
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Four-space indented code blocks are ambiguous with nested list continuations, which is exactly
|
|
77
|
+
* the kind of thing a rich editor gets wrong. Only the ambiguous case counts: an indented block
|
|
78
|
+
* that follows a blank line at column zero. Indented lines inside a list, or inside a fenced
|
|
79
|
+
* block, are ordinary content.
|
|
80
|
+
*/
|
|
81
|
+
function hasIndentedCodeBlock(body) {
|
|
82
|
+
const lines = body.split('\n');
|
|
83
|
+
let fenced = false;
|
|
84
|
+
let previousBlank = true;
|
|
85
|
+
for (const line of lines) {
|
|
86
|
+
if (/^\s{0,3}(`{3,}|~{3,})/.test(line)) {
|
|
87
|
+
fenced = !fenced;
|
|
88
|
+
previousBlank = false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (fenced)
|
|
92
|
+
continue;
|
|
93
|
+
// Inside a list, indentation is a continuation or a nested item, not a code block.
|
|
94
|
+
if (/^\s*(?:[-*+]|\d+[.)])\s/.test(line)) {
|
|
95
|
+
previousBlank = false;
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
if (/^(?: {4}|\t)\S/.test(line)) {
|
|
99
|
+
if (previousBlank)
|
|
100
|
+
return true;
|
|
101
|
+
previousBlank = false;
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
previousBlank = line.trim() === '';
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
if (hasIndentedCodeBlock(body))
|
|
109
|
+
return { mode: 'source', reason: 'indented-code' };
|
|
110
|
+
return { mode: 'rich' };
|
|
111
|
+
}
|
|
112
|
+
/** Convenience for the UI: whether a body may be opened in the rich editor. */
|
|
113
|
+
export function canEditAsRichText(body, options = {}) {
|
|
114
|
+
return classifyBody(body, options).mode === 'rich';
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=body-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"body-mode.js","sourceRoot":"","sources":["../src/body-mode.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,iEAAiE;AACjE,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,SAAS;IACT,KAAK;IACL,MAAM;IACN,OAAO;IACP,UAAU;IACV,UAAU;IACV,SAAS;IACT,SAAS;IACT,SAAS;IACT,UAAU;CACX,CAAC,CAAC;AAyBH;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,UAA2B,EAAE;IACtE,+FAA+F;IAC/F,wEAAwE;IACxE,IAAI,OAAO,CAAC,SAAS,KAAK,KAAK;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;IAC1E,IAAI,OAAO,CAAC,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC;IAEzE,MAAM,UAAU,GAAG,OAAO,CAAC,gBAAgB;QACzC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,iBAAiB,EAAE,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAAC;QAC9D,CAAC,CAAC,iBAAiB,CAAC;IAEtB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,OAAO,GAAG,KAAK,CAAC;IAEpB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,qCAAqC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC/D,IAAI,KAAK,EAAE,CAAC;YACV,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,MAAM,QAAQ,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;gBAChD,IAAI,QAAQ,IAAI,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;oBACzC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;gBACxD,CAAC;gBACD,OAAO,GAAG,IAAI,CAAC;YACjB,CAAC;iBAAM,CAAC;gBACN,OAAO,GAAG,KAAK,CAAC;YAClB,CAAC;YACD,SAAS;QACX,CAAC;QACD,IAAI,OAAO;YAAE,SAAS;QAEtB,8DAA8D;QAC9D,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;QAErF,8FAA8F;QAC9F,8EAA8E;QAC9E,IAAI,kCAAkC,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC;QAC5F,IAAI,oEAAoE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACpF,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;QAC5C,CAAC;QAED,6FAA6F;QAC7F,2DAA2D;QAC3D,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IACvE,CAAC;IAED;;;;;OAKG;IACH,SAAS,oBAAoB,CAAC,IAAY;QACxC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC/B,IAAI,MAAM,GAAG,KAAK,CAAC;QACnB,IAAI,aAAa,GAAG,IAAI,CAAC;QACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACvC,MAAM,GAAG,CAAC,MAAM,CAAC;gBACjB,aAAa,GAAG,KAAK,CAAC;gBACtB,SAAS;YACX,CAAC;YACD,IAAI,MAAM;gBAAE,SAAS;YACrB,mFAAmF;YACnF,IAAI,yBAAyB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACzC,aAAa,GAAG,KAAK,CAAC;gBACtB,SAAS;YACX,CAAC;YACD,IAAI,gBAAgB,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBAChC,IAAI,aAAa;oBAAE,OAAO,IAAI,CAAC;gBAC/B,aAAa,GAAG,KAAK,CAAC;gBACtB,SAAS;YACX,CAAC;YACD,aAAa,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC;QACrC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,oBAAoB,CAAC,IAAI,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IACnF,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;AAC1B,CAAC;AAED,+EAA+E;AAC/E,MAAM,UAAU,iBAAiB,CAAC,IAAY,EAAE,UAA2B,EAAE;IAC3E,OAAO,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config loading and validation.
|
|
3
|
+
*
|
|
4
|
+
* The config is the only thing that knows what a consumer's content looks like; everything else
|
|
5
|
+
* is generic. Validation is deliberately structural — it checks that the config itself makes
|
|
6
|
+
* sense (unique names, fields that exist) and leaves content values to the schema the consumer
|
|
7
|
+
* describes, because a CMS must not reject files it does not understand.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
import type { CMSConfig, Field } from './config.js';
|
|
11
|
+
export declare const configSchema: z.ZodObject<{
|
|
12
|
+
backend: z.ZodObject<{
|
|
13
|
+
name: z.ZodEnum<{
|
|
14
|
+
github: "github";
|
|
15
|
+
local: "local";
|
|
16
|
+
}>;
|
|
17
|
+
repo: z.ZodOptional<z.ZodString>;
|
|
18
|
+
branch: z.ZodOptional<z.ZodString>;
|
|
19
|
+
authBase: z.ZodOptional<z.ZodString>;
|
|
20
|
+
authEndpoint: z.ZodOptional<z.ZodString>;
|
|
21
|
+
local: z.ZodOptional<z.ZodObject<{
|
|
22
|
+
kind: z.ZodEnum<{
|
|
23
|
+
"fs-access": "fs-access";
|
|
24
|
+
proxy: "proxy";
|
|
25
|
+
memory: "memory";
|
|
26
|
+
}>;
|
|
27
|
+
url: z.ZodOptional<z.ZodString>;
|
|
28
|
+
files: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
|
|
29
|
+
}, z.core.$strip>>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
media: z.ZodOptional<z.ZodObject<{
|
|
32
|
+
provider: z.ZodEnum<{
|
|
33
|
+
repo: "repo";
|
|
34
|
+
s3: "s3";
|
|
35
|
+
}>;
|
|
36
|
+
repoPath: z.ZodOptional<z.ZodString>;
|
|
37
|
+
publicPath: z.ZodOptional<z.ZodString>;
|
|
38
|
+
maxEdge: z.ZodOptional<z.ZodNumber>;
|
|
39
|
+
exif: z.ZodOptional<z.ZodBoolean>;
|
|
40
|
+
s3: z.ZodOptional<z.ZodObject<{
|
|
41
|
+
endpoint: z.ZodString;
|
|
42
|
+
bucket: z.ZodString;
|
|
43
|
+
publicBase: z.ZodString;
|
|
44
|
+
}, z.core.$strip>>;
|
|
45
|
+
}, z.core.$strip>>;
|
|
46
|
+
collections: z.ZodArray<z.ZodUnion<readonly [z.ZodObject<{
|
|
47
|
+
kind: z.ZodLiteral<"fields">;
|
|
48
|
+
name: z.ZodString;
|
|
49
|
+
label: z.ZodString;
|
|
50
|
+
labelSingular: z.ZodOptional<z.ZodString>;
|
|
51
|
+
folder: z.ZodString;
|
|
52
|
+
extension: z.ZodEnum<{
|
|
53
|
+
mdx: "mdx";
|
|
54
|
+
md: "md";
|
|
55
|
+
json: "json";
|
|
56
|
+
yaml: "yaml";
|
|
57
|
+
}>;
|
|
58
|
+
format: z.ZodEnum<{
|
|
59
|
+
json: "json";
|
|
60
|
+
frontmatter: "frontmatter";
|
|
61
|
+
}>;
|
|
62
|
+
identifierField: z.ZodOptional<z.ZodString>;
|
|
63
|
+
contentField: z.ZodOptional<z.ZodString>;
|
|
64
|
+
nested: z.ZodOptional<z.ZodBoolean>;
|
|
65
|
+
media: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
66
|
+
create: z.ZodOptional<z.ZodBoolean>;
|
|
67
|
+
fields: z.ZodArray<z.ZodType<Field, unknown, z.core.$ZodTypeInternals<Field, unknown>>>;
|
|
68
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
69
|
+
kind: z.ZodLiteral<"file">;
|
|
70
|
+
name: z.ZodString;
|
|
71
|
+
label: z.ZodString;
|
|
72
|
+
format: z.ZodOptional<z.ZodEnum<{
|
|
73
|
+
json: "json";
|
|
74
|
+
yaml: "yaml";
|
|
75
|
+
}>>;
|
|
76
|
+
files: z.ZodArray<z.ZodObject<{
|
|
77
|
+
name: z.ZodString;
|
|
78
|
+
label: z.ZodString;
|
|
79
|
+
file: z.ZodString;
|
|
80
|
+
fields: z.ZodOptional<z.ZodArray<z.ZodType<Field, unknown, z.core.$ZodTypeInternals<Field, unknown>>>>;
|
|
81
|
+
inferSchema: z.ZodOptional<z.ZodBoolean>;
|
|
82
|
+
fieldOverrides: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodRecord<z.ZodString, z.ZodUnknown>>>;
|
|
83
|
+
}, z.core.$strip>>;
|
|
84
|
+
}, z.core.$strip>]>>;
|
|
85
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
86
|
+
editorialWorkflow: z.ZodOptional<z.ZodBoolean>;
|
|
87
|
+
preview: z.ZodOptional<z.ZodObject<{
|
|
88
|
+
devServerURL: z.ZodOptional<z.ZodString>;
|
|
89
|
+
pathTemplate: z.ZodOptional<z.ZodString>;
|
|
90
|
+
editAttribute: z.ZodOptional<z.ZodString>;
|
|
91
|
+
}, z.core.$strip>>;
|
|
92
|
+
plugins: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
93
|
+
}, z.core.$strip>;
|
|
94
|
+
export interface ConfigIssue {
|
|
95
|
+
path: string;
|
|
96
|
+
message: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Validate a config and report every problem at once, so a consumer fixing a typo does not have
|
|
100
|
+
* to reload repeatedly. Returns the parsed config on success.
|
|
101
|
+
*/
|
|
102
|
+
export declare function loadConfig(input: unknown): {
|
|
103
|
+
config: CMSConfig;
|
|
104
|
+
issues: ConfigIssue[];
|
|
105
|
+
};
|
|
106
|
+
//# sourceMappingURL=config-load.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-load.d.ts","sourceRoot":"","sources":["../src/config-load.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,SAAS,EAAc,KAAK,EAAE,MAAM,aAAa,CAAC;AA6DhE,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA+CvB,CAAC;AAEH,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG;IAC1C,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,WAAW,EAAE,CAAC;CACvB,CA2CA"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config loading and validation.
|
|
3
|
+
*
|
|
4
|
+
* The config is the only thing that knows what a consumer's content looks like; everything else
|
|
5
|
+
* is generic. Validation is deliberately structural — it checks that the config itself makes
|
|
6
|
+
* sense (unique names, fields that exist) and leaves content values to the schema the consumer
|
|
7
|
+
* describes, because a CMS must not reject files it does not understand.
|
|
8
|
+
*/
|
|
9
|
+
import { z } from 'zod';
|
|
10
|
+
const fieldSchema = z.lazy(() => z
|
|
11
|
+
.object({
|
|
12
|
+
name: z.string().min(1),
|
|
13
|
+
label: z.string().optional(),
|
|
14
|
+
widget: z.string().min(1),
|
|
15
|
+
required: z.boolean().optional(),
|
|
16
|
+
hint: z.string().optional(),
|
|
17
|
+
fields: z.array(fieldSchema).optional(),
|
|
18
|
+
field: fieldSchema.optional(),
|
|
19
|
+
options: z
|
|
20
|
+
.array(z.union([z.string(), z.object({ label: z.string(), value: z.string() })]))
|
|
21
|
+
.optional(),
|
|
22
|
+
default: z.unknown().optional(),
|
|
23
|
+
collapseEmpty: z.boolean().optional(),
|
|
24
|
+
collection: z.string().optional(),
|
|
25
|
+
valueField: z.string().optional(),
|
|
26
|
+
displayFields: z.array(z.string()).optional(),
|
|
27
|
+
format: z.string().optional(),
|
|
28
|
+
pattern: z.tuple([z.string(), z.string()]).optional(),
|
|
29
|
+
})
|
|
30
|
+
.loose());
|
|
31
|
+
const fieldsCollectionSchema = z.object({
|
|
32
|
+
kind: z.literal('fields'),
|
|
33
|
+
name: z.string().min(1),
|
|
34
|
+
label: z.string().min(1),
|
|
35
|
+
labelSingular: z.string().optional(),
|
|
36
|
+
folder: z.string().min(1),
|
|
37
|
+
extension: z.enum(['md', 'mdx', 'json', 'yaml']),
|
|
38
|
+
format: z.enum(['frontmatter', 'json']),
|
|
39
|
+
identifierField: z.string().optional(),
|
|
40
|
+
contentField: z.string().optional(),
|
|
41
|
+
nested: z.boolean().optional(),
|
|
42
|
+
media: z.record(z.string(), z.unknown()).optional(),
|
|
43
|
+
create: z.boolean().optional(),
|
|
44
|
+
fields: z.array(fieldSchema).min(1),
|
|
45
|
+
});
|
|
46
|
+
const fileCollectionSchema = z.object({
|
|
47
|
+
kind: z.literal('file'),
|
|
48
|
+
name: z.string().min(1),
|
|
49
|
+
label: z.string().min(1),
|
|
50
|
+
format: z.enum(['yaml', 'json']).optional(),
|
|
51
|
+
files: z
|
|
52
|
+
.array(z.object({
|
|
53
|
+
name: z.string().min(1),
|
|
54
|
+
label: z.string().min(1),
|
|
55
|
+
file: z.string().min(1),
|
|
56
|
+
fields: z.array(fieldSchema).optional(),
|
|
57
|
+
inferSchema: z.boolean().optional(),
|
|
58
|
+
fieldOverrides: z.record(z.string(), z.record(z.string(), z.unknown())).optional(),
|
|
59
|
+
}))
|
|
60
|
+
.min(1),
|
|
61
|
+
});
|
|
62
|
+
export const configSchema = z.object({
|
|
63
|
+
backend: z.object({
|
|
64
|
+
name: z.enum(['github', 'local']),
|
|
65
|
+
repo: z
|
|
66
|
+
.string()
|
|
67
|
+
.regex(/^[\w.-]+\/[\w.-]+$/, 'Use the owner/repo form.')
|
|
68
|
+
.optional(),
|
|
69
|
+
branch: z.string().min(1).optional(),
|
|
70
|
+
authBase: z.string().optional(),
|
|
71
|
+
authEndpoint: z.string().optional(),
|
|
72
|
+
local: z
|
|
73
|
+
.object({
|
|
74
|
+
// `memory` backs the tests, demos and previews; the others are real file access.
|
|
75
|
+
kind: z.enum(['fs-access', 'proxy', 'memory']),
|
|
76
|
+
url: z.string().optional(),
|
|
77
|
+
/** Seed files for the memory backend, keyed by repository path. */
|
|
78
|
+
files: z.record(z.string(), z.string()).optional(),
|
|
79
|
+
})
|
|
80
|
+
.optional(),
|
|
81
|
+
}),
|
|
82
|
+
media: z
|
|
83
|
+
.object({
|
|
84
|
+
provider: z.enum(['repo', 's3']),
|
|
85
|
+
repoPath: z.string().optional(),
|
|
86
|
+
publicPath: z.string().optional(),
|
|
87
|
+
maxEdge: z.number().int().positive().optional(),
|
|
88
|
+
exif: z.boolean().optional(),
|
|
89
|
+
s3: z
|
|
90
|
+
.object({
|
|
91
|
+
endpoint: z.string(),
|
|
92
|
+
bucket: z.string(),
|
|
93
|
+
publicBase: z.string(),
|
|
94
|
+
})
|
|
95
|
+
.optional(),
|
|
96
|
+
})
|
|
97
|
+
.optional(),
|
|
98
|
+
collections: z.array(z.union([fieldsCollectionSchema, fileCollectionSchema])).min(1),
|
|
99
|
+
locale: z.string().optional(),
|
|
100
|
+
editorialWorkflow: z.boolean().optional(),
|
|
101
|
+
preview: z
|
|
102
|
+
.object({
|
|
103
|
+
devServerURL: z.string().optional(),
|
|
104
|
+
pathTemplate: z.string().optional(),
|
|
105
|
+
editAttribute: z.string().optional(),
|
|
106
|
+
})
|
|
107
|
+
.optional(),
|
|
108
|
+
plugins: z.array(z.unknown()).optional(),
|
|
109
|
+
});
|
|
110
|
+
/**
|
|
111
|
+
* Validate a config and report every problem at once, so a consumer fixing a typo does not have
|
|
112
|
+
* to reload repeatedly. Returns the parsed config on success.
|
|
113
|
+
*/
|
|
114
|
+
export function loadConfig(input) {
|
|
115
|
+
const issues = [];
|
|
116
|
+
const result = configSchema.safeParse(input);
|
|
117
|
+
if (!result.success) {
|
|
118
|
+
for (const issue of result.error.issues) {
|
|
119
|
+
issues.push({ path: issue.path.join('.'), message: issue.message });
|
|
120
|
+
}
|
|
121
|
+
return { config: input, issues };
|
|
122
|
+
}
|
|
123
|
+
const config = result.data;
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
for (const collection of config.collections) {
|
|
126
|
+
if (seen.has(collection.name)) {
|
|
127
|
+
issues.push({
|
|
128
|
+
path: `collections.${collection.name}`,
|
|
129
|
+
message: 'Duplicate collection name.',
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
seen.add(collection.name);
|
|
133
|
+
collectFieldIssues(collection, issues);
|
|
134
|
+
}
|
|
135
|
+
if (config.backend.name === 'github' && !config.backend.repo) {
|
|
136
|
+
issues.push({
|
|
137
|
+
path: 'backend.repo',
|
|
138
|
+
message: 'The github backend needs a repo.',
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
if (config.backend.name === 'local' && !config.backend.local) {
|
|
142
|
+
issues.push({
|
|
143
|
+
path: 'backend.local',
|
|
144
|
+
message: 'The local backend needs a local mode.',
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
if (config.media?.provider === 's3' && !config.media.s3) {
|
|
148
|
+
issues.push({
|
|
149
|
+
path: 'media.s3',
|
|
150
|
+
message: 'The s3 media provider needs its settings.',
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return { config, issues };
|
|
154
|
+
}
|
|
155
|
+
function collectFieldIssues(collection, issues) {
|
|
156
|
+
if (collection.kind !== 'fields')
|
|
157
|
+
return;
|
|
158
|
+
const walk = (fields, prefix) => {
|
|
159
|
+
const names = new Set();
|
|
160
|
+
for (const field of fields) {
|
|
161
|
+
const path = `${prefix}${field.name}`;
|
|
162
|
+
if (names.has(field.name)) {
|
|
163
|
+
issues.push({
|
|
164
|
+
path,
|
|
165
|
+
message: 'Duplicate field name in the same object.',
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
names.add(field.name);
|
|
169
|
+
if (field.widget === 'object' && !field.fields?.length) {
|
|
170
|
+
issues.push({ path, message: 'An object field needs nested fields.' });
|
|
171
|
+
}
|
|
172
|
+
if (field.widget === 'list' && !field.fields?.length && !field.field) {
|
|
173
|
+
issues.push({
|
|
174
|
+
path,
|
|
175
|
+
message: 'A list field needs either `fields` or `field`.',
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (field.fields)
|
|
179
|
+
walk(field.fields, `${path}.`);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
walk(collection.fields, `${collection.name}.`);
|
|
183
|
+
if (collection.contentField) {
|
|
184
|
+
const top = collection.fields.some((field) => field.name === collection.contentField);
|
|
185
|
+
if (!top) {
|
|
186
|
+
issues.push({
|
|
187
|
+
path: `${collection.name}.contentField`,
|
|
188
|
+
message: `No field named "${collection.contentField}".`,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
//# sourceMappingURL=config-load.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-load.js","sourceRoot":"","sources":["../src/config-load.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The config a consumer writes. Everything the CMS does is derived from this object; the CMS
|
|
3
|
+
* itself never mentions a specific collection or field name.
|
|
4
|
+
*/
|
|
5
|
+
import type { CommonFieldOptions } from './fields.js';
|
|
6
|
+
export interface LocalizedString {
|
|
7
|
+
[locale: string]: string;
|
|
8
|
+
}
|
|
9
|
+
export interface BackendConfig {
|
|
10
|
+
/** `github` talks to the REST API. `local` uses a browser directory or the local proxy. */
|
|
11
|
+
name: 'github' | 'local';
|
|
12
|
+
/** `owner/repo`, required for the github backend. */
|
|
13
|
+
repo?: string;
|
|
14
|
+
branch?: string;
|
|
15
|
+
/** Base of the OAuth relay, e.g. `https://example.com`. Omit to use a personal access token. */
|
|
16
|
+
authBase?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Path of the relay's endpoint on that base. Defaults to `auth`. A relay mounted under a prefix
|
|
19
|
+
* — `/api/auth`, say — needs this, and without it the sign-in popup is a 404.
|
|
20
|
+
*/
|
|
21
|
+
authEndpoint?: string;
|
|
22
|
+
/** Where the local backend reads and writes. */
|
|
23
|
+
local?: {
|
|
24
|
+
/** `memory` backs the tests, demos and previews; the others are real file access. */
|
|
25
|
+
kind: 'fs-access' | 'proxy' | 'memory';
|
|
26
|
+
url?: string;
|
|
27
|
+
/** Seed files for the memory backend, keyed by repository path. */
|
|
28
|
+
files?: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
export type MediaProvider = 'repo' | 's3';
|
|
32
|
+
export interface MediaConfig {
|
|
33
|
+
provider: MediaProvider;
|
|
34
|
+
/** Path inside the repository, supports `{{slug}}` and `{{collection}}`. */
|
|
35
|
+
repoPath?: string;
|
|
36
|
+
/** Public URL prefix for the same files, e.g. `/images/albums/{{slug}}`. */
|
|
37
|
+
publicPath?: string;
|
|
38
|
+
/** Longest edge in pixels for uploaded images. */
|
|
39
|
+
maxEdge?: number;
|
|
40
|
+
/** Read EXIF from uploads to prefill photographic fields. */
|
|
41
|
+
exif?: boolean;
|
|
42
|
+
/** Object storage settings, used when `provider` is `s3`. */
|
|
43
|
+
s3?: {
|
|
44
|
+
endpoint: string;
|
|
45
|
+
bucket: string;
|
|
46
|
+
publicBase: string;
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** A field in a collection's schema. `widget` selects the field type. */
|
|
50
|
+
export interface Field extends CommonFieldOptions {
|
|
51
|
+
/** Nested fields for `object` and for the item shape of `list`. */
|
|
52
|
+
fields?: Field[];
|
|
53
|
+
/** Item field for a scalar `list`, e.g. a list of strings. */
|
|
54
|
+
field?: Field;
|
|
55
|
+
options?: Array<string | {
|
|
56
|
+
label: string;
|
|
57
|
+
value: string;
|
|
58
|
+
}>;
|
|
59
|
+
/** Default value written when a new entry or list item is created. */
|
|
60
|
+
default?: unknown;
|
|
61
|
+
/** For `object`: omit the whole object when every child is empty. */
|
|
62
|
+
collapseEmpty?: boolean;
|
|
63
|
+
/** For `relation`: which collection to draw values from. */
|
|
64
|
+
collection?: string;
|
|
65
|
+
valueField?: string;
|
|
66
|
+
displayFields?: string[];
|
|
67
|
+
/** For `date`: output format. */
|
|
68
|
+
format?: string;
|
|
69
|
+
/** Regex the value must match, plus the message to show when it does not. */
|
|
70
|
+
pattern?: [string, string];
|
|
71
|
+
/** Singular label for a list's add button, e.g. "Add 图片". */
|
|
72
|
+
labelSingular?: string;
|
|
73
|
+
/** Free-form options passed through to the field type. */
|
|
74
|
+
[key: string]: unknown;
|
|
75
|
+
}
|
|
76
|
+
export interface CollectionBase {
|
|
77
|
+
name: string;
|
|
78
|
+
label: string;
|
|
79
|
+
labelSingular?: string;
|
|
80
|
+
/** Directory the entries live in, relative to the repository root. */
|
|
81
|
+
folder: string;
|
|
82
|
+
/** Which file extension new entries get. */
|
|
83
|
+
extension: 'md' | 'mdx' | 'json' | 'yaml';
|
|
84
|
+
format: 'frontmatter' | 'json';
|
|
85
|
+
/** Field whose value names an entry, used in URLs and in the entry list. */
|
|
86
|
+
identifierField?: string;
|
|
87
|
+
/** Field holding the body; omit for data-only collections. */
|
|
88
|
+
contentField?: string;
|
|
89
|
+
/** Allow entries in subdirectories of `folder`. */
|
|
90
|
+
nested?: boolean;
|
|
91
|
+
media?: Partial<MediaConfig>;
|
|
92
|
+
fields: Field[];
|
|
93
|
+
}
|
|
94
|
+
export interface FieldsCollection extends CollectionBase {
|
|
95
|
+
kind: 'fields';
|
|
96
|
+
create?: boolean;
|
|
97
|
+
}
|
|
98
|
+
export interface FileEntry {
|
|
99
|
+
name: string;
|
|
100
|
+
label: string;
|
|
101
|
+
/** Single file this entry edits, relative to the repository root. */
|
|
102
|
+
file: string;
|
|
103
|
+
fields?: Field[];
|
|
104
|
+
/** Derive the field list from the file's current contents instead of declaring it. */
|
|
105
|
+
inferSchema?: boolean;
|
|
106
|
+
/** Override inferred fields by path, with `*` matching one segment. */
|
|
107
|
+
fieldOverrides?: Record<string, Partial<Field>>;
|
|
108
|
+
}
|
|
109
|
+
export interface FileCollection {
|
|
110
|
+
kind: 'file';
|
|
111
|
+
name: string;
|
|
112
|
+
label: string;
|
|
113
|
+
format?: 'yaml' | 'json';
|
|
114
|
+
files: FileEntry[];
|
|
115
|
+
}
|
|
116
|
+
export type Collection = FieldsCollection | FileCollection;
|
|
117
|
+
export type FieldsCollectionItem = FieldsCollection;
|
|
118
|
+
export type FileCollectionItem = FileCollection;
|
|
119
|
+
export interface PreviewConfig {
|
|
120
|
+
/** Dev server to embed, e.g. `http://localhost:4321`. */
|
|
121
|
+
devServerURL?: string;
|
|
122
|
+
/** URL of an entry inside that server, with `{{slug}}` and `{{collection}}` placeholders. */
|
|
123
|
+
pathTemplate?: string;
|
|
124
|
+
/**
|
|
125
|
+
* In-context editing: the attribute the site marks editable nodes with.
|
|
126
|
+
*
|
|
127
|
+
* A theme opts in by rendering `data-v7-field="title"` on the element that shows that field — the
|
|
128
|
+
* value is the dotted frontmatter path. The editor then outlines those nodes in the embedded
|
|
129
|
+
* page, and clicking one focuses the matching control.
|
|
130
|
+
*
|
|
131
|
+
* Off unless a site asks for it, because it can only work when the site cooperates: an editor
|
|
132
|
+
* that guessed which node showed which field would be wrong often enough to be worse than none.
|
|
133
|
+
*/
|
|
134
|
+
editAttribute?: string;
|
|
135
|
+
}
|
|
136
|
+
export interface CMSConfig {
|
|
137
|
+
backend: BackendConfig;
|
|
138
|
+
media?: MediaConfig;
|
|
139
|
+
collections: Collection[];
|
|
140
|
+
/** UI language. Built in: `en`, `zh-CN`. */
|
|
141
|
+
locale?: string;
|
|
142
|
+
/** Turn on draft branches and review status. */
|
|
143
|
+
editorialWorkflow?: boolean;
|
|
144
|
+
preview?: PreviewConfig;
|
|
145
|
+
/** Consumer plugins: custom field types, preview renderers, UI slots. */
|
|
146
|
+
plugins?: unknown[];
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":""}
|