@jxsuite/schema 1.2.0 → 1.4.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/LICENSE +21 -0
- package/class-schema.json +104 -1
- package/defs/children-value.schema.ts +10 -1
- package/defs/class-def.schema.ts +108 -1
- package/defs/element-def.schema.ts +15 -2
- package/defs/external-class-def.schema.ts +14 -1
- package/defs/function-def.schema.ts +8 -0
- package/defs/index.ts +4 -0
- package/defs/project-config.schema.ts +3 -2
- package/defs/style-object.schema.ts +4 -10
- package/package.json +1 -1
- package/project-schema.json +20 -2
- package/schema.json +1053 -19
- package/schemas/project.core.schema.json +20 -2
- package/src/asset-paths.ts +137 -2
- package/src/extension-registry.ts +5 -0
- package/src/format-registry.ts +4 -2
- package/src/guards.ts +10 -0
- package/src/schema.ts +41 -3
- package/src/validate-project.ts +13 -0
|
@@ -105,6 +105,24 @@
|
|
|
105
105
|
},
|
|
106
106
|
"required": ["$ref"],
|
|
107
107
|
"type": "object"
|
|
108
|
+
},
|
|
109
|
+
"StyleObject": {
|
|
110
|
+
"additionalProperties": {
|
|
111
|
+
"oneOf": [
|
|
112
|
+
{
|
|
113
|
+
"type": "string"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"type": "number"
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
"$ref": "#/$defs/StyleObject"
|
|
120
|
+
}
|
|
121
|
+
]
|
|
122
|
+
},
|
|
123
|
+
"description": "CSS style definition. camelCase property names follow CSSOM convention. Keys starting with :, ., &, or [ are treated as nested CSS selectors. Keys matching $media breakpoint names (or other @-prefixed at-rules) group nested rules. Nesting is recursive: selector and at-rule groups may nest to arbitrary depth (e.g. breakpoint → selector → pseudo-class), mirroring the compiler's recursive emission.",
|
|
124
|
+
"properties": {},
|
|
125
|
+
"type": "object"
|
|
108
126
|
}
|
|
109
127
|
},
|
|
110
128
|
"$id": "https://jxsuite.com/schema/project/core/v2",
|
|
@@ -366,11 +384,11 @@
|
|
|
366
384
|
"type": "number"
|
|
367
385
|
},
|
|
368
386
|
{
|
|
369
|
-
"
|
|
387
|
+
"$ref": "#/$defs/StyleObject"
|
|
370
388
|
}
|
|
371
389
|
]
|
|
372
390
|
},
|
|
373
|
-
"description": "Global CSS styles applied to the <body> element. Uses the same camelCase CSSOM convention as component styles.",
|
|
391
|
+
"description": "Global CSS styles applied to the <body> element. Uses the same camelCase CSSOM convention and recursive selector/at-rule nesting as component styles.",
|
|
374
392
|
"type": "object"
|
|
375
393
|
},
|
|
376
394
|
"url": {
|
package/src/asset-paths.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deterministic mapping from Function-def `$src` module specifiers to bundled asset URLs
|
|
3
|
-
*
|
|
2
|
+
* Deterministic mapping from Function-def `$src` module specifiers to bundled asset URLs, plus the
|
|
3
|
+
* asset-mount math that publishes files living outside the project root at a stable site URL.
|
|
4
|
+
* Shared between the compiler's sidecar bundler (which writes the bundle) and extension `lower()`
|
|
4
5
|
* implementations (which emit the URL into client code) so neither needs to depend on the other.
|
|
5
6
|
*
|
|
6
7
|
* The mapping is intentionally hash-free: the same specifier always yields the same URL, letting
|
|
7
8
|
* lowered defs reference bundles the compiler produces later in the build. Distinct specifiers that
|
|
8
9
|
* collide on a slug are rejected by the bundler at build time.
|
|
10
|
+
*
|
|
11
|
+
* Pure string math only — no node imports — so browser hosts can import this module.
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
/** Prefix for npm package specifiers in `$src` (spec §12): `npm:<pkg>[/subpath]`. */
|
|
@@ -40,3 +43,135 @@ export function sidecarAssetPath(specifier: string): string {
|
|
|
40
43
|
.replaceAll(/^-|-$/g, "");
|
|
41
44
|
return `${SIDECAR_ASSET_DIR}${base}.js`;
|
|
42
45
|
}
|
|
46
|
+
|
|
47
|
+
// ─── Asset mounts (specs/extensions.md §8.5) ─────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* A site URL prefix backed by a real directory. Extensions declaring the `assets` capability return
|
|
51
|
+
* mounts so hosts can publish files that live outside the project root (an external content
|
|
52
|
+
* source's co-located images) without knowing anything about the section that owns them.
|
|
53
|
+
*/
|
|
54
|
+
export interface AssetMount {
|
|
55
|
+
/** Site-absolute URL prefix, e.g. `/content/docs` (no trailing slash). */
|
|
56
|
+
urlPrefix: string;
|
|
57
|
+
/** Absolute directory the prefix maps onto. */
|
|
58
|
+
dir: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Normalize a path or prefix for comparison: forward slashes, no trailing slash. */
|
|
62
|
+
function normalizeDir(value: string): string {
|
|
63
|
+
const forward = value.replaceAll("\\", "/");
|
|
64
|
+
return forward.length > 1 && forward.endsWith("/") ? forward.slice(0, -1) : forward;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Normalize a mount's URL prefix: leading slash, no trailing slash. */
|
|
68
|
+
export function normalizeAssetPrefix(prefix: string): string {
|
|
69
|
+
const forward = normalizeDir(prefix);
|
|
70
|
+
return forward.startsWith("/") ? forward : `/${forward}`;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Mounts sorted so the most specific (longest) candidate matches first. */
|
|
74
|
+
function bySpecificity(mounts: readonly AssetMount[], key: "dir" | "urlPrefix"): AssetMount[] {
|
|
75
|
+
return [...mounts].toSorted((a, b) => b[key].length - a[key].length);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** True when `path` is `dir` itself or sits underneath it (both already normalized). */
|
|
79
|
+
function isUnder(path: string, dir: string): boolean {
|
|
80
|
+
return path === dir || path.startsWith(`${dir}/`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Map an absolute file path to its mounted site URL, or null when no mount contains it. Path
|
|
85
|
+
* segments are percent-encoded so the URL survives HTML attributes; `resolveAssetUrl` reverses it.
|
|
86
|
+
*
|
|
87
|
+
* @param {readonly AssetMount[]} mounts
|
|
88
|
+
* @param {string} absolutePath - Absolute filesystem path
|
|
89
|
+
* @returns {string | null}
|
|
90
|
+
*/
|
|
91
|
+
export function assetUrlFor(mounts: readonly AssetMount[], absolutePath: string): string | null {
|
|
92
|
+
const path = normalizeDir(absolutePath);
|
|
93
|
+
for (const mount of bySpecificity(mounts, "dir")) {
|
|
94
|
+
const dir = normalizeDir(mount.dir);
|
|
95
|
+
if (!isUnder(path, dir) || path === dir) {
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
const rest = path.slice(dir.length + 1);
|
|
99
|
+
const encoded = rest
|
|
100
|
+
.split("/")
|
|
101
|
+
.map((segment) => encodeURIComponent(segment))
|
|
102
|
+
.join("/");
|
|
103
|
+
return `${normalizeAssetPrefix(mount.urlPrefix)}/${encoded}`;
|
|
104
|
+
}
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Map a mounted site URL back to its absolute file path, or null when it matches no mount. Query
|
|
110
|
+
* and hash are dropped, the path is decoded ONCE (a still-encoded dot or slash after that decode is
|
|
111
|
+
* a bypass attempt and is rejected), and `.`/`..` segments are refused outright — so the result can
|
|
112
|
+
* never escape the mount directory.
|
|
113
|
+
*
|
|
114
|
+
* @param {readonly AssetMount[]} mounts
|
|
115
|
+
* @param {string} url - Site-absolute URL (e.g. `/content/docs/images/hero.png`)
|
|
116
|
+
* @returns {string | null}
|
|
117
|
+
*/
|
|
118
|
+
export function resolveAssetUrl(mounts: readonly AssetMount[], url: string): string | null {
|
|
119
|
+
const pathname = url.split("#")[0]!.split("?")[0]!;
|
|
120
|
+
if (!pathname.startsWith("/")) {
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
let decoded: string;
|
|
124
|
+
try {
|
|
125
|
+
decoded = decodeURIComponent(pathname);
|
|
126
|
+
} catch {
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (/%2e|%2f/i.test(decoded)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
for (const mount of bySpecificity(mounts, "urlPrefix")) {
|
|
133
|
+
const prefix = normalizeAssetPrefix(mount.urlPrefix);
|
|
134
|
+
if (!decoded.startsWith(`${prefix}/`)) {
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const rest = decoded.slice(prefix.length + 1);
|
|
138
|
+
const segments = rest.split("/");
|
|
139
|
+
if (segments.some((s) => s === "" || s === "." || s === "..")) {
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
return `${normalizeDir(mount.dir)}/${segments.join("/")}`;
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** Characters that terminate a URL inside markup: quotes, whitespace, CSS `url()` parens, `<>`. */
|
|
148
|
+
const URL_TERMINATOR = String.raw`"'\s<>()\\`;
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Collect every mounted URL referenced by a compiled artifact (HTML, CSS, JSON-LD). Scanning output
|
|
152
|
+
* text rather than the document tree catches references wherever they came from — markdown images,
|
|
153
|
+
* hand-authored pages, `$head` metadata, `url()` in stylesheets — with no per-attribute knowledge.
|
|
154
|
+
*
|
|
155
|
+
* @param {string} text - Compiled HTML or CSS
|
|
156
|
+
* @param {readonly AssetMount[]} mounts
|
|
157
|
+
* @returns {string[]} Unique mounted URLs, in first-seen order
|
|
158
|
+
*/
|
|
159
|
+
export function collectAssetUrls(text: string, mounts: readonly AssetMount[]): string[] {
|
|
160
|
+
const found = new Set<string>();
|
|
161
|
+
for (const mount of mounts) {
|
|
162
|
+
const prefix = normalizeAssetPrefix(mount.urlPrefix);
|
|
163
|
+
const pattern = new RegExp(
|
|
164
|
+
`${prefix.replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`)}/[^${URL_TERMINATOR}]+`,
|
|
165
|
+
"g",
|
|
166
|
+
);
|
|
167
|
+
for (const match of text.matchAll(pattern)) {
|
|
168
|
+
const url = match[0].split("#")[0]!.split("?")[0]!;
|
|
169
|
+
// A trailing slash names a directory, not a file — prose quoting the mount prefix
|
|
170
|
+
// ("published under /content/docs/images/") is a mention, not a reference.
|
|
171
|
+
if (!url.endsWith("/")) {
|
|
172
|
+
found.add(url);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return [...found].filter(Boolean);
|
|
177
|
+
}
|
|
@@ -156,6 +156,11 @@ export class ExtensionRegistry {
|
|
|
156
156
|
return this.#classes.filter((e) => e.capabilities.emit !== undefined);
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/** Classes declaring an `assets` capability (static asset mounts, §8.5), in declaration order. */
|
|
160
|
+
assetProviders(): FormatEntry[] {
|
|
161
|
+
return this.#classes.filter((e) => e.capabilities.assets !== undefined);
|
|
162
|
+
}
|
|
163
|
+
|
|
159
164
|
/** Server-mount classes, sorted by `server.order` (ascending; default 100), then name. */
|
|
160
165
|
serverMounts(): FormatEntry[] {
|
|
161
166
|
return this.#classes
|
package/src/format-registry.ts
CHANGED
|
@@ -22,8 +22,8 @@ export const FORMAT_CAPABILITIES: readonly FormatCapability[] = [
|
|
|
22
22
|
/**
|
|
23
23
|
* All well-known static capability roles across admission blocks (specs/extensions.md §8): format
|
|
24
24
|
* roles plus project-section (`projectData`, `resolvePaths`), compile-away (`lower`), build-time
|
|
25
|
-
* asset emission (`emit`), server-mount (`mount`), and connector
|
|
26
|
-
* `bindings`, `testConnection`) roles.
|
|
25
|
+
* asset emission (`emit`), static asset mounts (`assets`), server-mount (`mount`), and connector
|
|
26
|
+
* (`dialect`, `deploySchema`, `bindings`, `testConnection`) roles.
|
|
27
27
|
*/
|
|
28
28
|
export type ExtensionCapability =
|
|
29
29
|
| FormatCapability
|
|
@@ -31,6 +31,7 @@ export type ExtensionCapability =
|
|
|
31
31
|
| "resolvePaths"
|
|
32
32
|
| "lower"
|
|
33
33
|
| "emit"
|
|
34
|
+
| "assets"
|
|
34
35
|
| "mount"
|
|
35
36
|
| "dialect"
|
|
36
37
|
| "deploySchema"
|
|
@@ -43,6 +44,7 @@ export const EXTENSION_CAPABILITIES: readonly ExtensionCapability[] = [
|
|
|
43
44
|
"resolvePaths",
|
|
44
45
|
"lower",
|
|
45
46
|
"emit",
|
|
47
|
+
"assets",
|
|
46
48
|
"mount",
|
|
47
49
|
"dialect",
|
|
48
50
|
"deploySchema",
|
package/src/guards.ts
CHANGED
|
@@ -174,6 +174,16 @@ export function isTemplateString(value?: unknown): value is string {
|
|
|
174
174
|
return typeof value === "string" && value.includes("${");
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Whether a function `body` returns a value — the heuristic that classifies a `$prototype:
|
|
179
|
+
* "Function"` as a computed value vs an event handler. Uses a word boundary so identifiers like
|
|
180
|
+
* `returned` do not match. Shared by the runtime interpreter and the compiler so both agree on the
|
|
181
|
+
* classification.
|
|
182
|
+
*/
|
|
183
|
+
export function bodyReturnsValue(body: string): boolean {
|
|
184
|
+
return /\breturn\b/.test(body);
|
|
185
|
+
}
|
|
186
|
+
|
|
177
187
|
// ─── Style guards & accessors ───────────────────────────────────────────────────
|
|
178
188
|
|
|
179
189
|
/** A nested style object (selector/media block), as opposed to a scalar CSS value. */
|
package/src/schema.ts
CHANGED
|
@@ -92,7 +92,10 @@ import {
|
|
|
92
92
|
classFieldDefSchema,
|
|
93
93
|
classMethodDefSchema,
|
|
94
94
|
classParameterDefSchema,
|
|
95
|
+
connectorBlockDefSchema,
|
|
95
96
|
formatDefSchema,
|
|
97
|
+
projectBlockDefSchema,
|
|
98
|
+
serverBlockDefSchema,
|
|
96
99
|
studioHintsSchema,
|
|
97
100
|
} from "../defs/class-def.schema";
|
|
98
101
|
import { extensionManifestSchema } from "../defs/extension-manifest.schema";
|
|
@@ -182,7 +185,11 @@ function buildEventHandlerProperties(eventHandlers: string[]) {
|
|
|
182
185
|
for (const name of eventHandlers) {
|
|
183
186
|
properties[name] = {
|
|
184
187
|
description: `Event handler for the "${name.slice(2)}" event.`,
|
|
185
|
-
oneOf: [
|
|
188
|
+
oneOf: [
|
|
189
|
+
{ $ref: "#/$defs/RefObject" },
|
|
190
|
+
{ $ref: "#/$defs/ExpressionEntry" },
|
|
191
|
+
{ $ref: "#/$defs/FunctionDef" },
|
|
192
|
+
],
|
|
186
193
|
};
|
|
187
194
|
}
|
|
188
195
|
return properties;
|
|
@@ -216,6 +223,9 @@ export async function generateSchema() {
|
|
|
216
223
|
ClassConstructorDef: classConstructorDefSchema,
|
|
217
224
|
ClassMethodDef: classMethodDefSchema,
|
|
218
225
|
FormatDef: formatDefSchema,
|
|
226
|
+
ProjectBlockDef: projectBlockDefSchema,
|
|
227
|
+
ServerBlockDef: serverBlockDefSchema,
|
|
228
|
+
ConnectorBlockDef: connectorBlockDefSchema,
|
|
219
229
|
StudioHints: studioHintsSchema,
|
|
220
230
|
ExternalClassDef: externalClassDefSchema,
|
|
221
231
|
ExpressionPointer: expressionPointerSchema,
|
|
@@ -415,6 +425,7 @@ export function generateProjectSchema() {
|
|
|
415
425
|
return {
|
|
416
426
|
$defs: {
|
|
417
427
|
ImageConfig: imageConfigSchema,
|
|
428
|
+
StyleObject: styleObjectSchema,
|
|
418
429
|
},
|
|
419
430
|
$id: "https://jxsuite.com/schema/project/v1",
|
|
420
431
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -446,6 +457,7 @@ export function generateProjectCoreSchema() {
|
|
|
446
457
|
ImageConfig: imageConfigSchema,
|
|
447
458
|
JxFieldSchema: jxFieldSchemaDef,
|
|
448
459
|
RelationshipRef: relationshipRefSchema,
|
|
460
|
+
StyleObject: styleObjectSchema,
|
|
449
461
|
},
|
|
450
462
|
$id: "https://jxsuite.com/schema/project/core/v2",
|
|
451
463
|
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
@@ -514,7 +526,10 @@ export function generateClassSchema() {
|
|
|
514
526
|
ClassFieldDef: classFieldDefSchema,
|
|
515
527
|
ClassMethodDef: classMethodDefSchema,
|
|
516
528
|
ClassParameterDef: classParameterDefSchema,
|
|
529
|
+
ConnectorBlockDef: connectorBlockDefSchema,
|
|
517
530
|
FormatDef: formatDefSchema,
|
|
531
|
+
ProjectBlockDef: projectBlockDefSchema,
|
|
532
|
+
ServerBlockDef: serverBlockDefSchema,
|
|
518
533
|
StudioHints: studioHintsSchema,
|
|
519
534
|
},
|
|
520
535
|
$id: "https://jxsuite.com/schema/class/v1",
|
|
@@ -565,6 +580,7 @@ export function generateClassSchema() {
|
|
|
565
580
|
},
|
|
566
581
|
$schema: { type: "string" },
|
|
567
582
|
$studio: { $ref: "#/$defs/StudioHints" },
|
|
583
|
+
connector: { $ref: "#/$defs/ConnectorBlockDef" },
|
|
568
584
|
description: { type: "string" },
|
|
569
585
|
extends: {
|
|
570
586
|
description: "Base class — string name or $ref to another .class.json.",
|
|
@@ -579,6 +595,8 @@ export function generateClassSchema() {
|
|
|
579
595
|
],
|
|
580
596
|
},
|
|
581
597
|
format: { $ref: "#/$defs/FormatDef" },
|
|
598
|
+
project: { $ref: "#/$defs/ProjectBlockDef" },
|
|
599
|
+
server: { $ref: "#/$defs/ServerBlockDef" },
|
|
582
600
|
title: {
|
|
583
601
|
description: "PascalCase class name, used as the export name.",
|
|
584
602
|
examples: ["MarkdownFile", "DataSource", "Calculator"],
|
|
@@ -613,7 +631,19 @@ type AjvCtor = new (opts: {
|
|
|
613
631
|
}) => AjvInstance;
|
|
614
632
|
type AddFormatsFn = (ajv: AjvInstance) => void;
|
|
615
633
|
|
|
616
|
-
|
|
634
|
+
/**
|
|
635
|
+
* Validate a document against an arbitrary JSON Schema 2020-12 document (e.g. a bundled per-project
|
|
636
|
+
* document.schema.json from `jx schema`). The schema must be self-contained — external refs are not
|
|
637
|
+
* resolved here.
|
|
638
|
+
*
|
|
639
|
+
* @param {Record<string, unknown>} doc - The document to validate
|
|
640
|
+
* @param {Record<string, unknown>} schema - A self-contained 2020-12 schema
|
|
641
|
+
* @returns {Promise<{ errors: unknown[] | null; valid: boolean }>}
|
|
642
|
+
*/
|
|
643
|
+
export async function validateWithSchema(
|
|
644
|
+
doc: Record<string, unknown>,
|
|
645
|
+
schema: Record<string, unknown>,
|
|
646
|
+
) {
|
|
617
647
|
let Ajv: AjvCtor, addFormats: AddFormatsFn;
|
|
618
648
|
try {
|
|
619
649
|
// The generated schema is JSON Schema 2020-12, so use the matching Ajv build
|
|
@@ -628,13 +658,21 @@ export async function validateDocument(doc: Record<string, unknown>) {
|
|
|
628
658
|
const ajv = new Ajv({ allErrors: true, ownProperties: true, strict: false });
|
|
629
659
|
addFormats(ajv);
|
|
630
660
|
|
|
631
|
-
const schema = await generateSchema();
|
|
632
661
|
const validate = ajv.compile(schema);
|
|
633
662
|
const valid = validate(doc);
|
|
634
663
|
|
|
635
664
|
return { errors: validate.errors ?? null, valid };
|
|
636
665
|
}
|
|
637
666
|
|
|
667
|
+
export async function validateDocument(doc: Record<string, unknown>) {
|
|
668
|
+
return validateWithSchema(doc, (await generateSchema()) as Record<string, unknown>);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
/** Validate a .class.json class definition against the generated class schema. */
|
|
672
|
+
export async function validateClass(doc: Record<string, unknown>) {
|
|
673
|
+
return validateWithSchema(doc, generateClassSchema() as Record<string, unknown>);
|
|
674
|
+
}
|
|
675
|
+
|
|
638
676
|
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
639
677
|
|
|
640
678
|
async function runSchemaCli() {
|
package/src/validate-project.ts
CHANGED
|
@@ -133,6 +133,19 @@ async function loadProjectSchemaRef(uri: string, root: string): Promise<Record<s
|
|
|
133
133
|
const selfRequire = createRequire(import.meta.url);
|
|
134
134
|
resolved = selfRequire.resolve(specifier);
|
|
135
135
|
} catch (error) {
|
|
136
|
+
// Exports-map subpath resolution is brittle against version-skewed shadowing installs (a
|
|
137
|
+
// Stale local copy terminates Node's walk-up with ERR_PACKAGE_PATH_NOT_EXPORTED). Our own
|
|
138
|
+
// Schema files are pure data and the running validator's copy is guaranteed to match, so
|
|
139
|
+
// Read them straight off this package's disk as the last resort.
|
|
140
|
+
const ownPrefix = "@jxsuite/schema/";
|
|
141
|
+
if (specifier.startsWith(ownPrefix)) {
|
|
142
|
+
const ownFile = fileURLToPath(
|
|
143
|
+
new URL(`../${specifier.slice(ownPrefix.length)}`, import.meta.url),
|
|
144
|
+
);
|
|
145
|
+
if (existsSync(ownFile)) {
|
|
146
|
+
return JSON.parse(readFileSync(ownFile, "utf8")) as Record<string, unknown>;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
136
149
|
throw new Error(
|
|
137
150
|
`Schema ref "${uri}" does not exist and "${specifier}" is not resolvable from the ` +
|
|
138
151
|
`project or the host`,
|