@jxsuite/schema 0.34.0 → 1.0.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/defs/extension-manifest.schema.ts +47 -0
- package/defs/field-schema.schema.ts +58 -0
- package/defs/index.ts +5 -2
- package/defs/project-config.schema.ts +49 -11
- package/extension-manifest.schema.json +54 -0
- package/package.json +12 -4
- package/project-schema.json +54 -56
- package/schema.json +0 -44
- package/schemas/document.paths.schema.json +6 -0
- package/schemas/project.core.schema.json +441 -0
- package/schemas/project.fields.schema.json +14 -0
- package/src/extension-registry.ts +270 -0
- package/src/format-registry.ts +83 -13
- package/src/project-schemas.ts +262 -0
- package/src/schema.ts +107 -7
- package/src/validate-project.ts +144 -0
- package/types.ts +62 -9
- package/defs/content-type-def.schema.ts +0 -37
package/src/schema.ts
CHANGED
|
@@ -90,7 +90,13 @@ import {
|
|
|
90
90
|
formatDefSchema,
|
|
91
91
|
studioHintsSchema,
|
|
92
92
|
} from "../defs/class-def.schema";
|
|
93
|
-
import {
|
|
93
|
+
import { extensionManifestSchema } from "../defs/extension-manifest.schema";
|
|
94
|
+
import {
|
|
95
|
+
DOCUMENT_PATHS_SCHEMA_ID,
|
|
96
|
+
PROJECT_FIELDS_SCHEMA_ID,
|
|
97
|
+
jxFieldSchemaDef,
|
|
98
|
+
relationshipRefSchema,
|
|
99
|
+
} from "../defs/field-schema.schema";
|
|
94
100
|
import { imageConfigSchema } from "../defs/image-config.schema";
|
|
95
101
|
import { projectConfigSchema } from "../defs/project-config.schema";
|
|
96
102
|
|
|
@@ -261,7 +267,6 @@ export async function generateSchema() {
|
|
|
261
267
|
JsonSchemaType: jsonSchemaTypeSchema,
|
|
262
268
|
HeadEntry: headEntrySchema,
|
|
263
269
|
ImageConfig: imageConfigSchema,
|
|
264
|
-
ContentTypeDef: contentTypeDefSchema,
|
|
265
270
|
},
|
|
266
271
|
$id: "https://jxsuite.com/schema/v1",
|
|
267
272
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -395,23 +400,97 @@ export async function generateSchema() {
|
|
|
395
400
|
export function generateProjectSchema() {
|
|
396
401
|
return {
|
|
397
402
|
$defs: {
|
|
398
|
-
ContentTypeDef: contentTypeDefSchema,
|
|
399
403
|
ImageConfig: imageConfigSchema,
|
|
400
404
|
},
|
|
401
405
|
$id: "https://jxsuite.com/schema/project/v1",
|
|
402
406
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
403
|
-
additionalProperties: false,
|
|
404
407
|
description:
|
|
405
408
|
"Schema for Jx project.json files. " +
|
|
406
409
|
"A project.json file is the root anchor file for a Jx project, " +
|
|
407
|
-
"declaring site metadata, default settings, global styles,
|
|
408
|
-
"and build configuration."
|
|
410
|
+
"declaring site metadata, default settings, global styles, extensions, " +
|
|
411
|
+
"and build configuration. Open by design: extension-contributed sections " +
|
|
412
|
+
"are opaque top-level keys; the generated per-project entry document " +
|
|
413
|
+
"(project.schema.json) closes the composition.",
|
|
409
414
|
properties: projectConfigSchema.properties,
|
|
410
415
|
title: "Jx Project",
|
|
411
416
|
type: "object",
|
|
412
417
|
};
|
|
413
418
|
}
|
|
414
419
|
|
|
420
|
+
// ─── Project Core Fragment Generator ─────────────────────────────────────────
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* The core fragment of the composed per-project schema (specs/extensions.md §5.1): core
|
|
424
|
+
* project.json properties only — no extension sections — and deliberately open, because closure
|
|
425
|
+
* (`unevaluatedProperties: false`) happens in the generated entry document. Publishes the
|
|
426
|
+
* `JxFieldSchema` and `RelationshipRef` defs the entry document unions into the `jxFieldSchema`
|
|
427
|
+
* dynamic anchor.
|
|
428
|
+
*/
|
|
429
|
+
export function generateProjectCoreSchema() {
|
|
430
|
+
return {
|
|
431
|
+
$defs: {
|
|
432
|
+
ImageConfig: imageConfigSchema,
|
|
433
|
+
JxFieldSchema: jxFieldSchemaDef,
|
|
434
|
+
RelationshipRef: relationshipRefSchema,
|
|
435
|
+
},
|
|
436
|
+
$id: "https://jxsuite.com/schema/project/core/v2",
|
|
437
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
438
|
+
description:
|
|
439
|
+
"Core fragment of the Jx project.json schema. Open by design: extension fragments " +
|
|
440
|
+
"contribute their own top-level sections and the generated per-project entry document " +
|
|
441
|
+
"(project.schema.json) closes the composition. See specs/extensions.md §5.",
|
|
442
|
+
properties: projectConfigSchema.properties,
|
|
443
|
+
title: "Jx Project Core",
|
|
444
|
+
type: "object",
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// ─── Union Resource Generators ───────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The shipped DEFAULT field-union resource. The generated per-project entry document re-embeds a
|
|
452
|
+
* resource under the same $id with the effective union (adding extension extras); by standard
|
|
453
|
+
* compound-document $id resolution the embed shadows this default wherever both are in play.
|
|
454
|
+
*/
|
|
455
|
+
export function generateProjectFieldsSchema() {
|
|
456
|
+
return {
|
|
457
|
+
$id: PROJECT_FIELDS_SCHEMA_ID,
|
|
458
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
459
|
+
anyOf: [
|
|
460
|
+
{ $ref: "https://jxsuite.com/schema/project/core/v2#/$defs/JxFieldSchema" },
|
|
461
|
+
{ $ref: "https://jxsuite.com/schema/project/core/v2#/$defs/RelationshipRef" },
|
|
462
|
+
],
|
|
463
|
+
description:
|
|
464
|
+
"Default field-schema union for section entry schemas: the core field shape plus " +
|
|
465
|
+
"relationship references. Per-project entry documents override this resource with the " +
|
|
466
|
+
"effective union. See specs/extensions.md §5.3.",
|
|
467
|
+
title: "Jx Project Field Union",
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** The shipped DEFAULT $paths-value resource: permissive until extensions contribute shapes. */
|
|
472
|
+
export function generateDocumentPathsSchema() {
|
|
473
|
+
return {
|
|
474
|
+
$id: DOCUMENT_PATHS_SCHEMA_ID,
|
|
475
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
476
|
+
description:
|
|
477
|
+
"Default $paths-value union for documents. Per-project entry documents override this " +
|
|
478
|
+
"resource with the union of extension-contributed paths shapes.",
|
|
479
|
+
title: "Jx Document Paths Union",
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// ─── Extension Manifest Schema Generator ─────────────────────────────────────
|
|
484
|
+
|
|
485
|
+
export function generateExtensionManifestSchema() {
|
|
486
|
+
return {
|
|
487
|
+
$id: "https://jxsuite.com/schema/extension-manifest/v1",
|
|
488
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
489
|
+
title: "Jx Extension Manifest",
|
|
490
|
+
...extensionManifestSchema,
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
|
|
415
494
|
// ─── Class Schema Generator ─────────────────────────────────────────────────
|
|
416
495
|
|
|
417
496
|
export function generateClassSchema() {
|
|
@@ -545,7 +624,7 @@ export async function validateDocument(doc: Record<string, unknown>) {
|
|
|
545
624
|
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
546
625
|
|
|
547
626
|
async function runSchemaCli() {
|
|
548
|
-
const { writeFileSync } = await import("node:fs");
|
|
627
|
+
const { mkdirSync, writeFileSync } = await import("node:fs");
|
|
549
628
|
const { resolve, dirname } = await import("node:path");
|
|
550
629
|
|
|
551
630
|
const schemaDir = dirname(resolve(process.argv[1] as string, ".."));
|
|
@@ -553,10 +632,18 @@ async function runSchemaCli() {
|
|
|
553
632
|
const componentSchema = await generateSchema();
|
|
554
633
|
const projectSchema = generateProjectSchema();
|
|
555
634
|
const classSchema = generateClassSchema();
|
|
635
|
+
const projectCoreSchema = generateProjectCoreSchema();
|
|
636
|
+
const manifestSchema = generateExtensionManifestSchema();
|
|
637
|
+
const fieldsSchema = generateProjectFieldsSchema();
|
|
638
|
+
const pathsSchema = generateDocumentPathsSchema();
|
|
556
639
|
|
|
557
640
|
const componentStr = JSON.stringify(componentSchema, null, 2);
|
|
558
641
|
const projectStr = JSON.stringify(projectSchema, null, 2);
|
|
559
642
|
const classStr = JSON.stringify(classSchema, null, 2);
|
|
643
|
+
const projectCoreStr = JSON.stringify(projectCoreSchema, null, 2);
|
|
644
|
+
const manifestStr = JSON.stringify(manifestSchema, null, 2);
|
|
645
|
+
const fieldsStr = JSON.stringify(fieldsSchema, null, 2);
|
|
646
|
+
const pathsStr = JSON.stringify(pathsSchema, null, 2);
|
|
560
647
|
|
|
561
648
|
const [out] = process.argv.slice(2);
|
|
562
649
|
|
|
@@ -567,10 +654,23 @@ async function runSchemaCli() {
|
|
|
567
654
|
writeFileSync(resolve(schemaDir, "schema.json"), componentStr, "utf8");
|
|
568
655
|
writeFileSync(resolve(schemaDir, "project-schema.json"), projectStr, "utf8");
|
|
569
656
|
writeFileSync(resolve(schemaDir, "class-schema.json"), classStr, "utf8");
|
|
657
|
+
mkdirSync(resolve(schemaDir, "schemas"), { recursive: true });
|
|
658
|
+
writeFileSync(
|
|
659
|
+
resolve(schemaDir, "schemas", "project.core.schema.json"),
|
|
660
|
+
projectCoreStr,
|
|
661
|
+
"utf8",
|
|
662
|
+
);
|
|
663
|
+
writeFileSync(resolve(schemaDir, "schemas", "project.fields.schema.json"), fieldsStr, "utf8");
|
|
664
|
+
writeFileSync(resolve(schemaDir, "schemas", "document.paths.schema.json"), pathsStr, "utf8");
|
|
665
|
+
writeFileSync(resolve(schemaDir, "extension-manifest.schema.json"), manifestStr, "utf8");
|
|
570
666
|
console.error("Generated:");
|
|
571
667
|
console.error(" schema.json (component)");
|
|
572
668
|
console.error(" project-schema.json");
|
|
573
669
|
console.error(" class-schema.json");
|
|
670
|
+
console.error(" schemas/project.core.schema.json");
|
|
671
|
+
console.error(" schemas/project.fields.schema.json");
|
|
672
|
+
console.error(" schemas/document.paths.schema.json");
|
|
673
|
+
console.error(" extension-manifest.schema.json");
|
|
574
674
|
}
|
|
575
675
|
}
|
|
576
676
|
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate-project — project.json validation against the generated per-project entry schema
|
|
3
|
+
* (specs/extensions.md §5.4).
|
|
4
|
+
*
|
|
5
|
+
* Node-only by design (mirrors the dynamic-ajv-import pattern of `validateDocument`): reads
|
|
6
|
+
* `<root>/project.json` and `<root>/project.schema.json`, compiles the entry document with
|
|
7
|
+
* ajv-2020, and resolves its relative `$ref`s through a file loader restricted to the project root
|
|
8
|
+
* and its node_modules. The entry file's URL is injected as `$id` at load time so the committed,
|
|
9
|
+
* machine-path-free relative refs resolve.
|
|
10
|
+
*
|
|
11
|
+
* When a `./node_modules/...` ref does not exist on disk (workspace hoisting leaves in-repo
|
|
12
|
+
* starters without their own node_modules), the loader falls back to host resolution of the bare
|
|
13
|
+
* specifier — the same project-first-then-host order as createNodeFormatIO.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
import { createRequire } from "node:module";
|
|
18
|
+
import { isAbsolute, relative, resolve } from "node:path";
|
|
19
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
20
|
+
|
|
21
|
+
export interface ProjectValidationResult {
|
|
22
|
+
valid: boolean;
|
|
23
|
+
errors: unknown[] | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Minimal structural types for the optional `ajv` peer dependency (no direct dependency, and it
|
|
27
|
+
// Ships no types usable from here) — same pattern as validateDocument in src/schema.ts.
|
|
28
|
+
interface AjvValidateFn {
|
|
29
|
+
(doc: unknown): boolean;
|
|
30
|
+
errors?: unknown[] | null;
|
|
31
|
+
}
|
|
32
|
+
interface AjvInstance {
|
|
33
|
+
compileAsync: (schema: unknown) => Promise<AjvValidateFn>;
|
|
34
|
+
}
|
|
35
|
+
type AjvCtor = new (opts: {
|
|
36
|
+
allErrors: boolean;
|
|
37
|
+
strict: boolean;
|
|
38
|
+
loadSchema: (uri: string) => Promise<Record<string, unknown>>;
|
|
39
|
+
}) => AjvInstance;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Validate `<projectRoot>/project.json` against `<projectRoot>/project.schema.json`.
|
|
43
|
+
*
|
|
44
|
+
* Validation failures are data (`{ valid: false, errors }`); infrastructure failures — missing
|
|
45
|
+
* files, unresolvable refs, refs escaping the project root — throw.
|
|
46
|
+
*
|
|
47
|
+
* @param {string} projectRoot - Absolute path to the project directory
|
|
48
|
+
* @returns {Promise<ProjectValidationResult>}
|
|
49
|
+
*/
|
|
50
|
+
export async function validateProjectFile(projectRoot: string): Promise<ProjectValidationResult> {
|
|
51
|
+
const root = resolve(projectRoot);
|
|
52
|
+
const projectPath = resolve(root, "project.json");
|
|
53
|
+
const schemaPath = resolve(root, "project.schema.json");
|
|
54
|
+
|
|
55
|
+
if (!existsSync(projectPath)) {
|
|
56
|
+
throw new Error(`project.json not found in ${root}`);
|
|
57
|
+
}
|
|
58
|
+
if (!existsSync(schemaPath)) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
`project.schema.json not found in ${root} — run \`jx schema\` to generate it from ` +
|
|
61
|
+
`project.json#/extensions`,
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const project = JSON.parse(readFileSync(projectPath, "utf8")) as Record<string, unknown>;
|
|
66
|
+
const entrySchema = JSON.parse(readFileSync(schemaPath, "utf8")) as Record<string, unknown>;
|
|
67
|
+
// The committed entry document is machine-path-free; injecting its file URL as $id makes its
|
|
68
|
+
// Relative ./node_modules/... refs resolve against the project root.
|
|
69
|
+
entrySchema.$id = pathToFileURL(schemaPath).href;
|
|
70
|
+
|
|
71
|
+
let Ajv: AjvCtor;
|
|
72
|
+
try {
|
|
73
|
+
// The entry document is JSON Schema 2020-12, so use the matching Ajv build.
|
|
74
|
+
({ default: Ajv } = (await import("ajv/dist/2020")) as { default: AjvCtor });
|
|
75
|
+
} catch (error) {
|
|
76
|
+
throw new Error("Project validation requires ajv: bun add ajv", { cause: error });
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const ajv = new Ajv({
|
|
80
|
+
allErrors: true,
|
|
81
|
+
loadSchema: (uri) => loadProjectSchemaRef(uri, root),
|
|
82
|
+
strict: false,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
const validate = await ajv.compileAsync(entrySchema);
|
|
86
|
+
const valid = validate(project);
|
|
87
|
+
return { errors: validate.errors ?? null, valid };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Restricted schema loader: only `file:` URLs inside the project root are served. Missing files
|
|
92
|
+
* under `<root>/node_modules/` fall back to host resolution of the corresponding bare specifier
|
|
93
|
+
* (in-repo starters have no local node_modules; the fragments still ship with the host).
|
|
94
|
+
*
|
|
95
|
+
* @param {string} uri - The `$ref` target ajv could not resolve from already-loaded resources
|
|
96
|
+
* @param {string} root - Absolute project root
|
|
97
|
+
* @returns {Promise<Record<string, unknown>>}
|
|
98
|
+
*/
|
|
99
|
+
async function loadProjectSchemaRef(uri: string, root: string): Promise<Record<string, unknown>> {
|
|
100
|
+
if (!uri.startsWith("file:")) {
|
|
101
|
+
throw new Error(
|
|
102
|
+
`Cannot resolve schema ref "${uri}": only project-relative file refs are loadable ` +
|
|
103
|
+
`(canonical URIs must resolve from already-loaded fragments)`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const filePath = fileURLToPath(uri);
|
|
108
|
+
const rel = relative(root, filePath);
|
|
109
|
+
if (rel.startsWith("..") || isAbsolute(rel)) {
|
|
110
|
+
throw new Error(`Schema ref "${uri}" escapes the project root ${root}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (existsSync(filePath)) {
|
|
114
|
+
return JSON.parse(readFileSync(filePath, "utf8")) as Record<string, unknown>;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Host fallback: <root>/node_modules/<specifier> is absent — resolve the specifier through the
|
|
118
|
+
// Project's own resolution first, then the host's (workspace symlinks resolve transparently).
|
|
119
|
+
const posixRel = rel.split("\\").join("/");
|
|
120
|
+
const marker = "node_modules/";
|
|
121
|
+
const markerIndex = posixRel.lastIndexOf(marker);
|
|
122
|
+
if (markerIndex === -1) {
|
|
123
|
+
throw new Error(`Schema ref "${uri}" does not exist`);
|
|
124
|
+
}
|
|
125
|
+
const specifier = posixRel.slice(markerIndex + marker.length);
|
|
126
|
+
|
|
127
|
+
let resolved: string;
|
|
128
|
+
try {
|
|
129
|
+
const projRequire = createRequire(resolve(root, "package.json"));
|
|
130
|
+
resolved = projRequire.resolve(specifier);
|
|
131
|
+
} catch {
|
|
132
|
+
try {
|
|
133
|
+
const selfRequire = createRequire(import.meta.url);
|
|
134
|
+
resolved = selfRequire.resolve(specifier);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
throw new Error(
|
|
137
|
+
`Schema ref "${uri}" does not exist and "${specifier}" is not resolvable from the ` +
|
|
138
|
+
`project or the host`,
|
|
139
|
+
{ cause: error },
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return JSON.parse(readFileSync(resolved, "utf8")) as Record<string, unknown>;
|
|
144
|
+
}
|
package/types.ts
CHANGED
|
@@ -3,7 +3,6 @@ import type { FromSchema } from "json-schema-to-ts";
|
|
|
3
3
|
import type { headEntrySchema } from "./defs/head-entry.schema";
|
|
4
4
|
import type { imageConfigSchema } from "./defs/image-config.schema";
|
|
5
5
|
import type { cemEventSchema, cemParameterSchema } from "./defs/cem.schema";
|
|
6
|
-
import type { contentTypeDefSchema } from "./defs/content-type-def.schema";
|
|
7
6
|
import type { projectConfigSchema } from "./defs/project-config.schema";
|
|
8
7
|
import type { refObjectSchema } from "./defs/ref-object.schema";
|
|
9
8
|
import type { styleObjectSchema } from "./defs/style-object.schema";
|
|
@@ -32,7 +31,6 @@ export namespace Strict {
|
|
|
32
31
|
export type ImageConfig = FromSchema<typeof imageConfigSchema>;
|
|
33
32
|
export type CemParameter = FromSchema<typeof cemParameterSchema>;
|
|
34
33
|
export type CemEvent = FromSchema<typeof cemEventSchema>;
|
|
35
|
-
export type ContentTypeDef = FromSchema<typeof contentTypeDefSchema>;
|
|
36
34
|
export type RefObject = FromSchema<typeof refObjectSchema>;
|
|
37
35
|
export type Style = FromSchema<typeof styleObjectSchema>;
|
|
38
36
|
export type Element = FromSchema<typeof elementDefSchema>;
|
|
@@ -186,8 +184,6 @@ export interface CemEvent {
|
|
|
186
184
|
deprecated?: boolean | string;
|
|
187
185
|
}
|
|
188
186
|
|
|
189
|
-
export type ContentTypeDef = FromSchema<typeof contentTypeDefSchema>;
|
|
190
|
-
|
|
191
187
|
export type RefObject = FromSchema<typeof refObjectSchema>;
|
|
192
188
|
|
|
193
189
|
export interface JxStyle {
|
|
@@ -429,7 +425,24 @@ export interface JxClassDef {
|
|
|
429
425
|
[key: string]: unknown;
|
|
430
426
|
}
|
|
431
427
|
|
|
428
|
+
/** The adapters the compiler actually implements (site-loader VALID_ADAPTERS + "static"). */
|
|
429
|
+
export type AdapterId = "static" | "cloudflare-pages" | "cloudflare-workers" | "node" | "bun";
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Deployment tracking (project.json `build.deploy`): the hosting project this repo publishes to.
|
|
433
|
+
* Identifiers only — no secrets — so it travels with the repo and any Studio (local or cloud) can
|
|
434
|
+
* tell whether publishing is set up.
|
|
435
|
+
*/
|
|
436
|
+
export interface DeployConfig {
|
|
437
|
+
provider: "cloudflare-pages";
|
|
438
|
+
accountId: string;
|
|
439
|
+
projectName: string;
|
|
440
|
+
productionUrl?: string | undefined;
|
|
441
|
+
}
|
|
442
|
+
|
|
432
443
|
export interface ProjectConfig {
|
|
444
|
+
/** Relative path to the generated per-project schema (written by `jx schema`). */
|
|
445
|
+
$schema?: string;
|
|
433
446
|
name?: string;
|
|
434
447
|
url?: string;
|
|
435
448
|
state?: Record<string, unknown>;
|
|
@@ -437,10 +450,20 @@ export interface ProjectConfig {
|
|
|
437
450
|
$elements?: (string | JxElement)[];
|
|
438
451
|
$head?: JxHeadEntry[];
|
|
439
452
|
$defs?: Record<string, unknown>;
|
|
440
|
-
build?: {
|
|
453
|
+
build?: {
|
|
454
|
+
adapter?: AdapterId | (string & Record<never, never>);
|
|
455
|
+
deploy?: DeployConfig;
|
|
456
|
+
sitemap?: boolean;
|
|
457
|
+
[key: string]: unknown;
|
|
458
|
+
};
|
|
441
459
|
images?: ImageConfig;
|
|
442
460
|
imports?: Record<string, string>;
|
|
443
|
-
|
|
461
|
+
/**
|
|
462
|
+
* Extension packages: bare package names (resolved project-first) or relative paths. Each must
|
|
463
|
+
* export jx-extension.json. Extension-contributed sections (e.g. `content`) are opaque top-level
|
|
464
|
+
* keys absorbed by the index signature.
|
|
465
|
+
*/
|
|
466
|
+
extensions?: string[];
|
|
444
467
|
/** Redirect map: source path → destination (or destination with HTTP status). */
|
|
445
468
|
redirects?: Record<string, string | { destination: string; status?: number }>;
|
|
446
469
|
defaults?: {
|
|
@@ -460,13 +483,15 @@ export interface ProjectConfig {
|
|
|
460
483
|
export type JxAttributeValue = Bindable<string | number | boolean>;
|
|
461
484
|
|
|
462
485
|
/**
|
|
463
|
-
* A dynamic-route path source (spec §4.3):
|
|
464
|
-
*
|
|
486
|
+
* A dynamic-route path source (spec §4.3): explicit values, data-file ref, a legacy array of param
|
|
487
|
+
* objects, or an extension-discriminated source (an object carrying a `resolvePaths` discriminator
|
|
488
|
+
* key registered by an enabled extension, e.g. the parser's `contentType` — the extension owns the
|
|
489
|
+
* narrow shape).
|
|
465
490
|
*/
|
|
466
491
|
export type JxPathsDef =
|
|
467
|
-
| { contentType: string; param?: string; field?: string }
|
|
468
492
|
| { values: JsonValue[]; param?: string }
|
|
469
493
|
| { $ref: string; param?: string; field?: string }
|
|
494
|
+
| { param?: string; field?: string; [discriminator: string]: unknown }
|
|
470
495
|
| Record<string, JsonValue>[];
|
|
471
496
|
|
|
472
497
|
/**
|
|
@@ -521,6 +546,8 @@ export interface JxMutableNode {
|
|
|
521
546
|
export type JxPath = (string | number)[];
|
|
522
547
|
|
|
523
548
|
// ─── Content Type Schema ────────────────────────────────────────────────────────
|
|
549
|
+
// Part-3 cleanup: move to parser — the parser extension owns the content-section shapes now; these
|
|
550
|
+
// Stay here only while the studio still imports them from @jxsuite/schema/types.
|
|
524
551
|
|
|
525
552
|
/** One field within a content-type JSON schema; recursive for nested objects. */
|
|
526
553
|
export interface ContentTypeSchemaField {
|
|
@@ -538,3 +565,29 @@ export interface ContentTypeSchema {
|
|
|
538
565
|
required?: string[];
|
|
539
566
|
[key: string]: unknown;
|
|
540
567
|
}
|
|
568
|
+
|
|
569
|
+
// ─── Content Wire Types ─────────────────────────────────────────────────────────
|
|
570
|
+
// Core wire shapes for content loading (specs/extensions.md §8): format classes' `load`
|
|
571
|
+
// Capabilities return ContentLoaderEntry[], and hosts pass the entries through untouched.
|
|
572
|
+
// Extensions implement them; core only threads them.
|
|
573
|
+
|
|
574
|
+
/** A table-of-contents entry extracted from a loaded content document. */
|
|
575
|
+
export interface TocEntry {
|
|
576
|
+
depth: number;
|
|
577
|
+
text: string;
|
|
578
|
+
id: string;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** One loaded content entry, as produced by a format class's `load` capability. */
|
|
582
|
+
export interface ContentLoaderEntry {
|
|
583
|
+
id: string;
|
|
584
|
+
data: Record<string, unknown>;
|
|
585
|
+
body: string | null;
|
|
586
|
+
$children?: (JxElement | string)[];
|
|
587
|
+
_meta?: {
|
|
588
|
+
excerpt?: string;
|
|
589
|
+
toc?: TocEntry[];
|
|
590
|
+
readingTime?: number;
|
|
591
|
+
wordCount?: number;
|
|
592
|
+
};
|
|
593
|
+
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
export const contentTypeDefSchema = {
|
|
2
|
-
description:
|
|
3
|
-
"Content type definition. Defines the source directory, frontmatter schema, and element dependencies.",
|
|
4
|
-
properties: {
|
|
5
|
-
$elements: {
|
|
6
|
-
description: "Custom elements available in markdown directives for this content type.",
|
|
7
|
-
items: {
|
|
8
|
-
oneOf: [
|
|
9
|
-
{
|
|
10
|
-
properties: { $ref: { type: "string" } },
|
|
11
|
-
required: ["$ref"],
|
|
12
|
-
type: "object",
|
|
13
|
-
},
|
|
14
|
-
{ type: "string" },
|
|
15
|
-
],
|
|
16
|
-
},
|
|
17
|
-
type: "array",
|
|
18
|
-
},
|
|
19
|
-
format: {
|
|
20
|
-
description:
|
|
21
|
-
"Import name of a format class declared in the project imports map " +
|
|
22
|
-
'(e.g. "Markdown", "Csv"). When omitted, the format is derived from the ' +
|
|
23
|
-
'source file extension via the format registry. "json" is the only built-in.',
|
|
24
|
-
type: "string",
|
|
25
|
-
},
|
|
26
|
-
schema: {
|
|
27
|
-
description: "JSON Schema for validating frontmatter of content type entries.",
|
|
28
|
-
type: "object",
|
|
29
|
-
},
|
|
30
|
-
source: {
|
|
31
|
-
description: "Path to the content directory relative to the project root.",
|
|
32
|
-
examples: ["./content/blog/", "./content/docs/"],
|
|
33
|
-
type: "string",
|
|
34
|
-
},
|
|
35
|
-
},
|
|
36
|
-
type: "object",
|
|
37
|
-
} as const;
|