Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use JSON Schema to define and document MermaidConfig #4112

Merged
Merged
4 changes: 4 additions & 0 deletions .eslintrc.cjs
Expand Up @@ -38,6 +38,10 @@ module.exports = {
'lodash',
'unicorn',
],
ignorePatterns: [
// this file is automatically generated by `pnpm run --filter mermaid types:build-config`
'packages/mermaid/src/config.type.ts',
],
rules: {
curly: 'error',
'no-console': 'error',
Expand Down
13 changes: 13 additions & 0 deletions .github/workflows/lint.yml
Expand Up @@ -53,6 +53,19 @@ jobs:
exit 1
fi

- name: Verify `./src/config.type.ts` is in sync with `./src/schemas/config.schema.yaml`
shell: bash
run: |
if ! pnpm run --filter mermaid types:verify-config; then
ERROR_MESSAGE='Running `pnpm run --filter mermaid types:verify-config` failed.'
ERROR_MESSAGE+=' This should be fixed by running'
ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`'
ERROR_MESSAGE+=' on your local machine.'
echo "::error title=Lint failure::${ERROR_MESSAGE}"
# make sure to return an error exitcode so that GitHub actions shows a red-cross
exit 1
fi

- name: Verify Docs
id: verifyDocs
working-directory: ./packages/mermaid
Expand Down
2 changes: 2 additions & 0 deletions .prettierignore
Expand Up @@ -7,3 +7,5 @@ pnpm-lock.yaml
stats
packages/mermaid/src/docs/.vitepress/components.d.ts
.nyc_output
# Autogenerated by `pnpm run --filter mermaid types:build-config`
packages/mermaid/src/config.type.ts
2 changes: 2 additions & 0 deletions .vite/build.ts
Expand Up @@ -2,6 +2,7 @@ import { build, InlineConfig, type PluginOption } from 'vite';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import jisonPlugin from './jisonPlugin.js';
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
import { readFileSync } from 'fs';
import typescript from '@rollup/plugin-typescript';
import { visualizer } from 'rollup-plugin-visualizer';
Expand Down Expand Up @@ -121,6 +122,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
},
plugins: [
jisonPlugin(),
jsonSchemaPlugin(), // handles `.schema.yaml` files
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
typescript({ compilerOptions: { declaration: false } }),
istanbul({
Expand Down
150 changes: 150 additions & 0 deletions .vite/jsonSchemaPlugin.ts
@@ -0,0 +1,150 @@
import { load, JSON_SCHEMA } from 'js-yaml';
import assert from 'node:assert';
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
import { PluginOption } from 'vite';

import type { MermaidConfig, BaseDiagramConfig } from '../packages/mermaid/src/config.type.js';

/**
* All of the keys in the mermaid config that have a mermaid diagram config.
*/
const MERMAID_CONFIG_DIAGRAM_KEYS = [
'flowchart',
'sequence',
'gantt',
'journey',
'class',
'state',
'er',
'pie',
'quadrantChart',
'requirement',
'mindmap',
'timeline',
'gitGraph',
'c4',
'sankey',
] as const;

/**
* Generate default values from the JSON Schema.
*
* AJV does not support nested default values yet (or default values with $ref),
* so we need to manually find them (this may be fixed in ajv v9).
*
* @param mermaidConfigSchema - The Mermaid JSON Schema to use.
* @returns The default mermaid config object.
*/
function generateDefaults(mermaidConfigSchema: JSONSchemaType<MermaidConfig>) {
const ajv = new Ajv2019({
useDefaults: true,
allowUnionTypes: true,
strict: true,
});

ajv.addKeyword({
keyword: 'meta:enum', // used by jsonschema2md
errors: false,
});
ajv.addKeyword({
keyword: 'tsType', // used by json-schema-to-typescript
errors: false,
});

// ajv currently doesn't support nested default values, see https://github.com/ajv-validator/ajv/issues/1718
// (may be fixed in v9) so we need to manually use sub-schemas
const mermaidDefaultConfig = {};

assert.ok(mermaidConfigSchema.$defs);
const baseDiagramConfig = mermaidConfigSchema.$defs.BaseDiagramConfig;

for (const key of MERMAID_CONFIG_DIAGRAM_KEYS) {
const subSchemaRef = mermaidConfigSchema.properties[key].$ref;
const [root, defs, defName] = subSchemaRef.split('/');
assert.strictEqual(root, '#');
assert.strictEqual(defs, '$defs');
const subSchema = {
$schema: mermaidConfigSchema.$schema,
$defs: mermaidConfigSchema.$defs,
...mermaidConfigSchema.$defs[defName],
} as JSONSchemaType<BaseDiagramConfig>;

const validate = ajv.compile(subSchema);

mermaidDefaultConfig[key] = {};

for (const required of subSchema.required ?? []) {
if (subSchema.properties[required] === undefined && baseDiagramConfig.properties[required]) {
mermaidDefaultConfig[key][required] = baseDiagramConfig.properties[required].default;
}
}
if (!validate(mermaidDefaultConfig[key])) {
throw new Error(
`schema for subconfig ${key} does not have valid defaults! Errors were ${JSON.stringify(
validate.errors,
undefined,
2
)}`
);
}
}

const validate = ajv.compile(mermaidConfigSchema);

if (!validate(mermaidDefaultConfig)) {
throw new Error(
`Mermaid config JSON Schema does not have valid defaults! Errors were ${JSON.stringify(
validate.errors,
undefined,
2
)}`
);
}

return mermaidDefaultConfig;
}

/**
* Vite plugin that handles JSON Schemas saved as a `.schema.yaml` file.
*
* Use `my-example.schema.yaml?only-defaults=true` to only load the default values.
*/
export default function jsonSchemaPlugin(): PluginOption {
return {
name: 'json-schema-plugin',
transform(src: string, id: string) {
const idAsUrl = new URL(id, 'file:///');

if (!idAsUrl.pathname.endsWith('schema.yaml')) {
return;
}

if (idAsUrl.searchParams.get('only-defaults')) {
const jsonSchema = load(src, {
filename: idAsUrl.pathname,
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
schema: JSON_SCHEMA,
}) as JSONSchemaType<MermaidConfig>;
return {
code: `export default ${JSON.stringify(generateDefaults(jsonSchema), undefined, 2)};`,
map: null, // no source map
};
} else {
return {
code: `export default ${JSON.stringify(
load(src, {
filename: idAsUrl.pathname,
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
schema: JSON_SCHEMA,
}),
undefined,
2
)};`,
map: null, // provide source map if available
};
}
},
};
}
35 changes: 6 additions & 29 deletions docs/config/setup/modules/defaultConfig.md
Expand Up @@ -14,43 +14,20 @@

#### Defined in

[defaultConfig.ts:2300](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2300)
[defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266)

---

### default

• `Const` **default**: `Partial`<`MermaidConfig`>

**Configuration methods in Mermaid version 8.6.0 have been updated, to learn more\[[click
here](8.6.0_docs.md)].**
Default mermaid configuration options.

## **What follows are config instructions for older versions**

These are the default options which can be overridden with the initialization call like so:

**Example 1:**

```js
mermaid.initialize({ flowchart: { htmlLabels: false } });
```

**Example 2:**

```html
<script>
const config = {
startOnLoad: true,
flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' },
securityLevel: 'loose',
};
mermaid.initialize(config);
</script>
```

A summary of all options and their defaults is found [here](#mermaidapi-configuration-defaults).
A description of each option follows below.
Please see the Mermaid config JSON Schema for the default JSON values.
Non-JSON JS default values are listed in this file, e.g. functions, or
`undefined` (explicitly set so that `configKeys` finds them).

#### Defined in

[defaultConfig.ts:33](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L33)
[defaultConfig.ts:16](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L16)
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -81,6 +81,7 @@
"@vitest/coverage-v8": "^0.32.2",
"@vitest/spy": "^0.32.2",
"@vitest/ui": "^0.32.2",
"ajv": "^8.12.0",
sidharthv96 marked this conversation as resolved.
Show resolved Hide resolved
"concurrently": "^8.0.1",
"cors": "^2.8.5",
"coveralls": "^3.1.1",
Expand Down
1 change: 1 addition & 0 deletions packages/mermaid/.lintstagedrc.mjs
Expand Up @@ -4,4 +4,5 @@ export default {
'src/docs/**': ['pnpm --filter mermaid run docs:build --git'],
'src/docs.mts': ['pnpm --filter mermaid run docs:build --git'],
'src/(defaultConfig|config|mermaidAPI).ts': ['pnpm --filter mermaid run docs:build --git'],
'src/schemas/config.schema.yaml': ['pnpm --filter mermaid run types:build-config --git'],
};
6 changes: 6 additions & 0 deletions packages/mermaid/package.json
Expand Up @@ -32,6 +32,8 @@
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
"types:build-config": "ts-node-esm --transpileOnly scripts/create-types-from-json-schema.mts",
"types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
"release": "pnpm build",
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
},
Expand Down Expand Up @@ -74,6 +76,7 @@
"web-worker": "^1.2.0"
},
"devDependencies": {
"@adobe/jsonschema2md": "^7.1.4",
"@types/cytoscape": "^3.19.9",
"@types/d3": "^7.4.0",
"@types/d3-sankey": "^0.12.1",
Expand All @@ -87,6 +90,7 @@
"@types/uuid": "^9.0.1",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
"ajv": "^8.11.2",
"chokidar": "^3.5.3",
"concurrently": "^8.0.1",
"coveralls": "^3.1.1",
Expand All @@ -97,6 +101,7 @@
"jison": "^0.4.18",
"js-base64": "^3.7.5",
"jsdom": "^22.0.0",
"json-schema-to-typescript": "^11.0.3",
"micromatch": "^4.0.5",
"path-browserify": "^1.0.1",
"prettier": "^2.8.8",
Expand All @@ -109,6 +114,7 @@
"typedoc-plugin-markdown": "^3.15.2",
"typescript": "^5.0.4",
"unist-util-flatmap": "^1.0.0",
"unist-util-visit": "^4.1.2",
"vitepress": "^1.0.0-alpha.72",
"vitepress-plugin-search": "^1.0.4-alpha.20"
},
Expand Down