Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ To install dependencies:
bun install
```

To run:
To run tests:

```bash
bun run index.ts
bun test
```

This project was created using `bun init` in bun v1.3.1. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime.
174 changes: 48 additions & 126 deletions bun.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion bunfig.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
[test]
# Always enable coverage
coverage = true
coverageReporter = ["lcov","text"]
coverageReporter = ["lcov","text"]

[alias]
"@open-rpc/json-schema-renderer" = "./packages/json-schema-renderer/src/index.ts"
22 changes: 13 additions & 9 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@ export default [
{
ignores: [
"package.json",
"node_modules/",
"node_modules/",
"**/dist/",
"dist/",
"dist/",
"docs/", // This will now work
"build/",
"build/",
"coverage/",
"*.config.js",
"**/*.config.ts",
"vitest.config.ts",
"*.config.js",
"**/*.config.ts",
"vitest.config.ts",
"vite.config.ts",
"build.ts",
"**/build.ts",
Expand All @@ -39,8 +39,12 @@ export default [
languageOptions: {
parser: tsParser,
parserOptions: {
project: ['./packages/markdown-generator/tsconfig.json', './packages/docusaurus-plugin/tsconfig.json'],

project: [
'./packages/markdown-generator/tsconfig.json',
'./packages/docusaurus-plugin/tsconfig.json',
'./packages/json-schema-renderer/tsconfig.json',
'./packages/json-schema-renderer/tsconfig.eslint.json'
],
tsconfigRootDir: __dirname,
ecmaVersion: 2022,
sourceType: 'module',
Expand Down Expand Up @@ -69,4 +73,4 @@ export default [
settings: { }
},
prettierConfig
];
];
17 changes: 13 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,16 @@
],
"version": "0.0.0-development",
"scripts": {
"types": "bun run types:md-gen && bun run types:plugin",
"types": "bun run types:json-schema-renderer && bun run types:md-gen && bun run types:plugin",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"postinstall": "bun run build:json-schema-renderer",
"build:json-schema-renderer": "bun run --cwd packages/json-schema-renderer build.ts",
"build:md-gen": "bun run --cwd packages/markdown-generator build.ts",
"build:plugin": "bun run --cwd packages/docusaurus-plugin build.ts",
"types:md-gen": "bun run --cwd packages/markdown-generator tsc -p tsconfig.json",
"types:plugin": "bun run --cwd packages/docusaurus-plugin tsc -p tsconfig.json"
"types:plugin": "bun run --cwd packages/docusaurus-plugin tsc -p tsconfig.json",
"types:json-schema-renderer": "bun run --cwd packages/json-schema-renderer tsc -p tsconfig.json"
},
"devDependencies": {
"@eslint/js": "^9.39.1",
Expand All @@ -40,5 +43,11 @@
"globals": "^16.5.0",
"typescript": "5.9"
},
"dependencies": {}
}
"dependencies": {
"@json-schema-tools/traverse": "^1.11.0",
"@open-rpc/examples": "^1.0.1",
"remark-gfm": "^4.0.0",
"remark-stringify": "^11.0.0",
"unified": "^11.0.5"
}
}
22 changes: 22 additions & 0 deletions packages/json-schema-renderer/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bun
import { $ } from "bun";

const baseConfig = {
entrypoints: ["./src/index.ts"],
};

await Bun.build({
...baseConfig,
outdir: "./dist",
target: "node",
format: "esm",
sourcemap: "external",
});

await Bun.build({
...baseConfig,
outdir: "./dist/browser",
target: "browser",
format: "esm",
sourcemap: "external",
});
39 changes: 39 additions & 0 deletions packages/json-schema-renderer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@open-rpc/json-schema-renderer",
"module": "src/index.ts",
"version": "0.0.0-development",
"private": false,
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"browser": "./dist/browser/index.js",
"import": "./dist/index.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "bun run build.ts",
"test": "bun test",
"types": "tsc -p tsconfig.json",
"lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"dependencies": {
"@json-schema-tools/traverse": "^1.11.0",
"@types/mdast": "^4.0.4"
},
"devDependencies": {
"@types/bun": "^1.3.1",
"@types/node": "22.13.5",
"typescript": "5.9",
"@typescript-eslint/eslint-plugin": "^8.46.2",
"eslint": "^9.38.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-prettier": "^5.5.4"
}
}
162 changes: 162 additions & 0 deletions packages/json-schema-renderer/src/array.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import type { BlockContent, Content, ListItem, PhrasingContent } from "mdast";

import { buildConstraintSection } from "./base";
import type {
JsonSchema,
SchemaRenderContext,
SchemaRendererHelpers,
TypeRenderResult,
} from "./types";
import {
listItem,
listItemFromText,
paragraphFromPhrasing,
paragraphFromText,
text,
unorderedList,
} from "./utils";

function pickFirstItemSchema(items: JsonSchema | JsonSchema[] | undefined) {
if (!items) {
return undefined;
}

return Array.isArray(items) ? items[0] : items;
}

export function renderArray(
schema: JsonSchema,
context: SchemaRenderContext,
path: string,
helpers: SchemaRendererHelpers,
): TypeRenderResult {
const firstItemSchema = pickFirstItemSchema(schema.items);
const summarySuffix: PhrasingContent[] = [];

if (firstItemSchema?.type) {
summarySuffix.push(text(` of ${formatTypeLabel(firstItemSchema.type)}`));
}

const blocks: Content[] = [];

if (firstItemSchema) {
const itemPath = Array.isArray(schema.items)
? `${path}.items[0]`
: `${path}.items`;
const renderedItem = helpers.getResult(itemPath);
const renderedContext = helpers.getContext(itemPath);

if (renderedItem?.inline.length) {
blocks.push(
paragraphFromPhrasing([
text("Items: "),
...(renderedContext
? removeLeadingName(renderedItem.inline, renderedContext.name)
: renderedItem.inline),
]),
);
}

if (renderedItem?.blocks.length) {
blocks.push(...renderedItem.blocks);
}
}

const constraintItems = gatherArrayConstraintItems(schema);
if (constraintItems.length > 0) {
blocks.push(
...buildConstraintSection("Array Constraints", constraintItems),
);
}

return {
summarySuffix,
blocks,
};
}

function formatTypeLabel(type: JsonSchema["type"]): string {
if (!type) {
return "unknown";
}

if (Array.isArray(type)) {
return type.join(" | ");
}

return type;
}

function gatherArrayConstraintItems(schema: JsonSchema): ListItem[] {
const items: ListItem[] = [];

if (typeof schema.minItems === "number") {
items.push(
listItemFromText(
`Must contain at least ${schema.minItems} item${
schema.minItems === 1 ? "" : "s"
}.`,
),
);
}

if (typeof schema.maxItems === "number") {
items.push(
listItemFromText(
`Must not contain more than ${schema.maxItems} item${
schema.maxItems === 1 ? "" : "s"
}.`,
),
);
}

if (schema.uniqueItems) {
items.push(listItemFromText("All items must be unique."));
}

if (schema.contains) {
const requirements: BlockContent[] = [
paragraphFromText("Must contain items matching the defined schema."),
];

const detailConstraints: ListItem[] = [];
if (typeof schema.minContains === "number") {
detailConstraints.push(
listItemFromText(
`At least ${schema.minContains} matching item${schema.minContains === 1 ? "" : "s"}.`,
),
);
}
if (typeof schema.maxContains === "number") {
detailConstraints.push(
listItemFromText(
`At most ${schema.maxContains} matching item${schema.maxContains === 1 ? "" : "s"}.`,
),
);
}

if (detailConstraints.length > 0) {
requirements.push(unorderedList(detailConstraints));
}

items.push(listItem(requirements));
}

return items;
}

function removeLeadingName(
inlineChildren: PhrasingContent[],
name: string,
): PhrasingContent[] {
if (!inlineChildren.length) {
return inlineChildren;
}

const [first, ...rest] = inlineChildren;
if (first && first.type === "inlineCode" && first.value === name) {
return rest;
}

return inlineChildren;
}
Loading