@jxsuite/schema 1.4.0 → 1.8.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/class-schema.json +1 -0
- package/defs/children-value.schema.ts +10 -1
- package/defs/class-def.schema.ts +1 -0
- package/defs/element-def.schema.ts +1 -1
- package/defs/expression-node.schema.ts +4 -1
- package/defs/external-class-def.schema.ts +20 -2
- package/defs/field-schema.schema.ts +75 -0
- package/defs/head-entry.schema.ts +6 -1
- package/defs/image-config.schema.ts +7 -0
- package/defs/index.ts +2 -1
- package/defs/project-config.schema.ts +299 -7
- package/defs/tag-expression.schema.ts +98 -0
- package/defs/tag-name.schema.ts +13 -1
- package/package.json +7 -3
- package/project-schema.json +441 -5
- package/schema.json +289 -13
- package/schemas/document.paths.schema.json +54 -1
- package/schemas/project.core.schema.json +401 -5
- package/src/asset-paths.ts +21 -0
- package/src/extension-registry.ts +12 -0
- package/src/format-registry.ts +32 -2
- package/src/guards.ts +68 -2
- package/src/ijson.ts +205 -0
- package/src/intl.ts +155 -0
- package/src/locale.ts +429 -0
- package/src/media-type.ts +217 -0
- package/src/parse.ts +78 -1
- package/src/project-schemas.ts +358 -12
- package/src/schema.ts +85 -8
- package/types.ts +191 -4
package/class-schema.json
CHANGED
|
@@ -23,8 +23,17 @@ export const childrenValueSchema = {
|
|
|
23
23
|
oneOf: [
|
|
24
24
|
{
|
|
25
25
|
items: {
|
|
26
|
-
oneOf
|
|
26
|
+
/* `anyOf`, not `oneOf`, and SwitchNode is the reason for both halves of that.
|
|
27
|
+
`$defs.SwitchNode` was defined and referenced from nowhere — a node the compiler fully
|
|
28
|
+
renders (`shared.ts` renderSwitch) that no position in the schema admitted, so every
|
|
29
|
+
document with a `$switch` child failed `jx validate`. Adding the ref under `oneOf` would
|
|
30
|
+
have traded one break for another: a switch child that also carries `tagName` (its
|
|
31
|
+
container element) matches ElementDef AND SwitchNode, and "exactly one" then rejects the
|
|
32
|
+
form that works today. `anyOf` accepts both, and still rejects a child object that is
|
|
33
|
+
neither. */
|
|
34
|
+
anyOf: [
|
|
27
35
|
{ $ref: "#/$defs/ElementDef" },
|
|
36
|
+
{ $ref: "#/$defs/SwitchNode" },
|
|
28
37
|
{ $ref: "#/$defs/ArrayNamespace" },
|
|
29
38
|
{ type: "string" },
|
|
30
39
|
{ type: "number" },
|
package/defs/class-def.schema.ts
CHANGED
|
@@ -100,7 +100,7 @@ export const elementDefSchema = {
|
|
|
100
100
|
src: { $ref: "#/$defs/StringOrRef" },
|
|
101
101
|
style: { $ref: "#/$defs/StyleObject" },
|
|
102
102
|
tabIndex: { $ref: "#/$defs/NumberOrRef" },
|
|
103
|
-
tagName: { $ref: "#/$defs/
|
|
103
|
+
tagName: { $ref: "#/$defs/ElementTagName" },
|
|
104
104
|
textContent: { $ref: "#/$defs/StringOrRef" },
|
|
105
105
|
title: { $ref: "#/$defs/StringOrRef" },
|
|
106
106
|
type: { $ref: "#/$defs/StringOrRef" },
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { intlHelpersClause } from "../src/intl.ts";
|
|
2
|
+
|
|
1
3
|
export const expressionPointerSchema = {
|
|
2
4
|
additionalProperties: false,
|
|
3
5
|
description:
|
|
@@ -199,7 +201,8 @@ export const expressionNodeSchema = {
|
|
|
199
201
|
},
|
|
200
202
|
{
|
|
201
203
|
description:
|
|
202
|
-
"Invoke a callable, pure in formula position: target is the callee pointer — a named formula entry (#/state/…) or a blessed pure global via window#/ (Math.*, JSON.*, Object.keys/values/entries, and
|
|
204
|
+
"Invoke a callable, pure in formula position: target is the callee pointer — a named formula entry (#/state/…) or a blessed pure global via window#/ (Math.*, JSON.*, Object.keys/values/entries, and " +
|
|
205
|
+
`${intlHelpersClause()}) — and value is the positional argument list (the splice args-in-value precedent). Argument order follows the callee's declared parameters.`,
|
|
203
206
|
not: { required: ["initial"] },
|
|
204
207
|
properties: {
|
|
205
208
|
operator: { $ref: "#/$defs/CallOperator" },
|
|
@@ -57,8 +57,26 @@ export const externalClassDefSchema = {
|
|
|
57
57
|
default: {},
|
|
58
58
|
description: { type: "string" },
|
|
59
59
|
domain: { type: "string" },
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
/* Widened to mirror `sort` below, because this one grab-bag of properties is shared by EVERY
|
|
61
|
+
state `$prototype` with no discrimination on which prototype is in play. `filter` as a
|
|
62
|
+
reactive `$ref` is what the built-in `Array` prototype wants; `@jxsuite/parser`'s
|
|
63
|
+
ContentCollection and `@jxsuite/connector`'s TableQuery both declare their own `filter` as a
|
|
64
|
+
rule ARRAY, and the core shape silently overrode the class's own declaration — so a document
|
|
65
|
+
using a documented class parameter correctly failed `jx validate` with "must be object". */
|
|
66
|
+
filter: {
|
|
67
|
+
anyOf: [
|
|
68
|
+
{ $ref: "#/$defs/RefObject" },
|
|
69
|
+
{ description: "A single filter object, e.g. an equality shorthand.", type: "object" },
|
|
70
|
+
{
|
|
71
|
+
description: 'Ordered filter rules, e.g. [{ field: "Slug", op: "not empty" }].',
|
|
72
|
+
items: { type: "object" },
|
|
73
|
+
type: "array",
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
description:
|
|
77
|
+
"Filter configuration: a reactive $ref binding, a single filter object, or an ordered " +
|
|
78
|
+
"array of rules.",
|
|
79
|
+
},
|
|
62
80
|
headers: { additionalProperties: { type: "string" }, type: "object" },
|
|
63
81
|
indexes: {
|
|
64
82
|
items: {
|
|
@@ -56,3 +56,78 @@ export const relationshipRefSchema = {
|
|
|
56
56
|
required: ["$ref"],
|
|
57
57
|
type: "object",
|
|
58
58
|
} as const;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The CORE `$paths` source shapes — the members the shipped default paths union carries, and the
|
|
62
|
+
* ones every per-project entry document unions extension shapes on top of.
|
|
63
|
+
*
|
|
64
|
+
* These mirror `resolvePathEntries` in the compiler's pages-discovery exactly (specs/
|
|
65
|
+
* site-architecture.md §4.3): an explicit value list, a JSON data-file reference, and the legacy
|
|
66
|
+
* bare array of parameter objects. Anything else is dispatched by discriminator key to an
|
|
67
|
+
* extension's `resolvePaths` capability, so extension shapes reach the union through the entry
|
|
68
|
+
* document rather than from here.
|
|
69
|
+
*
|
|
70
|
+
* Each shape is CLOSED (`additionalProperties: false`). `$paths` is a build-time instruction with a
|
|
71
|
+
* fully specified grammar and no forward-compatible extension points of its own — a stray or
|
|
72
|
+
* misspelled key means the route silently expands to nothing, which is exactly the failure worth
|
|
73
|
+
* catching in the editor.
|
|
74
|
+
*/
|
|
75
|
+
export const documentPathsCoreMembers = [
|
|
76
|
+
{
|
|
77
|
+
additionalProperties: false,
|
|
78
|
+
description:
|
|
79
|
+
'Explicit value list: generates one page per value, bound to `param` (default "value").',
|
|
80
|
+
properties: {
|
|
81
|
+
param: { description: 'Route parameter to fill (default "value").', type: "string" },
|
|
82
|
+
values: { description: "The values to expand.", type: "array" },
|
|
83
|
+
},
|
|
84
|
+
required: ["values"],
|
|
85
|
+
type: "object",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
additionalProperties: false,
|
|
89
|
+
description:
|
|
90
|
+
"JSON data-file source: loads a project-relative file that must contain an array, and " +
|
|
91
|
+
"generates one page per item.",
|
|
92
|
+
properties: {
|
|
93
|
+
$ref: {
|
|
94
|
+
description: "Project-relative path to a .json file containing an array.",
|
|
95
|
+
type: "string",
|
|
96
|
+
},
|
|
97
|
+
field: {
|
|
98
|
+
description: 'Item field providing the parameter value (default "id").',
|
|
99
|
+
type: "string",
|
|
100
|
+
},
|
|
101
|
+
param: { description: 'Route parameter to fill (default "id").', type: "string" },
|
|
102
|
+
},
|
|
103
|
+
required: ["$ref"],
|
|
104
|
+
type: "object",
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
description:
|
|
108
|
+
"Legacy explicit form: an array of ready-made parameter objects, used as-is with no " +
|
|
109
|
+
"expansion. Prefer one of the source shapes above.",
|
|
110
|
+
items: { additionalProperties: { type: "string" }, type: "object" },
|
|
111
|
+
type: "array",
|
|
112
|
+
},
|
|
113
|
+
] as const;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* The extra member the SHIPPED DEFAULT union carries and a per-project entry document does not.
|
|
117
|
+
*
|
|
118
|
+
* A default cannot know which extensions a project enables, so without this any extension source
|
|
119
|
+
* shape — `{ contentType: "blog" }` with the parser on — would be a FALSE ERROR wherever the
|
|
120
|
+
* default is what is in play: the studio's offline fallback before per-project schemas arrive, and
|
|
121
|
+
* clients that fetch the canonical URL. Spec §5.4 promises that path degrades to
|
|
122
|
+
* _under-suggestion_, never false errors, so the default accepts any non-empty object and the entry
|
|
123
|
+
* document — which does know the enabled extensions, and is what `jx validate` and every editor
|
|
124
|
+
* actually resolve — is the strict one.
|
|
125
|
+
*/
|
|
126
|
+
export const documentPathsUnknownSourceMember = {
|
|
127
|
+
description:
|
|
128
|
+
"A source shape contributed by an extension this schema cannot see. The project's generated " +
|
|
129
|
+
"document.schema.json validates these exactly; enable the owning extension and regenerate " +
|
|
130
|
+
"with `jx schema` to get precise checking here.",
|
|
131
|
+
minProperties: 1,
|
|
132
|
+
type: "object",
|
|
133
|
+
} as const;
|
|
@@ -10,7 +10,12 @@ export const headEntrySchema = {
|
|
|
10
10
|
},
|
|
11
11
|
children: { items: { $ref: "#/$defs/HeadEntry" }, type: "array" },
|
|
12
12
|
tagName: { type: "string" },
|
|
13
|
-
textContent: {
|
|
13
|
+
textContent: {
|
|
14
|
+
description:
|
|
15
|
+
"Element content. An object is serialized to JSON inside the tag — that is how a " +
|
|
16
|
+
'JSON-LD block (`<script type="application/ld+json">`) is authored.',
|
|
17
|
+
oneOf: [{ type: "string" }, { type: "object" }],
|
|
18
|
+
},
|
|
14
19
|
},
|
|
15
20
|
required: ["tagName"],
|
|
16
21
|
type: "object",
|
|
@@ -4,6 +4,13 @@ export const imageConfigSchema = {
|
|
|
4
4
|
formats: { items: { type: "string" }, type: "array" },
|
|
5
5
|
lazyLoad: { type: "boolean" },
|
|
6
6
|
optimize: { type: "boolean" },
|
|
7
|
+
picture: {
|
|
8
|
+
description:
|
|
9
|
+
"Wrap a multi-format image in a <picture> with one <source> per format (default true). " +
|
|
10
|
+
"With it off the <img> carries a srcset in the preferred format alone, which browsers " +
|
|
11
|
+
"that cannot decode that format will still select from.",
|
|
12
|
+
type: "boolean",
|
|
13
|
+
},
|
|
7
14
|
quality: {
|
|
8
15
|
properties: {
|
|
9
16
|
avif: { type: "number" },
|
package/defs/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
// Leaf schemas
|
|
2
|
+
export { elementTagNameSchema, tagExpressionSchema } from "./tag-expression.schema";
|
|
2
3
|
export { tagNameSchema } from "./tag-name.schema";
|
|
3
4
|
export { stringOrRefSchema, boolOrRefSchema, numberOrRefSchema } from "./string-or-ref.schema";
|
|
4
5
|
export { headEntrySchema } from "./head-entry.schema";
|
|
@@ -83,7 +84,7 @@ export {
|
|
|
83
84
|
} from "./class-def.schema";
|
|
84
85
|
|
|
85
86
|
// Project
|
|
86
|
-
export { projectConfigSchema } from "./project-config.schema";
|
|
87
|
+
export { projectConfigSchema, REDIRECT_STATUSES } from "./project-config.schema";
|
|
87
88
|
|
|
88
89
|
// Extensions (specs/extensions.md)
|
|
89
90
|
export { extensionManifestSchema } from "./extension-manifest.schema";
|
|
@@ -1,3 +1,14 @@
|
|
|
1
|
+
import { LANGUAGE_TAG_PATTERN } from "../src/locale.ts";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The RFC 9110 §15.4 redirection statuses a static host can express. 303 preserves the "see other,
|
|
5
|
+
* with GET" semantics; 307 and 308 preserve the request method, which 301 and 302 historically do
|
|
6
|
+
* not. **200 is not here**: a host's rewrite/proxy convention is not a redirection status, and
|
|
7
|
+
* modelling it as one is what let the compiler emit a meta-refresh page for a rewrite. Rewrites are
|
|
8
|
+
* `{ destination, rewrite: true }`.
|
|
9
|
+
*/
|
|
10
|
+
export const REDIRECT_STATUSES = [301, 302, 303, 307, 308] as const;
|
|
11
|
+
|
|
1
12
|
export const projectConfigSchema = {
|
|
2
13
|
description:
|
|
3
14
|
"Schema for Jx project.json files. " +
|
|
@@ -126,6 +137,116 @@ export const projectConfigSchema = {
|
|
|
126
137
|
description: "Output directory for compiled site.",
|
|
127
138
|
type: "string",
|
|
128
139
|
},
|
|
140
|
+
headers: {
|
|
141
|
+
additionalProperties: false,
|
|
142
|
+
description:
|
|
143
|
+
"Emit dist/_headers — the response headers only the build can know. Honored by " +
|
|
144
|
+
"Cloudflare Pages/Workers assets and Netlify; on the node/bun adapters the file " +
|
|
145
|
+
"documents what a reverse proxy in front of them must send.",
|
|
146
|
+
properties: {
|
|
147
|
+
cache: {
|
|
148
|
+
default: "auto",
|
|
149
|
+
description:
|
|
150
|
+
'"auto" marks the content-addressed image variants immutable (RFC 8246) and ' +
|
|
151
|
+
'revalidates everything else (RFC 9111). "off" emits no Cache-Control at all.',
|
|
152
|
+
enum: ["auto", "off"],
|
|
153
|
+
type: "string",
|
|
154
|
+
},
|
|
155
|
+
enabled: { default: true, type: "boolean" },
|
|
156
|
+
rules: {
|
|
157
|
+
additionalProperties: { additionalProperties: { type: "string" }, type: "object" },
|
|
158
|
+
description: "Verbatim rules, merged after the generated block.",
|
|
159
|
+
propertyNames: { pattern: "^/" },
|
|
160
|
+
type: "object",
|
|
161
|
+
},
|
|
162
|
+
security: {
|
|
163
|
+
additionalProperties: false,
|
|
164
|
+
properties: {
|
|
165
|
+
contentTypeOptions: { default: true, type: "boolean" },
|
|
166
|
+
csp: {
|
|
167
|
+
default: false,
|
|
168
|
+
description:
|
|
169
|
+
"CSP Level 3. Off by default: unlike the other security headers this one " +
|
|
170
|
+
"governs code the build cannot see — a third-party script that loads a " +
|
|
171
|
+
"second script, a widget that opens a frame — so turning it on is a change " +
|
|
172
|
+
"to verify in the browser console, not a safe default. `script-src` is " +
|
|
173
|
+
"derived from the inline scripts and external origins the build emitted; " +
|
|
174
|
+
"`style-src` keeps 'unsafe-inline', because per-page <style> blocks and " +
|
|
175
|
+
"per-element style attributes have no site-wide hash.",
|
|
176
|
+
oneOf: [
|
|
177
|
+
{ type: "boolean" },
|
|
178
|
+
{ const: "report-only" },
|
|
179
|
+
{
|
|
180
|
+
additionalProperties: false,
|
|
181
|
+
properties: {
|
|
182
|
+
directives: {
|
|
183
|
+
additionalProperties: {
|
|
184
|
+
oneOf: [{ type: "string" }, { const: false }],
|
|
185
|
+
},
|
|
186
|
+
description:
|
|
187
|
+
"Replace (string) or remove (false) a computed directive. A " +
|
|
188
|
+
"replacement is wholesale, so an addition restates the defaults.",
|
|
189
|
+
type: "object",
|
|
190
|
+
},
|
|
191
|
+
mode: { default: "enforce", enum: ["enforce", "report-only"] },
|
|
192
|
+
reportUri: { format: "uri-reference", type: "string" },
|
|
193
|
+
},
|
|
194
|
+
type: "object",
|
|
195
|
+
},
|
|
196
|
+
],
|
|
197
|
+
},
|
|
198
|
+
frameOptions: {
|
|
199
|
+
default: "SAMEORIGIN",
|
|
200
|
+
oneOf: [{ enum: ["DENY", "SAMEORIGIN"], type: "string" }, { const: false }],
|
|
201
|
+
},
|
|
202
|
+
hsts: {
|
|
203
|
+
default: false,
|
|
204
|
+
description:
|
|
205
|
+
"RFC 6797. Off by default: a wrong max-age locks the apex domain to HTTPS " +
|
|
206
|
+
"for that long, and the mistake is invisible until a certificate lapses. " +
|
|
207
|
+
"`preload` requires `includeSubDomains` and is a build error without it.",
|
|
208
|
+
oneOf: [
|
|
209
|
+
{ type: "boolean" },
|
|
210
|
+
{
|
|
211
|
+
additionalProperties: false,
|
|
212
|
+
properties: {
|
|
213
|
+
includeSubDomains: { default: true, type: "boolean" },
|
|
214
|
+
maxAge: { default: 31_536_000, minimum: 0, type: "integer" },
|
|
215
|
+
preload: { default: false, type: "boolean" },
|
|
216
|
+
},
|
|
217
|
+
type: "object",
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
permissionsPolicy: {
|
|
222
|
+
default: "camera=(), microphone=(), geolocation=()",
|
|
223
|
+
oneOf: [{ type: "string" }, { const: false }],
|
|
224
|
+
},
|
|
225
|
+
referrerPolicy: {
|
|
226
|
+
default: "strict-origin-when-cross-origin",
|
|
227
|
+
oneOf: [
|
|
228
|
+
{
|
|
229
|
+
enum: [
|
|
230
|
+
"no-referrer",
|
|
231
|
+
"no-referrer-when-downgrade",
|
|
232
|
+
"origin",
|
|
233
|
+
"origin-when-cross-origin",
|
|
234
|
+
"same-origin",
|
|
235
|
+
"strict-origin",
|
|
236
|
+
"strict-origin-when-cross-origin",
|
|
237
|
+
"unsafe-url",
|
|
238
|
+
],
|
|
239
|
+
type: "string",
|
|
240
|
+
},
|
|
241
|
+
{ const: false },
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
type: "object",
|
|
246
|
+
},
|
|
247
|
+
},
|
|
248
|
+
type: "object",
|
|
249
|
+
},
|
|
129
250
|
sitemap: {
|
|
130
251
|
default: true,
|
|
131
252
|
description:
|
|
@@ -155,9 +276,17 @@ export const projectConfigSchema = {
|
|
|
155
276
|
description: "Default charset for the page.",
|
|
156
277
|
type: "string",
|
|
157
278
|
},
|
|
279
|
+
dir: {
|
|
280
|
+
description:
|
|
281
|
+
"Base direction for the <html> element. Omitted when unset — a page's `$dir` " +
|
|
282
|
+
"overrides it. Right-to-left content renders left-to-right without this.",
|
|
283
|
+
enum: ["ltr", "rtl", "auto"],
|
|
284
|
+
type: "string",
|
|
285
|
+
},
|
|
158
286
|
lang: {
|
|
159
287
|
default: "en",
|
|
160
|
-
description:
|
|
288
|
+
description:
|
|
289
|
+
"Default lang attribute for the <html> element. A page's `$lang` overrides it.",
|
|
161
290
|
type: "string",
|
|
162
291
|
},
|
|
163
292
|
layout: {
|
|
@@ -168,6 +297,18 @@ export const projectConfigSchema = {
|
|
|
168
297
|
examples: ["./layouts/base.json"],
|
|
169
298
|
oneOf: [{ type: "string" }, { type: "null" }],
|
|
170
299
|
},
|
|
300
|
+
shadow: {
|
|
301
|
+
default: false,
|
|
302
|
+
description:
|
|
303
|
+
"Default shadow-DOM mode for every component (spec.md §16.6). Off unless set: a " +
|
|
304
|
+
"shadow root isolates component styles from the page and replaces the <slot> " +
|
|
305
|
+
"emulation with real slot distribution. A component's own `$shadow` overrides this " +
|
|
306
|
+
"in both directions.",
|
|
307
|
+
oneOf: [
|
|
308
|
+
{ enum: ["open", "closed"], type: "string" },
|
|
309
|
+
{ const: false, type: "boolean" },
|
|
310
|
+
],
|
|
311
|
+
},
|
|
171
312
|
},
|
|
172
313
|
type: "object",
|
|
173
314
|
},
|
|
@@ -179,18 +320,127 @@ export const projectConfigSchema = {
|
|
|
179
320
|
items: { type: "string" },
|
|
180
321
|
type: "array",
|
|
181
322
|
},
|
|
323
|
+
manifest: {
|
|
324
|
+
additionalProperties: false,
|
|
325
|
+
description:
|
|
326
|
+
'W3C Web App Manifest, emitted as manifest.webmanifest with a <link rel="manifest"> ' +
|
|
327
|
+
"on every page. Absent means no manifest: it is a claim that a site is meant to be " +
|
|
328
|
+
"installed, and most are not.",
|
|
329
|
+
properties: {
|
|
330
|
+
backgroundColor: { type: "string" },
|
|
331
|
+
categories: { items: { type: "string" }, type: "array" },
|
|
332
|
+
description: { type: "string" },
|
|
333
|
+
dir: { enum: ["ltr", "rtl", "auto"], type: "string" },
|
|
334
|
+
display: {
|
|
335
|
+
default: "standalone",
|
|
336
|
+
enum: ["fullscreen", "standalone", "minimal-ui", "browser"],
|
|
337
|
+
type: "string",
|
|
338
|
+
},
|
|
339
|
+
enabled: { default: true, type: "boolean" },
|
|
340
|
+
icons: {
|
|
341
|
+
description:
|
|
342
|
+
"Icons at 192px and 512px are what browsers require before offering to install " +
|
|
343
|
+
"the site; the build warns when either is missing.",
|
|
344
|
+
items: {
|
|
345
|
+
additionalProperties: false,
|
|
346
|
+
properties: {
|
|
347
|
+
purpose: { type: "string" },
|
|
348
|
+
sizes: { examples: ["192x192"], type: "string" },
|
|
349
|
+
src: { type: "string" },
|
|
350
|
+
type: { type: "string" },
|
|
351
|
+
},
|
|
352
|
+
required: ["src"],
|
|
353
|
+
type: "object",
|
|
354
|
+
},
|
|
355
|
+
type: "array",
|
|
356
|
+
},
|
|
357
|
+
lang: { type: "string" },
|
|
358
|
+
name: { description: "Defaults to the project name.", type: "string" },
|
|
359
|
+
orientation: { type: "string" },
|
|
360
|
+
scope: { type: "string" },
|
|
361
|
+
shortName: { type: "string" },
|
|
362
|
+
startUrl: { default: "/", type: "string" },
|
|
363
|
+
themeColor: { type: "string" },
|
|
364
|
+
},
|
|
365
|
+
type: "object",
|
|
366
|
+
},
|
|
367
|
+
serviceWorker: {
|
|
368
|
+
default: false,
|
|
369
|
+
description:
|
|
370
|
+
"W3C Service Worker, emitted as sw.js. Off unless declared — a worker is sticky, so it " +
|
|
371
|
+
"is the one output that can keep breaking visitors after a deploy tries to fix them. " +
|
|
372
|
+
"**Turning it off means `false`, not deleting the key**: `false` emits a tombstone at " +
|
|
373
|
+
"the same URL that unregisters the old worker and clears its caches, and a 404 there is " +
|
|
374
|
+
"not an instruction to stop. HTML is always network-first; only the content-addressed " +
|
|
375
|
+
"/images/_optimized/* is served cache-first.",
|
|
376
|
+
oneOf: [
|
|
377
|
+
{ type: "boolean" },
|
|
378
|
+
{
|
|
379
|
+
additionalProperties: false,
|
|
380
|
+
properties: {
|
|
381
|
+
enabled: { default: true, type: "boolean" },
|
|
382
|
+
offlineFallback: {
|
|
383
|
+
description:
|
|
384
|
+
"Page served for a failed navigation. Added to `precache` if not already there.",
|
|
385
|
+
type: "string",
|
|
386
|
+
},
|
|
387
|
+
precache: {
|
|
388
|
+
description:
|
|
389
|
+
"Site-absolute URLs fetched at install time. Each must be a file this build " +
|
|
390
|
+
"produces; one that is not fails the build, because a single unreachable entry " +
|
|
391
|
+
"stops the worker installing at all.",
|
|
392
|
+
items: { pattern: "^/", type: "string" },
|
|
393
|
+
type: "array",
|
|
394
|
+
},
|
|
395
|
+
scope: { default: "/", type: "string" },
|
|
396
|
+
},
|
|
397
|
+
type: "object",
|
|
398
|
+
},
|
|
399
|
+
],
|
|
400
|
+
},
|
|
401
|
+
securityTxt: {
|
|
402
|
+
additionalProperties: false,
|
|
403
|
+
description:
|
|
404
|
+
"RFC 9116 security.txt, emitted at .well-known/security.txt. `expires` is required by " +
|
|
405
|
+
"§2.5.5 and an absent or past value is a build error — an expired file advertises a " +
|
|
406
|
+
"reporting channel while telling the reporter not to trust it. A hand-placed " +
|
|
407
|
+
"public/.well-known/security.txt shadows this, which is how a clearsigned file ships.",
|
|
408
|
+
properties: {
|
|
409
|
+
acknowledgments: { items: { type: "string" }, type: "array" },
|
|
410
|
+
canonical: { type: "string" },
|
|
411
|
+
contact: {
|
|
412
|
+
description: "At least one mailto:, https: or tel: URI (§2.5.3).",
|
|
413
|
+
items: { type: "string" },
|
|
414
|
+
type: "array",
|
|
415
|
+
},
|
|
416
|
+
enabled: { default: true, type: "boolean" },
|
|
417
|
+
encryption: { items: { type: "string" }, type: "array" },
|
|
418
|
+
expires: { format: "date-time", type: "string" },
|
|
419
|
+
hiring: { items: { type: "string" }, type: "array" },
|
|
420
|
+
policy: { items: { type: "string" }, type: "array" },
|
|
421
|
+
preferredLanguages: {
|
|
422
|
+
description: "BCP 47 tags, validated and canonicalized like i18n.locales.",
|
|
423
|
+
items: { pattern: LANGUAGE_TAG_PATTERN, type: "string" },
|
|
424
|
+
type: "array",
|
|
425
|
+
},
|
|
426
|
+
},
|
|
427
|
+
type: "object",
|
|
428
|
+
},
|
|
182
429
|
i18n: {
|
|
183
430
|
description: "Internationalization configuration.",
|
|
184
431
|
properties: {
|
|
185
432
|
defaultLocale: {
|
|
186
|
-
description:
|
|
433
|
+
description:
|
|
434
|
+
"Default locale, as a BCP 47 language tag. The pattern catches shape errors " +
|
|
435
|
+
"(`en_US`, `en--US`); the build is the authority on well-formedness.",
|
|
187
436
|
examples: ["en"],
|
|
437
|
+
pattern: LANGUAGE_TAG_PATTERN,
|
|
188
438
|
type: "string",
|
|
189
439
|
},
|
|
190
440
|
locales: {
|
|
191
|
-
description: "Available
|
|
441
|
+
description: "Available locales, as BCP 47 language tags.",
|
|
192
442
|
examples: [["en", "fr", "de"]],
|
|
193
|
-
items: { type: "string" },
|
|
443
|
+
items: { pattern: LANGUAGE_TAG_PATTERN, type: "string" },
|
|
194
444
|
type: "array",
|
|
195
445
|
},
|
|
196
446
|
routing: {
|
|
@@ -222,9 +472,51 @@ export const projectConfigSchema = {
|
|
|
222
472
|
type: "string",
|
|
223
473
|
},
|
|
224
474
|
redirects: {
|
|
225
|
-
additionalProperties: {
|
|
226
|
-
|
|
227
|
-
|
|
475
|
+
additionalProperties: {
|
|
476
|
+
oneOf: [
|
|
477
|
+
{ description: "Destination path or URL; implies a 301.", type: "string" },
|
|
478
|
+
{
|
|
479
|
+
additionalProperties: false,
|
|
480
|
+
description: "A redirect with an explicit RFC 9110 §15.4 status.",
|
|
481
|
+
properties: {
|
|
482
|
+
destination: { type: "string" },
|
|
483
|
+
status: {
|
|
484
|
+
default: 301,
|
|
485
|
+
description:
|
|
486
|
+
"RFC 9110 §15.4 redirection status. 307 and 308 preserve the request method; " +
|
|
487
|
+
"301 and 302 historically do not.",
|
|
488
|
+
enum: [...REDIRECT_STATUSES],
|
|
489
|
+
type: "integer",
|
|
490
|
+
},
|
|
491
|
+
},
|
|
492
|
+
required: ["destination"],
|
|
493
|
+
type: "object",
|
|
494
|
+
},
|
|
495
|
+
{
|
|
496
|
+
additionalProperties: false,
|
|
497
|
+
description:
|
|
498
|
+
"A rewrite (proxy): the destination's content is served AT the source URL, with no " +
|
|
499
|
+
"redirect. Emitted as status 200 in `_redirects`, which is the host convention — " +
|
|
500
|
+
"not an HTTP redirection status.",
|
|
501
|
+
properties: {
|
|
502
|
+
destination: { type: "string" },
|
|
503
|
+
rewrite: { const: true, type: "boolean" },
|
|
504
|
+
},
|
|
505
|
+
required: ["destination", "rewrite"],
|
|
506
|
+
type: "object",
|
|
507
|
+
},
|
|
508
|
+
],
|
|
509
|
+
},
|
|
510
|
+
description:
|
|
511
|
+
"Static redirect and rewrite rules. Keys are source paths in URLPattern pathname syntax.",
|
|
512
|
+
examples: [
|
|
513
|
+
{
|
|
514
|
+
"/api/*": { destination: "https://api.example.com/*", rewrite: true },
|
|
515
|
+
"/legacy/*": { destination: "/archive/*", status: 308 },
|
|
516
|
+
"/old-about": "/about",
|
|
517
|
+
},
|
|
518
|
+
],
|
|
519
|
+
propertyNames: { pattern: "^/" },
|
|
228
520
|
type: "object",
|
|
229
521
|
},
|
|
230
522
|
state: {
|