@likec4/config 1.57.0 → 1.59.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/chunks/index.d.mts +596 -0
- package/dist/chunks/rolldown-runtime.mjs +1 -0
- package/dist/chunks/src.mjs +17 -0
- package/dist/index.d.mts +1 -595
- package/dist/index.mjs +1 -421
- package/dist/node/index.d.mts +2 -595
- package/dist/node/index.mjs +2 -537
- package/package.json +17 -17
- package/src/node/load-config.ts +1 -1
- package/dist/THIRD-PARTY-LICENSES.md +0 -41
- package/dist/_chunks/libs/defu.mjs +0 -29
- package/dist/_chunks/libs/remeda.mjs +0 -56
package/dist/node/index.mjs
CHANGED
|
@@ -1,528 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { i as t, n as t$1, r as e, t as n } from "../_chunks/libs/remeda.mjs";
|
|
3
|
-
import JSON5 from "json5";
|
|
4
|
-
import z from "zod/v4";
|
|
5
|
-
import { BorderStyles, ElementShapes, IconPositions, RelationshipArrowTypes, Sizes, ThemeColors, computeColorValues } from "@likec4/core/styles";
|
|
6
|
-
import { exact } from "@likec4/core/types";
|
|
7
|
-
import { invariant } from "@likec4/core";
|
|
8
|
-
import { logger, wrapError } from "@likec4/log";
|
|
9
|
-
import { bundleRequire } from "bundle-require";
|
|
10
|
-
import { formatMessagesSync } from "esbuild";
|
|
11
|
-
import * as fs from "node:fs/promises";
|
|
12
|
-
import { basename, dirname, relative, resolve } from "node:path";
|
|
13
|
-
import { cwd } from "node:process";
|
|
14
|
-
//#region src/schema.image-alias.ts
|
|
15
|
-
const IMAGE_ALIAS_KEY_REGEX = /^@[A-Za-z0-9_-]*$/;
|
|
16
|
-
const IMAGE_ALIAS_VALUE_REGEX = /^(?!\/|[A-Za-z]:[\\\/])(?!.*:\/\/).*$/;
|
|
17
|
-
const ImageAliasKey = z.string().min(1, "Image alias key cannot be empty").regex(IMAGE_ALIAS_KEY_REGEX, "Image alias key must match /^@\\w+$/");
|
|
18
|
-
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)");
|
|
19
|
-
const ImageAliasesSchema = z.record(ImageAliasKey, ImageAliasValue).meta({
|
|
20
|
-
id: "ImageAliases",
|
|
21
|
-
description: "Map of image alias prefixes to relative paths (keys must match /^@\\w+$/; values must be relative paths without protocol or leading slash)."
|
|
22
|
-
});
|
|
23
|
-
//#endregion
|
|
24
|
-
//#region src/schema.include.ts
|
|
25
|
-
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)");
|
|
26
|
-
const IncludeSchema = z.strictObject({
|
|
27
|
-
paths: z.array(IncludePathValue).meta({ description: [
|
|
28
|
-
"Additional relative directory paths to include LikeC4 source files from, searched recursively.",
|
|
29
|
-
"Paths are relative to the project folder (the folder containing this config file).",
|
|
30
|
-
"Example: [\"../shared\", \"../common/specs\"]"
|
|
31
|
-
].join("\n") }),
|
|
32
|
-
maxDepth: z.number().int().min(1).max(20).default(3).meta({ description: [
|
|
33
|
-
"Maximum directory depth to scan when searching for .c4 files in include paths.",
|
|
34
|
-
"Prevents excessive scanning of deeply nested directories.",
|
|
35
|
-
"Default: 3"
|
|
36
|
-
].join("\n") }),
|
|
37
|
-
fileThreshold: z.number().int().min(1).max(1e4).default(30).meta({ description: [
|
|
38
|
-
"Maximum number of files to load from include paths before warning.",
|
|
39
|
-
"Helps identify performance issues from accidentally including large directories.",
|
|
40
|
-
"Default: 30"
|
|
41
|
-
].join("\n") })
|
|
42
|
-
}).meta({
|
|
43
|
-
id: "include-config",
|
|
44
|
-
description: ["Configuration for including additional LikeC4 source files from other directories.", "Example: { \"paths\": [\"../shared\", \"../common/specs\"], \"maxDepth\": 5, \"fileThreshold\": 50 }"].join("\n")
|
|
45
|
-
});
|
|
46
|
-
//#endregion
|
|
47
|
-
//#region src/schema.theme.ts
|
|
48
|
-
const opacity = z.int().min(0, "Opacity must be between 0 and 100").max(100, "Opacity must be between 0 and 100").meta({
|
|
49
|
-
id: "Opacity",
|
|
50
|
-
description: "Opacity 0-100%"
|
|
51
|
-
});
|
|
52
|
-
const shape = z.enum(ElementShapes).meta({ id: "ElementShape" });
|
|
53
|
-
const border = z.enum(BorderStyles).meta({ id: "BorderStyle" });
|
|
54
|
-
const size = z.enum(Sizes).meta({ id: "ElementSize" });
|
|
55
|
-
const iconPosition = z.enum(IconPositions).meta({ id: "IconPosition" });
|
|
56
|
-
const arrow = z.enum(RelationshipArrowTypes).meta({ id: "ArrowType" });
|
|
57
|
-
const line = z.enum([
|
|
58
|
-
"dashed",
|
|
59
|
-
"solid",
|
|
60
|
-
"dotted"
|
|
61
|
-
]).meta({ id: "LineType" });
|
|
62
|
-
const themeColor = z.enum(ThemeColors).meta({ id: "ThemeColorName" });
|
|
63
|
-
const customColor = z.custom().refine((v) => typeof v === "string", "Custom color name must be a string").transform((value) => value).meta({ id: "CustomColorName" });
|
|
64
|
-
const color = themeColor.or(customColor).transform((value) => value).meta({ id: "ColorName" });
|
|
65
|
-
const colorSchema = z.string().min(1, "Color value cannot be empty").meta({ id: "ColorLiteral" });
|
|
66
|
-
const ElementColorValuesSchema = z.strictObject({
|
|
67
|
-
fill: colorSchema.meta({ description: "Background color" }),
|
|
68
|
-
stroke: colorSchema.meta({ description: "Stroke color (border, paths above background)" }),
|
|
69
|
-
hiContrast: colorSchema.meta({ description: "High contrast text color (title)" }),
|
|
70
|
-
loContrast: colorSchema.meta({ description: "Low contrast text color (description)" })
|
|
71
|
-
}).meta({ id: "ElementColorValues" }).transform((value) => value);
|
|
72
|
-
const RelationshipColorValuesSchema = z.strictObject({
|
|
73
|
-
line: colorSchema.meta({ description: "Line color" }),
|
|
74
|
-
label: colorSchema.meta({ description: "Label text color" }),
|
|
75
|
-
labelBg: colorSchema.optional().default("rgba(0, 0, 0, 0.5)").meta({ description: "Label background color" })
|
|
76
|
-
}).meta({ id: "RelationshipColorValues" }).transform((value) => value);
|
|
77
|
-
const ThemeColorValuesSchema = z.strictObject({
|
|
78
|
-
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" }),
|
|
79
|
-
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" })
|
|
80
|
-
}).transform((value) => value).meta({
|
|
81
|
-
id: "StrictThemeColorValues",
|
|
82
|
-
description: "Exact color value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color value"
|
|
83
|
-
}).or(colorSchema.transform((v) => computeColorValues(v))).transform((value) => value).meta({
|
|
84
|
-
id: "ThemeColorValues",
|
|
85
|
-
description: "Exact value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color values"
|
|
86
|
-
});
|
|
87
|
-
const ThemeColorsSchema = z.partialRecord(color, ThemeColorValuesSchema).transform((value) => value);
|
|
88
|
-
const DimensionsSchema = z.strictObject({
|
|
89
|
-
width: z.number().min(50),
|
|
90
|
-
height: z.number().min(50)
|
|
91
|
-
}).meta({
|
|
92
|
-
id: "Dimensions",
|
|
93
|
-
description: "Defines dimensions for theme size"
|
|
94
|
-
});
|
|
95
|
-
const LikeC4Config_Styles_Theme_Sizes = z.partialRecord(size, DimensionsSchema);
|
|
96
|
-
const LikeC4Config_Styles_Theme = z.strictObject({
|
|
97
|
-
colors: ThemeColorsSchema.optional().meta({ description: "Override theme colors" }),
|
|
98
|
-
sizes: LikeC4Config_Styles_Theme_Sizes.optional().meta({ description: "Override theme sizes" })
|
|
99
|
-
}).meta({
|
|
100
|
-
id: "ThemeCustomization",
|
|
101
|
-
description: "Customize theme colors and sizes"
|
|
102
|
-
}).transform(({ colors, sizes }) => {
|
|
103
|
-
return exact({
|
|
104
|
-
colors: colors ? exact(colors) : void 0,
|
|
105
|
-
sizes: sizes ? exact(sizes) : void 0
|
|
106
|
-
});
|
|
107
|
-
});
|
|
108
|
-
const LikeC4Config_Styles_Defaults_Group = z.strictObject({
|
|
109
|
-
color: color.optional().meta({ description: "Default color for groups\n(must be a valid color name from the theme)" }),
|
|
110
|
-
opacity: opacity.optional().meta({ description: "Default opacity for groups" }),
|
|
111
|
-
border: border.optional().meta({ description: "Default border for groups" })
|
|
112
|
-
}).meta({ id: "GroupDefaultStyleValues" });
|
|
113
|
-
const LikeC4Config_Styles_Defaults_Relationship = z.strictObject({
|
|
114
|
-
color: color.optional().meta({ description: "Default color for relationships\n(must be a valid color name from the theme)" }),
|
|
115
|
-
line: line.optional().meta({ description: "Default line style for relationships" }),
|
|
116
|
-
arrow: arrow.optional().meta({ description: "Default arrow style for relationships" })
|
|
117
|
-
}).meta({
|
|
118
|
-
id: "RelationshipDefaultStyleValues",
|
|
119
|
-
description: "Override default values for relationship style properties\nThese values will be used if such property is not defined"
|
|
120
|
-
});
|
|
121
|
-
const LikeC4Config_Styles_Defaults = z.strictObject({
|
|
122
|
-
color: color.optional().meta({ description: "Default color for elements\n(must be a valid color name from the theme)" }),
|
|
123
|
-
opacity: opacity.optional().meta({ description: "Default opacity (0-100%) for elements when displayed as a group (like a container)" }),
|
|
124
|
-
border: border.optional().meta({ description: "Default border style for elements when displayed as a group (like a container)" }),
|
|
125
|
-
size: size.optional().meta({ description: "Default size for elements" }),
|
|
126
|
-
shape: shape.optional().meta({ description: "Default shape for elements" }),
|
|
127
|
-
iconPosition: iconPosition.optional().meta({ description: "Default icon position for elements" }),
|
|
128
|
-
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" }),
|
|
129
|
-
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" })
|
|
130
|
-
}).meta({ id: "DefaultStyleValues" });
|
|
131
|
-
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" });
|
|
132
|
-
const LikeC4StylesConfigSchema = z.strictObject({
|
|
133
|
-
theme: LikeC4Config_Styles_Theme.optional().meta({ description: "Project theme customization" }),
|
|
134
|
-
defaults: LikeC4Config_Styles_Defaults.optional().meta({ description: "Override default values for style properties\nThese values will be used if such property is not defined" }),
|
|
135
|
-
customCss: LikeC4Config_Styles_CustomStylesheets.optional().meta({ description: "Custom CSS (or list of CSS files) to be included in the generated diagrams" })
|
|
136
|
-
}).transform(({ theme, defaults, customCss }) => exact({
|
|
137
|
-
defaults: normalizeDefaults(defaults),
|
|
138
|
-
customCss: normalizeStylesheets(customCss),
|
|
139
|
-
theme
|
|
140
|
-
}));
|
|
141
|
-
function normalizeDefaults(defaults) {
|
|
142
|
-
if (!defaults) return;
|
|
143
|
-
const { relationship, group, ...rest } = defaults;
|
|
144
|
-
return exact({
|
|
145
|
-
...rest,
|
|
146
|
-
relationship: relationship && exact(relationship),
|
|
147
|
-
group: group && exact(group)
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
function normalizeStylesheets(stylesheets) {
|
|
151
|
-
if (!stylesheets) return;
|
|
152
|
-
const paths = (Array.isArray(stylesheets) ? stylesheets : [stylesheets]).filter(Boolean);
|
|
153
|
-
if (paths.length === 0) return;
|
|
154
|
-
return {
|
|
155
|
-
paths,
|
|
156
|
-
content: ""
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
//#endregion
|
|
160
|
-
//#region src/schema.ts
|
|
161
|
-
const ManualLayoutsConfigSchema = z.strictObject({ outDir: z.string().default(".likec4").meta({ description: [
|
|
162
|
-
"Path to the directory where manual layouts will be stored,",
|
|
163
|
-
"relative to the folder containing the project config. ",
|
|
164
|
-
"",
|
|
165
|
-
"Defaults to '.likec4'."
|
|
166
|
-
].join("\n") }) }).meta({
|
|
167
|
-
id: "ManualLayoutsConfig",
|
|
168
|
-
description: "Configuration for manual layouts"
|
|
169
|
-
});
|
|
170
|
-
const LandingPageSchema = z.union([
|
|
171
|
-
z.strictObject({ redirect: z.literal(true) }),
|
|
172
|
-
z.strictObject({ include: z.array(z.string().nonempty().refine((s) => s !== "#", { message: "selector cannot be \"#\"" })).nonempty("include list cannot be empty") }),
|
|
173
|
-
z.strictObject({ exclude: z.array(z.string().nonempty().refine((s) => s !== "#", { message: "selector cannot be \"#\"" })).nonempty("exclude list cannot be empty") })
|
|
174
|
-
]).meta({
|
|
175
|
-
id: "LandingPageConfig",
|
|
176
|
-
description: "Configure the landing page. Use redirect to go to the index view, or include/exclude to filter the view grid."
|
|
177
|
-
});
|
|
178
|
-
const LikeC4ProjectJsonConfigSchema = z.object({
|
|
179
|
-
name: z.string().nonempty("Project name cannot be empty").refine((value) => value !== "default", {
|
|
180
|
-
abort: true,
|
|
181
|
-
error: "Project name cannot be \"default\""
|
|
182
|
-
}).refine((value) => !value.includes(".") && !value.includes("@") && !value.includes("#"), {
|
|
183
|
-
abort: true,
|
|
184
|
-
error: "Project name cannot contain \".\", \"@\" or \"#\", try to use A-z, 0-9, _ and -"
|
|
185
|
-
}).meta({ description: "Project name, must be unique in the workspace" }),
|
|
186
|
-
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" }),
|
|
187
|
-
title: z.string().nonempty("Project title cannot be empty if specified").optional().meta({ description: "A human readable title for the project" }),
|
|
188
|
-
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" }),
|
|
189
|
-
metadata: z.record(z.string(), z.any()).optional().meta({ description: "Arbitrary metadata as key-value pairs for custom project information" }),
|
|
190
|
-
styles: LikeC4StylesConfigSchema.optional().meta({ description: "Project styles customization" }),
|
|
191
|
-
imageAliases: ImageAliasesSchema.optional(),
|
|
192
|
-
include: IncludeSchema.optional(),
|
|
193
|
-
exclude: z.array(z.string()).optional().meta({ description: "List of file patterns to exclude from the project, default is [\"**/node_modules/**\"]" }),
|
|
194
|
-
manualLayouts: ManualLayoutsConfigSchema.optional(),
|
|
195
|
-
inferTechnologyFromIcon: z.boolean().optional().meta({ description: [
|
|
196
|
-
"Automatically derive element technology from icon name when technology is not set explicitly.",
|
|
197
|
-
"Applies to aws:, azure:, gcp:, and tech: icons. Bootstrap icons are excluded.",
|
|
198
|
-
"Defaults to true."
|
|
199
|
-
].join("\n") }),
|
|
200
|
-
implicitViews: z.boolean().optional().meta({ description: "Auto-generate scoped views for elements without explicit views. Defaults to false." }),
|
|
201
|
-
landingPage: LandingPageSchema.optional()
|
|
202
|
-
}).meta({
|
|
203
|
-
id: "LikeC4ProjectConfig",
|
|
204
|
-
description: "LikeC4 Project Configuration"
|
|
205
|
-
});
|
|
206
|
-
const FunctionType = z.instanceof(Function);
|
|
207
|
-
const GeneratorsSchema = z.record(z.string(), FunctionType);
|
|
208
|
-
const LikeC4ProjectConfigSchema = LikeC4ProjectJsonConfigSchema.extend({ generators: GeneratorsSchema.optional() });
|
|
209
|
-
/**
|
|
210
|
-
* Validates Object into a LikeC4ProjectConfig object.
|
|
211
|
-
* Zod v4 can strip optional union keys (e.g. landingPage) from parse output;
|
|
212
|
-
* we validate landingPage once with LandingPageSchema and merge onto the result.
|
|
213
|
-
*/
|
|
214
|
-
function validateProjectConfig(config) {
|
|
215
|
-
const inputLandingPage = config["landingPage"];
|
|
216
|
-
let validatedLandingPage = null;
|
|
217
|
-
if (inputLandingPage != null) {
|
|
218
|
-
const lpResult = LandingPageSchema.safeParse(inputLandingPage);
|
|
219
|
-
if (!lpResult.success) throw new Error("Config validation failed:\n" + z.prettifyError(lpResult.error));
|
|
220
|
-
validatedLandingPage = lpResult.data;
|
|
221
|
-
}
|
|
222
|
-
const parsed = LikeC4ProjectJsonConfigSchema.safeParse(config);
|
|
223
|
-
if (!parsed.success) throw new Error("Config validation failed:\n" + z.prettifyError(parsed.error));
|
|
224
|
-
let data = parsed.data;
|
|
225
|
-
if (validatedLandingPage !== null) data = {
|
|
226
|
-
...data,
|
|
227
|
-
landingPage: validatedLandingPage
|
|
228
|
-
};
|
|
229
|
-
const generatorsInput = config["generators"];
|
|
230
|
-
if (generatorsInput != null && typeof generatorsInput === "object" && !Array.isArray(generatorsInput)) {
|
|
231
|
-
const genParsed = GeneratorsSchema.safeParse(generatorsInput);
|
|
232
|
-
if (!genParsed.success) throw new Error("Config validation failed (generators):\n" + z.prettifyError(genParsed.error));
|
|
233
|
-
return {
|
|
234
|
-
...data,
|
|
235
|
-
generators: genParsed.data
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
return data;
|
|
239
|
-
}
|
|
240
|
-
/**
|
|
241
|
-
* Parses JSON string into a LikeC4ProjectConfig object.
|
|
242
|
-
* Does not process "extends" - use `loadConfig` function instead
|
|
243
|
-
*/
|
|
244
|
-
function parseProjectConfigJSON(config) {
|
|
245
|
-
return validateProjectConfig(JSON5.parse(config.trim() || "{}"));
|
|
246
|
-
}
|
|
247
|
-
const LikeC4ProjectConfigOps = {
|
|
248
|
-
parse: parseProjectConfigJSON,
|
|
249
|
-
validate: validateProjectConfig,
|
|
250
|
-
normalizeInclude: (include) => {
|
|
251
|
-
const parsed = IncludeSchema.safeParse(include);
|
|
252
|
-
if (parsed.success) return parsed.data;
|
|
253
|
-
return {
|
|
254
|
-
paths: [],
|
|
255
|
-
maxDepth: 3,
|
|
256
|
-
fileThreshold: 30
|
|
257
|
-
};
|
|
258
|
-
}
|
|
259
|
-
};
|
|
260
|
-
//#endregion
|
|
261
|
-
//#region src/filenames.ts
|
|
262
|
-
/** Trim trailing slashes and backslashes (no regex, avoids S5852 ReDoS). */
|
|
263
|
-
function trimTrailingSlashes(s) {
|
|
264
|
-
let end = s.length;
|
|
265
|
-
while (end > 0 && (s[end - 1] === "/" || s[end - 1] === "\\")) end--;
|
|
266
|
-
return s.slice(0, end);
|
|
267
|
-
}
|
|
268
|
-
/** Split by / or \ without regex (avoids S5852 ReDoS). */
|
|
269
|
-
function splitPath(s) {
|
|
270
|
-
return s.split("/").flatMap((part) => part.split("\\"));
|
|
271
|
-
}
|
|
272
|
-
/** basename compatible with Node and browser (no node:path for Vite/playground bundle). */
|
|
273
|
-
function basename$1(path) {
|
|
274
|
-
const trimmed = trimTrailingSlashes(path);
|
|
275
|
-
const segments = splitPath(trimmed);
|
|
276
|
-
return segments[segments.length - 1] || trimmed;
|
|
277
|
-
}
|
|
278
|
-
/** Known LikeC4 JSON config filenames (RC and .json). */
|
|
279
|
-
const configJsonFilenames = [
|
|
280
|
-
".likec4rc",
|
|
281
|
-
".likec4.config.json",
|
|
282
|
-
"likec4.config.json"
|
|
283
|
-
];
|
|
284
|
-
/** Known LikeC4 non-JSON config filenames (JS, MJS, TS, MTS). */
|
|
285
|
-
const configNonJsonFilenames = [
|
|
286
|
-
"likec4.config.js",
|
|
287
|
-
"likec4.config.cjs",
|
|
288
|
-
"likec4.config.mjs",
|
|
289
|
-
"likec4.config.ts",
|
|
290
|
-
"likec4.config.cts",
|
|
291
|
-
"likec4.config.mts"
|
|
292
|
-
];
|
|
293
|
-
/** All known LikeC4 config filenames (JSON and non-JSON). */
|
|
294
|
-
const ConfigFilenames = [...configJsonFilenames, ...configNonJsonFilenames];
|
|
295
|
-
/** Returns true if the **basename** of the given path matches a known config filename. */
|
|
296
|
-
function isLikeC4JsonConfig(filename) {
|
|
297
|
-
return configJsonFilenames.includes(basename$1(filename));
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Returns true if the **basename** of the given path matches a known non-JSON config filename (JS, MJS, TS, MTS).
|
|
301
|
-
*/
|
|
302
|
-
function isLikeC4NonJsonConfig(filename) {
|
|
303
|
-
return configNonJsonFilenames.includes(basename$1(filename));
|
|
304
|
-
}
|
|
305
|
-
/**
|
|
306
|
-
* Returns true if the **basename** of the given path matches a known LikeC4 config file (JSON or non-JSON).
|
|
307
|
-
*/
|
|
308
|
-
function isLikeC4Config(filename) {
|
|
309
|
-
return isLikeC4JsonConfig(filename) || isLikeC4NonJsonConfig(filename);
|
|
310
|
-
}
|
|
311
|
-
//#endregion
|
|
312
|
-
//#region src/define-config.ts
|
|
313
|
-
/**
|
|
314
|
-
* Defines LikeC4 Project, allows custom generators that can be executed using CLI:
|
|
315
|
-
*
|
|
316
|
-
* `$ likec4 gen <generator-name>`
|
|
317
|
-
*
|
|
318
|
-
* or VSCode command `LikeC4: Run code generator`
|
|
319
|
-
*
|
|
320
|
-
* @example
|
|
321
|
-
* ```ts
|
|
322
|
-
* export default defineConfig({
|
|
323
|
-
* name: 'my-project',
|
|
324
|
-
* title: 'My Project',
|
|
325
|
-
*
|
|
326
|
-
* exclude: ['picomatch pattern'],
|
|
327
|
-
* generators: {
|
|
328
|
-
* '<generator-name>': async ({ likec4model, ctx }) => {
|
|
329
|
-
* await ctx.write('my-generator.txt', likec4model.project.id)
|
|
330
|
-
* }
|
|
331
|
-
* }
|
|
332
|
-
* })
|
|
333
|
-
* ```
|
|
334
|
-
*/
|
|
335
|
-
function defineConfig(config) {
|
|
336
|
-
return LikeC4ProjectConfigSchema.parse(config);
|
|
337
|
-
}
|
|
338
|
-
/**
|
|
339
|
-
* Define reusable custom generators
|
|
340
|
-
*
|
|
341
|
-
* @example
|
|
342
|
-
* ```ts
|
|
343
|
-
* // generators.ts
|
|
344
|
-
* export default defineGenerators({
|
|
345
|
-
* 'my-generator': async ({ likec4model, ctx }) => {
|
|
346
|
-
* await ctx.write('my-generator.txt', likec4model.project.id)
|
|
347
|
-
* }
|
|
348
|
-
* })
|
|
349
|
-
*
|
|
350
|
-
* // likec4.config.ts
|
|
351
|
-
* import generators from './generators'
|
|
352
|
-
*
|
|
353
|
-
* export default defineConfig({
|
|
354
|
-
* name: 'my-project',
|
|
355
|
-
* generators,
|
|
356
|
-
* })
|
|
357
|
-
* ```
|
|
358
|
-
*/
|
|
359
|
-
function defineGenerators(generators) {
|
|
360
|
-
return GeneratorsSchema.parse(generators);
|
|
361
|
-
}
|
|
362
|
-
/**
|
|
363
|
-
* Define reusable custom theme color
|
|
364
|
-
* @example
|
|
365
|
-
* ```ts
|
|
366
|
-
* export default defineThemeColor({
|
|
367
|
-
* element: {
|
|
368
|
-
* fill: 'red'
|
|
369
|
-
* }
|
|
370
|
-
* })
|
|
371
|
-
* ```
|
|
372
|
-
*/
|
|
373
|
-
function defineThemeColor(colors) {
|
|
374
|
-
return ThemeColorValuesSchema.parse(colors);
|
|
375
|
-
}
|
|
376
|
-
/**
|
|
377
|
-
* Define reusable custom theme
|
|
378
|
-
* @example
|
|
379
|
-
* ```ts
|
|
380
|
-
* import { defineThemeColor, defineTheme } from 'likec4/config'
|
|
381
|
-
*
|
|
382
|
-
* export default defineTheme({
|
|
383
|
-
* colors: {
|
|
384
|
-
* primary: '#FF0000',
|
|
385
|
-
* // Or use defineThemeColor
|
|
386
|
-
* red: defineThemeColor({
|
|
387
|
-
* elements: {
|
|
388
|
-
* fill: 'red'
|
|
389
|
-
* }
|
|
390
|
-
* })
|
|
391
|
-
* }
|
|
392
|
-
* })
|
|
393
|
-
* ```
|
|
394
|
-
*/
|
|
395
|
-
function defineTheme(theme) {
|
|
396
|
-
return LikeC4Config_Styles_Theme.parse(theme);
|
|
397
|
-
}
|
|
398
|
-
/**
|
|
399
|
-
* Define reusable custom style
|
|
400
|
-
* @example
|
|
401
|
-
* ```ts
|
|
402
|
-
* import { defineStyle, defineThemeColor } from 'likec4/config'
|
|
403
|
-
*
|
|
404
|
-
* export default defineStyle({
|
|
405
|
-
* theme: {
|
|
406
|
-
* colors: {
|
|
407
|
-
* red: defineThemeColor({
|
|
408
|
-
* elements: {
|
|
409
|
-
* fill: 'red'
|
|
410
|
-
* }
|
|
411
|
-
* })
|
|
412
|
-
* }
|
|
413
|
-
* },
|
|
414
|
-
* defaults: {
|
|
415
|
-
* color: 'red',
|
|
416
|
-
* opacity: 50,
|
|
417
|
-
* border: 'solid',
|
|
418
|
-
* size: 'sm',
|
|
419
|
-
* relationship: {
|
|
420
|
-
* color: 'grey',
|
|
421
|
-
* line: 'solid',
|
|
422
|
-
* }
|
|
423
|
-
* }
|
|
424
|
-
* })
|
|
425
|
-
*/
|
|
426
|
-
function defineStyle(styles) {
|
|
427
|
-
return LikeC4StylesConfigSchema.parse(styles);
|
|
428
|
-
}
|
|
429
|
-
//#endregion
|
|
430
|
-
//#region src/node/load-config.ts
|
|
431
|
-
const JsonConfigInputSchema = LikeC4ProjectJsonConfigSchema.pick({
|
|
432
|
-
extends: true,
|
|
433
|
-
styles: true
|
|
434
|
-
}).loose();
|
|
435
|
-
const normalizeExtends = (value) => {
|
|
436
|
-
if (!value) return [];
|
|
437
|
-
return Array.isArray(value) ? value : [value];
|
|
438
|
-
};
|
|
439
|
-
const parseJsonConfig = async (filepath) => {
|
|
440
|
-
const content = await fs.readFile(filepath, "utf-8");
|
|
441
|
-
let parsed;
|
|
442
|
-
try {
|
|
443
|
-
parsed = JSON5.parse(content.trim() || "{}");
|
|
444
|
-
} catch (e) {
|
|
445
|
-
throw wrapError(e, `${filepath}:`);
|
|
446
|
-
}
|
|
447
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error(`${filepath}: Config must be a JSON object`);
|
|
448
|
-
const result = JsonConfigInputSchema.safeParse(parsed);
|
|
449
|
-
if (!result.success) throw new Error(`${filepath}: Invalid config\n` + z.prettifyError(result.error));
|
|
450
|
-
return result.data;
|
|
451
|
-
};
|
|
452
|
-
const loadJsonConfigs = async (filepath, stack) => {
|
|
453
|
-
if (stack.includes(filepath)) {
|
|
454
|
-
const cycleStart = stack.indexOf(filepath);
|
|
455
|
-
const cycle = [...stack.slice(cycleStart), filepath].join(" -> ");
|
|
456
|
-
throw new Error(`Config extends cycle detected: ${cycle}`);
|
|
457
|
-
}
|
|
458
|
-
const parsed = await parseJsonConfig(filepath);
|
|
459
|
-
const extendsPaths = normalizeExtends(parsed.extends);
|
|
460
|
-
const nextStack = [...stack, filepath];
|
|
461
|
-
const configs = [];
|
|
462
|
-
for (const extendPath of extendsPaths) {
|
|
463
|
-
const resolvedPath = resolve(dirname(filepath), extendPath);
|
|
464
|
-
configs.push(...await loadJsonConfigs(resolvedPath, nextStack));
|
|
465
|
-
}
|
|
466
|
-
return [...configs, parsed];
|
|
467
|
-
};
|
|
468
|
-
/**
|
|
469
|
-
* Load LikeC4 Project config file.
|
|
470
|
-
* If filepath is a non-JSON file, it will be bundled and required
|
|
471
|
-
*/
|
|
472
|
-
async function loadConfig(filepath) {
|
|
473
|
-
filepath = typeof filepath === "string" ? filepath : filepath.fsPath;
|
|
474
|
-
logger.getChild("config").debug`Loading config: ${relative(cwd(), filepath)}`;
|
|
475
|
-
const folder = dirname(filepath);
|
|
476
|
-
const filename = basename(filepath);
|
|
477
|
-
const implicitcfg = { name: basename(folder) };
|
|
478
|
-
if (isLikeC4JsonConfig(filename)) {
|
|
479
|
-
const configs = await loadJsonConfigs(resolve(filepath), []);
|
|
480
|
-
invariant(t(configs, 1), "Expect at least one config");
|
|
481
|
-
const rootConfig = n(t$1(configs), ["extends", "styles"]);
|
|
482
|
-
const stylesChain = configs.map((config) => config.styles).filter(e);
|
|
483
|
-
const mergedStyles = stylesChain.length > 0 ? defu({}, ...stylesChain.reverse()) : void 0;
|
|
484
|
-
return validateProjectConfig({
|
|
485
|
-
...implicitcfg,
|
|
486
|
-
...rootConfig,
|
|
487
|
-
...mergedStyles ? { styles: mergedStyles } : {}
|
|
488
|
-
});
|
|
489
|
-
}
|
|
490
|
-
invariant(isLikeC4NonJsonConfig(filename), `Invalid name for config file: ${filepath}`);
|
|
491
|
-
const { mod } = await bundleRequire({
|
|
492
|
-
filepath,
|
|
493
|
-
cwd: folder,
|
|
494
|
-
esbuildOptions: {
|
|
495
|
-
resolveExtensions: [
|
|
496
|
-
".ts",
|
|
497
|
-
".mts",
|
|
498
|
-
".cts",
|
|
499
|
-
".mjs",
|
|
500
|
-
".js",
|
|
501
|
-
".cjs"
|
|
502
|
-
],
|
|
503
|
-
plugins: [{
|
|
504
|
-
name: "likec4-config",
|
|
505
|
-
setup(build) {
|
|
506
|
-
/**
|
|
507
|
-
* Intercept @likec4/config and likec4/config imports
|
|
508
|
-
*/
|
|
509
|
-
build.onResolve({ filter: /^@?likec4\/config$/ }, (args) => ({
|
|
510
|
-
path: args.path,
|
|
511
|
-
namespace: "likec4-config"
|
|
512
|
-
}));
|
|
513
|
-
build.onEnd((result) => {
|
|
514
|
-
const messages = formatMessagesSync(result.errors, { kind: "error" });
|
|
515
|
-
for (const message of messages) logger.error(message);
|
|
516
|
-
});
|
|
517
|
-
/**
|
|
518
|
-
* Mock implementation, this allows to skip redundant bundling @likec4/config
|
|
519
|
-
*/
|
|
520
|
-
build.onLoad({
|
|
521
|
-
filter: /.*/,
|
|
522
|
-
namespace: "likec4-config"
|
|
523
|
-
}, (_args) => {
|
|
524
|
-
return {
|
|
525
|
-
contents: `
|
|
1
|
+
import"../chunks/rolldown-runtime.mjs";import{a as e,c as t,d as n,f as r,i,l as a,n as o,o as s,p as c,r as l,s as u,t as d,u as f}from"../chunks/src.mjs";import p from"json5";import m from"zod/v4";import{invariant as h}from"@likec4/core";import{logger as g,wrapError as _}from"@likec4/log";import{bundleRequire as v}from"bundle-require";import{defu as y}from"defu";import{formatMessagesSync as b}from"esbuild";import*as x from"node:fs/promises";import{basename as S,dirname as C,relative as w,resolve as T}from"node:path";import{cwd as E}from"node:process";import{hasAtLeast as D,isNonNullish as O,last as k,omit as A}from"remeda";const j=n.pick({extends:!0,styles:!0}).loose(),normalizeExtends=e=>e?Array.isArray(e)?e:[e]:[],parseJsonConfig=async e=>{let t=await x.readFile(e,`utf-8`),n;try{n=p.parse(t.trim()||`{}`)}catch(t){throw _(t,`${e}:`)}if(!n||typeof n!=`object`||Array.isArray(n))throw Error(`${e}: Config must be a JSON object`);let r=j.safeParse(n);if(!r.success)throw Error(`${e}: Invalid config\n`+m.prettifyError(r.error));return r.data},loadJsonConfigs=async(e,t)=>{if(t.includes(e)){let n=t.indexOf(e),r=[...t.slice(n),e].join(` -> `);throw Error(`Config extends cycle detected: ${r}`)}let n=await parseJsonConfig(e),r=normalizeExtends(n.extends),i=[...t,e],a=[];for(let t of r){let n=T(C(e),t);a.push(...await loadJsonConfigs(n,i))}return[...a,n]};async function loadConfig(e){e=typeof e==`string`?e:e.fsPath,g.getChild(`config`).trace`Loading config: ${w(E(),e)}`;let n=C(e),i=S(e),o={name:S(n)};if(t(i)){let t=await loadJsonConfigs(T(e),[]);h(D(t,1),`Expect at least one config`);let n=A(k(t),[`extends`,`styles`]),i=t.map(e=>e.styles).filter(O),a=i.length>0?y({},...i.reverse()):void 0;return r({...o,...n,...a?{styles:a}:{}})}h(a(i),`Invalid name for config file: ${e}`);let{mod:s}=await v({filepath:e,cwd:n,esbuildOptions:{resolveExtensions:[`.ts`,`.mts`,`.cts`,`.mjs`,`.js`,`.cjs`],plugins:[{name:`likec4-config`,setup(e){e.onResolve({filter:/^@?likec4\/config$/},e=>({path:e.path,namespace:`likec4-config`})),e.onEnd(e=>{let t=b(e.errors,{kind:`error`});for(let e of t)g.error(e)}),e.onLoad({filter:/.*/,namespace:`likec4-config`},e=>({contents:`
|
|
526
2
|
// Mock implementation to allow loading config files without bundling @likec4/config
|
|
527
3
|
function mock(x) { return x }
|
|
528
4
|
export {
|
|
@@ -531,15 +7,4 @@ export {
|
|
|
531
7
|
mock as defineStyle,
|
|
532
8
|
mock as defineTheme,
|
|
533
9
|
mock as defineThemeColor,
|
|
534
|
-
}`,
|
|
535
|
-
loader: "js"
|
|
536
|
-
};
|
|
537
|
-
});
|
|
538
|
-
}
|
|
539
|
-
}]
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
return validateProjectConfig(Object.assign(implicitcfg, mod?.default ?? mod));
|
|
543
|
-
}
|
|
544
|
-
//#endregion
|
|
545
|
-
export { ConfigFilenames, LikeC4ProjectConfigOps, LikeC4StylesConfigSchema, defineConfig, defineGenerators, defineStyle, defineTheme, defineThemeColor, isLikeC4Config, isLikeC4JsonConfig, isLikeC4NonJsonConfig, loadConfig };
|
|
10
|
+
}`,loader:`js`}))}}]}});return r(Object.assign(o,s?.default??s))}export{s as ConfigFilenames,f as LikeC4ProjectConfigOps,c as LikeC4StylesConfigSchema,d as defineConfig,o as defineGenerators,l as defineStyle,i as defineTheme,e as defineThemeColor,u as isLikeC4Config,t as isLikeC4JsonConfig,a as isLikeC4NonJsonConfig,loadConfig};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.59.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://likec4.dev",
|
|
6
6
|
"author": "Denis Davydkov <denis@davydkov.com>",
|
|
@@ -54,14 +54,17 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"json5": "^2.2.3",
|
|
57
|
-
"zod": "^4.3
|
|
57
|
+
"zod": "^4.4.3",
|
|
58
|
+
"remeda": "^2.37.0",
|
|
59
|
+
"defu": "^6.1.7",
|
|
60
|
+
"ufo": "1.6.4",
|
|
58
61
|
"type-fest": "^4.41.0",
|
|
59
|
-
"@likec4/core": "1.
|
|
60
|
-
"@likec4/log": "1.
|
|
62
|
+
"@likec4/core": "1.59.0",
|
|
63
|
+
"@likec4/log": "1.59.0"
|
|
61
64
|
},
|
|
62
65
|
"peerDependencies": {
|
|
63
66
|
"bundle-require": "^5.1.0",
|
|
64
|
-
"esbuild": "0.
|
|
67
|
+
"esbuild": "0.28.1"
|
|
65
68
|
},
|
|
66
69
|
"peerDependenciesMeta": {
|
|
67
70
|
"esbuild": {
|
|
@@ -73,22 +76,19 @@
|
|
|
73
76
|
},
|
|
74
77
|
"devDependencies": {
|
|
75
78
|
"@types/node": "~22.19.19",
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
81
|
-
"
|
|
82
|
-
"
|
|
83
|
-
"
|
|
84
|
-
"vitest": "4.1.3",
|
|
85
|
-
"@likec4/tsconfig": "1.57.0",
|
|
86
|
-
"@likec4/devops": "1.57.0"
|
|
79
|
+
"tsx": "4.22.5",
|
|
80
|
+
"turbo": "2.10.5",
|
|
81
|
+
"typescript": "6.0.3",
|
|
82
|
+
"tsdown": "^0.22.9",
|
|
83
|
+
"nano-spawn": "^2.1.0",
|
|
84
|
+
"vitest": "4.1.9",
|
|
85
|
+
"@likec4/tsconfig": "1.59.0",
|
|
86
|
+
"@likec4/devops": "1.59.0"
|
|
87
87
|
},
|
|
88
88
|
"scripts": {
|
|
89
89
|
"generate": "tsx --conditions=sources scripts/generate.mts",
|
|
90
90
|
"typecheck": "tsc -b --verbose",
|
|
91
|
-
"build": "
|
|
91
|
+
"build": "tsdown",
|
|
92
92
|
"lint:package": "pnpx publint ./package.tgz",
|
|
93
93
|
"clean": "likec4ops clean",
|
|
94
94
|
"pack": "pnpm pack"
|
package/src/node/load-config.ts
CHANGED
|
@@ -70,7 +70,7 @@ const loadJsonConfigs = async (filepath: string, stack: string[]): Promise<[...J
|
|
|
70
70
|
*/
|
|
71
71
|
export async function loadConfig(filepath: VscodeURI | string): Promise<LikeC4ProjectConfig> {
|
|
72
72
|
filepath = typeof filepath === 'string' ? filepath : filepath.fsPath
|
|
73
|
-
logger.getChild('config').
|
|
73
|
+
logger.getChild('config').trace`Loading config: ${relative(cwd(), filepath)}`
|
|
74
74
|
|
|
75
75
|
const folder = dirname(filepath)
|
|
76
76
|
const filename = basename(filepath)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Licenses of Bundled Dependencies
|
|
2
|
-
|
|
3
|
-
The published artifact additionally contains code with the following licenses:
|
|
4
|
-
MIT
|
|
5
|
-
|
|
6
|
-
# Bundled Dependencies
|
|
7
|
-
|
|
8
|
-
## defu
|
|
9
|
-
|
|
10
|
-
License: MIT
|
|
11
|
-
Repository: https://github.com/unjs/defu
|
|
12
|
-
|
|
13
|
-
> MIT License
|
|
14
|
-
>
|
|
15
|
-
> Copyright (c) Pooya Parsa <pooya@pi0.io>
|
|
16
|
-
>
|
|
17
|
-
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
-
> of this software and associated documentation files (the "Software"), to deal
|
|
19
|
-
> in the Software without restriction, including without limitation the rights
|
|
20
|
-
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
-
> copies of the Software, and to permit persons to whom the Software is
|
|
22
|
-
> furnished to do so, subject to the following conditions:
|
|
23
|
-
>
|
|
24
|
-
> The above copyright notice and this permission notice shall be included in all
|
|
25
|
-
> copies or substantial portions of the Software.
|
|
26
|
-
>
|
|
27
|
-
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
-
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
-
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
-
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
-
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
-
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
-
> SOFTWARE.
|
|
34
|
-
|
|
35
|
-
---------------------------------------
|
|
36
|
-
|
|
37
|
-
## remeda
|
|
38
|
-
|
|
39
|
-
License: MIT
|
|
40
|
-
By: Łukasz Sentkiewicz
|
|
41
|
-
Repository: https://github.com/remeda/remeda
|