@jpeakai/mermaidjs-lint 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -9,7 +9,7 @@ import { parseArgs } from "node:util";
9
9
  // package.json
10
10
  var package_default = {
11
11
  name: "@jpeakai/mermaidjs-lint",
12
- version: "0.5.0",
12
+ version: "0.6.1",
13
13
  type: "module",
14
14
  description: "Deterministic lint gates and verified fixers for Mermaid diagrams in markdown",
15
15
  license: "MIT",
@@ -179,6 +179,15 @@ class RuleOptionError extends UsageError {
179
179
  }
180
180
  }
181
181
 
182
+ class CanvasProfileError extends UsageError {
183
+ file;
184
+ constructor(file, cause = "flag") {
185
+ const hint = cause === "flag" ? "run --canvas-light and --canvas-dark over the GitHub-rendered paths only, or set the canvas in the config, which a Material file sets aside" : "remove the mkdocs-material profile setting for this run, or remove the canvas";
186
+ super(`${file}: a canvas applies under the github profile only, and this file is scored under mkdocs-material, whose page is Material's own; ${hint}`, "canvas-profile");
187
+ this.file = file;
188
+ }
189
+ }
190
+
182
191
  class FixOutsideRootError extends UsageError {
183
192
  file;
184
193
  target;
@@ -1041,15 +1050,27 @@ var MKDOCS_MATERIAL = {
1041
1050
  light: { bg: "#ffffff", text: "#36464e" },
1042
1051
  dark: { bg: "#1e2129", text: "hsl(225 18% 86% / 0.82)" }
1043
1052
  };
1044
- var GITHUB = { light: { bg: "#ffffff" }, dark: { bg: "#0d1117" } };
1053
+ var GITHUB_CANVAS = { light: "#ffffff", dark: "#0d1117" };
1045
1054
  var THEMES = ["light", "dark"];
1055
+ var canvasColourProblem = (value) => {
1056
+ if (typeof value !== "string")
1057
+ return "is not a colour string";
1058
+ let alpha;
1059
+ try {
1060
+ alpha = alphaOf(value);
1061
+ } catch {
1062
+ return "is not a CSS colour";
1063
+ }
1064
+ return alpha < 1 ? "is translucent, and a page canvas must be opaque" : null;
1065
+ };
1066
+ var resolveCanvas = (setting) => ({ ...GITHUB_CANVAS, ...setting });
1046
1067
  var MERMAID_EDGE_LABEL = {
1047
1068
  light: { box: "rgba(232,232,232,0.8)", backing: "rgba(232,232,232,0.5)" },
1048
1069
  dark: { box: "#585858", backing: "rgba(88,88,88,0.5)" }
1049
1070
  };
1050
- var edgeLabelBackground = (theme) => {
1071
+ var edgeLabelBackground = (theme, canvas = GITHUB_CANVAS) => {
1051
1072
  const { box, backing } = MERMAID_EDGE_LABEL[theme];
1052
- return compositeOver(box, compositeOver(box, compositeOver(backing, GITHUB[theme].bg)));
1073
+ return compositeOver(box, compositeOver(box, compositeOver(backing, canvas[theme])));
1053
1074
  };
1054
1075
  var MERMAID_ER = {
1055
1076
  light: { odd: "#ffffff", even: "#f1f1ff", label: "#333333" },
@@ -1071,12 +1092,12 @@ var scorePair = (kind, d, fg, bg) => {
1071
1092
  };
1072
1093
  };
1073
1094
  var message = (err) => err instanceof Error ? err.message : String(err);
1074
- var scoreEdgeLabel = (d, pairs, skipped) => {
1095
+ var scoreEdgeLabel = (d, canvas, pairs, skipped) => {
1075
1096
  const { color } = d.properties;
1076
1097
  if (!color)
1077
1098
  return;
1078
1099
  try {
1079
- pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, color, edgeLabelBackground(t))));
1100
+ pairs.push(...THEMES.map((t) => scoreHostPair("text", d, t, color, edgeLabelBackground(t, canvas))));
1080
1101
  } catch (err) {
1081
1102
  skipped.push({
1082
1103
  selector: d.selector,
@@ -1086,12 +1107,12 @@ var scoreEdgeLabel = (d, pairs, skipped) => {
1086
1107
  });
1087
1108
  }
1088
1109
  };
1089
- var scoreGithub = (directives) => {
1110
+ var scoreGithub = (directives, canvas = GITHUB_CANVAS) => {
1090
1111
  const pairs = [];
1091
1112
  const skipped = [];
1092
1113
  for (const d of directives) {
1093
1114
  if (d.kind === "linkStyle")
1094
- scoreEdgeLabel(d, pairs, skipped);
1115
+ scoreEdgeLabel(d, canvas, pairs, skipped);
1095
1116
  if (d.kind !== "classDef" && d.kind !== "style")
1096
1117
  continue;
1097
1118
  const { fill, color, stroke } = d.properties;
@@ -1113,7 +1134,7 @@ var scoreGithub = (directives) => {
1113
1134
  note("fill is fully transparent, so there is no box to anchor contrast");
1114
1135
  continue;
1115
1136
  }
1116
- const boxes = alpha < 1 ? THEMES.map((t) => [compositeOver(fill, GITHUB[t].bg), t]) : [[fill, undefined]];
1137
+ const boxes = alpha < 1 ? THEMES.map((t) => [compositeOver(fill, canvas[t]), t]) : [[fill, undefined]];
1117
1138
  if (!color) {
1118
1139
  note("no color: declared beside fill:, and the github profile requires one " + "(if the host forces label text, use the mkdocs-material profile)", true);
1119
1140
  }
@@ -1246,10 +1267,10 @@ var scoreEr = (directives) => {
1246
1267
  }
1247
1268
  return { pairs, skipped };
1248
1269
  };
1249
- var scoreForProfile = (directives, profile, keyword) => {
1270
+ var scoreForProfile = (directives, profile, keyword, canvas = GITHUB_CANVAS) => {
1250
1271
  if (profile === "mkdocs-material")
1251
1272
  return scoreMkdocs(directives);
1252
- return keyword === "erDiagram" ? scoreEr(directives) : scoreGithub(directives);
1273
+ return keyword === "erDiagram" ? scoreEr(directives) : scoreGithub(directives, canvas);
1253
1274
  };
1254
1275
  var SEQUENCE_THEMES = ["base", "default", "dark", "forest", "neutral"];
1255
1276
  var LINE = { NOTE: 2, ACTIVE_START: 17, RECT_START: 22, AUTONUMBER: 26 };
@@ -1286,7 +1307,7 @@ var themeVariable = (selector, line) => ({
1286
1307
  spans: {},
1287
1308
  line
1288
1309
  });
1289
- var scoreSequence = async (source, profile) => {
1310
+ var scoreSequence = async (source, profile, canvas = GITHUB_CANVAS) => {
1290
1311
  const pairs = [];
1291
1312
  const skipped = [];
1292
1313
  const lines = source.split(`
@@ -1326,7 +1347,7 @@ var scoreSequence = async (source, profile) => {
1326
1347
  return block("themeVariables", `Mermaid could not resolve the theme: ${message(err)}`);
1327
1348
  }
1328
1349
  for (const t of THEMES) {
1329
- const page = (host ? MKDOCS_MATERIAL : GITHUB)[t].bg;
1350
+ const page = host ? MKDOCS_MATERIAL[t].bg : canvas[t];
1330
1351
  const vars = variables[t];
1331
1352
  const color = (key) => {
1332
1353
  const value = vars[key];
@@ -1383,7 +1404,7 @@ var scoreSequence = async (source, profile) => {
1383
1404
  }
1384
1405
  return { pairs, skipped };
1385
1406
  };
1386
- var scoreDiagram = (source, directives, profile, keyword) => keyword === "sequenceDiagram" ? scoreSequence(source, profile) : Promise.resolve(scoreForProfile(directives, profile, keyword));
1407
+ var scoreDiagram = (source, directives, profile, keyword, canvas = GITHUB_CANVAS) => keyword === "sequenceDiagram" ? scoreSequence(source, profile, canvas) : Promise.resolve(scoreForProfile(directives, profile, keyword, canvas));
1387
1408
  var detectProfile = (filePath) => {
1388
1409
  let dir = dirname(resolve(filePath));
1389
1410
  for (let i = 0;i < 20; i++) {
@@ -1547,7 +1568,7 @@ var COMPLEXITY_OPTIONS = {
1547
1568
  subgraphWeight: "number",
1548
1569
  depthWeight: "number"
1549
1570
  };
1550
- var CONTRAST_OPTIONS = { profile: "string" };
1571
+ var CONTRAST_OPTIONS = { profile: "string", canvas: "canvas" };
1551
1572
  var measure = async (diagram, config) => {
1552
1573
  const stats = await diagram.stats();
1553
1574
  if (parseFailed(diagram.source, stats))
@@ -1577,17 +1598,36 @@ var profileOf = (ctx) => {
1577
1598
  }
1578
1599
  return wanted === "auto" ? detectProfile(ctx.file) : wanted;
1579
1600
  };
1580
- var scored = new WeakMap;
1581
- var contrastOf = (diagram, profile) => {
1582
- let byProfile = scored.get(diagram);
1583
- if (!byProfile) {
1584
- byProfile = new Map;
1585
- scored.set(diagram, byProfile);
1601
+ var settingsOf = (ctx) => {
1602
+ const profile = profileOf(ctx);
1603
+ const setting = ctx.options.canvas;
1604
+ if (setting === undefined)
1605
+ return { profile, canvas: GITHUB_CANVAS };
1606
+ for (const side of THEMES) {
1607
+ if (!(side in setting))
1608
+ continue;
1609
+ const problem = canvasColourProblem(setting[side]);
1610
+ if (problem)
1611
+ throw new Error(`canvas.${side} ${problem}: ${JSON.stringify(setting[side])}`);
1586
1612
  }
1587
- let result = byProfile.get(profile);
1613
+ if (profile === "mkdocs-material") {
1614
+ throw new CanvasProfileError(ctx.file, ctx.options.profile === "mkdocs-material" ? "profile" : "flag");
1615
+ }
1616
+ return { profile, canvas: resolveCanvas(setting) };
1617
+ };
1618
+ var scored = new WeakMap;
1619
+ var contrastOf = (diagram, settings) => {
1620
+ const { profile, canvas } = settings;
1621
+ const key = `${profile}\x00${canvas.light}\x00${canvas.dark}`;
1622
+ let bySettings = scored.get(diagram);
1623
+ if (!bySettings) {
1624
+ bySettings = new Map;
1625
+ scored.set(diagram, bySettings);
1626
+ }
1627
+ let result = bySettings.get(key);
1588
1628
  if (!result) {
1589
- result = scoreDiagram(diagram.source, diagram.directives, profile, diagram.keyword);
1590
- byProfile.set(profile, result);
1629
+ result = scoreDiagram(diagram.source, diagram.directives, profile, diagram.keyword, canvas);
1630
+ bySettings.set(key, result);
1591
1631
  }
1592
1632
  return result;
1593
1633
  };
@@ -1700,10 +1740,10 @@ var contrastBorderBelowUiMinimum = {
1700
1740
  summary: "a border below 3:1 against its fill",
1701
1741
  options: CONTRAST_OPTIONS,
1702
1742
  check: async (ctx) => {
1703
- const profile = profileOf(ctx);
1743
+ const settings = settingsOf(ctx);
1704
1744
  const found = [];
1705
1745
  for (const diagram of ctx.doc.diagrams) {
1706
- for (const p of (await contrastOf(diagram, profile)).pairs) {
1746
+ for (const p of (await contrastOf(diagram, settings)).pairs) {
1707
1747
  if (p.kind !== "border" || p.passes || p.advisory)
1708
1748
  continue;
1709
1749
  const line = fileLine(diagram, p.line);
@@ -1728,10 +1768,10 @@ var contrastDirectiveUnauditable = {
1728
1768
  summary: "a style directive whose contrast cannot be audited",
1729
1769
  options: CONTRAST_OPTIONS,
1730
1770
  check: async (ctx) => {
1731
- const profile = profileOf(ctx);
1771
+ const settings = settingsOf(ctx);
1732
1772
  const found = [];
1733
1773
  for (const diagram of ctx.doc.diagrams) {
1734
- for (const s of (await contrastOf(diagram, profile)).skipped) {
1774
+ for (const s of (await contrastOf(diagram, settings)).skipped) {
1735
1775
  if (!s.blocking)
1736
1776
  continue;
1737
1777
  const line = fileLine(diagram, s.line);
@@ -1759,10 +1799,10 @@ var contrastTextBelowAa = {
1759
1799
  summary: "label text below 4.5:1 against its fill",
1760
1800
  options: CONTRAST_OPTIONS,
1761
1801
  check: async (ctx) => {
1762
- const profile = profileOf(ctx);
1802
+ const settings = settingsOf(ctx);
1763
1803
  const found = [];
1764
1804
  for (const diagram of ctx.doc.diagrams) {
1765
- for (const p of (await contrastOf(diagram, profile)).pairs) {
1805
+ for (const p of (await contrastOf(diagram, settings)).pairs) {
1766
1806
  if (p.kind !== "text" || p.passes)
1767
1807
  continue;
1768
1808
  const where = [p.theme, p.row].filter(Boolean).join("/");
@@ -1773,19 +1813,19 @@ var contrastTextBelowAa = {
1773
1813
  endLine: line,
1774
1814
  rule: RULE.TEXT,
1775
1815
  message: `${p.directive.kind} ${p.selector}: text ${p.assessment.ratio.toFixed(2)}:1 < ${AA_NORMAL2} ` + `(${p.foreground} on ${p.background}${where ? `, ${where}` : ""})`,
1776
- remediation: profile === "mkdocs-material" ? "The host theme forces the label text, so change the fill until the host text reads on it in both themes." : p.directive.kind === "themeVariables" ? "A sequence diagram colour is a theme variable, and text with no box of its own sits on the page, so set the variable to a colour that reads on its background in both themes, or remove it and let the theme set it." : p.directive.kind === "linkStyle" ? "An edge label sits on Mermaid's own label box, light in one theme and grey in the other, and no one colour reads on both, so drop color: from the linkStyle and let the theme set the label text." : "Darken or lighten the fill, or change color:, until the ratio reaches 4.5:1 in every theme."
1816
+ remediation: settings.profile === "mkdocs-material" ? "The host theme forces the label text, so change the fill until the host text reads on it in both themes." : p.directive.kind === "themeVariables" ? "A sequence diagram colour is a theme variable, and text with no box of its own sits on the page, so set the variable to a colour that reads on its background in both themes, or remove it and let the theme set it." : p.directive.kind === "linkStyle" ? "An edge label sits on Mermaid's own label box, light in one theme and grey in the other, and no one colour reads on both, so drop color: from the linkStyle and let the theme set the label text." : "Darken or lighten the fill, or change color:, until the ratio reaches 4.5:1 in every theme."
1777
1817
  });
1778
1818
  }
1779
1819
  }
1780
1820
  return found;
1781
1821
  },
1782
1822
  fix: async (ctx) => {
1783
- const profile = profileOf(ctx);
1784
- if (profile === "mkdocs-material")
1823
+ const settings = settingsOf(ctx);
1824
+ if (settings.profile === "mkdocs-material")
1785
1825
  return [];
1786
1826
  const edits = [];
1787
1827
  for (const diagram of ctx.doc.diagrams) {
1788
- const { pairs } = await contrastOf(diagram, profile);
1828
+ const { pairs } = await contrastOf(diagram, settings);
1789
1829
  const failing = new Set(pairs.filter((p) => p.kind === "text" && !p.passes).map((p) => p.directive));
1790
1830
  for (const directive of failing) {
1791
1831
  const span = directive.spans.color;
@@ -1905,8 +1945,27 @@ var typeOf = (value) => {
1905
1945
  return "boolean";
1906
1946
  if (Array.isArray(value) && value.every((v) => typeof v === "string"))
1907
1947
  return "string[]";
1948
+ if (isCanvasShape(value))
1949
+ return "canvas";
1908
1950
  return null;
1909
1951
  };
1952
+ var isCanvasShape = (value) => {
1953
+ if (typeof value !== "object" || value === null || Array.isArray(value))
1954
+ return false;
1955
+ const keys = Object.keys(value);
1956
+ return keys.length > 0 && keys.every((k) => THEMES.includes(k));
1957
+ };
1958
+ var describeType = (want) => want === "canvas" ? 'an object such as { "light": "#ffffff", "dark": "#0d1117" }' : `a ${want}`;
1959
+ var checkCanvas = (where, rule, name, value) => {
1960
+ for (const side of THEMES) {
1961
+ if (!(side in value))
1962
+ continue;
1963
+ const problem = canvasColourProblem(value[side]);
1964
+ if (problem) {
1965
+ throw new RuleOptionError(where, rule.id, name, `option "${name}.${side}" of "${rule.id}" ${problem}: ${JSON.stringify(value[side])}`);
1966
+ }
1967
+ }
1968
+ };
1910
1969
  var checkOptions = (where, rule, options) => {
1911
1970
  const accepted = Object.keys(rule.options ?? {});
1912
1971
  for (const [name, value] of Object.entries(options)) {
@@ -1916,8 +1975,13 @@ var checkOptions = (where, rule, options) => {
1916
1975
  throw new RuleOptionError(where, rule.id, name, `rule "${rule.id}" has no option "${name}"; ${list}`);
1917
1976
  }
1918
1977
  if (typeOf(value) !== want) {
1919
- throw new RuleOptionError(where, rule.id, name, `option "${name}" of "${rule.id}" must be a ${want}`);
1978
+ throw new RuleOptionError(where, rule.id, name, `option "${name}" of "${rule.id}" must be ${describeType(want)}`);
1920
1979
  }
1980
+ if (want === "canvas")
1981
+ checkCanvas(where, rule, name, value);
1982
+ }
1983
+ if (options.profile === "mkdocs-material" && options.canvas !== undefined && rule.options?.canvas === "canvas") {
1984
+ throw new RuleOptionError(where, rule.id, "canvas", `option "canvas" of "${rule.id}" applies under the github profile only, and this rule sets profile mkdocs-material, whose page is Material's own`);
1921
1985
  }
1922
1986
  if (typeof options.preset === "string" && canonicalPreset(options.preset) === null) {
1923
1987
  throw new RuleOptionError(where, rule.id, "preset", `option "preset" of "${rule.id}" is not a known preset`);
@@ -1961,8 +2025,24 @@ var buildRegistry = (input) => {
1961
2025
  var BUILTIN = buildRegistry({ rules: RULES, fixOrder: FIX_ORDER, categories: BUILTIN_CATEGORIES });
1962
2026
 
1963
2027
  // src/check.ts
2028
+ var canvasName = (side, prefix) => prefix ? `${prefix}canvas-${side}` : `canvas.${side}`;
1964
2029
  var checkRunFlags = (flags, prefix = "") => {
1965
- const { preset, profile } = flags;
2030
+ const { preset, profile, canvas } = flags;
2031
+ for (const side of THEMES) {
2032
+ if (canvas?.[side] === undefined)
2033
+ continue;
2034
+ const problem = canvasColourProblem(canvas[side]);
2035
+ if (problem) {
2036
+ const name = canvasName(side, prefix);
2037
+ throw new FlagError(name, `${name} ${problem}: "${canvas[side]}"; give an opaque CSS colour, such as #ffffff`);
2038
+ }
2039
+ }
2040
+ const sides = THEMES.filter((side) => canvas?.[side] !== undefined);
2041
+ const first = sides[0];
2042
+ if (profile === "mkdocs-material" && first !== undefined) {
2043
+ const name = canvasName(first, prefix);
2044
+ throw new FlagError(name, `${name} applies under the github profile only; mkdocs-material scores Material's own page colours`);
2045
+ }
1966
2046
  if (preset !== undefined && canonicalPreset(preset) === null) {
1967
2047
  throw new FlagError(`${prefix}preset`, `${prefix}preset must be one of ${PRESET_NAMES.join(", ")}, not "${preset}"`);
1968
2048
  }
@@ -1970,7 +2050,10 @@ var checkRunFlags = (flags, prefix = "") => {
1970
2050
  throw new FlagError(`${prefix}profile`, `${prefix}profile must be one of ${PROFILES.join(", ")}, not "${profile}"`);
1971
2051
  }
1972
2052
  };
1973
- var optionsFor = (rule, configured, flags) => {
2053
+ var canvasOptions = (rule) => Object.entries(rule.options ?? {}).filter(([, type]) => type === "canvas").map(([name]) => name);
2054
+ var hasFlagCanvas = (flags) => THEMES.some((side) => flags.canvas?.[side] !== undefined);
2055
+ var setsAside = (rule, options, flags, file) => !hasFlagCanvas(flags) && rule.options?.profile !== undefined && (options.profile ?? "auto") === "auto" && canvasOptions(rule).some((name) => options[name] !== undefined) && detectProfile(file) === "mkdocs-material";
2056
+ var optionsFor = (rule, configured, flags, file) => {
1974
2057
  const options = { ...configured };
1975
2058
  for (const name of ["preset", "profile"]) {
1976
2059
  const value = flags[name];
@@ -1979,11 +2062,34 @@ var optionsFor = (rule, configured, flags) => {
1979
2062
  }
1980
2063
  if (typeof options.preset === "string")
1981
2064
  options.preset = canonicalPreset(options.preset) ?? options.preset;
2065
+ const canvas = flags.canvas;
2066
+ if (canvas && hasFlagCanvas(flags)) {
2067
+ for (const name of canvasOptions(rule)) {
2068
+ const current = options[name];
2069
+ const merged = {
2070
+ ...typeof current === "object" && current !== null ? current : {}
2071
+ };
2072
+ for (const side of THEMES) {
2073
+ const value = canvas[side];
2074
+ if (value !== undefined)
2075
+ merged[side] = value;
2076
+ }
2077
+ options[name] = merged;
2078
+ }
2079
+ }
2080
+ if (file !== undefined && setsAside(rule, options, flags, file)) {
2081
+ for (const name of canvasOptions(rule))
2082
+ delete options[name];
2083
+ }
1982
2084
  return options;
1983
2085
  };
2086
+ var canvasSetAside = (file, flags = {}, registry = BUILTIN) => registry.rules.some((rule) => {
2087
+ const options = optionsFor(rule, registry.options.get(rule.id), flags);
2088
+ return setsAside(rule, options, flags, file);
2089
+ });
1984
2090
  var checkModel = async (doc, file, flags = {}, registry = BUILTIN) => {
1985
2091
  checkRunFlags(flags);
1986
- const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags), helpers: HELPERS }));
2092
+ const perRule = registry.rules.map((rule) => rule.check({ doc, file, options: optionsFor(rule, registry.options.get(rule.id), flags, file), helpers: HELPERS }));
1987
2093
  const findings = (await Promise.all(perRule)).flat();
1988
2094
  return findings.sort((a, b) => a.line - b.line || a.rule.localeCompare(b.rule));
1989
2095
  };
@@ -2155,7 +2261,7 @@ var fixSourceReport = async (src, file, flags = {}, registry = BUILTIN) => {
2155
2261
  };
2156
2262
  let progressed = false;
2157
2263
  for (const rule of registry.fixOrder) {
2158
- const options = optionsFor(rule, registry.options.get(rule.id), flags);
2264
+ const options = optionsFor(rule, registry.options.get(rule.id), flags, file);
2159
2265
  const proposed = await rule.fix?.({ doc, file, options, helpers: HELPERS }) ?? [];
2160
2266
  const sound = proposed.filter((e) => {
2161
2267
  if (wellFormed(e, current.length))
@@ -2425,7 +2531,7 @@ var importModule = async (source, runtime) => {
2425
2531
  throw new PluginImportError(source.spec, message2(err));
2426
2532
  }
2427
2533
  };
2428
- var OPTION_TYPES = new Set(["number", "string", "boolean", "string[]"]);
2534
+ var OPTION_TYPES = new Set(["number", "string", "boolean", "string[]", "canvas"]);
2429
2535
  var RESERVED_OPTIONS = ["preset", "profile"];
2430
2536
  var checkRule = (spec, key, raw, own) => {
2431
2537
  const where = `rule "${key}"`;
@@ -2454,7 +2560,7 @@ var checkRule = (spec, key, raw, own) => {
2454
2560
  if (options !== undefined) {
2455
2561
  const valid = isRecord3(options) && Object.values(options).every((t) => OPTION_TYPES.has(t));
2456
2562
  if (!valid) {
2457
- throw new PluginContractError(spec, `${where} options must map names to number, string, boolean or string[]`);
2563
+ throw new PluginContractError(spec, `${where} options must map names to number, string, boolean, string[] or canvas`);
2458
2564
  }
2459
2565
  for (const name of RESERVED_OPTIONS) {
2460
2566
  if (name in options && options[name] !== "string") {
@@ -2721,7 +2827,7 @@ var setUp = async ({
2721
2827
 
2722
2828
  // src/cli.ts
2723
2829
  var SYNOPSIS = `usage: mermaidjs-lint <path...> [--fix] [--json] [--preset NAME] [--profile NAME] [--config FILE]
2724
- [--no-plugins | --lenient-plugins]
2830
+ [--canvas-light COLOUR] [--canvas-dark COLOUR] [--no-plugins | --lenient-plugins]
2725
2831
  mermaidjs-lint --list-rules
2726
2832
  mermaidjs-lint --validate-plugins [--json] [--config FILE]
2727
2833
  mermaidjs-lint --version | -h, --help`;
@@ -2747,6 +2853,11 @@ Examples of every rule, with fixes: https://jpeakai.github.io/jpai-mermaidjs-lin
2747
2853
  numeric threshold set in the config overrides both.
2748
2854
  --profile NAME sets how contrast is read: ${PROFILES.join(", ")}. It overrides a profile set in the config.
2749
2855
  auto reads mkdocs-material when any parent folder holds mkdocs.yml or mkdocs.yaml, else github.
2856
+ --canvas-light COLOUR and --canvas-dark COLOUR set the page a host draws in each theme, under the github
2857
+ profile only. A translucent fill, an edge label box and a sequence diagram's page-backed text are scored
2858
+ on it. Each is an opaque CSS colour such as #faf8f9, and a side not given keeps the config's, or GitHub's
2859
+ (#ffffff light, #0d1117 dark). A file scored under mkdocs-material refuses a canvas flag with a usage
2860
+ error, and sets a config canvas aside when auto picked the profile.
2750
2861
  --json prints one JSON object instead of text lines.
2751
2862
  --fix applies every fix it can prove safe, then reports what remains.
2752
2863
  A fixer that is unsure leaves the text alone and the finding stays.
@@ -2770,6 +2881,8 @@ var parse2 = (argv) => parseArgs({
2770
2881
  help: { type: "boolean", short: "h", default: false },
2771
2882
  preset: { type: "string", short: "p" },
2772
2883
  profile: { type: "string" },
2884
+ "canvas-light": { type: "string" },
2885
+ "canvas-dark": { type: "string" },
2773
2886
  config: { type: "string" },
2774
2887
  "no-plugins": { type: "boolean", default: false },
2775
2888
  "lenient-plugins": { type: "boolean", default: false },
@@ -2875,7 +2988,16 @@ var run = async (argv, cwd) => {
2875
2988
  }
2876
2989
  const listing = values["list-rules"];
2877
2990
  const validating = values["validate-plugins"];
2878
- checkRunFlags({ preset: values.preset, profile: values.profile }, "--");
2991
+ const canvas = {
2992
+ ...values["canvas-light"] !== undefined && { light: values["canvas-light"] },
2993
+ ...values["canvas-dark"] !== undefined && { dark: values["canvas-dark"] }
2994
+ };
2995
+ const flags = {
2996
+ preset: values.preset,
2997
+ profile: values.profile,
2998
+ ...Object.keys(canvas).length > 0 && { canvas }
2999
+ };
3000
+ checkRunFlags(flags, "--");
2879
3001
  if (values["no-plugins"] && values["lenient-plugins"]) {
2880
3002
  throw new FlagError("--lenient-plugins", "--no-plugins and --lenient-plugins contradict each other");
2881
3003
  }
@@ -2921,7 +3043,10 @@ var run = async (argv, cwd) => {
2921
3043
  throw new FlagError("files", "no matching files");
2922
3044
  if (values.fix)
2923
3045
  await refuseOutsideLinks(files, root, cwd);
2924
- const flags = { preset: values.preset, profile: values.profile };
3046
+ const setAside = files.filter((file) => canvasSetAside(file, flags, registry));
3047
+ if (setAside.length > 0) {
3048
+ console.error(`mermaidjs-lint: config canvas set aside for ${plural(setAside.length, "file")} scored under mkdocs-material: ${setAside.map((f) => display(f, cwd)).join(", ")}`);
3049
+ }
2925
3050
  const perFile = await Promise.all(files.map(async (file) => {
2926
3051
  let src = await readFile3(file, "utf8");
2927
3052
  if (values.fix) {