@likec4/config 1.48.0 → 1.49.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 +4 -0
- package/dist/THIRD-PARTY-LICENSES.md +41 -0
- package/dist/_chunks/libs/defu.mjs +39 -0
- package/dist/_chunks/libs/remeda.mjs +25 -18
- package/dist/index.d.mts +223 -138
- package/dist/index.mjs +72 -242
- package/dist/node/index.d.mts +224 -139
- package/dist/node/index.mjs +168 -304
- package/package.json +13 -10
- package/schema.json +155 -143
- package/src/filenames.ts +6 -14
- package/src/index.ts +6 -5
- package/src/node/index.ts +1 -38
- package/src/node/load-config.ts +121 -51
- package/src/schema.image-alias.ts +10 -46
- package/src/schema.include.ts +36 -74
- package/src/schema.theme.ts +87 -83
- package/src/schema.ts +52 -35
package/src/node/load-config.ts
CHANGED
|
@@ -1,54 +1,128 @@
|
|
|
1
1
|
import { invariant } from '@likec4/core'
|
|
2
|
-
import {
|
|
2
|
+
import { logger, wrapError } from '@likec4/log'
|
|
3
3
|
import { bundleRequire } from 'bundle-require'
|
|
4
|
+
import { defu } from 'defu'
|
|
5
|
+
import { formatMessagesSync } from 'esbuild'
|
|
6
|
+
import JSON5 from 'json5'
|
|
4
7
|
import * as fs from 'node:fs/promises'
|
|
5
|
-
import { dirname } from 'node:path'
|
|
6
|
-
import {
|
|
8
|
+
import { basename, dirname, resolve } from 'node:path'
|
|
9
|
+
import { hasAtLeast, isNonNullish, last, omit } from 'remeda'
|
|
10
|
+
import z from 'zod/v4'
|
|
7
11
|
import { isLikeC4JsonConfig, isLikeC4NonJsonConfig } from '../filenames'
|
|
8
12
|
import type { LikeC4ProjectConfig, VscodeURI } from '../schema'
|
|
9
|
-
import { validateProjectConfig } from '../schema'
|
|
13
|
+
import { LikeC4ProjectJsonConfigSchema, validateProjectConfig } from '../schema'
|
|
14
|
+
|
|
15
|
+
const JsonConfigInputSchema = LikeC4ProjectJsonConfigSchema.pick({
|
|
16
|
+
extends: true,
|
|
17
|
+
styles: true,
|
|
18
|
+
}).loose()
|
|
19
|
+
|
|
20
|
+
type JsonConfigInput = z.infer<typeof JsonConfigInputSchema>
|
|
21
|
+
type JsonConfigStyles = NonNullable<JsonConfigInput['styles']>
|
|
22
|
+
|
|
23
|
+
const normalizeExtends = (value: JsonConfigInput['extends']): string[] => {
|
|
24
|
+
if (!value) {
|
|
25
|
+
return []
|
|
26
|
+
}
|
|
27
|
+
return Array.isArray(value) ? value : [value]
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const parseJsonConfig = async (filepath: string): Promise<JsonConfigInput> => {
|
|
31
|
+
const content = await fs.readFile(filepath, 'utf-8')
|
|
32
|
+
let parsed: unknown
|
|
33
|
+
try {
|
|
34
|
+
parsed = JSON5.parse(content.trim() || '{}')
|
|
35
|
+
} catch (e) {
|
|
36
|
+
throw wrapError(e, `${filepath}:`)
|
|
37
|
+
}
|
|
38
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
39
|
+
throw new Error(`${filepath}: Config must be a JSON object`)
|
|
40
|
+
}
|
|
41
|
+
const result = JsonConfigInputSchema.safeParse(parsed)
|
|
42
|
+
if (!result.success) {
|
|
43
|
+
throw new Error(`${filepath}: Invalid config\n` + z.prettifyError(result.error))
|
|
44
|
+
}
|
|
45
|
+
return result.data
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const loadJsonConfigs = async (filepath: string, stack: string[]): Promise<[...JsonConfigInput[], JsonConfigInput]> => {
|
|
49
|
+
if (stack.includes(filepath)) {
|
|
50
|
+
const cycleStart = stack.indexOf(filepath)
|
|
51
|
+
const cycle = [...stack.slice(cycleStart), filepath].join(' -> ')
|
|
52
|
+
throw new Error(`Config extends cycle detected: ${cycle}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parsed = await parseJsonConfig(filepath)
|
|
56
|
+
const extendsPaths = normalizeExtends(parsed.extends)
|
|
57
|
+
const nextStack = [...stack, filepath]
|
|
58
|
+
|
|
59
|
+
const configs: JsonConfigInput[] = []
|
|
60
|
+
for (const extendPath of extendsPaths) {
|
|
61
|
+
const resolvedPath = resolve(dirname(filepath), extendPath)
|
|
62
|
+
configs.push(...await loadJsonConfigs(resolvedPath, nextStack))
|
|
63
|
+
}
|
|
64
|
+
return [...configs, parsed]
|
|
65
|
+
}
|
|
10
66
|
|
|
11
67
|
/**
|
|
12
68
|
* Load LikeC4 Project config file.
|
|
13
69
|
* If filepath is a non-JSON file, it will be bundled and required
|
|
14
70
|
*/
|
|
15
|
-
export async function loadConfig(filepath: VscodeURI): Promise<LikeC4ProjectConfig> {
|
|
16
|
-
|
|
17
|
-
logger.debug`Loading config
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
71
|
+
export async function loadConfig(filepath: VscodeURI | string): Promise<LikeC4ProjectConfig> {
|
|
72
|
+
filepath = typeof filepath === 'string' ? filepath : filepath.fsPath
|
|
73
|
+
logger.getChild('config').debug`Loading config: ${filepath}`
|
|
74
|
+
|
|
75
|
+
const folder = dirname(filepath)
|
|
76
|
+
const filename = basename(filepath)
|
|
77
|
+
const implicitcfg = { name: basename(folder) }
|
|
78
|
+
|
|
79
|
+
if (isLikeC4JsonConfig(filename)) {
|
|
80
|
+
const configs = await loadJsonConfigs(resolve(filepath), [])
|
|
81
|
+
invariant(hasAtLeast(configs, 1), 'Expect at least one config')
|
|
82
|
+
const rootConfig = omit(last(configs), ['extends', 'styles'])
|
|
83
|
+
const stylesChain = configs
|
|
84
|
+
.map(config => config.styles)
|
|
85
|
+
.filter(isNonNullish)
|
|
86
|
+
|
|
87
|
+
const mergedStyles = stylesChain.length > 0
|
|
88
|
+
? defu({}, ...stylesChain.reverse())
|
|
89
|
+
: undefined
|
|
90
|
+
|
|
91
|
+
return validateProjectConfig({
|
|
92
|
+
...implicitcfg,
|
|
93
|
+
...rootConfig,
|
|
94
|
+
...(mergedStyles ? { styles: mergedStyles } : {}),
|
|
95
|
+
})
|
|
26
96
|
}
|
|
27
97
|
|
|
28
|
-
invariant(isLikeC4NonJsonConfig(
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
98
|
+
invariant(isLikeC4NonJsonConfig(filename), `Invalid name for config file: ${filepath}`)
|
|
99
|
+
const { mod } = await bundleRequire({
|
|
100
|
+
filepath,
|
|
101
|
+
cwd: folder,
|
|
102
|
+
esbuildOptions: {
|
|
103
|
+
resolveExtensions: ['.ts', '.mts', '.cts', '.mjs', '.js', '.cjs'],
|
|
104
|
+
plugins: [{
|
|
105
|
+
name: 'likec4-config',
|
|
106
|
+
setup(build) {
|
|
107
|
+
/**
|
|
108
|
+
* Intercept @likec4/config and likec4/config imports
|
|
109
|
+
*/
|
|
110
|
+
build.onResolve({ filter: /^@?likec4\/config$/ }, (args) => ({
|
|
111
|
+
path: args.path,
|
|
112
|
+
namespace: 'likec4-config',
|
|
113
|
+
}))
|
|
114
|
+
build.onEnd((result) => {
|
|
115
|
+
const messages = formatMessagesSync(result.errors, { kind: 'error' })
|
|
116
|
+
for (const message of messages) {
|
|
117
|
+
logger.error(message)
|
|
118
|
+
}
|
|
119
|
+
})
|
|
120
|
+
/**
|
|
121
|
+
* Mock implementation, this allows to skip redundant bundling @likec4/config
|
|
122
|
+
*/
|
|
123
|
+
build.onLoad({ filter: /.*/, namespace: 'likec4-config' }, (_args) => {
|
|
124
|
+
return {
|
|
125
|
+
contents: `
|
|
52
126
|
// Mock implementation to allow loading config files without bundling @likec4/config
|
|
53
127
|
function mock(x) { return x }
|
|
54
128
|
export {
|
|
@@ -58,16 +132,12 @@ export {
|
|
|
58
132
|
mock as defineTheme,
|
|
59
133
|
mock as defineThemeColor,
|
|
60
134
|
}`,
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
} catch (err) {
|
|
70
|
-
logger.error(`Failed to load config file: ${filepath.fsPath}`, { err })
|
|
71
|
-
throw err
|
|
72
|
-
}
|
|
135
|
+
loader: 'js',
|
|
136
|
+
}
|
|
137
|
+
})
|
|
138
|
+
},
|
|
139
|
+
}],
|
|
140
|
+
},
|
|
141
|
+
})
|
|
142
|
+
return validateProjectConfig(Object.assign(implicitcfg, mod?.default ?? mod))
|
|
73
143
|
}
|
|
@@ -5,6 +5,14 @@ const IMAGE_ALIAS_KEY_REGEX = /^@[A-Za-z0-9_-]*$/
|
|
|
5
5
|
// Relative path (no leading slash, drive letter, or protocol)
|
|
6
6
|
const IMAGE_ALIAS_VALUE_REGEX = /^(?!\/|[A-Za-z]:[\\\/])(?!.*:\/\/).*$/
|
|
7
7
|
|
|
8
|
+
const ImageAliasKey = z
|
|
9
|
+
.string()
|
|
10
|
+
.min(1, 'Image alias key cannot be empty')
|
|
11
|
+
.regex(
|
|
12
|
+
IMAGE_ALIAS_KEY_REGEX,
|
|
13
|
+
'Image alias key must match /^@\\w+$/',
|
|
14
|
+
)
|
|
15
|
+
|
|
8
16
|
// Schema for an image alias value: must be a non-empty string representing a relative path (no leading slash, drive letter, or protocol).
|
|
9
17
|
const ImageAliasValue = z
|
|
10
18
|
.string()
|
|
@@ -15,54 +23,10 @@ const ImageAliasValue = z
|
|
|
15
23
|
)
|
|
16
24
|
|
|
17
25
|
export const ImageAliasesSchema = z.record(
|
|
18
|
-
|
|
26
|
+
ImageAliasKey, // PLAIN key schema - zod JSON schema export-safe.
|
|
19
27
|
ImageAliasValue,
|
|
20
28
|
).meta({
|
|
29
|
+
id: 'ImageAliases',
|
|
21
30
|
description:
|
|
22
31
|
'Map of image alias prefixes to relative paths (keys must match /^@\\w+$/; values must be relative paths without protocol or leading slash).',
|
|
23
32
|
})
|
|
24
|
-
|
|
25
|
-
// This just allows us to have a typed validate function.
|
|
26
|
-
type LikeC4ImageAliasConfig = z.infer<typeof ImageAliasesSchema>
|
|
27
|
-
|
|
28
|
-
export function validateImageAliases(imageAliases?: LikeC4ImageAliasConfig) {
|
|
29
|
-
const invalidKeys: string[] = []
|
|
30
|
-
const invalidValues: string[] = []
|
|
31
|
-
|
|
32
|
-
if (imageAliases) {
|
|
33
|
-
for (const [key, value] of Object.entries(imageAliases)) {
|
|
34
|
-
if (!IMAGE_ALIAS_KEY_REGEX.test(key)) {
|
|
35
|
-
invalidKeys.push(key)
|
|
36
|
-
}
|
|
37
|
-
// Value regex is technically already enforced by zod,
|
|
38
|
-
// so this check is purely defensive.
|
|
39
|
-
if (!IMAGE_ALIAS_VALUE_REGEX.test(value)) {
|
|
40
|
-
invalidValues.push(`${key} -> ${value}`)
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (invalidKeys.length || invalidValues.length) {
|
|
46
|
-
const parts: string[] = []
|
|
47
|
-
if (invalidKeys.length) {
|
|
48
|
-
parts.push(
|
|
49
|
-
`Invalid image alias key(s): ${
|
|
50
|
-
invalidKeys
|
|
51
|
-
.map((k) => JSON.stringify(k))
|
|
52
|
-
.join(', ')
|
|
53
|
-
} (must match ${IMAGE_ALIAS_KEY_REGEX})`,
|
|
54
|
-
)
|
|
55
|
-
}
|
|
56
|
-
if (invalidValues.length) {
|
|
57
|
-
parts.push(
|
|
58
|
-
`Invalid image alias value(s): ${
|
|
59
|
-
invalidValues
|
|
60
|
-
.map((kv) => JSON.stringify(kv))
|
|
61
|
-
.join(', ')
|
|
62
|
-
} (must match ${IMAGE_ALIAS_VALUE_REGEX})`,
|
|
63
|
-
)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
throw new Error(parts.join(' | '))
|
|
67
|
-
}
|
|
68
|
-
}
|
package/src/schema.include.ts
CHANGED
|
@@ -13,85 +13,47 @@ const IncludePathValue = z
|
|
|
13
13
|
'Include path must be a relative path (no leading slash, drive letter, or protocol)',
|
|
14
14
|
)
|
|
15
15
|
|
|
16
|
-
export const
|
|
17
|
-
|
|
18
|
-
.
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
})
|
|
50
|
-
.meta({
|
|
51
|
-
id: 'include-config',
|
|
52
|
-
description: 'Configuration for including additional LikeC4 source files',
|
|
16
|
+
export const IncludeSchema = z
|
|
17
|
+
.strictObject({
|
|
18
|
+
paths: z.array(IncludePathValue)
|
|
19
|
+
.meta({
|
|
20
|
+
description: [
|
|
21
|
+
'Additional relative directory paths to include LikeC4 source files from, searched recursively.',
|
|
22
|
+
'Paths are relative to the project folder (the folder containing this config file).',
|
|
23
|
+
'Example: ["../shared", "../common/specs"]',
|
|
24
|
+
].join('\n'),
|
|
25
|
+
}),
|
|
26
|
+
maxDepth: z.number()
|
|
27
|
+
.int()
|
|
28
|
+
.min(1)
|
|
29
|
+
.max(20)
|
|
30
|
+
.default(3)
|
|
31
|
+
.meta({
|
|
32
|
+
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
|
+
}),
|
|
38
|
+
fileThreshold: z.number()
|
|
39
|
+
.int()
|
|
40
|
+
.min(1)
|
|
41
|
+
.max(10000)
|
|
42
|
+
.default(30)
|
|
43
|
+
.meta({
|
|
44
|
+
description: [
|
|
45
|
+
'Maximum number of files to load from include paths before warning.',
|
|
46
|
+
'Helps identify performance issues from accidentally including large directories.',
|
|
47
|
+
'Default: 30',
|
|
48
|
+
].join('\n'),
|
|
49
|
+
}),
|
|
53
50
|
})
|
|
54
|
-
|
|
55
|
-
export type IncludeConfig = z.infer<typeof IncludeConfigSchema>
|
|
56
|
-
|
|
57
|
-
export const IncludeSchema = IncludeConfigSchema
|
|
58
|
-
.optional()
|
|
59
51
|
.meta({
|
|
52
|
+
id: 'include-config',
|
|
60
53
|
description: [
|
|
61
54
|
'Configuration for including additional LikeC4 source files from other directories.',
|
|
62
55
|
'Example: { "paths": ["../shared", "../common/specs"], "maxDepth": 5, "fileThreshold": 50 }',
|
|
63
56
|
].join('\n'),
|
|
64
57
|
})
|
|
65
58
|
|
|
66
|
-
type
|
|
67
|
-
|
|
68
|
-
export function normalizeIncludeConfig(include?: LikeC4IncludeConfig): IncludeConfig {
|
|
69
|
-
if (!include) {
|
|
70
|
-
return { paths: [], maxDepth: 3, fileThreshold: 30 }
|
|
71
|
-
}
|
|
72
|
-
return include
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export function validateIncludePaths(include?: LikeC4IncludeConfig): void {
|
|
76
|
-
if (!include?.paths) {
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const invalidPaths: string[] = []
|
|
81
|
-
|
|
82
|
-
for (const path of include.paths) {
|
|
83
|
-
if (!RELATIVE_PATH_REGEX.test(path)) {
|
|
84
|
-
invalidPaths.push(path)
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
if (invalidPaths.length > 0) {
|
|
89
|
-
throw new Error(
|
|
90
|
-
`Invalid include path(s): ${
|
|
91
|
-
invalidPaths
|
|
92
|
-
.map((p) => JSON.stringify(p))
|
|
93
|
-
.join(', ')
|
|
94
|
-
} (must be relative paths without leading slash, drive letter, or protocol)`,
|
|
95
|
-
)
|
|
96
|
-
}
|
|
97
|
-
}
|
|
59
|
+
export type IncludeConfig = z.infer<typeof IncludeSchema>
|
package/src/schema.theme.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// oxlint-disable no-unsafe-type-assertion
|
|
2
2
|
import type {
|
|
3
|
-
ColorLiteral,
|
|
4
3
|
ElementColorValues,
|
|
5
4
|
RelationshipColorValues,
|
|
6
5
|
ThemeColorValues,
|
|
@@ -15,6 +14,7 @@ import {
|
|
|
15
14
|
ThemeColors,
|
|
16
15
|
} from '@likec4/core/styles'
|
|
17
16
|
import type {
|
|
17
|
+
ColorLiteral,
|
|
18
18
|
CustomColor,
|
|
19
19
|
LikeC4ProjectStyleDefaults,
|
|
20
20
|
LikeC4ProjectStylesConfig,
|
|
@@ -23,7 +23,6 @@ import type {
|
|
|
23
23
|
ThemeColor,
|
|
24
24
|
} from '@likec4/core/types'
|
|
25
25
|
import { exact } from '@likec4/core/types'
|
|
26
|
-
import { fromKeys } from 'remeda'
|
|
27
26
|
import z from 'zod/v4'
|
|
28
27
|
|
|
29
28
|
const opacity = z
|
|
@@ -36,57 +35,74 @@ const opacity = z
|
|
|
36
35
|
})
|
|
37
36
|
|
|
38
37
|
const shape = z
|
|
39
|
-
.
|
|
38
|
+
.enum(ElementShapes)
|
|
40
39
|
.meta({ id: 'ElementShape' })
|
|
41
40
|
|
|
42
41
|
const border = z
|
|
43
|
-
.
|
|
42
|
+
.enum(BorderStyles)
|
|
44
43
|
.meta({ id: 'BorderStyle' })
|
|
45
44
|
|
|
46
45
|
const size = z
|
|
47
|
-
.
|
|
46
|
+
.enum(Sizes)
|
|
48
47
|
.meta({ id: 'ElementSize' })
|
|
49
48
|
|
|
50
49
|
const iconPosition = z
|
|
51
|
-
.
|
|
50
|
+
.enum(IconPositions)
|
|
52
51
|
.meta({ id: 'IconPosition' })
|
|
53
52
|
|
|
54
53
|
const arrow = z
|
|
55
|
-
.
|
|
54
|
+
.enum(RelationshipArrowTypes)
|
|
56
55
|
.meta({ id: 'ArrowType' })
|
|
57
56
|
|
|
58
57
|
const line = z
|
|
59
|
-
.
|
|
58
|
+
.enum(['dashed', 'solid', 'dotted'])
|
|
60
59
|
.meta({ id: 'LineType' })
|
|
61
60
|
|
|
62
61
|
const themeColor = z
|
|
63
|
-
.
|
|
64
|
-
.meta({ id: '
|
|
62
|
+
.enum(ThemeColors)
|
|
63
|
+
.meta({ id: 'ThemeColorName' })
|
|
65
64
|
|
|
66
65
|
const customColor = z
|
|
67
|
-
.string()
|
|
68
|
-
.
|
|
66
|
+
.custom<string & Record<never, never>>()
|
|
67
|
+
.refine(v => typeof v === 'string', 'Custom color name must be a string')
|
|
69
68
|
.transform(value => value as unknown as CustomColor)
|
|
70
69
|
.meta({ id: 'CustomColorName' })
|
|
71
70
|
|
|
72
|
-
//
|
|
71
|
+
// const color = z.custom<ColorNameLiteral>(
|
|
72
|
+
// (v) => {
|
|
73
|
+
// if (typeof v !== 'string' || v.length === 0) {
|
|
74
|
+
// throw new Error()
|
|
75
|
+
// }
|
|
76
|
+
// return v
|
|
77
|
+
// },
|
|
78
|
+
// {
|
|
79
|
+
// error: 'Invalid color name',
|
|
80
|
+
// },
|
|
81
|
+
// )
|
|
82
|
+
// .meta({ id: 'ColorName' })
|
|
73
83
|
|
|
74
|
-
const color =
|
|
75
|
-
// .custom<ColorNameLiteral>(v => typeof v === 'string')
|
|
76
|
-
.union([
|
|
77
|
-
themeColor,
|
|
78
|
-
customColor,
|
|
79
|
-
])
|
|
84
|
+
const color = themeColor.or(customColor)
|
|
80
85
|
.transform(value => value as ThemeColor)
|
|
81
86
|
.meta({ id: 'ColorName' })
|
|
87
|
+
// const color = themeColor.or(
|
|
88
|
+
// // z.never(),
|
|
89
|
+
// z.string().spa(),
|
|
90
|
+
// ).meta({ id: 'ColorName' })
|
|
91
|
+
// // .custom<ColorNameLiteral>(v => typeof v === 'string')
|
|
92
|
+
// .union([
|
|
93
|
+
// themeColor,
|
|
94
|
+
// customColor,
|
|
95
|
+
// ])
|
|
96
|
+
// .transform(value => value as ThemeColor)
|
|
97
|
+
// .meta({ id: 'ColorName' })
|
|
82
98
|
|
|
83
99
|
const colorValue = z
|
|
84
100
|
.string()
|
|
85
101
|
.min(1, 'Color value cannot be empty')
|
|
86
|
-
.transform(value => value as ColorLiteral)
|
|
102
|
+
// .transform(value => value as ColorLiteral)
|
|
87
103
|
.meta({
|
|
88
104
|
id: 'ColorLiteral',
|
|
89
|
-
description: 'Color value in any valid CSS format: hex, rgb, rgba, hsl, hsla ...',
|
|
105
|
+
// description: 'Color value in any valid CSS format: hex, rgb, rgba, hsl, hsla ...',
|
|
90
106
|
})
|
|
91
107
|
|
|
92
108
|
// const ColorSchema = z.union([ColorValue, LightDarkTuple])
|
|
@@ -116,51 +132,40 @@ const RelationshipColorValuesSchema = z
|
|
|
116
132
|
.transform(value => value as RelationshipColorValues)
|
|
117
133
|
|
|
118
134
|
const StrictThemeColorValuesSchema = z.strictObject({
|
|
119
|
-
elements:
|
|
120
|
-
.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
if (typeof value === 'string') {
|
|
124
|
-
return computeColorValues(value).elements
|
|
125
|
-
}
|
|
126
|
-
return value
|
|
127
|
-
}),
|
|
128
|
-
relationships: z
|
|
129
|
-
.union([colorSchema, RelationshipColorValuesSchema])
|
|
135
|
+
elements: ElementColorValuesSchema
|
|
136
|
+
.or(
|
|
137
|
+
colorSchema.transform(v => computeColorValues(v as ColorLiteral).elements),
|
|
138
|
+
)
|
|
130
139
|
.meta({
|
|
131
|
-
description: '
|
|
132
|
-
})
|
|
133
|
-
.transform((value): RelationshipColorValues => {
|
|
134
|
-
if (typeof value === 'string') {
|
|
135
|
-
return computeColorValues(value).relationships
|
|
136
|
-
}
|
|
137
|
-
return value
|
|
140
|
+
description: 'Exact color value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color values',
|
|
138
141
|
}),
|
|
142
|
+
relationships: RelationshipColorValuesSchema
|
|
143
|
+
.or(
|
|
144
|
+
colorSchema.transform(v => computeColorValues(v as ColorLiteral).relationships),
|
|
145
|
+
)
|
|
146
|
+
.meta({ description: 'Exact color value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color values' }),
|
|
139
147
|
})
|
|
140
|
-
.
|
|
148
|
+
.transform(value => value as ThemeColorValues)
|
|
149
|
+
.meta({
|
|
150
|
+
id: 'StrictThemeColorValues',
|
|
151
|
+
description: 'Exact color value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color value',
|
|
152
|
+
})
|
|
141
153
|
|
|
142
|
-
export const ThemeColorValuesSchema =
|
|
143
|
-
.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
])
|
|
154
|
+
export const ThemeColorValuesSchema = StrictThemeColorValuesSchema.or(
|
|
155
|
+
colorSchema.transform(v => computeColorValues(v as ColorLiteral)),
|
|
156
|
+
)
|
|
157
|
+
.transform(value => value as ThemeColorValues)
|
|
147
158
|
.meta({
|
|
148
159
|
id: 'ThemeColorValues',
|
|
149
160
|
description: 'Exact value (hex, rgb, rgba, hsl, hsla ...) or break down of specific color values',
|
|
150
161
|
})
|
|
151
|
-
|
|
152
|
-
if (typeof value === 'string') {
|
|
153
|
-
return computeColorValues(value)
|
|
154
|
-
}
|
|
155
|
-
return value
|
|
156
|
-
})
|
|
162
|
+
|
|
157
163
|
export type ThemeColorValuesInput = z.input<typeof ThemeColorValuesSchema>
|
|
158
164
|
|
|
159
|
-
const ThemeColorsSchema = z.
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
})
|
|
165
|
+
const ThemeColorsSchema = z.partialRecord(
|
|
166
|
+
color,
|
|
167
|
+
ThemeColorValuesSchema,
|
|
168
|
+
)
|
|
164
169
|
.transform(value => value as Record<ThemeColor, ThemeColorValues>)
|
|
165
170
|
|
|
166
171
|
const DimensionsSchema = z.strictObject({
|
|
@@ -168,26 +173,23 @@ const DimensionsSchema = z.strictObject({
|
|
|
168
173
|
height: z.number().min(50),
|
|
169
174
|
}).meta({
|
|
170
175
|
id: 'Dimensions',
|
|
171
|
-
description: '
|
|
176
|
+
description: 'Defines dimensions for theme size',
|
|
172
177
|
})
|
|
173
178
|
|
|
174
|
-
const LikeC4Config_Styles_Theme_Sizes = z
|
|
175
|
-
.strictObject(
|
|
176
|
-
fromKeys(Sizes, () => DimensionsSchema.optional()),
|
|
177
|
-
)
|
|
178
|
-
.meta({
|
|
179
|
-
id: 'ThemeSizes',
|
|
180
|
-
description: 'Override theme sizes',
|
|
181
|
-
})
|
|
179
|
+
const LikeC4Config_Styles_Theme_Sizes = z.partialRecord(size, DimensionsSchema)
|
|
182
180
|
|
|
183
181
|
export const LikeC4Config_Styles_Theme = z
|
|
184
182
|
.strictObject({
|
|
185
|
-
colors: ThemeColorsSchema.optional()
|
|
186
|
-
|
|
183
|
+
colors: ThemeColorsSchema.optional().meta({
|
|
184
|
+
description: 'Override theme colors',
|
|
185
|
+
}),
|
|
186
|
+
sizes: LikeC4Config_Styles_Theme_Sizes.optional().meta({
|
|
187
|
+
description: 'Override theme sizes',
|
|
188
|
+
}),
|
|
187
189
|
})
|
|
188
190
|
.meta({
|
|
189
191
|
id: 'ThemeCustomization',
|
|
190
|
-
description: '
|
|
192
|
+
description: 'Customize theme colors and sizes',
|
|
191
193
|
})
|
|
192
194
|
.transform(({ colors, sizes }): LikeC4ProjectTheme => {
|
|
193
195
|
return exact({
|
|
@@ -207,8 +209,6 @@ const LikeC4Config_Styles_Defaults_Group = z
|
|
|
207
209
|
})
|
|
208
210
|
.meta({
|
|
209
211
|
id: 'GroupDefaultStyleValues',
|
|
210
|
-
description:
|
|
211
|
-
'Override default values for group style properties\nThese values will be used if such property is not defined',
|
|
212
212
|
})
|
|
213
213
|
|
|
214
214
|
const LikeC4Config_Styles_Defaults_Relationship = z
|
|
@@ -239,15 +239,17 @@ const LikeC4Config_Styles_Defaults = z
|
|
|
239
239
|
size: size.optional().meta({ description: 'Default size for elements' }),
|
|
240
240
|
shape: shape.optional().meta({ description: 'Default shape for elements' }),
|
|
241
241
|
iconPosition: iconPosition.optional().meta({ description: 'Default icon position for elements' }),
|
|
242
|
-
group: LikeC4Config_Styles_Defaults_Group.optional().meta({
|
|
242
|
+
group: LikeC4Config_Styles_Defaults_Group.optional().meta({
|
|
243
|
+
description:
|
|
244
|
+
'Override default values for group style properties\nThese values will be used if such property is not defined',
|
|
245
|
+
}),
|
|
243
246
|
relationship: LikeC4Config_Styles_Defaults_Relationship.optional().meta({
|
|
244
|
-
description:
|
|
247
|
+
description:
|
|
248
|
+
'Override default values for relationship style properties\nThese values will be used if such property is not defined',
|
|
245
249
|
}),
|
|
246
250
|
})
|
|
247
251
|
.meta({
|
|
248
252
|
id: 'DefaultStyleValues',
|
|
249
|
-
description:
|
|
250
|
-
'Override default values for style properties\nThese values will be used if such property is not defined',
|
|
251
253
|
})
|
|
252
254
|
|
|
253
255
|
const LikeC4Config_Styles_CustomStylesheets = z
|
|
@@ -259,18 +261,20 @@ const LikeC4Config_Styles_CustomStylesheets = z
|
|
|
259
261
|
])
|
|
260
262
|
.meta({
|
|
261
263
|
id: 'CustomStylesheets',
|
|
262
|
-
description: 'Custom CSS (or list of CSS files) to be included in the generated diagrams',
|
|
263
264
|
})
|
|
264
265
|
|
|
265
266
|
export const LikeC4StylesConfigSchema = z
|
|
266
267
|
.strictObject({
|
|
267
|
-
theme: LikeC4Config_Styles_Theme.optional()
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
268
|
+
theme: LikeC4Config_Styles_Theme.optional().meta({
|
|
269
|
+
description: 'Project theme customization',
|
|
270
|
+
}),
|
|
271
|
+
defaults: LikeC4Config_Styles_Defaults.optional().meta({
|
|
272
|
+
description:
|
|
273
|
+
'Override default values for style properties\nThese values will be used if such property is not defined',
|
|
274
|
+
}),
|
|
275
|
+
customCss: LikeC4Config_Styles_CustomStylesheets.optional().meta({
|
|
276
|
+
description: 'Custom CSS (or list of CSS files) to be included in the generated diagrams',
|
|
277
|
+
}),
|
|
274
278
|
})
|
|
275
279
|
.transform(({ theme, defaults, customCss }): LikeC4ProjectStylesConfig =>
|
|
276
280
|
exact({
|
|
@@ -294,7 +298,7 @@ function normalizeDefaults(
|
|
|
294
298
|
...rest,
|
|
295
299
|
relationship: relationship && exact(relationship) satisfies LikeC4ProjectStyleDefaults['relationship'],
|
|
296
300
|
group: group && exact(group) satisfies LikeC4ProjectStyleDefaults['group'],
|
|
297
|
-
})
|
|
301
|
+
}) satisfies LikeC4ProjectStyleDefaults
|
|
298
302
|
}
|
|
299
303
|
|
|
300
304
|
function normalizeStylesheets(
|