Merge branch 'next' into chore/5125-fix-duplicate-config-types
This commit is contained in:
commit
d732a146c2
|
@ -0,0 +1,30 @@
|
|||
/**
|
||||
* Shared common options for both ESBuild and Vite
|
||||
*/
|
||||
export const packageOptions = {
|
||||
parser: {
|
||||
name: 'mermaid-parser',
|
||||
packageName: 'parser',
|
||||
file: 'index.ts',
|
||||
},
|
||||
mermaid: {
|
||||
name: 'mermaid',
|
||||
packageName: 'mermaid',
|
||||
file: 'mermaid.ts',
|
||||
},
|
||||
'mermaid-example-diagram': {
|
||||
name: 'mermaid-example-diagram',
|
||||
packageName: 'mermaid-example-diagram',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
'mermaid-zenuml': {
|
||||
name: 'mermaid-zenuml',
|
||||
packageName: 'mermaid-zenuml',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
'mermaid-flowchart-elk': {
|
||||
name: 'mermaid-flowchart-elk',
|
||||
packageName: 'mermaid-flowchart-elk',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
} as const;
|
|
@ -0,0 +1,5 @@
|
|||
import { generate } from 'langium-cli';
|
||||
|
||||
export async function generateLangium() {
|
||||
await generate({ file: `./packages/parser/langium-config.json` });
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
import { load, JSON_SCHEMA } from 'js-yaml';
|
||||
import assert from 'node:assert';
|
||||
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
|
||||
|
||||
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',
|
||||
'xyChart',
|
||||
'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;
|
||||
}
|
||||
|
||||
export const loadSchema = (src: string, filename: string): JSONSchemaType<MermaidConfig> => {
|
||||
const jsonSchema = load(src, {
|
||||
filename,
|
||||
// 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 jsonSchema;
|
||||
};
|
||||
|
||||
export const getDefaults = (schema: JSONSchemaType<MermaidConfig>) => {
|
||||
return `export default ${JSON.stringify(generateDefaults(schema), undefined, 2)};`;
|
||||
};
|
||||
|
||||
export const getSchema = (schema: JSONSchemaType<MermaidConfig>) => {
|
||||
return `export default ${JSON.stringify(schema, undefined, 2)};`;
|
||||
};
|
|
@ -0,0 +1,9 @@
|
|||
declare module 'langium-cli' {
|
||||
export interface GenerateOptions {
|
||||
file?: string;
|
||||
mode?: 'development' | 'production';
|
||||
watch?: boolean;
|
||||
}
|
||||
|
||||
export function generate(options: GenerateOptions): Promise<boolean>;
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import { packageOptions } from './common.js';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
const buildType = (packageName: string) => {
|
||||
console.log(`Building types for ${packageName}`);
|
||||
try {
|
||||
const out = execSync(`tsc -p ./packages/${packageName}/tsconfig.json --emitDeclarationOnly`);
|
||||
out.length > 0 && console.log(out.toString());
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
e.stdout.length > 0 && console.error(e.stdout.toString());
|
||||
e.stderr.length > 0 && console.error(e.stderr.toString());
|
||||
}
|
||||
};
|
||||
|
||||
for (const { packageName } of Object.values(packageOptions)) {
|
||||
buildType(packageName);
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
import { build } from 'esbuild';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { MermaidBuildOptions, defaultOptions, getBuildConfig } from './util.js';
|
||||
import { packageOptions } from '../.build/common.js';
|
||||
import { generateLangium } from '../.build/generateLangium.js';
|
||||
|
||||
const shouldVisualize = process.argv.includes('--visualize');
|
||||
|
||||
const buildPackage = async (entryName: keyof typeof packageOptions) => {
|
||||
const commonOptions = { ...defaultOptions, entryName } as const;
|
||||
const buildConfigs = [
|
||||
// package.mjs
|
||||
{ ...commonOptions },
|
||||
// package.min.mjs
|
||||
{
|
||||
...commonOptions,
|
||||
minify: true,
|
||||
metafile: shouldVisualize,
|
||||
},
|
||||
// package.core.mjs
|
||||
{ ...commonOptions, core: true },
|
||||
];
|
||||
|
||||
if (entryName === 'mermaid') {
|
||||
const iifeOptions: MermaidBuildOptions = { ...commonOptions, format: 'iife' };
|
||||
buildConfigs.push(
|
||||
// mermaid.js
|
||||
{ ...iifeOptions },
|
||||
// mermaid.min.js
|
||||
{ ...iifeOptions, minify: true, metafile: shouldVisualize }
|
||||
);
|
||||
}
|
||||
|
||||
const results = await Promise.all(buildConfigs.map((option) => build(getBuildConfig(option))));
|
||||
|
||||
if (shouldVisualize) {
|
||||
for (const { metafile } of results) {
|
||||
if (!metafile) {
|
||||
continue;
|
||||
}
|
||||
const fileName = Object.keys(metafile.outputs)
|
||||
.filter((file) => !file.includes('chunks') && file.endsWith('js'))[0]
|
||||
.replace('dist/', '');
|
||||
// Upload metafile into https://esbuild.github.io/analyze/
|
||||
await writeFile(`stats/${fileName}.meta.json`, JSON.stringify(metafile));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handler = (e) => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
await generateLangium();
|
||||
await mkdir('stats').catch(() => {});
|
||||
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
|
||||
// it should build `parser` before `mermaid` because it's a dependecy
|
||||
for (const pkg of packageNames) {
|
||||
await buildPackage(pkg).catch(handler);
|
||||
}
|
||||
};
|
||||
|
||||
void main();
|
|
@ -0,0 +1,15 @@
|
|||
import { readFile } from 'node:fs/promises';
|
||||
import { transformJison } from '../.build/jisonTransformer.js';
|
||||
import { Plugin } from 'esbuild';
|
||||
|
||||
export const jisonPlugin: Plugin = {
|
||||
name: 'jison',
|
||||
setup(build) {
|
||||
build.onLoad({ filter: /\.jison$/ }, async (args) => {
|
||||
// Load the file from the file system
|
||||
const source = await readFile(args.path, 'utf8');
|
||||
const contents = transformJison(source);
|
||||
return { contents, warnings: [] };
|
||||
});
|
||||
},
|
||||
};
|
|
@ -0,0 +1,35 @@
|
|||
import type { JSONSchemaType } from 'ajv/dist/2019.js';
|
||||
import type { MermaidConfig } from '../packages/mermaid/src/config.type.js';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { getDefaults, getSchema, loadSchema } from '../.build/jsonSchema.js';
|
||||
|
||||
/**
|
||||
* ESBuild 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 const jsonSchemaPlugin = {
|
||||
name: 'json-schema-plugin',
|
||||
setup(build) {
|
||||
let schema: JSONSchemaType<MermaidConfig> | undefined = undefined;
|
||||
let content = '';
|
||||
|
||||
build.onLoad({ filter: /config\.schema\.yaml$/ }, async (args) => {
|
||||
// Load the file from the file system
|
||||
const source = await readFile(args.path, 'utf8');
|
||||
const resolvedSchema: JSONSchemaType<MermaidConfig> =
|
||||
content === source && schema ? schema : loadSchema(source, args.path);
|
||||
if (content !== source) {
|
||||
content = source;
|
||||
schema = resolvedSchema;
|
||||
}
|
||||
const contents = args.suffix.includes('only-defaults')
|
||||
? getDefaults(resolvedSchema)
|
||||
: getSchema(resolvedSchema);
|
||||
return { contents, warnings: [] };
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
export default jsonSchemaPlugin;
|
|
@ -0,0 +1,102 @@
|
|||
import express from 'express';
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import { getBuildConfig, defaultOptions } from './util.js';
|
||||
import { context } from 'esbuild';
|
||||
import chokidar from 'chokidar';
|
||||
import { generateLangium } from '../.build/generateLangium.js';
|
||||
import { packageOptions } from '../.build/common.js';
|
||||
|
||||
const configs = Object.values(packageOptions).map(({ packageName }) =>
|
||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: packageName })
|
||||
);
|
||||
const mermaidIIFEConfig = getBuildConfig({
|
||||
...defaultOptions,
|
||||
minify: false,
|
||||
core: false,
|
||||
entryName: 'mermaid',
|
||||
format: 'iife',
|
||||
});
|
||||
configs.push(mermaidIIFEConfig);
|
||||
|
||||
const contexts = await Promise.all(configs.map((config) => context(config)));
|
||||
|
||||
const rebuildAll = async () => {
|
||||
console.time('Rebuild time');
|
||||
await Promise.all(contexts.map((ctx) => ctx.rebuild())).catch((e) => console.error(e));
|
||||
console.timeEnd('Rebuild time');
|
||||
};
|
||||
|
||||
let clients: { id: number; response: Response }[] = [];
|
||||
function eventsHandler(request: Request, response: Response, next: NextFunction) {
|
||||
const headers = {
|
||||
'Content-Type': 'text/event-stream',
|
||||
Connection: 'keep-alive',
|
||||
'Cache-Control': 'no-cache',
|
||||
};
|
||||
response.writeHead(200, headers);
|
||||
const clientId = Date.now();
|
||||
clients.push({
|
||||
id: clientId,
|
||||
response,
|
||||
});
|
||||
request.on('close', () => {
|
||||
clients = clients.filter((client) => client.id !== clientId);
|
||||
});
|
||||
}
|
||||
|
||||
let timeoutId: NodeJS.Timeout | undefined = undefined;
|
||||
|
||||
/**
|
||||
* Debounce file change events to avoid rebuilding multiple times.
|
||||
*/
|
||||
function handleFileChange() {
|
||||
if (timeoutId !== undefined) {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
timeoutId = setTimeout(async () => {
|
||||
await rebuildAll();
|
||||
sendEventsToAll();
|
||||
timeoutId = undefined;
|
||||
}, 100);
|
||||
}
|
||||
|
||||
function sendEventsToAll() {
|
||||
clients.forEach(({ response }) => response.write(`data: ${Date.now()}\n\n`));
|
||||
}
|
||||
|
||||
async function createServer() {
|
||||
await generateLangium();
|
||||
handleFileChange();
|
||||
const app = express();
|
||||
chokidar
|
||||
.watch('**/src/**/*.{js,ts,langium,yaml,json}', {
|
||||
ignoreInitial: true,
|
||||
ignored: [/node_modules/, /dist/, /docs/, /coverage/],
|
||||
})
|
||||
.on('all', async (event, path) => {
|
||||
// Ignore other events.
|
||||
if (!['add', 'change'].includes(event)) {
|
||||
return;
|
||||
}
|
||||
if (/\.langium$/.test(path)) {
|
||||
await generateLangium();
|
||||
}
|
||||
console.log(`${path} changed. Rebuilding...`);
|
||||
handleFileChange();
|
||||
});
|
||||
|
||||
app.use(cors());
|
||||
app.get('/events', eventsHandler);
|
||||
for (const { packageName } of Object.values(packageOptions)) {
|
||||
app.use(express.static(`./packages/${packageName}/dist`));
|
||||
}
|
||||
app.use(express.static('demos'));
|
||||
app.use(express.static('cypress/platform'));
|
||||
|
||||
app.listen(9000, () => {
|
||||
console.log(`Listening on http://localhost:9000`);
|
||||
});
|
||||
}
|
||||
|
||||
createServer();
|
|
@ -0,0 +1,101 @@
|
|||
import { resolve } from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import type { BuildOptions } from 'esbuild';
|
||||
import { readFileSync } from 'fs';
|
||||
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
|
||||
import { packageOptions } from '../.build/common.js';
|
||||
import { jisonPlugin } from './jisonPlugin.js';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
export interface MermaidBuildOptions {
|
||||
minify: boolean;
|
||||
core: boolean;
|
||||
metafile: boolean;
|
||||
format: 'esm' | 'iife';
|
||||
entryName: keyof typeof packageOptions;
|
||||
}
|
||||
|
||||
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName'> = {
|
||||
minify: false,
|
||||
metafile: false,
|
||||
core: false,
|
||||
format: 'esm',
|
||||
} as const;
|
||||
|
||||
const buildOptions = (override: BuildOptions): BuildOptions => {
|
||||
return {
|
||||
bundle: true,
|
||||
minify: true,
|
||||
keepNames: true,
|
||||
platform: 'browser',
|
||||
tsconfig: 'tsconfig.json',
|
||||
resolveExtensions: ['.ts', '.js', '.json', '.jison', '.yaml'],
|
||||
external: ['require', 'fs', 'path'],
|
||||
outdir: 'dist',
|
||||
plugins: [jisonPlugin, jsonSchemaPlugin],
|
||||
sourcemap: 'external',
|
||||
...override,
|
||||
};
|
||||
};
|
||||
|
||||
const getFileName = (fileName: string, { core, format, minify }: MermaidBuildOptions) => {
|
||||
if (core) {
|
||||
fileName += '.core';
|
||||
} else if (format === 'esm') {
|
||||
fileName += '.esm';
|
||||
}
|
||||
if (minify) {
|
||||
fileName += '.min';
|
||||
}
|
||||
return fileName;
|
||||
};
|
||||
|
||||
export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
|
||||
const { core, entryName, metafile, format, minify } = options;
|
||||
const external: string[] = ['require', 'fs', 'path'];
|
||||
const { name, file, packageName } = packageOptions[entryName];
|
||||
const outFileName = getFileName(name, options);
|
||||
let output: BuildOptions = buildOptions({
|
||||
absWorkingDir: resolve(__dirname, `../packages/${packageName}`),
|
||||
entryPoints: {
|
||||
[outFileName]: `src/${file}`,
|
||||
},
|
||||
metafile,
|
||||
minify,
|
||||
logLevel: 'info',
|
||||
chunkNames: `chunks/${outFileName}/[name]-[hash]`,
|
||||
define: {
|
||||
'import.meta.vitest': 'undefined',
|
||||
},
|
||||
});
|
||||
|
||||
if (core) {
|
||||
const { dependencies } = JSON.parse(
|
||||
readFileSync(resolve(__dirname, `../packages/${packageName}/package.json`), 'utf-8')
|
||||
);
|
||||
// Core build is used to generate file without bundled dependencies.
|
||||
// This is used by downstream projects to bundle dependencies themselves.
|
||||
// Ignore dependencies and any dependencies of dependencies
|
||||
external.push(...Object.keys(dependencies));
|
||||
output.external = external;
|
||||
}
|
||||
|
||||
if (format === 'iife') {
|
||||
output.format = 'iife';
|
||||
output.splitting = false;
|
||||
output.globalName = '__esbuild_esm_mermaid';
|
||||
// Workaround for removing the .default access in esbuild IIFE.
|
||||
// https://github.com/mermaid-js/mermaid/pull/4109#discussion_r1292317396
|
||||
output.footer = {
|
||||
js: 'globalThis.mermaid = globalThis.__esbuild_esm_mermaid.default;',
|
||||
};
|
||||
output.outExtension = { '.js': '.js' };
|
||||
} else {
|
||||
output.format = 'esm';
|
||||
output.splitting = true;
|
||||
output.outExtension = { '.js': '.mjs' };
|
||||
}
|
||||
|
||||
return output;
|
||||
};
|
|
@ -6,3 +6,6 @@ cypress/plugins/index.js
|
|||
coverage
|
||||
*.json
|
||||
node_modules
|
||||
|
||||
# autogenereated by langium-cli
|
||||
generated/
|
||||
|
|
|
@ -15,3 +15,4 @@ coverage:
|
|||
# Turing off for now as code coverage isn't stable and causes unnecessary build failures.
|
||||
# default:
|
||||
# threshold: 2%
|
||||
patch: off
|
||||
|
|
|
@ -46,3 +46,7 @@ stats/
|
|||
|
||||
demos/dev/**
|
||||
!/demos/dev/example.html
|
||||
!/demos/dev/reload.js
|
||||
|
||||
# autogenereated by langium-cli
|
||||
generated/
|
||||
|
|
|
@ -10,6 +10,8 @@ stats
|
|||
.nyc_output
|
||||
# Autogenerated by `pnpm run --filter mermaid types:build-config`
|
||||
packages/mermaid/src/config.type.ts
|
||||
# autogenereated by langium-cli
|
||||
generated/
|
||||
# Ignore the files creates in /demos/dev except for example.html
|
||||
demos/dev/**
|
||||
!/demos/dev/example.html
|
||||
!/demos/dev/example.html
|
||||
|
|
|
@ -3,11 +3,12 @@ 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';
|
||||
import type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template-types.js';
|
||||
import istanbul from 'vite-plugin-istanbul';
|
||||
import { packageOptions } from '../.build/common.js';
|
||||
import { generateLangium } from '../.build/generateLangium.js';
|
||||
|
||||
const visualize = process.argv.includes('--visualize');
|
||||
const watch = process.argv.includes('--watch');
|
||||
|
@ -36,24 +37,6 @@ const visualizerOptions = (packageName: string, core = false): PluginOption[] =>
|
|||
);
|
||||
};
|
||||
|
||||
const packageOptions = {
|
||||
mermaid: {
|
||||
name: 'mermaid',
|
||||
packageName: 'mermaid',
|
||||
file: 'mermaid.ts',
|
||||
},
|
||||
'mermaid-example-diagram': {
|
||||
name: 'mermaid-example-diagram',
|
||||
packageName: 'mermaid-example-diagram',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
'mermaid-zenuml': {
|
||||
name: 'mermaid-zenuml',
|
||||
packageName: 'mermaid-zenuml',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
};
|
||||
|
||||
interface BuildOptions {
|
||||
minify: boolean | 'esbuild';
|
||||
core?: boolean;
|
||||
|
@ -72,34 +55,8 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
|||
sourcemap,
|
||||
entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`,
|
||||
},
|
||||
{
|
||||
name,
|
||||
format: 'umd',
|
||||
sourcemap,
|
||||
entryFileNames: `${name}${minify ? '.min' : ''}.js`,
|
||||
},
|
||||
];
|
||||
|
||||
if (core) {
|
||||
const { dependencies } = JSON.parse(
|
||||
readFileSync(resolve(__dirname, `../packages/${packageName}/package.json`), 'utf-8')
|
||||
);
|
||||
// Core build is used to generate file without bundled dependencies.
|
||||
// This is used by downstream projects to bundle dependencies themselves.
|
||||
// Ignore dependencies and any dependencies of dependencies
|
||||
// Adapted from the RegEx used by `rollup-plugin-node`
|
||||
external.push(new RegExp('^(?:' + Object.keys(dependencies).join('|') + ')(?:/.+)?$'));
|
||||
// This needs to be an array. Otherwise vite will build esm & umd with same name and overwrite esm with umd.
|
||||
output = [
|
||||
{
|
||||
name,
|
||||
format: 'esm',
|
||||
sourcemap,
|
||||
entryFileNames: `${name}.core.mjs`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
const config: InlineConfig = {
|
||||
configFile: false,
|
||||
build: {
|
||||
|
@ -129,7 +86,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
|||
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
|
||||
typescript({ compilerOptions: { declaration: false } }),
|
||||
istanbul({
|
||||
exclude: ['node_modules', 'test/', '__mocks__'],
|
||||
exclude: ['node_modules', 'test/', '__mocks__', 'generated'],
|
||||
extension: ['.js', '.ts'],
|
||||
requireEnv: true,
|
||||
forceBuildInstrument: coverage,
|
||||
|
@ -149,24 +106,28 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
|||
|
||||
const buildPackage = async (entryName: keyof typeof packageOptions) => {
|
||||
await build(getBuildConfig({ minify: false, entryName }));
|
||||
await build(getBuildConfig({ minify: 'esbuild', entryName }));
|
||||
await build(getBuildConfig({ minify: false, core: true, entryName }));
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
|
||||
for (const pkg of packageNames.filter((pkg) => !mermaidOnly || pkg === 'mermaid')) {
|
||||
for (const pkg of packageNames.filter(
|
||||
(pkg) => !mermaidOnly || pkg === 'mermaid' || pkg === 'parser'
|
||||
)) {
|
||||
await buildPackage(pkg);
|
||||
}
|
||||
};
|
||||
|
||||
await generateLangium();
|
||||
|
||||
if (watch) {
|
||||
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' }));
|
||||
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
|
||||
if (!mermaidOnly) {
|
||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
|
||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
|
||||
}
|
||||
} else if (visualize) {
|
||||
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' }));
|
||||
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));
|
||||
await build(getBuildConfig({ minify: false, core: false, entryName: 'mermaid' }));
|
||||
} else {
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import { transformJison } from './jisonTransformer.js';
|
||||
import { transformJison } from '../.build/jisonTransformer.js';
|
||||
|
||||
const fileRegex = /\.(jison)$/;
|
||||
|
||||
export default function jison() {
|
||||
return {
|
||||
name: 'jison',
|
||||
|
||||
transform(src: string, id: string) {
|
||||
if (fileRegex.test(id)) {
|
||||
return {
|
||||
|
|
|
@ -1,109 +1,5 @@
|
|||
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',
|
||||
'xyChart',
|
||||
'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;
|
||||
}
|
||||
import { getDefaults, getSchema, loadSchema } from '../.build/jsonSchema.js';
|
||||
|
||||
/**
|
||||
* Vite plugin that handles JSON Schemas saved as a `.schema.yaml` file.
|
||||
|
@ -120,32 +16,13 @@ export default function jsonSchemaPlugin(): PluginOption {
|
|||
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
|
||||
};
|
||||
}
|
||||
const jsonSchema = loadSchema(src, idAsUrl.pathname);
|
||||
return {
|
||||
code: idAsUrl.searchParams.get('only-defaults')
|
||||
? getDefaults(jsonSchema)
|
||||
: getSchema(jsonSchema),
|
||||
map: null, // no source map
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import { createServer as createViteServer } from 'vite';
|
||||
import { packageOptions } from '../.build/common.js';
|
||||
|
||||
async function createServer() {
|
||||
const app = express();
|
||||
|
@ -14,9 +15,9 @@ async function createServer() {
|
|||
});
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.static('./packages/mermaid/dist'));
|
||||
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||
for (const { packageName } of Object.values(packageOptions)) {
|
||||
app.use(express.static(`./packages/${packageName}/dist`));
|
||||
}
|
||||
app.use(vite.middlewares);
|
||||
app.use(express.static('demos'));
|
||||
app.use(express.static('cypress/platform'));
|
||||
|
|
|
@ -61,6 +61,7 @@
|
|||
"gzipped",
|
||||
"huynh",
|
||||
"huynhicode",
|
||||
"iife",
|
||||
"inkdrop",
|
||||
"jaoude",
|
||||
"jgreywolf",
|
||||
|
@ -74,6 +75,7 @@
|
|||
"knut",
|
||||
"knutsveidqvist",
|
||||
"laganeckas",
|
||||
"langium",
|
||||
"linetype",
|
||||
"lintstagedrc",
|
||||
"logmsg",
|
||||
|
@ -85,6 +87,7 @@
|
|||
"mdbook",
|
||||
"mermaidjs",
|
||||
"mermerd",
|
||||
"metafile",
|
||||
"mindaugas",
|
||||
"mindmap",
|
||||
"mindmaps",
|
||||
|
@ -98,6 +101,7 @@
|
|||
"nirname",
|
||||
"npmjs",
|
||||
"orlandoni",
|
||||
"outdir",
|
||||
"pathe",
|
||||
"pbrolin",
|
||||
"phpbb",
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.ts';
|
||||
|
||||
describe('Flowchart elk', () => {
|
||||
it('should use dagre as fallback', () => {
|
||||
urlSnapshotTest('http://localhost:9000/flow-elk.html', {
|
||||
name: 'flow-elk fallback to dagre',
|
||||
});
|
||||
});
|
||||
it('should allow overriding with external package', () => {
|
||||
urlSnapshotTest('http://localhost:9000/flow-elk.html?elk=true', {
|
||||
name: 'flow-elk overriding dagre with elk',
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
describe('IIFE', () => {
|
||||
beforeEach(() => {
|
||||
cy.visit('http://localhost:9000/iife.html');
|
||||
});
|
||||
|
||||
it('should render when using mermaid.min.js', () => {
|
||||
cy.window().should('have.property', 'rendered', true);
|
||||
cy.get('svg').should('be.visible');
|
||||
cy.get('#d2').should('contain', 'Hello');
|
||||
});
|
||||
});
|
|
@ -1,16 +0,0 @@
|
|||
describe('Sequencediagram', () => {
|
||||
it('should render a simple sequence diagrams', () => {
|
||||
const url = 'http://localhost:9000/webpackUsage.html';
|
||||
|
||||
cy.visit(url);
|
||||
cy.get('body').find('svg').should('have.length', 1);
|
||||
});
|
||||
it('should handle html escapings properly', () => {
|
||||
const url = 'http://localhost:9000/webpackUsage.html?test-html-escaping=true';
|
||||
|
||||
cy.visit(url);
|
||||
cy.get('body').find('svg').should('have.length', 1);
|
||||
|
||||
cy.get('g.label > foreignobject > div').should('not.contain.text', '<b>');
|
||||
});
|
||||
});
|
|
@ -1,7 +1,7 @@
|
|||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<script src="./viewer.js" type="module"></script>
|
||||
<script type="module" src="./viewer.js"></script>
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||
rel="stylesheet"
|
||||
|
|
|
@ -11,8 +11,7 @@ example-diagram
|
|||
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
|
||||
<!-- <script type="module" src="./external-diagrams-mindmap.mjs" /> -->
|
||||
<script type="module">
|
||||
import exampleDiagram from '../../packages/mermaid-example-diagram/dist/mermaid-example-diagram.core.mjs';
|
||||
// import example from '../../packages/mermaid-example-diagram/src/detector';
|
||||
import exampleDiagram from './mermaid-example-diagram.esm.mjs';
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
|
||||
await mermaid.registerExternalDiagrams([exampleDiagram]);
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
<html>
|
||||
<body>
|
||||
<pre class="mermaid">
|
||||
flowchart-elk
|
||||
a[hello] --> b[world]
|
||||
b --> c{test}
|
||||
c --> one
|
||||
c --> two
|
||||
c --> three
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import elk from './mermaid-flowchart-elk.esm.min.mjs';
|
||||
if (window.location.search.includes('elk')) {
|
||||
await mermaid.registerExternalDiagrams([elk]);
|
||||
}
|
||||
mermaid.initialize({
|
||||
logLevel: 3,
|
||||
startOnLoad: false,
|
||||
});
|
||||
await mermaid.run();
|
||||
if (window.Cypress) {
|
||||
window.rendered = true;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
<html>
|
||||
<body>
|
||||
<pre id="diagram" class="mermaid">
|
||||
graph TB
|
||||
a --> b
|
||||
a --> c
|
||||
b --> d
|
||||
c --> d
|
||||
</pre>
|
||||
|
||||
<div id="d2"></div>
|
||||
|
||||
<script src="/mermaid.min.js"></script>
|
||||
<script>
|
||||
mermaid.initialize({
|
||||
startOnLoad: true,
|
||||
});
|
||||
const value = `graph TD\nHello --> World`;
|
||||
const el = document.getElementById('d2');
|
||||
mermaid.render('did', value).then(({ svg }) => {
|
||||
console.log(svg);
|
||||
el.innerHTML = svg;
|
||||
if (window.Cypress) {
|
||||
window.rendered = true;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -17,20 +17,20 @@
|
|||
graph TB
|
||||
Function-->URL
|
||||
click Function clickByFlow "Add a div"
|
||||
click URL "http://localhost:9000/webpackUsage.html" "Visit <strong>mermaid docs</strong>"
|
||||
click URL "http://localhost:9000/info.html" "Visit <strong>mermaid docs</strong>"
|
||||
</pre>
|
||||
<pre id="FirstLine" class="mermaid2">
|
||||
graph TB
|
||||
1Function-->2URL
|
||||
click 1Function clickByFlow "Add a div"
|
||||
click 2URL "http://localhost:9000/webpackUsage.html" "Visit <strong>mermaid docs</strong>"
|
||||
click 2URL "http://localhost:9000/info.html" "Visit <strong>mermaid docs</strong>"
|
||||
</pre>
|
||||
|
||||
<pre id="FirstLine" class="mermaid2">
|
||||
classDiagram
|
||||
class Test
|
||||
class ShapeLink
|
||||
link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
|
||||
link ShapeLink "http://localhost:9000/info.html" "This is a tooltip for a link"
|
||||
class ShapeCallback
|
||||
callback ShapeCallback "clickByClass" "This is a tooltip for a callback"
|
||||
</pre>
|
||||
|
@ -42,7 +42,7 @@
|
|||
<pre id="FirstLine" class="mermaid">
|
||||
classDiagram-v2
|
||||
class ShapeLink
|
||||
link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
|
||||
link ShapeLink "http://localhost:9000/info.html" "This is a tooltip for a link"
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
|
@ -77,7 +77,7 @@
|
|||
Calling a Callback (look at the console log) :cl2, after cl1, 3d
|
||||
Calling a Callback with args :cl3, after cl1, 3d
|
||||
|
||||
click cl1 href "http://localhost:9000/webpackUsage.html"
|
||||
click cl1 href "http://localhost:9000/info.html"
|
||||
click cl2 call clickByGantt()
|
||||
click cl3 call clickByGantt("test1", test2, test3)
|
||||
|
||||
|
@ -102,9 +102,15 @@
|
|||
div.className = 'created-by-gant-click';
|
||||
div.style = 'padding: 20px; background: green; color: white;';
|
||||
div.innerText = 'Clicked By Gant';
|
||||
if (arg1) div.innerText += ' ' + arg1;
|
||||
if (arg2) div.innerText += ' ' + arg2;
|
||||
if (arg3) div.innerText += ' ' + arg3;
|
||||
if (arg1) {
|
||||
div.innerText += ' ' + arg1;
|
||||
}
|
||||
if (arg2) {
|
||||
div.innerText += ' ' + arg2;
|
||||
}
|
||||
if (arg3) {
|
||||
div.innerText += ' ' + arg3;
|
||||
}
|
||||
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import mermaid2 from './mermaid.esm.mjs';
|
||||
import externalExample from '../../packages/mermaid-example-diagram/dist/mermaid-example-diagram.core.mjs';
|
||||
import zenUml from '../../packages/mermaid-zenuml/dist/mermaid-zenuml.core.mjs';
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import externalExample from './mermaid-example-diagram.esm.mjs';
|
||||
import zenUml from './mermaid-zenuml.esm.mjs';
|
||||
|
||||
function b64ToUtf8(str) {
|
||||
return decodeURIComponent(escape(window.atob(str)));
|
||||
|
@ -45,9 +45,9 @@ const contentLoaded = async function () {
|
|||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
}
|
||||
|
||||
await mermaid2.registerExternalDiagrams([externalExample, zenUml]);
|
||||
mermaid2.initialize(graphObj.mermaid);
|
||||
await mermaid2.run();
|
||||
await mermaid.registerExternalDiagrams([externalExample, zenUml]);
|
||||
mermaid.initialize(graphObj.mermaid);
|
||||
await mermaid.run();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -95,18 +95,14 @@ const contentLoadedApi = async function () {
|
|||
divs[i] = div;
|
||||
}
|
||||
|
||||
const defaultE2eCnf = { theme: 'forest' };
|
||||
const defaultE2eCnf = { theme: 'forest', startOnLoad: false };
|
||||
|
||||
const cnf = merge(defaultE2eCnf, graphObj.mermaid);
|
||||
|
||||
mermaid2.initialize(cnf);
|
||||
mermaid.initialize(cnf);
|
||||
|
||||
for (let i = 0; i < numCodes; i++) {
|
||||
const { svg, bindFunctions } = await mermaid2.render(
|
||||
'newid' + i,
|
||||
graphObj.code[i],
|
||||
divs[i]
|
||||
);
|
||||
const { svg, bindFunctions } = await mermaid.render('newid' + i, graphObj.code[i], divs[i]);
|
||||
div.innerHTML = svg;
|
||||
bindFunctions(div);
|
||||
}
|
||||
|
@ -114,18 +110,21 @@ const contentLoadedApi = async function () {
|
|||
const div = document.createElement('div');
|
||||
div.id = 'block';
|
||||
div.className = 'mermaid';
|
||||
console.warn('graphObj.mermaid', graphObj.mermaid);
|
||||
console.warn('graphObj', graphObj);
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
mermaid2.initialize(graphObj.mermaid);
|
||||
|
||||
const { svg, bindFunctions } = await mermaid2.render('newid', graphObj.code, div);
|
||||
mermaid.initialize(graphObj.mermaid);
|
||||
const { svg, bindFunctions } = await mermaid.render('newid', graphObj.code, div);
|
||||
div.innerHTML = svg;
|
||||
console.log(div.innerHTML);
|
||||
bindFunctions(div);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (typeof document !== 'undefined') {
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
});
|
||||
/*!
|
||||
* Wait for document loaded before starting the execution
|
||||
*/
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<style>
|
||||
/* .mermaid {
|
||||
font-family: "trebuchet ms", verdana, arial;;
|
||||
} */
|
||||
/* .mermaid {
|
||||
font-family: 'arial';
|
||||
} */
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="graph-to-be"></div>
|
||||
<script type="module" charset="utf-8">
|
||||
import './bundle-test.js';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,6 +1,5 @@
|
|||
<html>
|
||||
<head>
|
||||
<script src="./viewer.js" type="module"></script>
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||
<style>
|
||||
.malware {
|
||||
|
@ -33,12 +32,6 @@
|
|||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
startOnLoad: false,
|
||||
useMaxWidth: true,
|
||||
});
|
||||
</script>
|
||||
<script type="module" src="./viewer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
<title>Mermaid development page</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre class="mermaid">info</pre>
|
||||
|
||||
<pre id="diagram" class="mermaid">
|
||||
graph TB
|
||||
a --> b
|
||||
|
@ -30,5 +32,7 @@ graph TB
|
|||
console.log(svg);
|
||||
el.innerHTML = svg;
|
||||
</script>
|
||||
|
||||
<script src="/dev/reload.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
// Connect to the server and reload the page if the server sends a reload message
|
||||
const connectToEvents = () => {
|
||||
const events = new EventSource('/events');
|
||||
const loadTime = Date.now();
|
||||
events.onmessage = (event) => {
|
||||
const time = JSON.parse(event.data);
|
||||
if (time && time > loadTime) {
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
events.onerror = (error) => {
|
||||
console.error(error);
|
||||
events.close();
|
||||
// Try to reconnect after 1 second in case of errors
|
||||
setTimeout(connectToEvents, 1000);
|
||||
};
|
||||
events.onopen = () => {
|
||||
console.log('Connected to live reload server');
|
||||
};
|
||||
};
|
||||
|
||||
setTimeout(connectToEvents, 500);
|
|
@ -0,0 +1,35 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Mermaid Flowchart ELK Test Page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Flowchart ELK</h1>
|
||||
<pre class="mermaid">
|
||||
flowchart-elk TD
|
||||
A([Start]) ==> B[Step 1]
|
||||
B ==> C{Flow 1}
|
||||
C -- Choice 1.1 --> D[Step 2.1]
|
||||
C -- Choice 1.3 --> I[Step 2.3]
|
||||
C == Choice 1.2 ==> E[Step 2.2]
|
||||
D --> F{Flow 2}
|
||||
E ==> F{Flow 2}
|
||||
F{Flow 2} == Choice 2.1 ==> H[Feedback node]
|
||||
H[Feedback node] ==> B[Step 1]
|
||||
F{Flow 2} == Choice 2.2 ==> G((Finish))
|
||||
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import flowchartELK from './mermaid-flowchart-elk.esm.mjs';
|
||||
await mermaid.registerExternalDiagrams([flowchartELK]);
|
||||
mermaid.initialize({
|
||||
logLevel: 3,
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -37,7 +37,7 @@
|
|||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import mermaid from '/mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
logLevel: 3,
|
||||
|
|
|
@ -64,7 +64,7 @@ Example:
|
|||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
</script>
|
||||
```
|
||||
|
||||
|
@ -83,7 +83,7 @@ Example:
|
|||
B-->D(fa:fa-spinner);
|
||||
</pre>
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -128,7 +128,7 @@ b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.es
|
|||
```html
|
||||
<body>
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
</body>
|
||||
|
@ -168,7 +168,7 @@ Rendering in Mermaid is initialized by `mermaid.initialize()` call. However, doi
|
|||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
</body>
|
||||
|
|
|
@ -317,7 +317,7 @@ To select a version:
|
|||
|
||||
Replace `<version>` with the desired version number.
|
||||
|
||||
Latest Version: <https://cdn.jsdelivr.net/npm/mermaid@10>
|
||||
Latest Version: <https://cdn.jsdelivr.net/npm/mermaid@11>
|
||||
|
||||
## Deploying Mermaid
|
||||
|
||||
|
@ -335,7 +335,7 @@ To Deploy Mermaid:
|
|||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
mermaid.initialize({ startOnLoad: true });
|
||||
</script>
|
||||
```
|
||||
|
|
|
@ -300,7 +300,7 @@ From version 9.4.0 you can simplify this code to:
|
|||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
</script>
|
||||
```
|
||||
|
||||
|
|
|
@ -469,7 +469,7 @@ You can use this method to add mermaid including the timeline diagram to a web p
|
|||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
|
||||
</script>
|
||||
```
|
||||
|
||||
|
|
20
package.json
20
package.json
|
@ -15,15 +15,15 @@
|
|||
"git graph"
|
||||
],
|
||||
"scripts": {
|
||||
"build:vite": "tsx .vite/build.ts",
|
||||
"build:mermaid": "pnpm build:vite --mermaid",
|
||||
"build:viz": "pnpm build:mermaid --visualize",
|
||||
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
|
||||
"build": "pnpm build:esbuild && pnpm build:types",
|
||||
"build:esbuild": "pnpm run -r clean && tsx .esbuild/build.ts",
|
||||
"build:mermaid": "pnpm build:esbuild --mermaid",
|
||||
"build:viz": "pnpm build:esbuild --visualize",
|
||||
"build:types": "tsx .build/types.ts",
|
||||
"build:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch",
|
||||
"build:watch": "pnpm build:vite --watch",
|
||||
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite",
|
||||
"dev": "concurrently \"pnpm build:vite --watch\" \"tsx .vite/server.ts\"",
|
||||
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev",
|
||||
"dev": "tsx .esbuild/server.ts",
|
||||
"dev:vite": "tsx .vite/server.ts",
|
||||
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
|
||||
"release": "pnpm build",
|
||||
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
|
||||
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && tsx scripts/fixCSpell.ts",
|
||||
|
@ -32,8 +32,8 @@
|
|||
"cypress": "cypress run",
|
||||
"cypress:open": "cypress open",
|
||||
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
|
||||
"e2e:coverage": "start-server-and-test dev:coverage http://localhost:9000/ cypress",
|
||||
"coverage:cypress:clean": "rimraf .nyc_output coverage/cypress",
|
||||
"e2e:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm e2e",
|
||||
"coverage:merge": "tsx scripts/coverage.ts",
|
||||
"coverage": "pnpm test:coverage --run && pnpm e2e:coverage && pnpm coverage:merge",
|
||||
"ci": "vitest run",
|
||||
|
@ -83,6 +83,7 @@
|
|||
"@vitest/spy": "^0.34.0",
|
||||
"@vitest/ui": "^0.34.0",
|
||||
"ajv": "^8.12.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"concurrently": "^8.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"cypress": "^12.10.0",
|
||||
|
@ -107,6 +108,7 @@
|
|||
"jison": "^0.4.18",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^22.0.0",
|
||||
"langium-cli": "2.0.1",
|
||||
"lint-staged": "^13.2.1",
|
||||
"nyc": "^15.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
|
|
|
@ -43,8 +43,7 @@
|
|||
"cytoscape-cose-bilkent": "^4.1.0",
|
||||
"cytoscape-fcose": "^2.1.0",
|
||||
"d3": "^7.0.0",
|
||||
"khroma": "^2.0.0",
|
||||
"non-layered-tidy-tree-layout": "^2.0.2"
|
||||
"khroma": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "@mermaid-js/flowchart-elk",
|
||||
"version": "1.0.0-rc.1",
|
||||
"description": "Flowchart plugin for mermaid with ELK layout",
|
||||
"module": "dist/mermaid-flowchart-elk.core.mjs",
|
||||
"types": "dist/packages/mermaid-flowchart-elk/src/detector.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-flowchart-elk.core.mjs",
|
||||
"types": "./dist/packages/mermaid-flowchart-elk/src/detector.d.ts"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
"flowchart",
|
||||
"elk",
|
||||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid"
|
||||
},
|
||||
"author": "Knut Sveidqvist",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d3": "^7.4.0",
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"khroma": "^2.0.0",
|
||||
"elkjs": "^0.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.0.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"mermaid": "workspace:^"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
import type {
|
||||
ExternalDiagramDefinition,
|
||||
DiagramDetector,
|
||||
DiagramLoader,
|
||||
} from '../../mermaid/src/diagram-api/types.js';
|
||||
|
||||
const id = 'flowchart-elk';
|
||||
|
||||
const detector: DiagramDetector = (txt, config): boolean => {
|
||||
if (
|
||||
// If diagram explicitly states flowchart-elk
|
||||
/^\s*flowchart-elk/.test(txt) ||
|
||||
// If a flowchart/graph diagram has their default renderer set to elk
|
||||
(/^\s*flowchart|graph/.test(txt) && config?.flowchart?.defaultRenderer === 'elk')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const loader: DiagramLoader = async () => {
|
||||
const { diagram } = await import('./diagram-definition.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
|
@ -0,0 +1,12 @@
|
|||
// @ts-ignore: JISON typing missing
|
||||
import parser from '../../mermaid/src/diagrams/flowchart/parser/flow.jison';
|
||||
import * as db from '../../mermaid/src/diagrams/flowchart/flowDb.js';
|
||||
import styles from '../../mermaid/src/diagrams/flowchart/styles.js';
|
||||
import renderer from './flowRenderer-elk.js';
|
||||
|
||||
export const diagram = {
|
||||
db,
|
||||
renderer,
|
||||
parser,
|
||||
styles,
|
||||
};
|
|
@ -1,17 +1,17 @@
|
|||
import { select, line, curveLinear } from 'd3';
|
||||
import { insertNode } from '../../../dagre-wrapper/nodes.js';
|
||||
import insertMarkers from '../../../dagre-wrapper/markers.js';
|
||||
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js';
|
||||
import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js';
|
||||
import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js';
|
||||
import { insertEdgeLabel } from '../../mermaid/src/dagre-wrapper/edges.js';
|
||||
import { findCommonAncestor } from './render-utils.js';
|
||||
import { labelHelper } from '../../../dagre-wrapper/shapes/util.js';
|
||||
import { getConfig } from '../../../config.js';
|
||||
import { log } from '../../../logger.js';
|
||||
import { setupGraphViewbox } from '../../../setupGraphViewbox.js';
|
||||
import common from '../../common/common.js';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../../utils.js';
|
||||
import { labelHelper } from '../../mermaid/src/dagre-wrapper/shapes/util.js';
|
||||
import { getConfig } from '../../mermaid/src/config.js';
|
||||
import { log } from '../../mermaid/src/logger.js';
|
||||
import { setupGraphViewbox } from '../../mermaid/src/setupGraphViewbox.js';
|
||||
import common from '../../mermaid/src/diagrams/common/common.js';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../mermaid/src/utils.js';
|
||||
import ELK from 'elkjs/lib/elk.bundled.js';
|
||||
import { getLineFunctionsWithOffset } from '../../../utils/lineWithOffset.js';
|
||||
import { addEdgeMarkers } from '../../../dagre-wrapper/edgeMarker.js';
|
||||
import { getLineFunctionsWithOffset } from '../../mermaid/src/utils/lineWithOffset.js';
|
||||
import { addEdgeMarkers } from '../../mermaid/src/dagre-wrapper/edgeMarker.js';
|
||||
|
||||
const elk = new ELK();
|
||||
|
||||
|
@ -595,7 +595,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb
|
|||
*
|
||||
* @param text
|
||||
* @param diagObj
|
||||
* @returns {Record<string, import('../../../diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
|
||||
* @returns {Record<string, import('../../mermaid/src/diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
|
||||
*/
|
||||
export const getClasses = function (text, diagObj) {
|
||||
log.info('Extracting classes');
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "../..",
|
||||
"outDir": "./dist",
|
||||
"types": ["vitest/importMeta", "vitest/globals"]
|
||||
},
|
||||
"include": ["./src/**/*.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
|
@ -19,6 +19,7 @@
|
|||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"repository": {
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* This is a dummy parser that satisfies the mermaid API logic.
|
||||
*/
|
||||
export default {
|
||||
parser: { yy: {} },
|
||||
parse: () => {
|
||||
// no op
|
||||
},
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "mermaid",
|
||||
"version": "10.6.1",
|
||||
"version": "11.0.0-alpha.6",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
|
@ -60,8 +60,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^6.0.1",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
"@mermaid-js/parser": "workspace:^",
|
||||
"cytoscape": "^3.23.0",
|
||||
"cytoscape-cose-bilkent": "^4.1.0",
|
||||
"cytoscape-fcose": "^2.1.0",
|
||||
|
@ -70,15 +69,12 @@
|
|||
"dagre-d3-es": "7.0.10",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "^3.0.5",
|
||||
"elkjs": "^0.8.2",
|
||||
"khroma": "^2.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mdast-util-from-markdown": "^1.3.0",
|
||||
"non-layered-tidy-tree-layout": "^2.0.2",
|
||||
"stylis": "^4.1.3",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"uuid": "^9.0.0",
|
||||
"web-worker": "^1.2.0"
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@adobe/jsonschema2md": "^7.1.4",
|
||||
|
@ -86,6 +82,7 @@
|
|||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-sankey": "^0.12.1",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
"@types/d3-selection": "^3.0.5",
|
||||
"@types/d3-shape": "^3.1.1",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
|
|
|
@ -38,7 +38,10 @@ export class Diagram {
|
|||
this.db = diagram.db;
|
||||
this.renderer = diagram.renderer;
|
||||
this.parser = diagram.parser;
|
||||
this.parser.parser.yy = this.db;
|
||||
if (this.parser.parser) {
|
||||
// The parser.parser.yy is only present in JISON parsers. So, we'll only set if required.
|
||||
this.parser.parser.yy = this.db;
|
||||
}
|
||||
this.init = diagram.init;
|
||||
this.parse();
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ export interface MermaidConfig {
|
|||
* You may also use `themeCSS` to override this value.
|
||||
*
|
||||
*/
|
||||
theme?: string | 'default' | 'forest' | 'dark' | 'neutral' | 'null';
|
||||
theme?: 'default' | 'forest' | 'dark' | 'neutral' | 'null';
|
||||
themeVariables?: any;
|
||||
themeCSS?: string;
|
||||
/**
|
||||
|
@ -82,26 +82,11 @@ export interface MermaidConfig {
|
|||
* This option decides the amount of logging to be used by mermaid.
|
||||
*
|
||||
*/
|
||||
logLevel?:
|
||||
| number
|
||||
| string
|
||||
| 0
|
||||
| 2
|
||||
| 1
|
||||
| 'trace'
|
||||
| 'debug'
|
||||
| 'info'
|
||||
| 'warn'
|
||||
| 'error'
|
||||
| 'fatal'
|
||||
| 3
|
||||
| 4
|
||||
| 5
|
||||
| undefined;
|
||||
logLevel?: 'trace' | 0 | 'debug' | 1 | 'info' | 2 | 'warn' | 3 | 'error' | 4 | 'fatal' | 5;
|
||||
/**
|
||||
* Level of trust for parsed diagram
|
||||
*/
|
||||
securityLevel?: string | 'strict' | 'loose' | 'antiscript' | 'sandbox' | undefined;
|
||||
securityLevel?: 'strict' | 'loose' | 'antiscript' | 'sandbox';
|
||||
/**
|
||||
* Dictates whether mermaid starts on Page load
|
||||
*/
|
||||
|
@ -814,7 +799,7 @@ export interface ErDiagramConfig extends BaseDiagramConfig {
|
|||
/**
|
||||
* Directional bias for layout of entities
|
||||
*/
|
||||
layoutDirection?: string | 'TB' | 'BT' | 'LR' | 'RL';
|
||||
layoutDirection?: 'TB' | 'BT' | 'LR' | 'RL';
|
||||
/**
|
||||
* The minimum width of an entity box. Expressed in pixels.
|
||||
*/
|
||||
|
@ -879,7 +864,7 @@ export interface StateDiagramConfig extends BaseDiagramConfig {
|
|||
* Decides which rendering engine that is to be used for the rendering.
|
||||
*
|
||||
*/
|
||||
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||
|
@ -903,7 +888,7 @@ export interface ClassDiagramConfig extends BaseDiagramConfig {
|
|||
* Decides which rendering engine that is to be used for the rendering.
|
||||
*
|
||||
*/
|
||||
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
nodeSpacing?: number;
|
||||
rankSpacing?: number;
|
||||
/**
|
||||
|
@ -963,7 +948,7 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig {
|
|||
/**
|
||||
* Multiline message alignment
|
||||
*/
|
||||
messageAlign?: string | 'left' | 'center' | 'right';
|
||||
messageAlign?: 'left' | 'center' | 'right';
|
||||
/**
|
||||
* Prolongs the edge of the diagram downwards.
|
||||
*
|
||||
|
@ -1042,7 +1027,7 @@ export interface TimelineDiagramConfig extends BaseDiagramConfig {
|
|||
/**
|
||||
* Multiline message alignment
|
||||
*/
|
||||
messageAlign?: string | 'left' | 'center' | 'right';
|
||||
messageAlign?: 'left' | 'center' | 'right';
|
||||
/**
|
||||
* Prolongs the edge of the diagram downwards.
|
||||
*
|
||||
|
@ -1153,7 +1138,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
|
|||
* Controls the display mode.
|
||||
*
|
||||
*/
|
||||
displayMode?: string | 'compact';
|
||||
displayMode?: '' | 'compact';
|
||||
/**
|
||||
* On which day a week-based interval should start
|
||||
*
|
||||
|
@ -1212,7 +1197,7 @@ export interface SequenceDiagramConfig extends BaseDiagramConfig {
|
|||
/**
|
||||
* Multiline message alignment
|
||||
*/
|
||||
messageAlign?: string | 'left' | 'center' | 'right';
|
||||
messageAlign?: 'left' | 'center' | 'right';
|
||||
/**
|
||||
* Mirror actors under diagram
|
||||
*
|
||||
|
@ -1269,7 +1254,7 @@ export interface SequenceDiagramConfig extends BaseDiagramConfig {
|
|||
/**
|
||||
* This sets the text alignment of actor-attached notes
|
||||
*/
|
||||
noteAlign?: string | 'left' | 'center' | 'right';
|
||||
noteAlign?: 'left' | 'center' | 'right';
|
||||
/**
|
||||
* This sets the font size of actor messages
|
||||
*/
|
||||
|
@ -1353,7 +1338,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
|
|||
* Defines how mermaid renders curves for flowcharts.
|
||||
*
|
||||
*/
|
||||
curve?: string | 'basis' | 'linear' | 'cardinal';
|
||||
curve?: 'basis' | 'linear' | 'cardinal';
|
||||
/**
|
||||
* Represents the padding between the labels and the shape
|
||||
*
|
||||
|
@ -1365,7 +1350,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
|
|||
* Decides which rendering engine that is to be used for the rendering.
|
||||
*
|
||||
*/
|
||||
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
|
||||
/**
|
||||
* Width of nodes where text is wrapped.
|
||||
*
|
||||
|
|
|
@ -71,10 +71,9 @@ export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinitio
|
|||
|
||||
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
|
||||
if (detectors[key]) {
|
||||
log.error(`Detector with key ${key} already exists`);
|
||||
} else {
|
||||
detectors[key] = { detector, loader };
|
||||
log.warn(`Detector with key ${key} already exists. Overwriting.`);
|
||||
}
|
||||
detectors[key] = { detector, loader };
|
||||
log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
|
||||
};
|
||||
|
||||
|
|
|
@ -50,7 +50,6 @@ export const addDiagrams = () => {
|
|||
},
|
||||
},
|
||||
parser: {
|
||||
parser: { yy: {} },
|
||||
parse: () => {
|
||||
throw new Error(
|
||||
'Diagrams beginning with --- are not valid. ' +
|
||||
|
|
|
@ -39,7 +39,6 @@ describe('DiagramAPI', () => {
|
|||
parse: (_text) => {
|
||||
return;
|
||||
},
|
||||
parser: { yy: {} },
|
||||
},
|
||||
renderer: {
|
||||
draw: () => {
|
||||
|
|
|
@ -49,7 +49,7 @@ export const registerDiagram = (
|
|||
detector?: DiagramDetector
|
||||
) => {
|
||||
if (diagrams[id]) {
|
||||
throw new Error(`Diagram ${id} already registered.`);
|
||||
log.warn(`Diagram with id ${id} already registered. Overwriting.`);
|
||||
}
|
||||
diagrams[id] = diagram;
|
||||
if (detector) {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import type { MermaidConfig } from '../config.type.js';
|
||||
import type { GanttDiagramConfig, MermaidConfig } from '../config.type.js';
|
||||
import { frontMatterRegex } from './regexes.js';
|
||||
// The "* as yaml" part is necessary for tree-shaking
|
||||
import * as yaml from 'js-yaml';
|
||||
|
@ -6,7 +6,7 @@ import * as yaml from 'js-yaml';
|
|||
interface FrontMatterMetadata {
|
||||
title?: string;
|
||||
// Allows custom display modes. Currently used for compact mode in gantt charts.
|
||||
displayMode?: string;
|
||||
displayMode?: GanttDiagramConfig['displayMode'];
|
||||
config?: MermaidConfig;
|
||||
}
|
||||
|
||||
|
@ -44,7 +44,7 @@ export function extractFrontMatter(text: string): FrontMatterResult {
|
|||
|
||||
// Only add properties that are explicitly supported, if they exist
|
||||
if (parsed.displayMode) {
|
||||
metadata.displayMode = parsed.displayMode.toString();
|
||||
metadata.displayMode = parsed.displayMode.toString() as GanttDiagramConfig['displayMode'];
|
||||
}
|
||||
if (parsed.title) {
|
||||
metadata.title = parsed.title.toString();
|
||||
|
|
|
@ -105,7 +105,7 @@ export type DrawDefinition = (
|
|||
|
||||
export interface ParserDefinition {
|
||||
parse: (text: string) => void;
|
||||
parser: { yy: DiagramDB };
|
||||
parser?: { yy: DiagramDB };
|
||||
}
|
||||
|
||||
export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element | null, unknown>;
|
||||
|
|
|
@ -2,9 +2,32 @@ import { describe, test, expect } from 'vitest';
|
|||
import { Diagram, getDiagramFromText } from './Diagram.js';
|
||||
import { addDetector } from './diagram-api/detectType.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
import type { DiagramLoader } from './diagram-api/types.js';
|
||||
|
||||
addDiagrams();
|
||||
|
||||
const getDummyDiagram = (id: string, title?: string): Awaited<ReturnType<DiagramLoader>> => {
|
||||
return {
|
||||
id,
|
||||
diagram: {
|
||||
db: {
|
||||
getDiagramTitle: () => title ?? id,
|
||||
},
|
||||
parser: {
|
||||
parse: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
draw: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
describe('diagram detection', () => {
|
||||
test('should detect inbuilt diagrams', async () => {
|
||||
const graph = (await getDiagramFromText('graph TD; A-->B')) as Diagram;
|
||||
|
@ -21,33 +44,25 @@ describe('diagram detection', () => {
|
|||
addDetector(
|
||||
'loki',
|
||||
(str) => str.startsWith('loki'),
|
||||
() =>
|
||||
Promise.resolve({
|
||||
id: 'loki',
|
||||
diagram: {
|
||||
db: {},
|
||||
parser: {
|
||||
parse: () => {
|
||||
// no-op
|
||||
},
|
||||
parser: {
|
||||
yy: {},
|
||||
},
|
||||
},
|
||||
renderer: {
|
||||
draw: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
},
|
||||
})
|
||||
() => Promise.resolve(getDummyDiagram('loki'))
|
||||
);
|
||||
const diagram = (await getDiagramFromText('loki TD; A-->B')) as Diagram;
|
||||
const diagram = await getDiagramFromText('loki TD; A-->B');
|
||||
expect(diagram).toBeInstanceOf(Diagram);
|
||||
expect(diagram.type).toBe('loki');
|
||||
});
|
||||
|
||||
test('should allow external diagrams to override internal ones with same ID', async () => {
|
||||
const title = 'overridden';
|
||||
addDetector(
|
||||
'flowchart-elk',
|
||||
(str) => str.startsWith('flowchart-elk'),
|
||||
() => Promise.resolve(getDummyDiagram('flowchart-elk', title))
|
||||
);
|
||||
const diagram = await getDiagramFromText('flowchart-elk TD; A-->B');
|
||||
expect(diagram).toBeInstanceOf(Diagram);
|
||||
expect(diagram.db.getDiagramTitle?.()).toBe(title);
|
||||
});
|
||||
|
||||
test('should throw the right error for incorrect diagram', async () => {
|
||||
await expect(getDiagramFromText('graph TD; A-->')).rejects.toThrowErrorMatchingInlineSnapshot(`
|
||||
"Parse error on line 2:
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import type { DiagramAST } from '@mermaid-js/parser';
|
||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||
|
||||
export function populateCommonDb(ast: DiagramAST, db: DiagramDB) {
|
||||
if (ast.accDescr) {
|
||||
db.setAccDescription?.(ast.accDescr);
|
||||
}
|
||||
if (ast.accTitle) {
|
||||
db.setAccTitle?.(ast.accTitle);
|
||||
}
|
||||
if (ast.title) {
|
||||
db.setDiagramTitle?.(ast.title);
|
||||
}
|
||||
}
|
|
@ -5,7 +5,6 @@ const diagram: DiagramDefinition = {
|
|||
db: {},
|
||||
renderer,
|
||||
parser: {
|
||||
parser: { yy: {} },
|
||||
parse: (): void => {
|
||||
return;
|
||||
},
|
||||
|
|
|
@ -3,6 +3,7 @@ import type {
|
|||
DiagramDetector,
|
||||
DiagramLoader,
|
||||
} from '../../../diagram-api/types.js';
|
||||
import { log } from '../../../logger.js';
|
||||
|
||||
const id = 'flowchart-elk';
|
||||
|
||||
|
@ -13,13 +14,21 @@ const detector: DiagramDetector = (txt, config): boolean => {
|
|||
// If a flowchart/graph diagram has their default renderer set to elk
|
||||
(/^\s*flowchart|graph/.test(txt) && config?.flowchart?.defaultRenderer === 'elk')
|
||||
) {
|
||||
// This will log at the end, hopefully.
|
||||
setTimeout(
|
||||
() =>
|
||||
log.warn(
|
||||
'flowchart-elk was moved to an external package in Mermaid v11. Please refer [release notes](link) for more details. This diagram will be rendered using `dagre` layout as a fallback.'
|
||||
),
|
||||
500
|
||||
);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const loader: DiagramLoader = async () => {
|
||||
const { diagram } = await import('./flowchart-elk-definition.js');
|
||||
const { diagram } = await import('../flowDiagram-v2.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
// @ts-ignore: JISON typing missing
|
||||
import parser from '../parser/flow.jison';
|
||||
|
||||
import * as db from '../flowDb.js';
|
||||
import renderer from './flowRenderer-elk.js';
|
||||
import styles from './styles.js';
|
||||
|
||||
export const diagram = {
|
||||
db,
|
||||
renderer,
|
||||
parser,
|
||||
styles,
|
||||
};
|
|
@ -1,24 +1,31 @@
|
|||
// @ts-ignore - jison doesn't export types
|
||||
import { parser } from './parser/info.jison';
|
||||
import { db } from './infoDb.js';
|
||||
|
||||
describe('info diagram', () => {
|
||||
beforeEach(() => {
|
||||
parser.yy = db;
|
||||
parser.yy.clear();
|
||||
});
|
||||
import { parser } from './infoParser.js';
|
||||
|
||||
describe('info', () => {
|
||||
it('should handle an info definition', () => {
|
||||
const str = `info`;
|
||||
parser.parse(str);
|
||||
|
||||
expect(db.getInfo()).toBeFalsy();
|
||||
expect(() => {
|
||||
parser.parse(str);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle an info definition with showInfo', () => {
|
||||
const str = `info showInfo`;
|
||||
parser.parse(str);
|
||||
expect(() => {
|
||||
parser.parse(str);
|
||||
}).not.toThrow();
|
||||
});
|
||||
|
||||
expect(db.getInfo()).toBeTruthy();
|
||||
it('should throw because of unsupported info grammar', () => {
|
||||
const str = `info unsupported`;
|
||||
expect(() => {
|
||||
parser.parse(str);
|
||||
}).toThrow('Parsing failed: unexpected character: ->u<- at offset: 5, skipped 11 characters.');
|
||||
});
|
||||
|
||||
it('should throw because of unsupported info grammar', () => {
|
||||
const str = `info unsupported`;
|
||||
expect(() => {
|
||||
parser.parse(str);
|
||||
}).toThrow('Parsing failed: unexpected character: ->u<- at offset: 5, skipped 11 characters.');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,23 +1,10 @@
|
|||
import type { InfoFields, InfoDB } from './infoTypes.js';
|
||||
import { version } from '../../../package.json';
|
||||
|
||||
export const DEFAULT_INFO_DB: InfoFields = {
|
||||
info: false,
|
||||
} as const;
|
||||
export const DEFAULT_INFO_DB: InfoFields = { version } as const;
|
||||
|
||||
let info: boolean = DEFAULT_INFO_DB.info;
|
||||
|
||||
export const setInfo = (toggle: boolean): void => {
|
||||
info = toggle;
|
||||
};
|
||||
|
||||
export const getInfo = (): boolean => info;
|
||||
|
||||
const clear = (): void => {
|
||||
info = DEFAULT_INFO_DB.info;
|
||||
};
|
||||
export const getVersion = (): string => DEFAULT_INFO_DB.version;
|
||||
|
||||
export const db: InfoDB = {
|
||||
clear,
|
||||
setInfo,
|
||||
getInfo,
|
||||
getVersion,
|
||||
};
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
// @ts-ignore - jison doesn't export types
|
||||
import parser from './parser/info.jison';
|
||||
import { parser } from './infoParser.js';
|
||||
import { db } from './infoDb.js';
|
||||
import { renderer } from './infoRenderer.js';
|
||||
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
import type { Info } from '@mermaid-js/parser';
|
||||
import { parse } from '@mermaid-js/parser';
|
||||
|
||||
import { log } from '../../logger.js';
|
||||
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||
|
||||
export const parser: ParserDefinition = {
|
||||
parse: (input: string): void => {
|
||||
const ast: Info = parse('info', input);
|
||||
log.debug(ast);
|
||||
},
|
||||
};
|
|
@ -1,11 +1,9 @@
|
|||
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||
|
||||
export interface InfoFields {
|
||||
info: boolean;
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface InfoDB extends DiagramDB {
|
||||
clear: () => void;
|
||||
setInfo: (info: boolean) => void;
|
||||
getInfo: () => boolean;
|
||||
getVersion: () => string;
|
||||
}
|
||||
|
|
|
@ -1,48 +0,0 @@
|
|||
/** mermaid
|
||||
* https://knsv.github.io/mermaid
|
||||
* (c) 2015 Knut Sveidqvist
|
||||
* MIT license.
|
||||
*/
|
||||
%lex
|
||||
|
||||
%options case-insensitive
|
||||
|
||||
%{
|
||||
// Pre-lexer code can go here
|
||||
%}
|
||||
|
||||
%%
|
||||
|
||||
"info" return 'info' ;
|
||||
[\s\n\r]+ return 'NL' ;
|
||||
[\s]+ return 'space';
|
||||
"showInfo" return 'showInfo';
|
||||
<<EOF>> return 'EOF' ;
|
||||
. return 'TXT' ;
|
||||
|
||||
/lex
|
||||
|
||||
%start start
|
||||
|
||||
%% /* language grammar */
|
||||
|
||||
start
|
||||
// %{ : info document 'EOF' { return yy; } }
|
||||
: info document 'EOF' { return yy; }
|
||||
;
|
||||
|
||||
document
|
||||
: /* empty */
|
||||
| document line
|
||||
;
|
||||
|
||||
line
|
||||
: statement { }
|
||||
| 'NL'
|
||||
;
|
||||
|
||||
statement
|
||||
: showInfo { yy.setInfo(true); }
|
||||
;
|
||||
|
||||
%%
|
|
@ -104,7 +104,6 @@ describe('when using mermaid and ', () => {
|
|||
parse: (_text) => {
|
||||
return;
|
||||
},
|
||||
parser: { yy: {} },
|
||||
},
|
||||
styles: () => {
|
||||
// do nothing
|
||||
|
|
|
@ -15,6 +15,7 @@ import { isDetailedError } from './utils.js';
|
|||
import type { DetailedError } from './utils.js';
|
||||
import type { ExternalDiagramDefinition } from './diagram-api/types.js';
|
||||
import type { UnknownDiagramError } from './errors.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
|
||||
export type {
|
||||
MermaidConfig,
|
||||
|
@ -243,6 +244,7 @@ const registerExternalDiagrams = async (
|
|||
lazyLoad?: boolean;
|
||||
} = {}
|
||||
) => {
|
||||
addDiagrams();
|
||||
registerLazyLoadedDiagrams(...diagrams);
|
||||
if (lazyLoad === false) {
|
||||
await loadRegisteredDiagrams();
|
||||
|
|
|
@ -561,7 +561,7 @@ describe('mermaidAPI', () => {
|
|||
const config = {
|
||||
logLevel: 0,
|
||||
securityLevel: 'loose',
|
||||
};
|
||||
} as const;
|
||||
mermaidAPI.initialize(config);
|
||||
mermaidAPI.setConfig({ securityLevel: 'strict', logLevel: 1 });
|
||||
expect(mermaidAPI.getConfig().logLevel).toBe(1);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
# - `scripts/create-types-from-json-schema.mjs`
|
||||
# Used to generate the `src/config.type.ts` TypeScript types for MermaidConfig
|
||||
# with the `json-schema-to-typescript` NPM package.
|
||||
# - `.vite/jsonSchemaPlugin.ts`
|
||||
# - `.build/jsonSchema.ts`
|
||||
# Used to generate the default values from the `default` keys in this
|
||||
# JSON Schema using the `ajv` NPM package.
|
||||
# Non-JSON values, like functions or `undefined`, still need to be manually
|
||||
|
@ -21,9 +21,9 @@
|
|||
# - Use `meta:enum` to document enum values (from jsonschema2md)
|
||||
# - Use `tsType` to override the TypeScript type (from json-schema-to-typescript)
|
||||
# - If adding a new object to `MermaidConfig` (e.g. a new diagram type),
|
||||
# you may need to add it to `.vite/jsonSchemaPlugin.ts`, `src/docs.mts`
|
||||
# you may need to add it to `.build/jsonSchema.ts`, `src/docs.mts`
|
||||
# and `scripts/create-types-from-json-schema.mjs`
|
||||
# to get the docs/default values/types to generate properly.
|
||||
# to get the default values/docs/types to generate properly.
|
||||
$id: https://mermaid-js.github.io/schemas/config.schema.json
|
||||
$schema: https://json-schema.org/draft/2019-09/schema
|
||||
title: Mermaid Config
|
||||
|
@ -65,8 +65,6 @@ properties:
|
|||
meta:enum:
|
||||
'null': Can be set to disable any pre-defined mermaid theme
|
||||
default: 'default'
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "default" | "forest" | "dark" | "neutral" | "null"'
|
||||
themeVariables:
|
||||
tsType: any
|
||||
themeCSS:
|
||||
|
@ -117,8 +115,6 @@ properties:
|
|||
error: Equivalent to 4
|
||||
fatal: Equivalent to 5 (default)
|
||||
default: 5
|
||||
# Allow any number or string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'number | string | 0 | 2 | 1 | "trace" | "debug" | "info" | "warn" | "error" | "fatal" | 3 | 4 | 5 | undefined'
|
||||
securityLevel:
|
||||
description: Level of trust for parsed diagram
|
||||
type: string
|
||||
|
@ -136,8 +132,6 @@ properties:
|
|||
This prevent any JavaScript from running in the context.
|
||||
This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, or links to other tabs or targets, etc.
|
||||
default: strict
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "strict" | "loose" | "antiscript" | "sandbox" | undefined'
|
||||
startOnLoad:
|
||||
description: Dictates whether mermaid starts on Page load
|
||||
type: boolean
|
||||
|
@ -1151,8 +1145,6 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
LR: Left-Right
|
||||
RL: Right to Left
|
||||
default: TB
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "TB" | "BT" | "LR" | "RL"'
|
||||
minEntityWidth:
|
||||
description: The minimum width of an entity box. Expressed in pixels.
|
||||
type: integer
|
||||
|
@ -1265,8 +1257,6 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
dagre-d3: The [dagre-d3-es](https://www.npmjs.com/package/dagre-d3-es) library.
|
||||
dagre-wrapper: wrapper for dagre implemented in mermaid
|
||||
elk: Layout using [elkjs](https://github.com/kieler/elkjs)
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "dagre-d3" | "dagre-wrapper" | "elk"'
|
||||
|
||||
ClassDiagramConfig:
|
||||
title: Class Diagram Config
|
||||
|
@ -1382,8 +1372,6 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
- center
|
||||
- right
|
||||
default: center
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "left" | "center" | "right"'
|
||||
bottomMarginAdj:
|
||||
description: |
|
||||
Prolongs the edge of the diagram downwards.
|
||||
|
@ -1508,8 +1496,6 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
- center
|
||||
- right
|
||||
default: center
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "left" | "center" | "right"'
|
||||
bottomMarginAdj:
|
||||
description: |
|
||||
Prolongs the edge of the diagram downwards.
|
||||
|
@ -1673,13 +1659,10 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
meta:enum:
|
||||
compact: Enables displaying multiple tasks on the same row.
|
||||
default: ''
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "compact"'
|
||||
weekday:
|
||||
description: |
|
||||
On which day a week-based interval should start
|
||||
type: string
|
||||
tsType: '"monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday" | "sunday"'
|
||||
enum:
|
||||
- monday
|
||||
- tuesday
|
||||
|
@ -1821,8 +1804,6 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
type: string
|
||||
enum: ['left', 'center', 'right']
|
||||
default: 'center'
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "left" | "center" | "right"'
|
||||
|
||||
messageFontSize:
|
||||
description: This sets the font size of actor messages
|
||||
|
@ -1925,8 +1906,6 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||
type: string
|
||||
enum: ['basis', 'linear', 'cardinal']
|
||||
default: 'basis'
|
||||
# Allow any string for typescript backwards compatibility (fix in Mermaid v10)
|
||||
tsType: 'string | "basis" | "linear" | "cardinal"'
|
||||
padding:
|
||||
description: |
|
||||
Represents the padding between the labels and the shape
|
||||
|
|
|
@ -17,7 +17,6 @@ import theme from './themes/index.js';
|
|||
import c4 from './diagrams/c4/styles.js';
|
||||
import classDiagram from './diagrams/class/styles.js';
|
||||
import flowchart from './diagrams/flowchart/styles.js';
|
||||
import flowchartElk from './diagrams/flowchart/elk/styles.js';
|
||||
import er from './diagrams/er/styles.js';
|
||||
import git from './diagrams/git/styles.js';
|
||||
import gantt from './diagrams/gantt/styles.js';
|
||||
|
@ -86,7 +85,6 @@ describe('styles', () => {
|
|||
classDiagram,
|
||||
er,
|
||||
flowchart,
|
||||
flowchartElk,
|
||||
gantt,
|
||||
git,
|
||||
journey,
|
||||
|
|
|
@ -286,56 +286,46 @@ describe('when formatting urls', function () {
|
|||
it('should handle links', function () {
|
||||
const url = 'https://mermaid-js.github.io/mermaid/#/';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual(url);
|
||||
});
|
||||
it('should handle anchors', function () {
|
||||
const url = '#interaction';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual(url);
|
||||
});
|
||||
it('should handle mailto', function () {
|
||||
const url = 'mailto:user@user.user';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual(url);
|
||||
});
|
||||
it('should handle other protocols', function () {
|
||||
const url = 'notes://do-your-thing/id';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual(url);
|
||||
});
|
||||
it('should handle scripts', function () {
|
||||
const url = 'javascript:alert("test")';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
let result = utils.formatUrl(url, { securityLevel: 'loose' });
|
||||
expect(result).toEqual(url);
|
||||
|
||||
config.securityLevel = 'strict';
|
||||
result = utils.formatUrl(url, config);
|
||||
result = utils.formatUrl(url, { securityLevel: 'strict' });
|
||||
expect(result).toEqual('about:blank');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2023 Yokozuna59
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
|
@ -0,0 +1,63 @@
|
|||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/mermaid-js/mermaid/develop/docs/public/favicon.svg" height="150">
|
||||
|
||||
</p>
|
||||
<h1 align="center">
|
||||
Mermaid Parser
|
||||
</h1>
|
||||
|
||||
<p align="center">
|
||||
Mermaid parser package
|
||||
<p>
|
||||
|
||||
[![NPM](https://img.shields.io/npm/v/@mermaid-js/parser)](https://www.npmjs.com/package/@mermaid-js/parser)
|
||||
|
||||
## How the package works
|
||||
|
||||
The package exports a `parse` function that has two parameters:
|
||||
|
||||
```ts
|
||||
declare function parse<T extends DiagramAST>(
|
||||
diagramType: keyof typeof initializers,
|
||||
text: string
|
||||
): T;
|
||||
```
|
||||
|
||||
## How does a Langium-based parser work?
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
actor Package
|
||||
participant Module
|
||||
participant TokenBuilder
|
||||
participant Lexer
|
||||
participant Parser
|
||||
participant ValueConverter
|
||||
|
||||
|
||||
Package ->> Module: Create services
|
||||
Module ->> TokenBuilder: Override or/and<br>reorder rules
|
||||
TokenBuilder ->> Lexer: Read the string and transform<br>it into a token stream
|
||||
Lexer ->> Parser: Parse token<br>stream into AST
|
||||
Parser ->> ValueConverter: Clean/modify tokenized<br>rules returned value
|
||||
ValueConverter -->> Package: Return AST
|
||||
```
|
||||
|
||||
- When to override `TokenBuilder`?
|
||||
|
||||
- To override keyword rules.
|
||||
- To override terminal rules that need a custom function.
|
||||
- To manually reorder the list of rules.
|
||||
|
||||
- When to override `Lexer`?
|
||||
|
||||
- To modify input before tokenizing.
|
||||
- To insert/modify tokens that cannot or have not been parsed.
|
||||
|
||||
- When to override `LangiumParser`?
|
||||
|
||||
- To insert or modify attributes that can't be parsed.
|
||||
|
||||
- When to override `ValueConverter`?
|
||||
|
||||
- To modify the returned value from the parser.
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"projectName": "Mermaid",
|
||||
"languages": [
|
||||
{
|
||||
"id": "info",
|
||||
"grammar": "src/language/info/info.langium",
|
||||
"fileExtensions": [".mmd", ".mermaid"]
|
||||
}
|
||||
],
|
||||
"mode": "production",
|
||||
"importExtension": ".js",
|
||||
"out": "src/language/generated"
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
{
|
||||
"name": "@mermaid-js/parser",
|
||||
"version": "0.1.0-rc.1",
|
||||
"description": "MermaidJS parser",
|
||||
"author": "Yokozuna59",
|
||||
"contributors": [
|
||||
"Yokozuna59",
|
||||
"Sidharth Vinod (https://sidharth.dev)"
|
||||
],
|
||||
"homepage": "https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/parser/#readme",
|
||||
"types": "dist/src/index.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-parser.esm.mjs",
|
||||
"types": "./dist/src/index.d.ts"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rimraf dist src/language/generated",
|
||||
"langium:generate": "langium generate",
|
||||
"langium:watch": "langium generate --watch",
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid.git",
|
||||
"directory": "packages/parser"
|
||||
},
|
||||
"license": "MIT",
|
||||
"keywords": [
|
||||
"mermaid",
|
||||
"parser",
|
||||
"ast"
|
||||
],
|
||||
"dependencies": {
|
||||
"langium": "2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"langium-cli": "2.0.1"
|
||||
},
|
||||
"files": [
|
||||
"dist/"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export type { Info } from './language/index.js';
|
||||
export type { DiagramAST } from './parse.js';
|
||||
export { parse, MermaidParseError } from './parse.js';
|
|
@ -0,0 +1,51 @@
|
|||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
type CustomPatternMatcherReturn = [string] & { payload?: any };
|
||||
|
||||
export type CustomPatternMatcherFunc = (
|
||||
text: string,
|
||||
offset: number,
|
||||
tokens: IToken[],
|
||||
groups: {
|
||||
[groupName: string]: IToken[];
|
||||
}
|
||||
) => CustomPatternMatcherReturn | RegExpExecArray | null;
|
||||
|
||||
interface ICustomPattern {
|
||||
exec: CustomPatternMatcherFunc;
|
||||
}
|
||||
|
||||
type TokenPattern = RegExp | string | CustomPatternMatcherFunc | ICustomPattern;
|
||||
|
||||
export interface IToken {
|
||||
image: string;
|
||||
startOffset: number;
|
||||
startLine?: number;
|
||||
startColumn?: number;
|
||||
endOffset?: number;
|
||||
endLine?: number;
|
||||
endColumn?: number;
|
||||
isInsertedInRecovery?: boolean;
|
||||
tokenTypeIdx: number;
|
||||
tokenType: TokenType;
|
||||
payload?: any;
|
||||
}
|
||||
|
||||
export interface TokenType {
|
||||
name: string;
|
||||
GROUP?: string;
|
||||
PATTERN?: TokenPattern;
|
||||
LABEL?: string;
|
||||
LONGER_ALT?: TokenType | TokenType[];
|
||||
POP_MODE?: boolean;
|
||||
PUSH_MODE?: string;
|
||||
LINE_BREAKS?: boolean;
|
||||
CATEGORIES?: TokenType[];
|
||||
tokenTypeIdx?: number;
|
||||
categoryMatches?: number[];
|
||||
categoryMatchesMap?: {
|
||||
[tokType: number]: boolean;
|
||||
};
|
||||
isParent?: boolean;
|
||||
START_CHARS_HINT?: (string | number)[];
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
interface Common {
|
||||
accDescr?: string;
|
||||
accTitle?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
fragment TitleAndAccessibilities:
|
||||
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) NEWLINE+)+
|
||||
;
|
||||
|
||||
terminal NEWLINE: /\r?\n/;
|
||||
terminal ACC_DESCR: /accDescr([\t ]*:[^\n\r]*(?=%%)|\s*{[^}]*})|accDescr([\t ]*:[^\n\r]*|\s*{[^}]*})/;
|
||||
terminal ACC_TITLE: /accTitle[\t ]*:[^\n\r]*(?=%%)|accTitle[\t ]*:[^\n\r]*/;
|
||||
terminal TITLE: /title([\t ][^\n\r]*|)(?=%%)|title([\t ][^\n\r]*|)/;
|
||||
|
||||
hidden terminal WHITESPACE: /[\t ]+/;
|
||||
hidden terminal YAML: /---[\t ]*\r?\n[\S\s]*?---[\t ]*(?!.)/;
|
||||
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%\s*/;
|
||||
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
|
|
@ -0,0 +1,3 @@
|
|||
export * from './lexer.js';
|
||||
export * from './tokenBuilder.js';
|
||||
export { MermaidValueConverter } from './valueConverter.js';
|
|
@ -0,0 +1,8 @@
|
|||
import type { LexerResult } from 'langium';
|
||||
import { DefaultLexer } from 'langium';
|
||||
|
||||
export class CommonLexer extends DefaultLexer {
|
||||
public override tokenize(text: string): LexerResult {
|
||||
return super.tokenize(text + '\n');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
/**
|
||||
* Matches single and multiline accessible description
|
||||
*/
|
||||
export const accessibilityDescrRegex = /accDescr(?:[\t ]*:[\t ]*([^\n\r]*)|\s*{([^}]*)})/;
|
||||
|
||||
/**
|
||||
* Matches single line accessible title
|
||||
*/
|
||||
export const accessibilityTitleRegex = /accTitle[\t ]*:[\t ]*([^\n\r]*)/;
|
||||
|
||||
/**
|
||||
* Matches a single line title
|
||||
*/
|
||||
export const titleRegex = /title([\t ]+([^\n\r]*)|)/;
|
|
@ -0,0 +1,28 @@
|
|||
import type { GrammarAST, Stream, TokenBuilderOptions } from 'langium';
|
||||
import type { TokenType } from '../chevrotainWrapper.js';
|
||||
|
||||
import { DefaultTokenBuilder } from 'langium';
|
||||
|
||||
export abstract class MermaidTokenBuilder extends DefaultTokenBuilder {
|
||||
private keywords: Set<string>;
|
||||
|
||||
public constructor(keywords: string[]) {
|
||||
super();
|
||||
this.keywords = new Set<string>(keywords);
|
||||
}
|
||||
|
||||
protected override buildKeywordTokens(
|
||||
rules: Stream<GrammarAST.AbstractRule>,
|
||||
terminalTokens: TokenType[],
|
||||
options?: TokenBuilderOptions
|
||||
): TokenType[] {
|
||||
const tokenTypes: TokenType[] = super.buildKeywordTokens(rules, terminalTokens, options);
|
||||
// to restrict users, they mustn't have any non-whitespace characters after the keyword.
|
||||
tokenTypes.forEach((tokenType: TokenType): void => {
|
||||
if (this.keywords.has(tokenType.name) && tokenType.PATTERN !== undefined) {
|
||||
tokenType.PATTERN = new RegExp(tokenType.PATTERN.toString() + '(?!\\S)');
|
||||
}
|
||||
});
|
||||
return tokenTypes;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
import type { CstNode, GrammarAST, ValueType } from 'langium';
|
||||
import { DefaultValueConverter } from 'langium';
|
||||
|
||||
import { accessibilityDescrRegex, accessibilityTitleRegex, titleRegex } from './matcher.js';
|
||||
|
||||
const rulesRegexes: Record<string, RegExp> = {
|
||||
ACC_DESCR: accessibilityDescrRegex,
|
||||
ACC_TITLE: accessibilityTitleRegex,
|
||||
TITLE: titleRegex,
|
||||
};
|
||||
|
||||
export abstract class MermaidValueConverter extends DefaultValueConverter {
|
||||
/**
|
||||
* A method contains convert logic to be used by class.
|
||||
*
|
||||
* @param rule - Parsed rule.
|
||||
* @param input - Matched string.
|
||||
* @param cstNode - Node in the Concrete Syntax Tree (CST).
|
||||
* @returns converted the value if it's available or `undefined` if it's not.
|
||||
*/
|
||||
protected abstract runCustomConverter(
|
||||
rule: GrammarAST.AbstractRule,
|
||||
input: string,
|
||||
cstNode: CstNode
|
||||
): ValueType | undefined;
|
||||
|
||||
protected override runConverter(
|
||||
rule: GrammarAST.AbstractRule,
|
||||
input: string,
|
||||
cstNode: CstNode
|
||||
): ValueType {
|
||||
let value: ValueType | undefined = this.runCommonConverter(rule, input, cstNode);
|
||||
|
||||
if (value === undefined) {
|
||||
value = this.runCustomConverter(rule, input, cstNode);
|
||||
}
|
||||
if (value === undefined) {
|
||||
return super.runConverter(rule, input, cstNode);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
private runCommonConverter(
|
||||
rule: GrammarAST.AbstractRule,
|
||||
input: string,
|
||||
_cstNode: CstNode
|
||||
): ValueType | undefined {
|
||||
const regex: RegExp | undefined = rulesRegexes[rule.name];
|
||||
if (regex === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
const match = regex.exec(input);
|
||||
if (match === null) {
|
||||
return undefined;
|
||||
}
|
||||
// single line title, accTitle, accDescr
|
||||
if (match[1] !== undefined) {
|
||||
return match[1].trim().replace(/[\t ]{2,}/gm, ' ');
|
||||
}
|
||||
// multi line accDescr
|
||||
if (match[2] !== undefined) {
|
||||
return match[2]
|
||||
.replace(/^\s*/gm, '')
|
||||
.replace(/\s+$/gm, '')
|
||||
.replace(/[\t ]{2,}/gm, ' ')
|
||||
.replace(/[\n\r]{2,}/gm, '\n');
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export class CommonValueConverter extends MermaidValueConverter {
|
||||
protected runCustomConverter(
|
||||
_rule: GrammarAST.AbstractRule,
|
||||
_input: string,
|
||||
_cstNode: CstNode
|
||||
): ValueType | undefined {
|
||||
return undefined;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
export * from './generated/ast.js';
|
||||
export * from './generated/grammar.js';
|
||||
export * from './generated/module.js';
|
||||
|
||||
export * from './common/index.js';
|
||||
export * from './info/index.js';
|
|
@ -0,0 +1 @@
|
|||
export * from './module.js';
|
|
@ -0,0 +1,9 @@
|
|||
grammar Info
|
||||
import "../common/common";
|
||||
|
||||
entry Info:
|
||||
NEWLINE*
|
||||
"info" NEWLINE*
|
||||
("showInfo" NEWLINE*)?
|
||||
TitleAndAccessibilities?
|
||||
;
|
|
@ -0,0 +1,72 @@
|
|||
import type {
|
||||
DefaultSharedModuleContext,
|
||||
LangiumServices,
|
||||
LangiumSharedServices,
|
||||
Module,
|
||||
PartialLangiumServices,
|
||||
} from 'langium';
|
||||
import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from 'langium';
|
||||
|
||||
import { CommonLexer } from '../common/lexer.js';
|
||||
import { CommonValueConverter } from '../common/valueConverter.js';
|
||||
import { InfoGeneratedModule, MermaidGeneratedSharedModule } from '../generated/module.js';
|
||||
import { InfoTokenBuilder } from './tokenBuilder.js';
|
||||
|
||||
/**
|
||||
* Declaration of `Info` services.
|
||||
*/
|
||||
type InfoAddedServices = {
|
||||
parser: {
|
||||
Lexer: CommonLexer;
|
||||
TokenBuilder: InfoTokenBuilder;
|
||||
ValueConverter: CommonValueConverter;
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Union of Langium default services and `Info` services.
|
||||
*/
|
||||
export type InfoServices = LangiumServices & InfoAddedServices;
|
||||
|
||||
/**
|
||||
* Dependency injection module that overrides Langium default services and
|
||||
* contributes the declared `Info` services.
|
||||
*/
|
||||
export const InfoModule: Module<InfoServices, PartialLangiumServices & InfoAddedServices> = {
|
||||
parser: {
|
||||
Lexer: (services: InfoServices) => new CommonLexer(services),
|
||||
TokenBuilder: () => new InfoTokenBuilder(),
|
||||
ValueConverter: () => new CommonValueConverter(),
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the full set of services required by Langium.
|
||||
*
|
||||
* First inject the shared services by merging two modules:
|
||||
* - Langium default shared services
|
||||
* - Services generated by langium-cli
|
||||
*
|
||||
* Then inject the language-specific services by merging three modules:
|
||||
* - Langium default language-specific services
|
||||
* - Services generated by langium-cli
|
||||
* - Services specified in this file
|
||||
* @param context - Optional module context with the LSP connection
|
||||
* @returns An object wrapping the shared services and the language-specific services
|
||||
*/
|
||||
export function createInfoServices(context: DefaultSharedModuleContext = EmptyFileSystem): {
|
||||
shared: LangiumSharedServices;
|
||||
Info: InfoServices;
|
||||
} {
|
||||
const shared: LangiumSharedServices = inject(
|
||||
createDefaultSharedModule(context),
|
||||
MermaidGeneratedSharedModule
|
||||
);
|
||||
const Info: InfoServices = inject(
|
||||
createDefaultModule({ shared }),
|
||||
InfoGeneratedModule,
|
||||
InfoModule
|
||||
);
|
||||
shared.ServiceRegistry.register(Info);
|
||||
return { shared, Info };
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
import { MermaidTokenBuilder } from '../common/index.js';
|
||||
|
||||
export class InfoTokenBuilder extends MermaidTokenBuilder {
|
||||
public constructor() {
|
||||
super(['info', 'showInfo']);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
import type { Info } from './index.js';
|
||||
import { createInfoServices } from './language/index.js';
|
||||
|
||||
export type DiagramAST = Info;
|
||||
|
||||
const parsers: Record<string, LangiumParser> = {};
|
||||
|
||||
const initializers = {
|
||||
info: () => {
|
||||
// Will have to make parse async to use this. Can try later...
|
||||
// const { createInfoServices } = await import('./language/info/index.js');
|
||||
const parser = createInfoServices().Info.parser.LangiumParser;
|
||||
parsers['info'] = parser;
|
||||
},
|
||||
} as const;
|
||||
export function parse(diagramType: 'info', text: string): Info;
|
||||
export function parse<T extends DiagramAST>(
|
||||
diagramType: keyof typeof initializers,
|
||||
text: string
|
||||
): T {
|
||||
const initializer = initializers[diagramType];
|
||||
if (!initializer) {
|
||||
throw new Error(`Unknown diagram type: ${diagramType}`);
|
||||
}
|
||||
if (!parsers[diagramType]) {
|
||||
initializer();
|
||||
}
|
||||
const parser: LangiumParser = parsers[diagramType];
|
||||
const result: ParseResult<T> = parser.parse<T>(text);
|
||||
if (result.lexerErrors.length > 0 || result.parserErrors.length > 0) {
|
||||
throw new MermaidParseError(result);
|
||||
}
|
||||
return result.value;
|
||||
}
|
||||
|
||||
export class MermaidParseError extends Error {
|
||||
constructor(public result: ParseResult<DiagramAST>) {
|
||||
const lexerErrors: string = result.lexerErrors.map((err) => err.message).join('\n');
|
||||
const parserErrors: string = result.parserErrors.map((err) => err.message).join('\n');
|
||||
super(`Parsing failed: ${lexerErrors} ${parserErrors}`);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import type { LangiumParser, ParseResult } from 'langium';
|
||||
|
||||
import type { InfoServices } from '../src/language/index.js';
|
||||
import { Info, createInfoServices } from '../src/language/index.js';
|
||||
import { noErrorsOrAlternatives } from './test-util.js';
|
||||
|
||||
const services: InfoServices = createInfoServices().Info;
|
||||
const parser: LangiumParser = services.parser.LangiumParser;
|
||||
function createInfoTestServices(): {
|
||||
services: InfoServices;
|
||||
parse: (input: string) => ParseResult<Info>;
|
||||
} {
|
||||
const parse = (input: string) => {
|
||||
return parser.parse<Info>(input);
|
||||
};
|
||||
|
||||
return { services, parse };
|
||||
}
|
||||
|
||||
describe('info', () => {
|
||||
const { parse } = createInfoTestServices();
|
||||
|
||||
it.each([
|
||||
`info`,
|
||||
`
|
||||
info`,
|
||||
`info
|
||||
`,
|
||||
`
|
||||
info
|
||||
`,
|
||||
])('should handle empty info', (context: string) => {
|
||||
const result = parse(context);
|
||||
noErrorsOrAlternatives(result);
|
||||
|
||||
expect(result.value.$type).toBe(Info);
|
||||
});
|
||||
|
||||
it.each([
|
||||
`info showInfo`,
|
||||
`
|
||||
info showInfo`,
|
||||
`info
|
||||
showInfo
|
||||
`,
|
||||
`
|
||||
info
|
||||
showInfo
|
||||
`,
|
||||
])('should handle showInfo', (context: string) => {
|
||||
const result = parse(context);
|
||||
noErrorsOrAlternatives(result);
|
||||
|
||||
expect(result.value.$type).toBe(Info);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
import { expect, vi } from 'vitest';
|
||||
import type { ParseResult } from 'langium';
|
||||
|
||||
const consoleMock = vi.spyOn(console, 'log').mockImplementation(() => undefined);
|
||||
|
||||
/**
|
||||
* A helper test function that validate that the result doesn't have errors
|
||||
* or any ambiguous alternatives from chevrotain.
|
||||
*
|
||||
* @param result - the result `parse` function.
|
||||
*/
|
||||
export function noErrorsOrAlternatives(result: ParseResult) {
|
||||
expect(result.lexerErrors).toHaveLength(0);
|
||||
expect(result.parserErrors).toHaveLength(0);
|
||||
|
||||
expect(consoleMock).not.toHaveBeenCalled();
|
||||
consoleMock.mockReset();
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "./dist",
|
||||
"allowJs": false,
|
||||
"preserveSymlinks": false,
|
||||
"strictPropertyInitialization": false
|
||||
},
|
||||
"include": ["./src/**/*.ts", "./tests/**/*.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue