@likec4/config 1.49.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/README.md +1 -0
- package/dist/_chunks/libs/remeda.mjs +18 -12
- package/dist/index.d.mts +10 -10
- package/dist/index.mjs +142 -2
- package/dist/node/index.d.mts +10 -10
- package/dist/node/index.mjs +157 -7
- package/package.json +8 -9
- package/schema.json +16 -0
- package/src/filenames.ts +28 -9
- package/src/schema.ts +24 -0
package/README.md
CHANGED
|
@@ -180,6 +180,7 @@ Fields:
|
|
|
180
180
|
- `name` (required): unique project id within the workspace
|
|
181
181
|
- `title` (optional): human-readable project title
|
|
182
182
|
- `contactPerson` (optional): maintainer/author
|
|
183
|
+
- `metadata` (optional): arbitrary project-level key-value pairs
|
|
183
184
|
- `extends` (optional): string or array of strings, paths to JSON configs to merge `styles` from (relative to each config file)
|
|
184
185
|
- `styles` (optional): theme/defaults/customCss customization
|
|
185
186
|
- `exclude` (optional): array of glob patterns (picomatch) to exclude (defaults to `['**/node_modules/**']`)
|
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
function e$
|
|
1
|
+
function e$2(e, t, n) {
|
|
2
2
|
let r = (n) => e(n, ...t);
|
|
3
3
|
return n === void 0 ? r : Object.assign(r, {
|
|
4
4
|
lazy: n,
|
|
5
5
|
lazyArgs: t
|
|
6
6
|
});
|
|
7
7
|
}
|
|
8
|
-
function t$
|
|
8
|
+
function t$3(t, n, r) {
|
|
9
9
|
let i = t.length - n.length;
|
|
10
10
|
if (i === 0) return t(...n);
|
|
11
|
-
if (i === 1) return e$
|
|
11
|
+
if (i === 1) return e$2(t, n, r);
|
|
12
12
|
throw Error(`Wrong number of arguments`);
|
|
13
13
|
}
|
|
14
|
-
function t$
|
|
15
|
-
return t$
|
|
14
|
+
function t$2(...t) {
|
|
15
|
+
return t$3(n$2, t);
|
|
16
16
|
}
|
|
17
17
|
const n$2 = (e, t) => e.length >= t;
|
|
18
|
-
function e(e) {
|
|
18
|
+
function e$1(e) {
|
|
19
19
|
return e != null;
|
|
20
20
|
}
|
|
21
|
-
function t(...t) {
|
|
22
|
-
return t$
|
|
21
|
+
function t$1(...t) {
|
|
22
|
+
return t$3(n$1, t);
|
|
23
23
|
}
|
|
24
24
|
const n$1 = (e) => e.at(-1);
|
|
25
25
|
function n(...t) {
|
|
26
|
-
return t$
|
|
26
|
+
return t$3(r, t);
|
|
27
27
|
}
|
|
28
28
|
function r(e, n) {
|
|
29
|
-
if (!t$
|
|
30
|
-
if (!t$
|
|
29
|
+
if (!t$2(n, 1)) return { ...e };
|
|
30
|
+
if (!t$2(n, 2)) {
|
|
31
31
|
let { [n[0]]: t, ...r } = e;
|
|
32
32
|
return r;
|
|
33
33
|
}
|
|
@@ -35,4 +35,10 @@ function r(e, n) {
|
|
|
35
35
|
for (let e of n) delete r[e];
|
|
36
36
|
return r;
|
|
37
37
|
}
|
|
38
|
-
|
|
38
|
+
new Set([
|
|
39
|
+
`-`,
|
|
40
|
+
`_`,
|
|
41
|
+
...` .
|
|
42
|
+
.\v.\f.\r. .
.\xA0. . . . . . . . . . . . .\u2028.\u2029. . . .`.split(`.`)
|
|
43
|
+
]);
|
|
44
|
+
export { t$2 as i, t$1 as n, e$1 as r, n as t };
|
package/dist/index.d.mts
CHANGED
|
@@ -25,6 +25,7 @@ declare const LikeC4ProjectJsonConfigSchema: z.ZodObject<{
|
|
|
25
25
|
extends: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
26
26
|
title: z.ZodOptional<z.ZodString>;
|
|
27
27
|
contactPerson: z.ZodOptional<z.ZodString>;
|
|
28
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
28
29
|
styles: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
29
30
|
theme: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
30
31
|
colors: z.ZodOptional<z.ZodPipe<z.ZodRecord<z.ZodPipe<z.ZodUnion<[z.ZodEnum<{
|
|
@@ -140,6 +141,8 @@ declare const LikeC4ProjectJsonConfigSchema: z.ZodObject<{
|
|
|
140
141
|
manualLayouts: z.ZodOptional<z.ZodObject<{
|
|
141
142
|
outDir: z.ZodDefault<z.ZodString>;
|
|
142
143
|
}, z.core.$strict>>;
|
|
144
|
+
inferTechnologyFromIcon: z.ZodOptional<z.ZodBoolean>;
|
|
145
|
+
implicitViews: z.ZodOptional<z.ZodBoolean>;
|
|
143
146
|
}, z.core.$strip>;
|
|
144
147
|
type LikeC4ProjectJsonConfig = z.input<typeof LikeC4ProjectJsonConfigSchema>;
|
|
145
148
|
/**
|
|
@@ -460,21 +463,18 @@ interface LikeC4StylesConfig extends z.infer<typeof LikeC4StylesConfigSchema> {}
|
|
|
460
463
|
type LikeC4StylesConfigInput = z.input<typeof LikeC4StylesConfigSchema>;
|
|
461
464
|
//#endregion
|
|
462
465
|
//#region src/filenames.d.ts
|
|
463
|
-
|
|
464
|
-
declare const configNonJsonFilenames: readonly ["likec4.config.js", "likec4.config.cjs", "likec4.config.mjs", "likec4.config.ts", "likec4.config.cts", "likec4.config.mts"];
|
|
466
|
+
/** All known LikeC4 config filenames (JSON and non-JSON). */
|
|
465
467
|
declare const ConfigFilenames: readonly [".likec4rc", ".likec4.config.json", "likec4.config.json", "likec4.config.js", "likec4.config.cjs", "likec4.config.mjs", "likec4.config.ts", "likec4.config.cts", "likec4.config.mts"];
|
|
468
|
+
/** Returns true if the **basename** of the given path matches a known config filename. */
|
|
469
|
+
declare function isLikeC4JsonConfig(filename: string): boolean;
|
|
466
470
|
/**
|
|
467
|
-
*
|
|
471
|
+
* Returns true if the **basename** of the given path matches a known non-JSON config filename (JS, MJS, TS, MTS).
|
|
468
472
|
*/
|
|
469
|
-
declare function
|
|
473
|
+
declare function isLikeC4NonJsonConfig(filename: string): boolean;
|
|
470
474
|
/**
|
|
471
|
-
*
|
|
475
|
+
* Returns true if the **basename** of the given path matches a known LikeC4 config file (JSON or non-JSON).
|
|
472
476
|
*/
|
|
473
|
-
declare function
|
|
474
|
-
/**
|
|
475
|
-
* Checks if the given filename is a LikeC4 config file (JSON or non-JSON)
|
|
476
|
-
*/
|
|
477
|
-
declare function isLikeC4Config(filename: string): filename is typeof ConfigFilenames[number];
|
|
477
|
+
declare function isLikeC4Config(filename: string): boolean;
|
|
478
478
|
//#endregion
|
|
479
479
|
//#region src/define-config.d.ts
|
|
480
480
|
/**
|
package/dist/index.mjs
CHANGED
|
@@ -2,7 +2,6 @@ import JSON5 from "json5";
|
|
|
2
2
|
import z from "zod/v4";
|
|
3
3
|
import { BorderStyles, ElementShapes, IconPositions, RelationshipArrowTypes, Sizes, ThemeColors, computeColorValues } from "@likec4/core/styles";
|
|
4
4
|
import { exact } from "@likec4/core/types";
|
|
5
|
-
import { basename } from "pathe";
|
|
6
5
|
const IMAGE_ALIAS_KEY_REGEX = /^@[A-Za-z0-9_-]*$/;
|
|
7
6
|
const IMAGE_ALIAS_VALUE_REGEX = /^(?!\/|[A-Za-z]:[\\\/])(?!.*:\/\/).*$/;
|
|
8
7
|
const ImageAliasKey = z.string().min(1, "Image alias key cannot be empty").regex(IMAGE_ALIAS_KEY_REGEX, "Image alias key must match /^@\\w+$/");
|
|
@@ -163,11 +162,18 @@ const LikeC4ProjectJsonConfigSchema = z.object({
|
|
|
163
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" }),
|
|
164
163
|
title: z.string().nonempty("Project title cannot be empty if specified").optional().meta({ description: "A human readable title for the project" }),
|
|
165
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
166
|
styles: LikeC4StylesConfigSchema.optional().meta({ description: "Project styles customization" }),
|
|
167
167
|
imageAliases: ImageAliasesSchema.optional(),
|
|
168
168
|
include: IncludeSchema.optional(),
|
|
169
169
|
exclude: z.array(z.string()).optional().meta({ description: "List of file patterns to exclude from the project, default is [\"**/node_modules/**\"]" }),
|
|
170
|
-
manualLayouts: ManualLayoutsConfigSchema.optional()
|
|
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." })
|
|
171
177
|
}).meta({
|
|
172
178
|
id: "LikeC4ProjectConfig",
|
|
173
179
|
description: "LikeC4 Project Configuration"
|
|
@@ -175,11 +181,18 @@ const LikeC4ProjectJsonConfigSchema = z.object({
|
|
|
175
181
|
const FunctionType = z.instanceof(Function);
|
|
176
182
|
const GeneratorsSchema = z.record(z.string(), FunctionType);
|
|
177
183
|
const LikeC4ProjectConfigSchema = LikeC4ProjectJsonConfigSchema.extend({ generators: GeneratorsSchema.optional() });
|
|
184
|
+
/**
|
|
185
|
+
* Validates Object into a LikeC4ProjectConfig object.
|
|
186
|
+
*/
|
|
178
187
|
function validateProjectConfig(config) {
|
|
179
188
|
const parsed = LikeC4ProjectConfigSchema.safeParse(config);
|
|
180
189
|
if (parsed.success) return parsed.data;
|
|
181
190
|
throw new Error("Config validation failed:\n" + z.prettifyError(parsed.error));
|
|
182
191
|
}
|
|
192
|
+
/**
|
|
193
|
+
* Parses JSON string into a LikeC4ProjectConfig object.
|
|
194
|
+
* Does not process "extends" - use `loadConfig` function instead
|
|
195
|
+
*/
|
|
183
196
|
function parseProjectConfigJSON(config) {
|
|
184
197
|
return validateProjectConfig(JSON5.parse(config.trim() || "{}"));
|
|
185
198
|
}
|
|
@@ -196,11 +209,29 @@ const LikeC4ProjectConfigOps = {
|
|
|
196
209
|
};
|
|
197
210
|
}
|
|
198
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). */
|
|
199
229
|
const configJsonFilenames = [
|
|
200
230
|
".likec4rc",
|
|
201
231
|
".likec4.config.json",
|
|
202
232
|
"likec4.config.json"
|
|
203
233
|
];
|
|
234
|
+
/** Known LikeC4 non-JSON config filenames (JS, MJS, TS, MTS). */
|
|
204
235
|
const configNonJsonFilenames = [
|
|
205
236
|
"likec4.config.js",
|
|
206
237
|
"likec4.config.cjs",
|
|
@@ -209,28 +240,137 @@ const configNonJsonFilenames = [
|
|
|
209
240
|
"likec4.config.cts",
|
|
210
241
|
"likec4.config.mts"
|
|
211
242
|
];
|
|
243
|
+
/** All known LikeC4 config filenames (JSON and non-JSON). */
|
|
212
244
|
const ConfigFilenames = [...configJsonFilenames, ...configNonJsonFilenames];
|
|
245
|
+
/** Returns true if the **basename** of the given path matches a known config filename. */
|
|
213
246
|
function isLikeC4JsonConfig(filename) {
|
|
214
247
|
return configJsonFilenames.includes(basename(filename));
|
|
215
248
|
}
|
|
249
|
+
/**
|
|
250
|
+
* Returns true if the **basename** of the given path matches a known non-JSON config filename (JS, MJS, TS, MTS).
|
|
251
|
+
*/
|
|
216
252
|
function isLikeC4NonJsonConfig(filename) {
|
|
217
253
|
return configNonJsonFilenames.includes(basename(filename));
|
|
218
254
|
}
|
|
255
|
+
/**
|
|
256
|
+
* Returns true if the **basename** of the given path matches a known LikeC4 config file (JSON or non-JSON).
|
|
257
|
+
*/
|
|
219
258
|
function isLikeC4Config(filename) {
|
|
220
259
|
return isLikeC4JsonConfig(filename) || isLikeC4NonJsonConfig(filename);
|
|
221
260
|
}
|
|
261
|
+
/**
|
|
262
|
+
* Defines LikeC4 Project, allows custom generators that can be executed using CLI:
|
|
263
|
+
*
|
|
264
|
+
* `$ likec4 gen <generator-name>`
|
|
265
|
+
*
|
|
266
|
+
* or VSCode command `LikeC4: Run code generator`
|
|
267
|
+
*
|
|
268
|
+
* @example
|
|
269
|
+
* ```ts
|
|
270
|
+
* export default defineConfig({
|
|
271
|
+
* name: 'my-project',
|
|
272
|
+
* title: 'My Project',
|
|
273
|
+
*
|
|
274
|
+
* exclude: ['picomatch pattern'],
|
|
275
|
+
* generators: {
|
|
276
|
+
* '<generator-name>': async ({ likec4model, ctx }) => {
|
|
277
|
+
* await ctx.write('my-generator.txt', likec4model.project.id)
|
|
278
|
+
* }
|
|
279
|
+
* }
|
|
280
|
+
* })
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
222
283
|
function defineConfig(config) {
|
|
223
284
|
return LikeC4ProjectConfigSchema.parse(config);
|
|
224
285
|
}
|
|
286
|
+
/**
|
|
287
|
+
* Define reusable custom generators
|
|
288
|
+
*
|
|
289
|
+
* @example
|
|
290
|
+
* ```ts
|
|
291
|
+
* // generators.ts
|
|
292
|
+
* export default defineGenerators({
|
|
293
|
+
* 'my-generator': async ({ likec4model, ctx }) => {
|
|
294
|
+
* await ctx.write('my-generator.txt', likec4model.project.id)
|
|
295
|
+
* }
|
|
296
|
+
* })
|
|
297
|
+
*
|
|
298
|
+
* // likec4.config.ts
|
|
299
|
+
* import generators from './generators'
|
|
300
|
+
*
|
|
301
|
+
* export default defineConfig({
|
|
302
|
+
* name: 'my-project',
|
|
303
|
+
* generators,
|
|
304
|
+
* })
|
|
305
|
+
* ```
|
|
306
|
+
*/
|
|
225
307
|
function defineGenerators(generators) {
|
|
226
308
|
return GeneratorsSchema.parse(generators);
|
|
227
309
|
}
|
|
310
|
+
/**
|
|
311
|
+
* Define reusable custom theme color
|
|
312
|
+
* @example
|
|
313
|
+
* ```ts
|
|
314
|
+
* export default defineThemeColor({
|
|
315
|
+
* element: {
|
|
316
|
+
* fill: 'red'
|
|
317
|
+
* }
|
|
318
|
+
* })
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
228
321
|
function defineThemeColor(colors) {
|
|
229
322
|
return ThemeColorValuesSchema.parse(colors);
|
|
230
323
|
}
|
|
324
|
+
/**
|
|
325
|
+
* Define reusable custom theme
|
|
326
|
+
* @example
|
|
327
|
+
* ```ts
|
|
328
|
+
* import { defineThemeColor, defineTheme } from 'likec4/config'
|
|
329
|
+
*
|
|
330
|
+
* export default defineTheme({
|
|
331
|
+
* colors: {
|
|
332
|
+
* primary: '#FF0000',
|
|
333
|
+
* // Or use defineThemeColor
|
|
334
|
+
* red: defineThemeColor({
|
|
335
|
+
* elements: {
|
|
336
|
+
* fill: 'red'
|
|
337
|
+
* }
|
|
338
|
+
* })
|
|
339
|
+
* }
|
|
340
|
+
* })
|
|
341
|
+
* ```
|
|
342
|
+
*/
|
|
231
343
|
function defineTheme(theme) {
|
|
232
344
|
return LikeC4Config_Styles_Theme.parse(theme);
|
|
233
345
|
}
|
|
346
|
+
/**
|
|
347
|
+
* Define reusable custom style
|
|
348
|
+
* @example
|
|
349
|
+
* ```ts
|
|
350
|
+
* import { defineStyle, defineThemeColor } from 'likec4/config'
|
|
351
|
+
*
|
|
352
|
+
* export default defineStyle({
|
|
353
|
+
* theme: {
|
|
354
|
+
* colors: {
|
|
355
|
+
* red: defineThemeColor({
|
|
356
|
+
* elements: {
|
|
357
|
+
* fill: 'red'
|
|
358
|
+
* }
|
|
359
|
+
* })
|
|
360
|
+
* }
|
|
361
|
+
* },
|
|
362
|
+
* defaults: {
|
|
363
|
+
* color: 'red',
|
|
364
|
+
* opacity: 50,
|
|
365
|
+
* border: 'solid',
|
|
366
|
+
* size: 'sm',
|
|
367
|
+
* relationship: {
|
|
368
|
+
* color: 'grey',
|
|
369
|
+
* line: 'solid',
|
|
370
|
+
* }
|
|
371
|
+
* }
|
|
372
|
+
* })
|
|
373
|
+
*/
|
|
234
374
|
function defineStyle(styles) {
|
|
235
375
|
return LikeC4StylesConfigSchema.parse(styles);
|
|
236
376
|
}
|
package/dist/node/index.d.mts
CHANGED
|
@@ -25,6 +25,7 @@ declare const LikeC4ProjectJsonConfigSchema: z.ZodObject<{
|
|
|
25
25
|
extends: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodArray<z.ZodString>]>>;
|
|
26
26
|
title: z.ZodOptional<z.ZodString>;
|
|
27
27
|
contactPerson: z.ZodOptional<z.ZodString>;
|
|
28
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodAny>>;
|
|
28
29
|
styles: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
29
30
|
theme: z.ZodOptional<z.ZodPipe<z.ZodObject<{
|
|
30
31
|
colors: z.ZodOptional<z.ZodPipe<z.ZodRecord<z.ZodPipe<z.ZodUnion<[z.ZodEnum<{
|
|
@@ -140,6 +141,8 @@ declare const LikeC4ProjectJsonConfigSchema: z.ZodObject<{
|
|
|
140
141
|
manualLayouts: z.ZodOptional<z.ZodObject<{
|
|
141
142
|
outDir: z.ZodDefault<z.ZodString>;
|
|
142
143
|
}, z.core.$strict>>;
|
|
144
|
+
inferTechnologyFromIcon: z.ZodOptional<z.ZodBoolean>;
|
|
145
|
+
implicitViews: z.ZodOptional<z.ZodBoolean>;
|
|
143
146
|
}, z.core.$strip>;
|
|
144
147
|
type LikeC4ProjectJsonConfig = z.input<typeof LikeC4ProjectJsonConfigSchema>;
|
|
145
148
|
/**
|
|
@@ -460,21 +463,18 @@ interface LikeC4StylesConfig extends z.infer<typeof LikeC4StylesConfigSchema> {}
|
|
|
460
463
|
type LikeC4StylesConfigInput = z.input<typeof LikeC4StylesConfigSchema>;
|
|
461
464
|
//#endregion
|
|
462
465
|
//#region src/filenames.d.ts
|
|
463
|
-
|
|
464
|
-
declare const configNonJsonFilenames: readonly ["likec4.config.js", "likec4.config.cjs", "likec4.config.mjs", "likec4.config.ts", "likec4.config.cts", "likec4.config.mts"];
|
|
466
|
+
/** All known LikeC4 config filenames (JSON and non-JSON). */
|
|
465
467
|
declare const ConfigFilenames: readonly [".likec4rc", ".likec4.config.json", "likec4.config.json", "likec4.config.js", "likec4.config.cjs", "likec4.config.mjs", "likec4.config.ts", "likec4.config.cts", "likec4.config.mts"];
|
|
468
|
+
/** Returns true if the **basename** of the given path matches a known config filename. */
|
|
469
|
+
declare function isLikeC4JsonConfig(filename: string): boolean;
|
|
466
470
|
/**
|
|
467
|
-
*
|
|
471
|
+
* Returns true if the **basename** of the given path matches a known non-JSON config filename (JS, MJS, TS, MTS).
|
|
468
472
|
*/
|
|
469
|
-
declare function
|
|
473
|
+
declare function isLikeC4NonJsonConfig(filename: string): boolean;
|
|
470
474
|
/**
|
|
471
|
-
*
|
|
475
|
+
* Returns true if the **basename** of the given path matches a known LikeC4 config file (JSON or non-JSON).
|
|
472
476
|
*/
|
|
473
|
-
declare function
|
|
474
|
-
/**
|
|
475
|
-
* Checks if the given filename is a LikeC4 config file (JSON or non-JSON)
|
|
476
|
-
*/
|
|
477
|
-
declare function isLikeC4Config(filename: string): filename is typeof ConfigFilenames[number];
|
|
477
|
+
declare function isLikeC4Config(filename: string): boolean;
|
|
478
478
|
//#endregion
|
|
479
479
|
//#region src/define-config.d.ts
|
|
480
480
|
/**
|
package/dist/node/index.mjs
CHANGED
|
@@ -4,13 +4,12 @@ import JSON5 from "json5";
|
|
|
4
4
|
import z from "zod/v4";
|
|
5
5
|
import { BorderStyles, ElementShapes, IconPositions, RelationshipArrowTypes, Sizes, ThemeColors, computeColorValues } from "@likec4/core/styles";
|
|
6
6
|
import { exact } from "@likec4/core/types";
|
|
7
|
-
import { basename } from "pathe";
|
|
8
7
|
import { invariant } from "@likec4/core";
|
|
9
8
|
import { logger, wrapError } from "@likec4/log";
|
|
10
9
|
import { bundleRequire } from "bundle-require";
|
|
11
10
|
import { formatMessagesSync } from "esbuild";
|
|
12
11
|
import * as fs from "node:fs/promises";
|
|
13
|
-
import { basename
|
|
12
|
+
import { basename, dirname, resolve } from "node:path";
|
|
14
13
|
const IMAGE_ALIAS_KEY_REGEX = /^@[A-Za-z0-9_-]*$/;
|
|
15
14
|
const IMAGE_ALIAS_VALUE_REGEX = /^(?!\/|[A-Za-z]:[\\\/])(?!.*:\/\/).*$/;
|
|
16
15
|
const ImageAliasKey = z.string().min(1, "Image alias key cannot be empty").regex(IMAGE_ALIAS_KEY_REGEX, "Image alias key must match /^@\\w+$/");
|
|
@@ -171,11 +170,18 @@ const LikeC4ProjectJsonConfigSchema = z.object({
|
|
|
171
170
|
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" }),
|
|
172
171
|
title: z.string().nonempty("Project title cannot be empty if specified").optional().meta({ description: "A human readable title for the project" }),
|
|
173
172
|
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" }),
|
|
173
|
+
metadata: z.record(z.string(), z.any()).optional().meta({ description: "Arbitrary metadata as key-value pairs for custom project information" }),
|
|
174
174
|
styles: LikeC4StylesConfigSchema.optional().meta({ description: "Project styles customization" }),
|
|
175
175
|
imageAliases: ImageAliasesSchema.optional(),
|
|
176
176
|
include: IncludeSchema.optional(),
|
|
177
177
|
exclude: z.array(z.string()).optional().meta({ description: "List of file patterns to exclude from the project, default is [\"**/node_modules/**\"]" }),
|
|
178
|
-
manualLayouts: ManualLayoutsConfigSchema.optional()
|
|
178
|
+
manualLayouts: ManualLayoutsConfigSchema.optional(),
|
|
179
|
+
inferTechnologyFromIcon: z.boolean().optional().meta({ description: [
|
|
180
|
+
"Automatically derive element technology from icon name when technology is not set explicitly.",
|
|
181
|
+
"Applies to aws:, azure:, gcp:, and tech: icons. Bootstrap icons are excluded.",
|
|
182
|
+
"Defaults to true."
|
|
183
|
+
].join("\n") }),
|
|
184
|
+
implicitViews: z.boolean().optional().meta({ description: "Auto-generate scoped views for elements without explicit views. Defaults to true." })
|
|
179
185
|
}).meta({
|
|
180
186
|
id: "LikeC4ProjectConfig",
|
|
181
187
|
description: "LikeC4 Project Configuration"
|
|
@@ -183,11 +189,18 @@ const LikeC4ProjectJsonConfigSchema = z.object({
|
|
|
183
189
|
const FunctionType = z.instanceof(Function);
|
|
184
190
|
const GeneratorsSchema = z.record(z.string(), FunctionType);
|
|
185
191
|
const LikeC4ProjectConfigSchema = LikeC4ProjectJsonConfigSchema.extend({ generators: GeneratorsSchema.optional() });
|
|
192
|
+
/**
|
|
193
|
+
* Validates Object into a LikeC4ProjectConfig object.
|
|
194
|
+
*/
|
|
186
195
|
function validateProjectConfig(config) {
|
|
187
196
|
const parsed = LikeC4ProjectConfigSchema.safeParse(config);
|
|
188
197
|
if (parsed.success) return parsed.data;
|
|
189
198
|
throw new Error("Config validation failed:\n" + z.prettifyError(parsed.error));
|
|
190
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* Parses JSON string into a LikeC4ProjectConfig object.
|
|
202
|
+
* Does not process "extends" - use `loadConfig` function instead
|
|
203
|
+
*/
|
|
191
204
|
function parseProjectConfigJSON(config) {
|
|
192
205
|
return validateProjectConfig(JSON5.parse(config.trim() || "{}"));
|
|
193
206
|
}
|
|
@@ -204,11 +217,29 @@ const LikeC4ProjectConfigOps = {
|
|
|
204
217
|
};
|
|
205
218
|
}
|
|
206
219
|
};
|
|
220
|
+
/** Trim trailing slashes and backslashes (no regex, avoids S5852 ReDoS). */
|
|
221
|
+
function trimTrailingSlashes(s) {
|
|
222
|
+
let end = s.length;
|
|
223
|
+
while (end > 0 && (s[end - 1] === "/" || s[end - 1] === "\\")) end--;
|
|
224
|
+
return s.slice(0, end);
|
|
225
|
+
}
|
|
226
|
+
/** Split by / or \ without regex (avoids S5852 ReDoS). */
|
|
227
|
+
function splitPath(s) {
|
|
228
|
+
return s.split("/").flatMap((part) => part.split("\\"));
|
|
229
|
+
}
|
|
230
|
+
/** basename compatible with Node and browser (no node:path for Vite/playground bundle). */
|
|
231
|
+
function basename$1(path) {
|
|
232
|
+
const trimmed = trimTrailingSlashes(path);
|
|
233
|
+
const segments = splitPath(trimmed);
|
|
234
|
+
return segments[segments.length - 1] || trimmed;
|
|
235
|
+
}
|
|
236
|
+
/** Known LikeC4 JSON config filenames (RC and .json). */
|
|
207
237
|
const configJsonFilenames = [
|
|
208
238
|
".likec4rc",
|
|
209
239
|
".likec4.config.json",
|
|
210
240
|
"likec4.config.json"
|
|
211
241
|
];
|
|
242
|
+
/** Known LikeC4 non-JSON config filenames (JS, MJS, TS, MTS). */
|
|
212
243
|
const configNonJsonFilenames = [
|
|
213
244
|
"likec4.config.js",
|
|
214
245
|
"likec4.config.cjs",
|
|
@@ -217,28 +248,137 @@ const configNonJsonFilenames = [
|
|
|
217
248
|
"likec4.config.cts",
|
|
218
249
|
"likec4.config.mts"
|
|
219
250
|
];
|
|
251
|
+
/** All known LikeC4 config filenames (JSON and non-JSON). */
|
|
220
252
|
const ConfigFilenames = [...configJsonFilenames, ...configNonJsonFilenames];
|
|
253
|
+
/** Returns true if the **basename** of the given path matches a known config filename. */
|
|
221
254
|
function isLikeC4JsonConfig(filename) {
|
|
222
|
-
return configJsonFilenames.includes(basename(filename));
|
|
255
|
+
return configJsonFilenames.includes(basename$1(filename));
|
|
223
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Returns true if the **basename** of the given path matches a known non-JSON config filename (JS, MJS, TS, MTS).
|
|
259
|
+
*/
|
|
224
260
|
function isLikeC4NonJsonConfig(filename) {
|
|
225
|
-
return configNonJsonFilenames.includes(basename(filename));
|
|
261
|
+
return configNonJsonFilenames.includes(basename$1(filename));
|
|
226
262
|
}
|
|
263
|
+
/**
|
|
264
|
+
* Returns true if the **basename** of the given path matches a known LikeC4 config file (JSON or non-JSON).
|
|
265
|
+
*/
|
|
227
266
|
function isLikeC4Config(filename) {
|
|
228
267
|
return isLikeC4JsonConfig(filename) || isLikeC4NonJsonConfig(filename);
|
|
229
268
|
}
|
|
269
|
+
/**
|
|
270
|
+
* Defines LikeC4 Project, allows custom generators that can be executed using CLI:
|
|
271
|
+
*
|
|
272
|
+
* `$ likec4 gen <generator-name>`
|
|
273
|
+
*
|
|
274
|
+
* or VSCode command `LikeC4: Run code generator`
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* ```ts
|
|
278
|
+
* export default defineConfig({
|
|
279
|
+
* name: 'my-project',
|
|
280
|
+
* title: 'My Project',
|
|
281
|
+
*
|
|
282
|
+
* exclude: ['picomatch pattern'],
|
|
283
|
+
* generators: {
|
|
284
|
+
* '<generator-name>': async ({ likec4model, ctx }) => {
|
|
285
|
+
* await ctx.write('my-generator.txt', likec4model.project.id)
|
|
286
|
+
* }
|
|
287
|
+
* }
|
|
288
|
+
* })
|
|
289
|
+
* ```
|
|
290
|
+
*/
|
|
230
291
|
function defineConfig(config) {
|
|
231
292
|
return LikeC4ProjectConfigSchema.parse(config);
|
|
232
293
|
}
|
|
294
|
+
/**
|
|
295
|
+
* Define reusable custom generators
|
|
296
|
+
*
|
|
297
|
+
* @example
|
|
298
|
+
* ```ts
|
|
299
|
+
* // generators.ts
|
|
300
|
+
* export default defineGenerators({
|
|
301
|
+
* 'my-generator': async ({ likec4model, ctx }) => {
|
|
302
|
+
* await ctx.write('my-generator.txt', likec4model.project.id)
|
|
303
|
+
* }
|
|
304
|
+
* })
|
|
305
|
+
*
|
|
306
|
+
* // likec4.config.ts
|
|
307
|
+
* import generators from './generators'
|
|
308
|
+
*
|
|
309
|
+
* export default defineConfig({
|
|
310
|
+
* name: 'my-project',
|
|
311
|
+
* generators,
|
|
312
|
+
* })
|
|
313
|
+
* ```
|
|
314
|
+
*/
|
|
233
315
|
function defineGenerators(generators) {
|
|
234
316
|
return GeneratorsSchema.parse(generators);
|
|
235
317
|
}
|
|
318
|
+
/**
|
|
319
|
+
* Define reusable custom theme color
|
|
320
|
+
* @example
|
|
321
|
+
* ```ts
|
|
322
|
+
* export default defineThemeColor({
|
|
323
|
+
* element: {
|
|
324
|
+
* fill: 'red'
|
|
325
|
+
* }
|
|
326
|
+
* })
|
|
327
|
+
* ```
|
|
328
|
+
*/
|
|
236
329
|
function defineThemeColor(colors) {
|
|
237
330
|
return ThemeColorValuesSchema.parse(colors);
|
|
238
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* Define reusable custom theme
|
|
334
|
+
* @example
|
|
335
|
+
* ```ts
|
|
336
|
+
* import { defineThemeColor, defineTheme } from 'likec4/config'
|
|
337
|
+
*
|
|
338
|
+
* export default defineTheme({
|
|
339
|
+
* colors: {
|
|
340
|
+
* primary: '#FF0000',
|
|
341
|
+
* // Or use defineThemeColor
|
|
342
|
+
* red: defineThemeColor({
|
|
343
|
+
* elements: {
|
|
344
|
+
* fill: 'red'
|
|
345
|
+
* }
|
|
346
|
+
* })
|
|
347
|
+
* }
|
|
348
|
+
* })
|
|
349
|
+
* ```
|
|
350
|
+
*/
|
|
239
351
|
function defineTheme(theme) {
|
|
240
352
|
return LikeC4Config_Styles_Theme.parse(theme);
|
|
241
353
|
}
|
|
354
|
+
/**
|
|
355
|
+
* Define reusable custom style
|
|
356
|
+
* @example
|
|
357
|
+
* ```ts
|
|
358
|
+
* import { defineStyle, defineThemeColor } from 'likec4/config'
|
|
359
|
+
*
|
|
360
|
+
* export default defineStyle({
|
|
361
|
+
* theme: {
|
|
362
|
+
* colors: {
|
|
363
|
+
* red: defineThemeColor({
|
|
364
|
+
* elements: {
|
|
365
|
+
* fill: 'red'
|
|
366
|
+
* }
|
|
367
|
+
* })
|
|
368
|
+
* }
|
|
369
|
+
* },
|
|
370
|
+
* defaults: {
|
|
371
|
+
* color: 'red',
|
|
372
|
+
* opacity: 50,
|
|
373
|
+
* border: 'solid',
|
|
374
|
+
* size: 'sm',
|
|
375
|
+
* relationship: {
|
|
376
|
+
* color: 'grey',
|
|
377
|
+
* line: 'solid',
|
|
378
|
+
* }
|
|
379
|
+
* }
|
|
380
|
+
* })
|
|
381
|
+
*/
|
|
242
382
|
function defineStyle(styles) {
|
|
243
383
|
return LikeC4StylesConfigSchema.parse(styles);
|
|
244
384
|
}
|
|
@@ -279,12 +419,16 @@ const loadJsonConfigs = async (filepath, stack) => {
|
|
|
279
419
|
}
|
|
280
420
|
return [...configs, parsed];
|
|
281
421
|
};
|
|
422
|
+
/**
|
|
423
|
+
* Load LikeC4 Project config file.
|
|
424
|
+
* If filepath is a non-JSON file, it will be bundled and required
|
|
425
|
+
*/
|
|
282
426
|
async function loadConfig(filepath) {
|
|
283
427
|
filepath = typeof filepath === "string" ? filepath : filepath.fsPath;
|
|
284
428
|
logger.getChild("config").debug`Loading config: ${filepath}`;
|
|
285
429
|
const folder = dirname(filepath);
|
|
286
|
-
const filename = basename
|
|
287
|
-
const implicitcfg = { name: basename
|
|
430
|
+
const filename = basename(filepath);
|
|
431
|
+
const implicitcfg = { name: basename(folder) };
|
|
288
432
|
if (isLikeC4JsonConfig(filename)) {
|
|
289
433
|
const configs = await loadJsonConfigs(resolve(filepath), []);
|
|
290
434
|
invariant(t(configs, 1), "Expect at least one config");
|
|
@@ -313,6 +457,9 @@ async function loadConfig(filepath) {
|
|
|
313
457
|
plugins: [{
|
|
314
458
|
name: "likec4-config",
|
|
315
459
|
setup(build) {
|
|
460
|
+
/**
|
|
461
|
+
* Intercept @likec4/config and likec4/config imports
|
|
462
|
+
*/
|
|
316
463
|
build.onResolve({ filter: /^@?likec4\/config$/ }, (args) => ({
|
|
317
464
|
path: args.path,
|
|
318
465
|
namespace: "likec4-config"
|
|
@@ -321,6 +468,9 @@ async function loadConfig(filepath) {
|
|
|
321
468
|
const messages = formatMessagesSync(result.errors, { kind: "error" });
|
|
322
469
|
for (const message of messages) logger.error(message);
|
|
323
470
|
});
|
|
471
|
+
/**
|
|
472
|
+
* Mock implementation, this allows to skip redundant bundling @likec4/config
|
|
473
|
+
*/
|
|
324
474
|
build.onLoad({
|
|
325
475
|
filter: /.*/,
|
|
326
476
|
namespace: "likec4-config"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/config",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.50.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"homepage": "https://likec4.dev",
|
|
6
6
|
"author": "Denis Davydkov <denis@davydkov.com>",
|
|
@@ -54,11 +54,10 @@
|
|
|
54
54
|
},
|
|
55
55
|
"dependencies": {
|
|
56
56
|
"json5": "^2.2.3",
|
|
57
|
-
"pathe": "^2.0.3",
|
|
58
57
|
"zod": "^3.25.76",
|
|
59
58
|
"type-fest": "^4.41.0",
|
|
60
|
-
"@likec4/
|
|
61
|
-
"@likec4/
|
|
59
|
+
"@likec4/core": "1.50.0",
|
|
60
|
+
"@likec4/log": "1.50.0"
|
|
62
61
|
},
|
|
63
62
|
"peerDependencies": {
|
|
64
63
|
"bundle-require": "^5.1.0",
|
|
@@ -73,18 +72,18 @@
|
|
|
73
72
|
}
|
|
74
73
|
},
|
|
75
74
|
"devDependencies": {
|
|
76
|
-
"@types/node": "~22.19.
|
|
75
|
+
"@types/node": "~22.19.11",
|
|
77
76
|
"remeda": "^2.33.5",
|
|
78
77
|
"defu": "^6.1.4",
|
|
79
78
|
"ufo": "1.6.3",
|
|
80
79
|
"tsx": "4.21.0",
|
|
81
|
-
"turbo": "2.8.
|
|
80
|
+
"turbo": "2.8.10",
|
|
82
81
|
"typescript": "5.9.3",
|
|
83
|
-
"obuild": "^0.4.
|
|
82
|
+
"obuild": "^0.4.31",
|
|
84
83
|
"nano-spawn": "^2.0.0",
|
|
85
84
|
"vitest": "4.0.18",
|
|
86
|
-
"@likec4/
|
|
87
|
-
"@likec4/
|
|
85
|
+
"@likec4/tsconfig": "1.50.0",
|
|
86
|
+
"@likec4/devops": "1.42.0"
|
|
88
87
|
},
|
|
89
88
|
"scripts": {
|
|
90
89
|
"generate": "tsx --conditions=sources scripts/generate.mts",
|
package/schema.json
CHANGED
|
@@ -36,6 +36,14 @@
|
|
|
36
36
|
"type": "string",
|
|
37
37
|
"minLength": 1
|
|
38
38
|
},
|
|
39
|
+
"metadata": {
|
|
40
|
+
"description": "Arbitrary metadata as key-value pairs for custom project information",
|
|
41
|
+
"type": "object",
|
|
42
|
+
"propertyNames": {
|
|
43
|
+
"type": "string"
|
|
44
|
+
},
|
|
45
|
+
"additionalProperties": {}
|
|
46
|
+
},
|
|
39
47
|
"styles": {
|
|
40
48
|
"description": "Project styles customization",
|
|
41
49
|
"type": "object",
|
|
@@ -70,6 +78,14 @@
|
|
|
70
78
|
},
|
|
71
79
|
"manualLayouts": {
|
|
72
80
|
"$ref": "#/$defs/ManualLayoutsConfig"
|
|
81
|
+
},
|
|
82
|
+
"inferTechnologyFromIcon": {
|
|
83
|
+
"description": "Automatically derive element technology from icon name when technology is not set explicitly.\nApplies to aws:, azure:, gcp:, and tech: icons. Bootstrap icons are excluded.\nDefaults to true.",
|
|
84
|
+
"type": "boolean"
|
|
85
|
+
},
|
|
86
|
+
"implicitViews": {
|
|
87
|
+
"description": "Auto-generate scoped views for elements without explicit views. Defaults to true.",
|
|
88
|
+
"type": "boolean"
|
|
73
89
|
}
|
|
74
90
|
},
|
|
75
91
|
"required": [
|
package/src/filenames.ts
CHANGED
|
@@ -1,11 +1,31 @@
|
|
|
1
|
-
|
|
1
|
+
/** Trim trailing slashes and backslashes (no regex, avoids S5852 ReDoS). */
|
|
2
|
+
function trimTrailingSlashes(s: string): string {
|
|
3
|
+
let end = s.length
|
|
4
|
+
while (end > 0 && (s[end - 1] === '/' || s[end - 1] === '\\')) end--
|
|
5
|
+
return s.slice(0, end)
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** Split by / or \ without regex (avoids S5852 ReDoS). */
|
|
9
|
+
function splitPath(s: string): string[] {
|
|
10
|
+
return s.split('/').flatMap(part => part.split('\\'))
|
|
11
|
+
}
|
|
2
12
|
|
|
13
|
+
/** basename compatible with Node and browser (no node:path for Vite/playground bundle). */
|
|
14
|
+
function basename(path: string): string {
|
|
15
|
+
const trimmed = trimTrailingSlashes(path)
|
|
16
|
+
const segments = splitPath(trimmed)
|
|
17
|
+
const last = segments[segments.length - 1]
|
|
18
|
+
return last || trimmed
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Known LikeC4 JSON config filenames (RC and .json). */
|
|
3
22
|
export const configJsonFilenames = [
|
|
4
23
|
'.likec4rc',
|
|
5
24
|
'.likec4.config.json',
|
|
6
25
|
'likec4.config.json',
|
|
7
26
|
] as const
|
|
8
27
|
|
|
28
|
+
/** Known LikeC4 non-JSON config filenames (JS, MJS, TS, MTS). */
|
|
9
29
|
export const configNonJsonFilenames = [
|
|
10
30
|
'likec4.config.js',
|
|
11
31
|
'likec4.config.cjs',
|
|
@@ -15,28 +35,27 @@ export const configNonJsonFilenames = [
|
|
|
15
35
|
'likec4.config.mts',
|
|
16
36
|
] as const
|
|
17
37
|
|
|
38
|
+
/** All known LikeC4 config filenames (JSON and non-JSON). */
|
|
18
39
|
export const ConfigFilenames = [
|
|
19
40
|
...configJsonFilenames,
|
|
20
41
|
...configNonJsonFilenames,
|
|
21
42
|
] as const
|
|
22
43
|
|
|
23
|
-
/**
|
|
24
|
-
|
|
25
|
-
*/
|
|
26
|
-
export function isLikeC4JsonConfig(filename: string): filename is typeof configJsonFilenames[number] {
|
|
44
|
+
/** Returns true if the **basename** of the given path matches a known config filename. */
|
|
45
|
+
export function isLikeC4JsonConfig(filename: string): boolean {
|
|
27
46
|
return (configJsonFilenames as readonly string[]).includes(basename(filename))
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
/**
|
|
31
|
-
*
|
|
50
|
+
* Returns true if the **basename** of the given path matches a known non-JSON config filename (JS, MJS, TS, MTS).
|
|
32
51
|
*/
|
|
33
|
-
export function isLikeC4NonJsonConfig(filename: string):
|
|
52
|
+
export function isLikeC4NonJsonConfig(filename: string): boolean {
|
|
34
53
|
return (configNonJsonFilenames as readonly string[]).includes(basename(filename))
|
|
35
54
|
}
|
|
36
55
|
|
|
37
56
|
/**
|
|
38
|
-
*
|
|
57
|
+
* Returns true if the **basename** of the given path matches a known LikeC4 config file (JSON or non-JSON).
|
|
39
58
|
*/
|
|
40
|
-
export function isLikeC4Config(filename: string):
|
|
59
|
+
export function isLikeC4Config(filename: string): boolean {
|
|
41
60
|
return isLikeC4JsonConfig(filename) || isLikeC4NonJsonConfig(filename)
|
|
42
61
|
}
|
package/src/schema.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
//
|
|
3
|
+
// Copyright (c) 2023-2026 Denis Davydkov
|
|
4
|
+
// Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
5
|
+
//
|
|
6
|
+
// Portions of this file have been modified by NVIDIA CORPORATION & AFFILIATES.
|
|
7
|
+
|
|
1
8
|
import type {
|
|
2
9
|
DeploymentElementModel,
|
|
3
10
|
DeploymentRelationModel,
|
|
@@ -72,6 +79,9 @@ export const LikeC4ProjectJsonConfigSchema = z.object({
|
|
|
72
79
|
.nonempty('Contact person cannot be empty if specified')
|
|
73
80
|
.optional()
|
|
74
81
|
.meta({ description: 'A person who has been involved in creating or maintaining this project' }),
|
|
82
|
+
metadata: z.record(z.string(), z.any())
|
|
83
|
+
.optional()
|
|
84
|
+
.meta({ description: 'Arbitrary metadata as key-value pairs for custom project information' }),
|
|
75
85
|
styles: LikeC4StylesConfigSchema.optional().meta({
|
|
76
86
|
description: 'Project styles customization',
|
|
77
87
|
}),
|
|
@@ -81,6 +91,20 @@ export const LikeC4ProjectJsonConfigSchema = z.object({
|
|
|
81
91
|
.optional()
|
|
82
92
|
.meta({ description: 'List of file patterns to exclude from the project, default is ["**/node_modules/**"]' }),
|
|
83
93
|
manualLayouts: ManualLayoutsConfigSchema.optional(),
|
|
94
|
+
inferTechnologyFromIcon: z.boolean()
|
|
95
|
+
.optional()
|
|
96
|
+
.meta({
|
|
97
|
+
description: [
|
|
98
|
+
'Automatically derive element technology from icon name when technology is not set explicitly.',
|
|
99
|
+
'Applies to aws:, azure:, gcp:, and tech: icons. Bootstrap icons are excluded.',
|
|
100
|
+
'Defaults to true.',
|
|
101
|
+
].join('\n'),
|
|
102
|
+
}),
|
|
103
|
+
implicitViews: z.boolean()
|
|
104
|
+
.optional()
|
|
105
|
+
.meta({
|
|
106
|
+
description: 'Auto-generate scoped views for elements without explicit views. Defaults to true.',
|
|
107
|
+
}),
|
|
84
108
|
})
|
|
85
109
|
.meta({
|
|
86
110
|
id: 'LikeC4ProjectConfig',
|