@likec4/config 1.48.0 → 1.50.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,35 +1,18 @@
1
- import { t } from "./_chunks/libs/remeda.mjs";
2
1
  import JSON5 from "json5";
3
2
  import z from "zod/v4";
4
3
  import { BorderStyles, ElementShapes, IconPositions, RelationshipArrowTypes, Sizes, ThemeColors, computeColorValues } from "@likec4/core/styles";
5
4
  import { exact } from "@likec4/core/types";
6
-
7
- //#region src/schema.image-alias.ts
8
5
  const IMAGE_ALIAS_KEY_REGEX = /^@[A-Za-z0-9_-]*$/;
9
6
  const IMAGE_ALIAS_VALUE_REGEX = /^(?!\/|[A-Za-z]:[\\\/])(?!.*:\/\/).*$/;
7
+ const ImageAliasKey = z.string().min(1, "Image alias key cannot be empty").regex(IMAGE_ALIAS_KEY_REGEX, "Image alias key must match /^@\\w+$/");
10
8
  const ImageAliasValue = z.string().min(1, "Image alias value cannot be empty").regex(IMAGE_ALIAS_VALUE_REGEX, "Image alias value must be a relative path (no leading slash or protocol)");
11
- const ImageAliasesSchema = z.record(z.string(), ImageAliasValue).meta({ description: "Map of image alias prefixes to relative paths (keys must match /^@\\w+$/; values must be relative paths without protocol or leading slash)." });
12
- function validateImageAliases(imageAliases) {
13
- const invalidKeys = [];
14
- const invalidValues = [];
15
- if (imageAliases) for (const [key, value] of Object.entries(imageAliases)) {
16
- if (!IMAGE_ALIAS_KEY_REGEX.test(key)) invalidKeys.push(key);
17
- if (!IMAGE_ALIAS_VALUE_REGEX.test(value)) invalidValues.push(`${key} -> ${value}`);
18
- }
19
- if (invalidKeys.length || invalidValues.length) {
20
- const parts = [];
21
- if (invalidKeys.length) parts.push(`Invalid image alias key(s): ${invalidKeys.map((k) => JSON.stringify(k)).join(", ")} (must match ${IMAGE_ALIAS_KEY_REGEX})`);
22
- if (invalidValues.length) parts.push(`Invalid image alias value(s): ${invalidValues.map((kv) => JSON.stringify(kv)).join(", ")} (must match ${IMAGE_ALIAS_VALUE_REGEX})`);
23
- throw new Error(parts.join(" | "));
24
- }
25
- }
26
-
27
- //#endregion
28
- //#region src/schema.include.ts
29
- const RELATIVE_PATH_REGEX = /^(?!\/|[A-Za-z]:[\\\/])(?!.*:\/\/).*$/;
30
- const IncludePathValue = z.string().min(1, "Include path cannot be empty").regex(RELATIVE_PATH_REGEX, "Include path must be a relative path (no leading slash, drive letter, or protocol)");
31
- const IncludeConfigSchema = z.strictObject({
32
- paths: z.array(IncludePathValue).min(1, "Include paths cannot be empty").meta({ description: [
9
+ const ImageAliasesSchema = z.record(ImageAliasKey, ImageAliasValue).meta({
10
+ id: "ImageAliases",
11
+ description: "Map of image alias prefixes to relative paths (keys must match /^@\\w+$/; values must be relative paths without protocol or leading slash)."
12
+ });
13
+ const IncludePathValue = z.string().min(1, "Include path cannot be empty").regex(/^(?!\/|[A-Za-z]:[\\\/])(?!.*:\/\/).*$/, "Include path must be a relative path (no leading slash, drive letter, or protocol)");
14
+ const IncludeSchema = z.strictObject({
15
+ paths: z.array(IncludePathValue).meta({ description: [
33
16
  "Additional relative directory paths to include LikeC4 source files from, searched recursively.",
34
17
  "Paths are relative to the project folder (the folder containing this config file).",
35
18
  "Example: [\"../shared\", \"../common/specs\"]"
@@ -39,54 +22,33 @@ const IncludeConfigSchema = z.strictObject({
39
22
  "Prevents excessive scanning of deeply nested directories.",
40
23
  "Default: 3"
41
24
  ].join("\n") }),
42
- fileThreshold: z.number().int().min(1).default(30).meta({ description: [
25
+ fileThreshold: z.number().int().min(1).max(1e4).default(30).meta({ description: [
43
26
  "Maximum number of files to load from include paths before warning.",
44
27
  "Helps identify performance issues from accidentally including large directories.",
45
28
  "Default: 30"
46
29
  ].join("\n") })
47
30
  }).meta({
48
31
  id: "include-config",
49
- description: "Configuration for including additional LikeC4 source files"
32
+ description: ["Configuration for including additional LikeC4 source files from other directories.", "Example: { \"paths\": [\"../shared\", \"../common/specs\"], \"maxDepth\": 5, \"fileThreshold\": 50 }"].join("\n")
50
33
  });
51
- const IncludeSchema = IncludeConfigSchema.optional().meta({ description: ["Configuration for including additional LikeC4 source files from other directories.", "Example: { \"paths\": [\"../shared\", \"../common/specs\"], \"maxDepth\": 5, \"fileThreshold\": 50 }"].join("\n") });
52
- function normalizeIncludeConfig(include) {
53
- if (!include) return {
54
- paths: [],
55
- maxDepth: 3,
56
- fileThreshold: 30
57
- };
58
- return include;
59
- }
60
- function validateIncludePaths(include) {
61
- if (!include?.paths) return;
62
- const invalidPaths = [];
63
- for (const path of include.paths) if (!RELATIVE_PATH_REGEX.test(path)) invalidPaths.push(path);
64
- if (invalidPaths.length > 0) throw new Error(`Invalid include path(s): ${invalidPaths.map((p) => JSON.stringify(p)).join(", ")} (must be relative paths without leading slash, drive letter, or protocol)`);
65
- }
66
-
67
- //#endregion
68
- //#region src/schema.theme.ts
69
34
  const opacity = z.int().min(0, "Opacity must be between 0 and 100").max(100, "Opacity must be between 0 and 100").meta({
70
35
  id: "Opacity",
71
36
  description: "Opacity 0-100%"
72
37
  });
73
- const shape = z.literal(ElementShapes).meta({ id: "ElementShape" });
74
- const border = z.literal(BorderStyles).meta({ id: "BorderStyle" });
75
- const size = z.literal(Sizes).meta({ id: "ElementSize" });
76
- const iconPosition = z.literal(IconPositions).meta({ id: "IconPosition" });
77
- const arrow = z.literal(RelationshipArrowTypes).meta({ id: "ArrowType" });
78
- const line = z.literal([
38
+ const shape = z.enum(ElementShapes).meta({ id: "ElementShape" });
39
+ const border = z.enum(BorderStyles).meta({ id: "BorderStyle" });
40
+ const size = z.enum(Sizes).meta({ id: "ElementSize" });
41
+ const iconPosition = z.enum(IconPositions).meta({ id: "IconPosition" });
42
+ const arrow = z.enum(RelationshipArrowTypes).meta({ id: "ArrowType" });
43
+ const line = z.enum([
79
44
  "dashed",
80
45
  "solid",
81
46
  "dotted"
82
47
  ]).meta({ id: "LineType" });
83
- const themeColor = z.literal(ThemeColors).meta({ id: "ThemeColor" });
84
- const customColor = z.string().min(1, "Custom color name cannot be empty").transform((value) => value).meta({ id: "CustomColorName" });
85
- const color = z.union([themeColor, customColor]).transform((value) => value).meta({ id: "ColorName" });
86
- const colorSchema = z.string().min(1, "Color value cannot be empty").transform((value) => value).meta({
87
- id: "ColorLiteral",
88
- description: "Color value in any valid CSS format: hex, rgb, rgba, hsl, hsla ..."
89
- });
48
+ const themeColor = z.enum(ThemeColors).meta({ id: "ThemeColorName" });
49
+ const customColor = z.custom().refine((v) => typeof v === "string", "Custom color name must be a string").transform((value) => value).meta({ id: "CustomColorName" });
50
+ const color = themeColor.or(customColor).transform((value) => value).meta({ id: "ColorName" });
51
+ const colorSchema = z.string().min(1, "Color value cannot be empty").meta({ id: "ColorLiteral" });
90
52
  const ElementColorValuesSchema = z.strictObject({
91
53
  fill: colorSchema.meta({ description: "Background color" }),
92
54
  stroke: colorSchema.meta({ description: "Stroke color (border, paths above background)" }),
@@ -98,44 +60,31 @@ const RelationshipColorValuesSchema = z.strictObject({
98
60
  label: colorSchema.meta({ description: "Label text color" }),
99
61
  labelBg: colorSchema.optional().default("rgba(0, 0, 0, 0.5)").meta({ description: "Label background color" })
100
62
  }).meta({ id: "RelationshipColorValues" }).transform((value) => value);
101
- const StrictThemeColorValuesSchema = z.strictObject({
102
- elements: z.union([colorSchema, ElementColorValuesSchema]).meta({ description: "Element color value (or a breakdown of specific color values)" }).transform((value) => {
103
- if (typeof value === "string") return computeColorValues(value).elements;
104
- return value;
105
- }),
106
- relationships: z.union([colorSchema, RelationshipColorValuesSchema]).meta({ description: "Relationship color value (or a breakdown of specific color values)" }).transform((value) => {
107
- if (typeof value === "string") return computeColorValues(value).relationships;
108
- return value;
109
- })
110
- }).meta({ id: "StrictThemeColorValues" });
111
- const ThemeColorValuesSchema = z.union([colorSchema, StrictThemeColorValuesSchema]).meta({
63
+ const ThemeColorValuesSchema = z.strictObject({
64
+ elements: ElementColorValuesSchema.or(colorSchema.transform((v) => computeColorValues(v).elements)).meta({ description: "Exact color value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color values" }),
65
+ relationships: RelationshipColorValuesSchema.or(colorSchema.transform((v) => computeColorValues(v).relationships)).meta({ description: "Exact color value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color values" })
66
+ }).transform((value) => value).meta({
67
+ id: "StrictThemeColorValues",
68
+ description: "Exact color value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color value"
69
+ }).or(colorSchema.transform((v) => computeColorValues(v))).transform((value) => value).meta({
112
70
  id: "ThemeColorValues",
113
71
  description: "Exact value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color values"
114
- }).transform((value) => {
115
- if (typeof value === "string") return computeColorValues(value);
116
- return value;
117
72
  });
118
- const ThemeColorsSchema = z.record(color, ThemeColorValuesSchema).meta({
119
- id: "ThemeColors",
120
- description: "Override theme colors"
121
- }).transform((value) => value);
73
+ const ThemeColorsSchema = z.partialRecord(color, ThemeColorValuesSchema).transform((value) => value);
122
74
  const DimensionsSchema = z.strictObject({
123
75
  width: z.number().min(50),
124
76
  height: z.number().min(50)
125
77
  }).meta({
126
78
  id: "Dimensions",
127
- description: "Dimensions"
128
- });
129
- const LikeC4Config_Styles_Theme_Sizes = z.strictObject(t(Sizes, () => DimensionsSchema.optional())).meta({
130
- id: "ThemeSizes",
131
- description: "Override theme sizes"
79
+ description: "Defines dimensions for theme size"
132
80
  });
81
+ const LikeC4Config_Styles_Theme_Sizes = z.partialRecord(size, DimensionsSchema);
133
82
  const LikeC4Config_Styles_Theme = z.strictObject({
134
- colors: ThemeColorsSchema.optional(),
135
- sizes: LikeC4Config_Styles_Theme_Sizes.optional()
83
+ colors: ThemeColorsSchema.optional().meta({ description: "Override theme colors" }),
84
+ sizes: LikeC4Config_Styles_Theme_Sizes.optional().meta({ description: "Override theme sizes" })
136
85
  }).meta({
137
86
  id: "ThemeCustomization",
138
- description: "Theme customization"
87
+ description: "Customize theme colors and sizes"
139
88
  }).transform(({ colors, sizes }) => {
140
89
  return exact({
141
90
  colors: colors ? exact(colors) : void 0,
@@ -146,10 +95,7 @@ const LikeC4Config_Styles_Defaults_Group = z.strictObject({
146
95
  color: color.optional().meta({ description: "Default color for groups\n(must be a valid color name from the theme)" }),
147
96
  opacity: opacity.optional().meta({ description: "Default opacity for groups" }),
148
97
  border: border.optional().meta({ description: "Default border for groups" })
149
- }).meta({
150
- id: "GroupDefaultStyleValues",
151
- description: "Override default values for group style properties\nThese values will be used if such property is not defined"
152
- });
98
+ }).meta({ id: "GroupDefaultStyleValues" });
153
99
  const LikeC4Config_Styles_Defaults_Relationship = z.strictObject({
154
100
  color: color.optional().meta({ description: "Default color for relationships\n(must be a valid color name from the theme)" }),
155
101
  line: line.optional().meta({ description: "Default line style for relationships" }),
@@ -165,23 +111,14 @@ const LikeC4Config_Styles_Defaults = z.strictObject({
165
111
  size: size.optional().meta({ description: "Default size for elements" }),
166
112
  shape: shape.optional().meta({ description: "Default shape for elements" }),
167
113
  iconPosition: iconPosition.optional().meta({ description: "Default icon position for elements" }),
168
- group: LikeC4Config_Styles_Defaults_Group.optional().meta({ description: "Default style values for groups" }),
169
- relationship: LikeC4Config_Styles_Defaults_Relationship.optional().meta({ description: "Default style values for relationships" })
170
- }).meta({
171
- id: "DefaultStyleValues",
172
- description: "Override default values for style properties\nThese values will be used if such property is not defined"
173
- });
174
- const LikeC4Config_Styles_CustomStylesheets = z.union([z.string().min(1, "Custom CSS file path cannot be empty"), z.array(z.string().min(1, "Custom CSS file path cannot be empty"))]).meta({
175
- id: "CustomStylesheets",
176
- description: "Custom CSS (or list of CSS files) to be included in the generated diagrams"
177
- });
114
+ group: LikeC4Config_Styles_Defaults_Group.optional().meta({ description: "Override default values for group style properties\nThese values will be used if such property is not defined" }),
115
+ relationship: LikeC4Config_Styles_Defaults_Relationship.optional().meta({ description: "Override default values for relationship style properties\nThese values will be used if such property is not defined" })
116
+ }).meta({ id: "DefaultStyleValues" });
117
+ const LikeC4Config_Styles_CustomStylesheets = z.union([z.string().min(1, "Custom CSS file path cannot be empty"), z.array(z.string().min(1, "Custom CSS file path cannot be empty"))]).meta({ id: "CustomStylesheets" });
178
118
  const LikeC4StylesConfigSchema = z.strictObject({
179
- theme: LikeC4Config_Styles_Theme.optional(),
180
- defaults: LikeC4Config_Styles_Defaults.optional(),
181
- customCss: LikeC4Config_Styles_CustomStylesheets.optional()
182
- }).meta({
183
- id: "StylesConfiguration",
184
- description: "Project styles customization"
119
+ theme: LikeC4Config_Styles_Theme.optional().meta({ description: "Project theme customization" }),
120
+ defaults: LikeC4Config_Styles_Defaults.optional().meta({ description: "Override default values for style properties\nThese values will be used if such property is not defined" }),
121
+ customCss: LikeC4Config_Styles_CustomStylesheets.optional().meta({ description: "Custom CSS (or list of CSS files) to be included in the generated diagrams" })
185
122
  }).transform(({ theme, defaults, customCss }) => exact({
186
123
  defaults: normalizeDefaults(defaults),
187
124
  customCss: normalizeStylesheets(customCss),
@@ -205,16 +142,13 @@ function normalizeStylesheets(stylesheets) {
205
142
  content: ""
206
143
  };
207
144
  }
208
-
209
- //#endregion
210
- //#region src/schema.ts
211
145
  const ManualLayoutsConfigSchema = z.strictObject({ outDir: z.string().default(".likec4").meta({ description: [
212
146
  "Path to the directory where manual layouts will be stored,",
213
147
  "relative to the folder containing the project config. ",
214
148
  "",
215
149
  "Defaults to '.likec4'."
216
150
  ].join("\n") }) }).meta({
217
- id: "manual-layouts-config",
151
+ id: "ManualLayoutsConfig",
218
152
  description: "Configuration for manual layouts"
219
153
  });
220
154
  const LikeC4ProjectJsonConfigSchema = z.object({
@@ -225,35 +159,79 @@ const LikeC4ProjectJsonConfigSchema = z.object({
225
159
  abort: true,
226
160
  error: "Project name cannot contain \".\", \"@\" or \"#\", try to use A-z, 0-9, _ and -"
227
161
  }).meta({ description: "Project name, must be unique in the workspace" }),
162
+ extends: z.union([z.string().min(1, "Extend path cannot be empty"), z.array(z.string().min(1, "Extend path cannot be empty")).min(1, "Extend list cannot be empty")]).optional().meta({ description: "Extend styles from other config files" }),
228
163
  title: z.string().nonempty("Project title cannot be empty if specified").optional().meta({ description: "A human readable title for the project" }),
229
164
  contactPerson: z.string().nonempty("Contact person cannot be empty if specified").optional().meta({ description: "A person who has been involved in creating or maintaining this project" }),
165
+ metadata: z.record(z.string(), z.any()).optional().meta({ description: "Arbitrary metadata as key-value pairs for custom project information" }),
166
+ styles: LikeC4StylesConfigSchema.optional().meta({ description: "Project styles customization" }),
230
167
  imageAliases: ImageAliasesSchema.optional(),
231
- include: IncludeSchema,
232
- styles: LikeC4StylesConfigSchema.optional(),
168
+ include: IncludeSchema.optional(),
233
169
  exclude: z.array(z.string()).optional().meta({ description: "List of file patterns to exclude from the project, default is [\"**/node_modules/**\"]" }),
234
- manualLayouts: ManualLayoutsConfigSchema.optional()
235
- }).meta({ description: "LikeC4 project configuration" });
170
+ manualLayouts: ManualLayoutsConfigSchema.optional(),
171
+ inferTechnologyFromIcon: z.boolean().optional().meta({ description: [
172
+ "Automatically derive element technology from icon name when technology is not set explicitly.",
173
+ "Applies to aws:, azure:, gcp:, and tech: icons. Bootstrap icons are excluded.",
174
+ "Defaults to true."
175
+ ].join("\n") }),
176
+ implicitViews: z.boolean().optional().meta({ description: "Auto-generate scoped views for elements without explicit views. Defaults to true." })
177
+ }).meta({
178
+ id: "LikeC4ProjectConfig",
179
+ description: "LikeC4 Project Configuration"
180
+ });
236
181
  const FunctionType = z.instanceof(Function);
237
182
  const GeneratorsSchema = z.record(z.string(), FunctionType);
238
183
  const LikeC4ProjectConfigSchema = LikeC4ProjectJsonConfigSchema.extend({ generators: GeneratorsSchema.optional() });
239
184
  /**
240
- * Validates JSON string or JSON object into a LikeC4ProjectConfig object.
185
+ * Validates Object into a LikeC4ProjectConfig object.
241
186
  */
242
187
  function validateProjectConfig(config) {
243
- const parsed = LikeC4ProjectConfigSchema.safeParse(typeof config === "string" ? JSON5.parse(config) : config);
244
- if (!parsed.success) throw new Error("Config validation failed:\n" + z.prettifyError(parsed.error));
245
- if (parsed.data.imageAliases) validateImageAliases(parsed.data.imageAliases);
246
- if (parsed.data.include) validateIncludePaths(parsed.data.include);
247
- return parsed.data;
188
+ const parsed = LikeC4ProjectConfigSchema.safeParse(config);
189
+ if (parsed.success) return parsed.data;
190
+ throw new Error("Config validation failed:\n" + z.prettifyError(parsed.error));
191
+ }
192
+ /**
193
+ * Parses JSON string into a LikeC4ProjectConfig object.
194
+ * Does not process "extends" - use `loadConfig` function instead
195
+ */
196
+ function parseProjectConfigJSON(config) {
197
+ return validateProjectConfig(JSON5.parse(config.trim() || "{}"));
248
198
  }
249
-
250
- //#endregion
251
- //#region src/filenames.ts
199
+ const LikeC4ProjectConfigOps = {
200
+ parse: parseProjectConfigJSON,
201
+ validate: validateProjectConfig,
202
+ normalizeInclude: (include) => {
203
+ const parsed = IncludeSchema.safeParse(include);
204
+ if (parsed.success) return parsed.data;
205
+ return {
206
+ paths: [],
207
+ maxDepth: 3,
208
+ fileThreshold: 30
209
+ };
210
+ }
211
+ };
212
+ /** Trim trailing slashes and backslashes (no regex, avoids S5852 ReDoS). */
213
+ function trimTrailingSlashes(s) {
214
+ let end = s.length;
215
+ while (end > 0 && (s[end - 1] === "/" || s[end - 1] === "\\")) end--;
216
+ return s.slice(0, end);
217
+ }
218
+ /** Split by / or \ without regex (avoids S5852 ReDoS). */
219
+ function splitPath(s) {
220
+ return s.split("/").flatMap((part) => part.split("\\"));
221
+ }
222
+ /** basename compatible with Node and browser (no node:path for Vite/playground bundle). */
223
+ function basename(path) {
224
+ const trimmed = trimTrailingSlashes(path);
225
+ const segments = splitPath(trimmed);
226
+ return segments[segments.length - 1] || trimmed;
227
+ }
228
+ /** Known LikeC4 JSON config filenames (RC and .json). */
252
229
  const configJsonFilenames = [
253
230
  ".likec4rc",
254
231
  ".likec4.config.json",
255
232
  "likec4.config.json"
256
233
  ];
234
+ /** Known LikeC4 non-JSON config filenames (JS, MJS, TS, MTS). */
257
235
  const configNonJsonFilenames = [
258
236
  "likec4.config.js",
259
237
  "likec4.config.cjs",
@@ -262,30 +240,24 @@ const configNonJsonFilenames = [
262
240
  "likec4.config.cts",
263
241
  "likec4.config.mts"
264
242
  ];
243
+ /** All known LikeC4 config filenames (JSON and non-JSON). */
265
244
  const ConfigFilenames = [...configJsonFilenames, ...configNonJsonFilenames];
266
- /**
267
- * Checks if the given filename is a LikeC4 JSON config file (JSON, RC).
268
- */
245
+ /** Returns true if the **basename** of the given path matches a known config filename. */
269
246
  function isLikeC4JsonConfig(filename) {
270
- for (const ext of configJsonFilenames) if (filename.endsWith(ext)) return true;
271
- return false;
247
+ return configJsonFilenames.includes(basename(filename));
272
248
  }
273
249
  /**
274
- * Checks if the given filename is a LikeC4 non-JSON config file (JS, MJS, TS, MTS)
250
+ * Returns true if the **basename** of the given path matches a known non-JSON config filename (JS, MJS, TS, MTS).
275
251
  */
276
252
  function isLikeC4NonJsonConfig(filename) {
277
- for (const ext of configNonJsonFilenames) if (filename.endsWith(ext)) return true;
278
- return false;
253
+ return configNonJsonFilenames.includes(basename(filename));
279
254
  }
280
255
  /**
281
- * Checks if the given filename is a LikeC4 config file (JSON or non-JSON)
256
+ * Returns true if the **basename** of the given path matches a known LikeC4 config file (JSON or non-JSON).
282
257
  */
283
258
  function isLikeC4Config(filename) {
284
259
  return isLikeC4JsonConfig(filename) || isLikeC4NonJsonConfig(filename);
285
260
  }
286
-
287
- //#endregion
288
- //#region src/define-config.ts
289
261
  /**
290
262
  * Defines LikeC4 Project, allows custom generators that can be executed using CLI:
291
263
  *
@@ -402,6 +374,4 @@ function defineTheme(theme) {
402
374
  function defineStyle(styles) {
403
375
  return LikeC4StylesConfigSchema.parse(styles);
404
376
  }
405
-
406
- //#endregion
407
- export { ConfigFilenames, defineConfig, defineGenerators, defineStyle, defineTheme, defineThemeColor, isLikeC4Config, isLikeC4JsonConfig, isLikeC4NonJsonConfig, normalizeIncludeConfig, validateIncludePaths, validateProjectConfig };
377
+ export { ConfigFilenames, LikeC4ProjectConfigOps, defineConfig, defineGenerators, defineStyle, defineTheme, defineThemeColor, isLikeC4Config, isLikeC4JsonConfig, isLikeC4NonJsonConfig };