@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
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A tag name chosen at element creation, from a set the schema can enumerate.
|
|
3
|
+
*
|
|
4
|
+
* The motivating case is one element that must be an `<a>` when a prop is set and a `<div>` when it
|
|
5
|
+
* is not, wrapping an identical subtree either way. Before this def there were two answers and both
|
|
6
|
+
* were bad: write `"${state.href ? 'a' : 'div'}"`, which nothing in the pipeline evaluates and each
|
|
7
|
+
* consumer then broke differently and silently (see {@link tagNameSchema}); or reach for a
|
|
8
|
+
* `$switch` CHILD, whose cases render in place of the container's content — so varying the wrapper
|
|
9
|
+
* means duplicating the whole subtree once per case, and still leaves the container `<div>` wrapped
|
|
10
|
+
* around the result.
|
|
11
|
+
*
|
|
12
|
+
* **The results are `TagName`s, not operands.** That is the property everything else here depends
|
|
13
|
+
* on. The discriminant in `target` may be any expression, but every branch's RESULT is a literal
|
|
14
|
+
* tag held to the same pattern a written-out tag is held to — so the candidate set is readable
|
|
15
|
+
* straight out of the JSON without evaluating anything. The compiler can emit one template per
|
|
16
|
+
* candidate, `jx validate` can refuse `"A LINK"` at authoring time, and the void-element,
|
|
17
|
+
* preformatted, `img` and slot analyses that read a tag structurally keep a finite set to reason
|
|
18
|
+
* about. A `${…}` template in this position would have surrendered all of that.
|
|
19
|
+
*
|
|
20
|
+
* **Vocabulary, not new keywords.** `$expression`, `?:` (test in `target`, consequent in `value`,
|
|
21
|
+
* alternate in `initial`) and `switch` (discriminant in `target`, results in `cases`/`default`) all
|
|
22
|
+
* already mean exactly this — see `expression-node.schema.ts` and spec §19.4b. The alternative
|
|
23
|
+
* considered was `$switch`/`cases` inside the tagName block, which would have been a THIRD switch
|
|
24
|
+
* syntax in a language that has two, and would have made `cases` a homograph inside one element
|
|
25
|
+
* object: tag strings in `tagName`, child subtrees three lines below.
|
|
26
|
+
*
|
|
27
|
+
* **`default`/`initial` are REQUIRED here.** The expression-level `switch` leaves `default`
|
|
28
|
+
* optional and yields `undefined` when nothing matches; an element with no tag is not a thing that
|
|
29
|
+
* can exist, so the fallback is mandatory rather than a shape the runtime has to invent a rule
|
|
30
|
+
* for.
|
|
31
|
+
*
|
|
32
|
+
* **Evaluated ONCE, when the element is created, and never re-read.** This is the one place a
|
|
33
|
+
* `$expression` is not live, and it is deliberate: a tag that changed after mount would mean
|
|
34
|
+
* replacing the node, which costs the subtree's listeners, its focus, its typed input values and
|
|
35
|
+
* its component instances — and the runtime has no dispose walk to pay that bill with. `jx
|
|
36
|
+
* validate` warns when a tag discriminant is also an assignment target anywhere in the document, so
|
|
37
|
+
* the one case where the rule bites is caught at authoring time rather than discovered as a `<div>`
|
|
38
|
+
* that never became an `<a>`.
|
|
39
|
+
*/
|
|
40
|
+
export const tagExpressionSchema = {
|
|
41
|
+
anyOf: [
|
|
42
|
+
{
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
description:
|
|
45
|
+
"Two-way: `target` is the test, `value` the tag when it is truthy, `initial` the tag when it is not.",
|
|
46
|
+
properties: {
|
|
47
|
+
initial: { $ref: "#/$defs/TagName" },
|
|
48
|
+
operator: { const: "?:", type: "string" },
|
|
49
|
+
target: { $ref: "#/$defs/ExpressionOperand" },
|
|
50
|
+
value: { $ref: "#/$defs/TagName" },
|
|
51
|
+
},
|
|
52
|
+
required: ["operator", "target", "value", "initial"],
|
|
53
|
+
title: "?: — the tag when the test holds, and the tag when it does not",
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
additionalProperties: false,
|
|
57
|
+
description:
|
|
58
|
+
"Multiway: `target` is the discriminant, matched against case keys by its string form. " +
|
|
59
|
+
"`default` is required — an element with no tag cannot exist.",
|
|
60
|
+
properties: {
|
|
61
|
+
cases: {
|
|
62
|
+
additionalProperties: { $ref: "#/$defs/TagName" },
|
|
63
|
+
type: "object",
|
|
64
|
+
},
|
|
65
|
+
default: { $ref: "#/$defs/TagName" },
|
|
66
|
+
operator: { const: "switch", type: "string" },
|
|
67
|
+
target: { $ref: "#/$defs/ExpressionOperand" },
|
|
68
|
+
},
|
|
69
|
+
required: ["operator", "target", "cases", "default"],
|
|
70
|
+
title: "switch — one tag per discriminant value, with a required fallback",
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
description:
|
|
74
|
+
"A tag name chosen at element creation. Every branch resolves to a `TagName`, so the candidate " +
|
|
75
|
+
"set is enumerable without evaluating anything.",
|
|
76
|
+
} as const;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* What an ELEMENT's `tagName` may be: a name, or a {@link tagExpressionSchema} choosing between
|
|
80
|
+
* names.
|
|
81
|
+
*
|
|
82
|
+
* Wired into `ElementDef.tagName` alone. The document ROOT and `SwitchNode`'s container keep the
|
|
83
|
+
* bare `TagName`, so `customElements.define`, the compiler's module emit and the output filenames
|
|
84
|
+
* derived from the root tag all keep receiving a string — a guarantee that lives in the type rather
|
|
85
|
+
* than in a positional branch someone can forget. `HeadEntry` declares its own `tagName` and never
|
|
86
|
+
* referenced the shared def, so a head tag is static for free.
|
|
87
|
+
*/
|
|
88
|
+
export const elementTagNameSchema = {
|
|
89
|
+
anyOf: [
|
|
90
|
+
{ $ref: "#/$defs/TagName" },
|
|
91
|
+
{
|
|
92
|
+
additionalProperties: false,
|
|
93
|
+
properties: { $expression: { $ref: "#/$defs/TagExpression" } },
|
|
94
|
+
required: ["$expression"],
|
|
95
|
+
type: "object",
|
|
96
|
+
},
|
|
97
|
+
],
|
|
98
|
+
} as const;
|
package/defs/tag-name.schema.ts
CHANGED
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
export const tagNameSchema = {
|
|
2
2
|
description:
|
|
3
|
-
"HTML element tag name or custom element name (must contain a hyphen per Web Components spec)."
|
|
3
|
+
"HTML element tag name or custom element name (must contain a hyphen per Web Components spec). " +
|
|
4
|
+
"A NAME, never an expression: no `${…}` position in the pipeline evaluates a tagName. Use " +
|
|
5
|
+
"`$switch`/`cases` to vary the element.",
|
|
4
6
|
minLength: 1,
|
|
7
|
+
/* THE PATTERN IS THE WHOLE POINT. Without it `"\${state.href ? 'a' : 'div'}"` validates, and every
|
|
8
|
+
consumer downstream then does something different and silent with it: the runtime hands it to
|
|
9
|
+
`document.createElement` and throws `InvalidCharacterError`; the compiler splices it into a lit
|
|
10
|
+
template as a binding in tag position, which lit cannot do; and the static renderer emits it into
|
|
11
|
+
the prerendered HTML and re-resolves that string against the PAGE scope, where the component's
|
|
12
|
+
own `state` is undefined — so a built site silently collapses to the fallback branch's tag with
|
|
13
|
+
the other branch's attributes. Found by driving Studio against a real site whose component did
|
|
14
|
+
exactly this. Every tagName in packages/, extensions/, sites/, examples/ and docs/ already
|
|
15
|
+
conforms, so the pattern only rejects what was already broken. */
|
|
16
|
+
pattern: "^[a-zA-Z][a-zA-Z0-9._-]*$",
|
|
5
17
|
type: "string",
|
|
6
18
|
} as const;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"dependencies": {
|
|
3
|
-
"@webref/css": "^8.7.
|
|
3
|
+
"@webref/css": "^8.7.2",
|
|
4
4
|
"@webref/elements": "^2.7.1",
|
|
5
|
-
"@webref/idl": "^3.82.
|
|
5
|
+
"@webref/idl": "^3.82.2",
|
|
6
6
|
"ajv": "^8.20.0",
|
|
7
7
|
"ajv-formats": "^3.0.1"
|
|
8
8
|
},
|
|
@@ -16,6 +16,10 @@
|
|
|
16
16
|
"./validate-project": "./src/validate-project.ts",
|
|
17
17
|
"./types": "./types.ts",
|
|
18
18
|
"./guards": "./src/guards.ts",
|
|
19
|
+
"./locale": "./src/locale.ts",
|
|
20
|
+
"./intl": "./src/intl.ts",
|
|
21
|
+
"./media-type": "./src/media-type.ts",
|
|
22
|
+
"./ijson": "./src/ijson.ts",
|
|
19
23
|
"./parse": "./src/parse.ts",
|
|
20
24
|
"./defs": "./defs/index.ts",
|
|
21
25
|
"./schema.json": "./schema.json",
|
|
@@ -50,7 +54,7 @@
|
|
|
50
54
|
"upgrade": "bunx npm-check-updates -u && bun install"
|
|
51
55
|
},
|
|
52
56
|
"type": "module",
|
|
53
|
-
"version": "1.
|
|
57
|
+
"version": "1.8.0",
|
|
54
58
|
"devDependencies": {
|
|
55
59
|
"json-schema-to-ts": "^3.1.1"
|
|
56
60
|
}
|
package/project-schema.json
CHANGED
|
@@ -15,6 +15,10 @@
|
|
|
15
15
|
"optimize": {
|
|
16
16
|
"type": "boolean"
|
|
17
17
|
},
|
|
18
|
+
"picture": {
|
|
19
|
+
"description": "Wrap a multi-format image in a <picture> with one <source> per format (default true). With it off the <img> carries a srcset in the preferred format alone, which browsers that cannot decode that format will still select from.",
|
|
20
|
+
"type": "boolean"
|
|
21
|
+
},
|
|
18
22
|
"quality": {
|
|
19
23
|
"properties": {
|
|
20
24
|
"avif": {
|
|
@@ -224,6 +228,167 @@
|
|
|
224
228
|
"description": "Output directory for compiled site.",
|
|
225
229
|
"type": "string"
|
|
226
230
|
},
|
|
231
|
+
"headers": {
|
|
232
|
+
"additionalProperties": false,
|
|
233
|
+
"description": "Emit dist/_headers — the response headers only the build can know. Honored by Cloudflare Pages/Workers assets and Netlify; on the node/bun adapters the file documents what a reverse proxy in front of them must send.",
|
|
234
|
+
"properties": {
|
|
235
|
+
"cache": {
|
|
236
|
+
"default": "auto",
|
|
237
|
+
"description": "\"auto\" marks the content-addressed image variants immutable (RFC 8246) and revalidates everything else (RFC 9111). \"off\" emits no Cache-Control at all.",
|
|
238
|
+
"enum": [
|
|
239
|
+
"auto",
|
|
240
|
+
"off"
|
|
241
|
+
],
|
|
242
|
+
"type": "string"
|
|
243
|
+
},
|
|
244
|
+
"enabled": {
|
|
245
|
+
"default": true,
|
|
246
|
+
"type": "boolean"
|
|
247
|
+
},
|
|
248
|
+
"rules": {
|
|
249
|
+
"additionalProperties": {
|
|
250
|
+
"additionalProperties": {
|
|
251
|
+
"type": "string"
|
|
252
|
+
},
|
|
253
|
+
"type": "object"
|
|
254
|
+
},
|
|
255
|
+
"description": "Verbatim rules, merged after the generated block.",
|
|
256
|
+
"propertyNames": {
|
|
257
|
+
"pattern": "^/"
|
|
258
|
+
},
|
|
259
|
+
"type": "object"
|
|
260
|
+
},
|
|
261
|
+
"security": {
|
|
262
|
+
"additionalProperties": false,
|
|
263
|
+
"properties": {
|
|
264
|
+
"contentTypeOptions": {
|
|
265
|
+
"default": true,
|
|
266
|
+
"type": "boolean"
|
|
267
|
+
},
|
|
268
|
+
"csp": {
|
|
269
|
+
"default": false,
|
|
270
|
+
"description": "CSP Level 3. Off by default: unlike the other security headers this one governs code the build cannot see — a third-party script that loads a second script, a widget that opens a frame — so turning it on is a change to verify in the browser console, not a safe default. `script-src` is derived from the inline scripts and external origins the build emitted; `style-src` keeps 'unsafe-inline', because per-page <style> blocks and per-element style attributes have no site-wide hash.",
|
|
271
|
+
"oneOf": [
|
|
272
|
+
{
|
|
273
|
+
"type": "boolean"
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
"const": "report-only"
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
"additionalProperties": false,
|
|
280
|
+
"properties": {
|
|
281
|
+
"directives": {
|
|
282
|
+
"additionalProperties": {
|
|
283
|
+
"oneOf": [
|
|
284
|
+
{
|
|
285
|
+
"type": "string"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"const": false
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
},
|
|
292
|
+
"description": "Replace (string) or remove (false) a computed directive. A replacement is wholesale, so an addition restates the defaults.",
|
|
293
|
+
"type": "object"
|
|
294
|
+
},
|
|
295
|
+
"mode": {
|
|
296
|
+
"default": "enforce",
|
|
297
|
+
"enum": [
|
|
298
|
+
"enforce",
|
|
299
|
+
"report-only"
|
|
300
|
+
]
|
|
301
|
+
},
|
|
302
|
+
"reportUri": {
|
|
303
|
+
"format": "uri-reference",
|
|
304
|
+
"type": "string"
|
|
305
|
+
}
|
|
306
|
+
},
|
|
307
|
+
"type": "object"
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
},
|
|
311
|
+
"frameOptions": {
|
|
312
|
+
"default": "SAMEORIGIN",
|
|
313
|
+
"oneOf": [
|
|
314
|
+
{
|
|
315
|
+
"enum": [
|
|
316
|
+
"DENY",
|
|
317
|
+
"SAMEORIGIN"
|
|
318
|
+
],
|
|
319
|
+
"type": "string"
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
"const": false
|
|
323
|
+
}
|
|
324
|
+
]
|
|
325
|
+
},
|
|
326
|
+
"hsts": {
|
|
327
|
+
"default": false,
|
|
328
|
+
"description": "RFC 6797. Off by default: a wrong max-age locks the apex domain to HTTPS for that long, and the mistake is invisible until a certificate lapses. `preload` requires `includeSubDomains` and is a build error without it.",
|
|
329
|
+
"oneOf": [
|
|
330
|
+
{
|
|
331
|
+
"type": "boolean"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"additionalProperties": false,
|
|
335
|
+
"properties": {
|
|
336
|
+
"includeSubDomains": {
|
|
337
|
+
"default": true,
|
|
338
|
+
"type": "boolean"
|
|
339
|
+
},
|
|
340
|
+
"maxAge": {
|
|
341
|
+
"default": 31536000,
|
|
342
|
+
"minimum": 0,
|
|
343
|
+
"type": "integer"
|
|
344
|
+
},
|
|
345
|
+
"preload": {
|
|
346
|
+
"default": false,
|
|
347
|
+
"type": "boolean"
|
|
348
|
+
}
|
|
349
|
+
},
|
|
350
|
+
"type": "object"
|
|
351
|
+
}
|
|
352
|
+
]
|
|
353
|
+
},
|
|
354
|
+
"permissionsPolicy": {
|
|
355
|
+
"default": "camera=(), microphone=(), geolocation=()",
|
|
356
|
+
"oneOf": [
|
|
357
|
+
{
|
|
358
|
+
"type": "string"
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
"const": false
|
|
362
|
+
}
|
|
363
|
+
]
|
|
364
|
+
},
|
|
365
|
+
"referrerPolicy": {
|
|
366
|
+
"default": "strict-origin-when-cross-origin",
|
|
367
|
+
"oneOf": [
|
|
368
|
+
{
|
|
369
|
+
"enum": [
|
|
370
|
+
"no-referrer",
|
|
371
|
+
"no-referrer-when-downgrade",
|
|
372
|
+
"origin",
|
|
373
|
+
"origin-when-cross-origin",
|
|
374
|
+
"same-origin",
|
|
375
|
+
"strict-origin",
|
|
376
|
+
"strict-origin-when-cross-origin",
|
|
377
|
+
"unsafe-url"
|
|
378
|
+
],
|
|
379
|
+
"type": "string"
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
"const": false
|
|
383
|
+
}
|
|
384
|
+
]
|
|
385
|
+
}
|
|
386
|
+
},
|
|
387
|
+
"type": "object"
|
|
388
|
+
}
|
|
389
|
+
},
|
|
390
|
+
"type": "object"
|
|
391
|
+
},
|
|
227
392
|
"sitemap": {
|
|
228
393
|
"default": true,
|
|
229
394
|
"description": "Generate sitemap.xml from the route table (requires `url`). Set false to disable.",
|
|
@@ -257,9 +422,18 @@
|
|
|
257
422
|
"description": "Default charset for the page.",
|
|
258
423
|
"type": "string"
|
|
259
424
|
},
|
|
425
|
+
"dir": {
|
|
426
|
+
"description": "Base direction for the <html> element. Omitted when unset — a page's `$dir` overrides it. Right-to-left content renders left-to-right without this.",
|
|
427
|
+
"enum": [
|
|
428
|
+
"ltr",
|
|
429
|
+
"rtl",
|
|
430
|
+
"auto"
|
|
431
|
+
],
|
|
432
|
+
"type": "string"
|
|
433
|
+
},
|
|
260
434
|
"lang": {
|
|
261
435
|
"default": "en",
|
|
262
|
-
"description": "Default lang attribute for the <html> element.",
|
|
436
|
+
"description": "Default lang attribute for the <html> element. A page's `$lang` overrides it.",
|
|
263
437
|
"type": "string"
|
|
264
438
|
},
|
|
265
439
|
"layout": {
|
|
@@ -276,6 +450,23 @@
|
|
|
276
450
|
"type": "null"
|
|
277
451
|
}
|
|
278
452
|
]
|
|
453
|
+
},
|
|
454
|
+
"shadow": {
|
|
455
|
+
"default": false,
|
|
456
|
+
"description": "Default shadow-DOM mode for every component (spec.md §16.6). Off unless set: a shadow root isolates component styles from the page and replaces the <slot> emulation with real slot distribution. A component's own `$shadow` overrides this in both directions.",
|
|
457
|
+
"oneOf": [
|
|
458
|
+
{
|
|
459
|
+
"enum": [
|
|
460
|
+
"open",
|
|
461
|
+
"closed"
|
|
462
|
+
],
|
|
463
|
+
"type": "string"
|
|
464
|
+
},
|
|
465
|
+
{
|
|
466
|
+
"const": false,
|
|
467
|
+
"type": "boolean"
|
|
468
|
+
}
|
|
469
|
+
]
|
|
279
470
|
}
|
|
280
471
|
},
|
|
281
472
|
"type": "object"
|
|
@@ -292,18 +483,203 @@
|
|
|
292
483
|
},
|
|
293
484
|
"type": "array"
|
|
294
485
|
},
|
|
486
|
+
"manifest": {
|
|
487
|
+
"additionalProperties": false,
|
|
488
|
+
"description": "W3C Web App Manifest, emitted as manifest.webmanifest with a <link rel=\"manifest\"> on every page. Absent means no manifest: it is a claim that a site is meant to be installed, and most are not.",
|
|
489
|
+
"properties": {
|
|
490
|
+
"backgroundColor": {
|
|
491
|
+
"type": "string"
|
|
492
|
+
},
|
|
493
|
+
"categories": {
|
|
494
|
+
"items": {
|
|
495
|
+
"type": "string"
|
|
496
|
+
},
|
|
497
|
+
"type": "array"
|
|
498
|
+
},
|
|
499
|
+
"description": {
|
|
500
|
+
"type": "string"
|
|
501
|
+
},
|
|
502
|
+
"dir": {
|
|
503
|
+
"enum": [
|
|
504
|
+
"ltr",
|
|
505
|
+
"rtl",
|
|
506
|
+
"auto"
|
|
507
|
+
],
|
|
508
|
+
"type": "string"
|
|
509
|
+
},
|
|
510
|
+
"display": {
|
|
511
|
+
"default": "standalone",
|
|
512
|
+
"enum": [
|
|
513
|
+
"fullscreen",
|
|
514
|
+
"standalone",
|
|
515
|
+
"minimal-ui",
|
|
516
|
+
"browser"
|
|
517
|
+
],
|
|
518
|
+
"type": "string"
|
|
519
|
+
},
|
|
520
|
+
"enabled": {
|
|
521
|
+
"default": true,
|
|
522
|
+
"type": "boolean"
|
|
523
|
+
},
|
|
524
|
+
"icons": {
|
|
525
|
+
"description": "Icons at 192px and 512px are what browsers require before offering to install the site; the build warns when either is missing.",
|
|
526
|
+
"items": {
|
|
527
|
+
"additionalProperties": false,
|
|
528
|
+
"properties": {
|
|
529
|
+
"purpose": {
|
|
530
|
+
"type": "string"
|
|
531
|
+
},
|
|
532
|
+
"sizes": {
|
|
533
|
+
"examples": [
|
|
534
|
+
"192x192"
|
|
535
|
+
],
|
|
536
|
+
"type": "string"
|
|
537
|
+
},
|
|
538
|
+
"src": {
|
|
539
|
+
"type": "string"
|
|
540
|
+
},
|
|
541
|
+
"type": {
|
|
542
|
+
"type": "string"
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
"required": [
|
|
546
|
+
"src"
|
|
547
|
+
],
|
|
548
|
+
"type": "object"
|
|
549
|
+
},
|
|
550
|
+
"type": "array"
|
|
551
|
+
},
|
|
552
|
+
"lang": {
|
|
553
|
+
"type": "string"
|
|
554
|
+
},
|
|
555
|
+
"name": {
|
|
556
|
+
"description": "Defaults to the project name.",
|
|
557
|
+
"type": "string"
|
|
558
|
+
},
|
|
559
|
+
"orientation": {
|
|
560
|
+
"type": "string"
|
|
561
|
+
},
|
|
562
|
+
"scope": {
|
|
563
|
+
"type": "string"
|
|
564
|
+
},
|
|
565
|
+
"shortName": {
|
|
566
|
+
"type": "string"
|
|
567
|
+
},
|
|
568
|
+
"startUrl": {
|
|
569
|
+
"default": "/",
|
|
570
|
+
"type": "string"
|
|
571
|
+
},
|
|
572
|
+
"themeColor": {
|
|
573
|
+
"type": "string"
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
"type": "object"
|
|
577
|
+
},
|
|
578
|
+
"serviceWorker": {
|
|
579
|
+
"default": false,
|
|
580
|
+
"description": "W3C Service Worker, emitted as sw.js. Off unless declared — a worker is sticky, so it is the one output that can keep breaking visitors after a deploy tries to fix them. **Turning it off means `false`, not deleting the key**: `false` emits a tombstone at the same URL that unregisters the old worker and clears its caches, and a 404 there is not an instruction to stop. HTML is always network-first; only the content-addressed /images/_optimized/* is served cache-first.",
|
|
581
|
+
"oneOf": [
|
|
582
|
+
{
|
|
583
|
+
"type": "boolean"
|
|
584
|
+
},
|
|
585
|
+
{
|
|
586
|
+
"additionalProperties": false,
|
|
587
|
+
"properties": {
|
|
588
|
+
"enabled": {
|
|
589
|
+
"default": true,
|
|
590
|
+
"type": "boolean"
|
|
591
|
+
},
|
|
592
|
+
"offlineFallback": {
|
|
593
|
+
"description": "Page served for a failed navigation. Added to `precache` if not already there.",
|
|
594
|
+
"type": "string"
|
|
595
|
+
},
|
|
596
|
+
"precache": {
|
|
597
|
+
"description": "Site-absolute URLs fetched at install time. Each must be a file this build produces; one that is not fails the build, because a single unreachable entry stops the worker installing at all.",
|
|
598
|
+
"items": {
|
|
599
|
+
"pattern": "^/",
|
|
600
|
+
"type": "string"
|
|
601
|
+
},
|
|
602
|
+
"type": "array"
|
|
603
|
+
},
|
|
604
|
+
"scope": {
|
|
605
|
+
"default": "/",
|
|
606
|
+
"type": "string"
|
|
607
|
+
}
|
|
608
|
+
},
|
|
609
|
+
"type": "object"
|
|
610
|
+
}
|
|
611
|
+
]
|
|
612
|
+
},
|
|
613
|
+
"securityTxt": {
|
|
614
|
+
"additionalProperties": false,
|
|
615
|
+
"description": "RFC 9116 security.txt, emitted at .well-known/security.txt. `expires` is required by §2.5.5 and an absent or past value is a build error — an expired file advertises a reporting channel while telling the reporter not to trust it. A hand-placed public/.well-known/security.txt shadows this, which is how a clearsigned file ships.",
|
|
616
|
+
"properties": {
|
|
617
|
+
"acknowledgments": {
|
|
618
|
+
"items": {
|
|
619
|
+
"type": "string"
|
|
620
|
+
},
|
|
621
|
+
"type": "array"
|
|
622
|
+
},
|
|
623
|
+
"canonical": {
|
|
624
|
+
"type": "string"
|
|
625
|
+
},
|
|
626
|
+
"contact": {
|
|
627
|
+
"description": "At least one mailto:, https: or tel: URI (§2.5.3).",
|
|
628
|
+
"items": {
|
|
629
|
+
"type": "string"
|
|
630
|
+
},
|
|
631
|
+
"type": "array"
|
|
632
|
+
},
|
|
633
|
+
"enabled": {
|
|
634
|
+
"default": true,
|
|
635
|
+
"type": "boolean"
|
|
636
|
+
},
|
|
637
|
+
"encryption": {
|
|
638
|
+
"items": {
|
|
639
|
+
"type": "string"
|
|
640
|
+
},
|
|
641
|
+
"type": "array"
|
|
642
|
+
},
|
|
643
|
+
"expires": {
|
|
644
|
+
"format": "date-time",
|
|
645
|
+
"type": "string"
|
|
646
|
+
},
|
|
647
|
+
"hiring": {
|
|
648
|
+
"items": {
|
|
649
|
+
"type": "string"
|
|
650
|
+
},
|
|
651
|
+
"type": "array"
|
|
652
|
+
},
|
|
653
|
+
"policy": {
|
|
654
|
+
"items": {
|
|
655
|
+
"type": "string"
|
|
656
|
+
},
|
|
657
|
+
"type": "array"
|
|
658
|
+
},
|
|
659
|
+
"preferredLanguages": {
|
|
660
|
+
"description": "BCP 47 tags, validated and canonicalized like i18n.locales.",
|
|
661
|
+
"items": {
|
|
662
|
+
"pattern": "^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$",
|
|
663
|
+
"type": "string"
|
|
664
|
+
},
|
|
665
|
+
"type": "array"
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
"type": "object"
|
|
669
|
+
},
|
|
295
670
|
"i18n": {
|
|
296
671
|
"description": "Internationalization configuration.",
|
|
297
672
|
"properties": {
|
|
298
673
|
"defaultLocale": {
|
|
299
|
-
"description": "Default locale
|
|
674
|
+
"description": "Default locale, as a BCP 47 language tag. The pattern catches shape errors (`en_US`, `en--US`); the build is the authority on well-formedness.",
|
|
300
675
|
"examples": [
|
|
301
676
|
"en"
|
|
302
677
|
],
|
|
678
|
+
"pattern": "^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$",
|
|
303
679
|
"type": "string"
|
|
304
680
|
},
|
|
305
681
|
"locales": {
|
|
306
|
-
"description": "Available
|
|
682
|
+
"description": "Available locales, as BCP 47 language tags.",
|
|
307
683
|
"examples": [
|
|
308
684
|
[
|
|
309
685
|
"en",
|
|
@@ -312,6 +688,7 @@
|
|
|
312
688
|
]
|
|
313
689
|
],
|
|
314
690
|
"items": {
|
|
691
|
+
"pattern": "^[A-Za-z]{2,8}(-[A-Za-z0-9]{1,8})*$",
|
|
315
692
|
"type": "string"
|
|
316
693
|
},
|
|
317
694
|
"type": "array"
|
|
@@ -354,14 +731,73 @@
|
|
|
354
731
|
},
|
|
355
732
|
"redirects": {
|
|
356
733
|
"additionalProperties": {
|
|
357
|
-
"
|
|
734
|
+
"oneOf": [
|
|
735
|
+
{
|
|
736
|
+
"description": "Destination path or URL; implies a 301.",
|
|
737
|
+
"type": "string"
|
|
738
|
+
},
|
|
739
|
+
{
|
|
740
|
+
"additionalProperties": false,
|
|
741
|
+
"description": "A redirect with an explicit RFC 9110 §15.4 status.",
|
|
742
|
+
"properties": {
|
|
743
|
+
"destination": {
|
|
744
|
+
"type": "string"
|
|
745
|
+
},
|
|
746
|
+
"status": {
|
|
747
|
+
"default": 301,
|
|
748
|
+
"description": "RFC 9110 §15.4 redirection status. 307 and 308 preserve the request method; 301 and 302 historically do not.",
|
|
749
|
+
"enum": [
|
|
750
|
+
301,
|
|
751
|
+
302,
|
|
752
|
+
303,
|
|
753
|
+
307,
|
|
754
|
+
308
|
|
755
|
+
],
|
|
756
|
+
"type": "integer"
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
"required": [
|
|
760
|
+
"destination"
|
|
761
|
+
],
|
|
762
|
+
"type": "object"
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
"additionalProperties": false,
|
|
766
|
+
"description": "A rewrite (proxy): the destination's content is served AT the source URL, with no redirect. Emitted as status 200 in `_redirects`, which is the host convention — not an HTTP redirection status.",
|
|
767
|
+
"properties": {
|
|
768
|
+
"destination": {
|
|
769
|
+
"type": "string"
|
|
770
|
+
},
|
|
771
|
+
"rewrite": {
|
|
772
|
+
"const": true,
|
|
773
|
+
"type": "boolean"
|
|
774
|
+
}
|
|
775
|
+
},
|
|
776
|
+
"required": [
|
|
777
|
+
"destination",
|
|
778
|
+
"rewrite"
|
|
779
|
+
],
|
|
780
|
+
"type": "object"
|
|
781
|
+
}
|
|
782
|
+
]
|
|
358
783
|
},
|
|
359
|
-
"description": "Static redirect rules.
|
|
784
|
+
"description": "Static redirect and rewrite rules. Keys are source paths in URLPattern pathname syntax.",
|
|
360
785
|
"examples": [
|
|
361
786
|
{
|
|
787
|
+
"/api/*": {
|
|
788
|
+
"destination": "https://api.example.com/*",
|
|
789
|
+
"rewrite": true
|
|
790
|
+
},
|
|
791
|
+
"/legacy/*": {
|
|
792
|
+
"destination": "/archive/*",
|
|
793
|
+
"status": 308
|
|
794
|
+
},
|
|
362
795
|
"/old-about": "/about"
|
|
363
796
|
}
|
|
364
797
|
],
|
|
798
|
+
"propertyNames": {
|
|
799
|
+
"pattern": "^/"
|
|
800
|
+
},
|
|
365
801
|
"type": "object"
|
|
366
802
|
},
|
|
367
803
|
"state": {
|