@alfacapital-tech/c4builder 0.3.0-rc.1 → 0.3.0-rc.3
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 +11 -18
- package/dist/cli/commands/help.js +0 -2
- package/dist/cli/commands/jre.js +10 -3
- package/dist/cli/commands/list.js +9 -1
- package/dist/cli/commands/new.js +10 -29
- package/dist/cli/dispatch.js +135 -37
- package/dist/cli/wizard/collect.js +51 -91
- package/dist/config/defaults.js +4 -2
- package/dist/config/options.js +29 -1
- package/dist/config/schema.js +53 -32
- package/dist/core/build.js +74 -48
- package/dist/core/compose/docsify.template.js +2 -2
- package/dist/core/compose/markdown.js +146 -135
- package/dist/core/render/d2renderer.js +67 -11
- package/dist/core/render/diagrams.js +256 -100
- package/dist/core/render/fonts.js +8 -0
- package/dist/core/render/jre.js +155 -51
- package/dist/core/render/pngraster.js +18 -24
- package/dist/core/scan/tree.js +64 -16
- package/dist/util/http.js +55 -0
- package/dist/util/lock.js +58 -0
- package/dist/util/utils.js +10 -25
- package/package.json +5 -1
- package/vendor/fonts/NimbusSans-Bold.otf +0 -0
- package/vendor/fonts/NimbusSans-BoldItalic.otf +0 -0
- package/vendor/fonts/NimbusSans-Italic.otf +0 -0
- package/vendor/fonts/NimbusSans-Regular.otf +0 -0
- package/vendor/fonts/README.md +13 -10
- package/vendor/fonts/LiberationSans-Bold.ttf +0 -0
- package/vendor/fonts/LiberationSans-BoldItalic.ttf +0 -0
- package/vendor/fonts/LiberationSans-Italic.ttf +0 -0
- package/vendor/fonts/LiberationSans-Regular.ttf +0 -0
|
@@ -3,8 +3,16 @@ import { z } from 'zod';
|
|
|
3
3
|
import fs from 'node:fs';
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { defaultConfig } from "../../config/defaults.js";
|
|
6
|
-
//
|
|
7
|
-
|
|
6
|
+
// Непустая строка: в zod `.trim()` — трансформ, а не проверка, поэтому пустую строку
|
|
7
|
+
// и пробелы отсекает явный `.min(1)`.
|
|
8
|
+
const nonEmptyString = z.string().trim().min(1);
|
|
9
|
+
// Валидатор ответа визарда: true при успешном safeParse, иначе текст ошибки для inquirer.
|
|
10
|
+
// Пустой Enter уже подставил дефолт (answer = value || default) — сообщение получают
|
|
11
|
+
// только явно введённые пробелы/пустая строка при отсутствии дефолта.
|
|
12
|
+
const validate = (schema, message = 'значение обязательно') => (answer) => schema.safeParse(answer).success || message;
|
|
13
|
+
// Пункт default-списка чекбокса inquirer: ключ отмечен, если он включён в текущем
|
|
14
|
+
// конфиге, а при незаданном значении — если включён по умолчанию.
|
|
15
|
+
const checkedKey = (current, dflt, key) => (current === undefined ? dflt : current) ? key : null;
|
|
8
16
|
export default async (currentConfiguration, conf, program) => {
|
|
9
17
|
// ESM исполняется в strict mode: под CommonJS `responses` был неявным глобалом,
|
|
10
18
|
// здесь объявляем его явно (иначе присваивание бросит ReferenceError).
|
|
@@ -15,7 +23,7 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
15
23
|
name: 'projectName',
|
|
16
24
|
message: 'Project Name',
|
|
17
25
|
default: currentConfiguration.PROJECT_NAME || path.parse(process.cwd()).name,
|
|
18
|
-
validate: validate(
|
|
26
|
+
validate: validate(nonEmptyString, 'укажите имя проекта')
|
|
19
27
|
});
|
|
20
28
|
conf.set('projectName', responses.projectName);
|
|
21
29
|
}
|
|
@@ -25,7 +33,7 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
25
33
|
name: 'homepageName',
|
|
26
34
|
message: 'HomePage Name',
|
|
27
35
|
default: currentConfiguration.HOMEPAGE_NAME || defaultConfig.homepageName,
|
|
28
|
-
validate: validate(
|
|
36
|
+
validate: validate(nonEmptyString, 'укажите имя главной страницы')
|
|
29
37
|
});
|
|
30
38
|
conf.set('homepageName', responses.homepageName);
|
|
31
39
|
}
|
|
@@ -35,17 +43,21 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
35
43
|
name: 'rootFolder',
|
|
36
44
|
message: 'Root documentation folder',
|
|
37
45
|
default: currentConfiguration.ROOT_FOLDER || defaultConfig.rootFolder,
|
|
38
|
-
validate: (
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
validate: (answer) => {
|
|
47
|
+
if (!nonEmptyString.safeParse(answer).success)
|
|
48
|
+
return 'укажите папку с документацией';
|
|
49
|
+
if (answer.indexOf('/') !== -1 || answer.indexOf('\\') !== -1)
|
|
50
|
+
return 'имя папки не должно содержать «/» или «\\»';
|
|
51
|
+
// statSync бросает ENOENT для несуществующего пути — ловим и превращаем в
|
|
52
|
+
// сообщение, иначе async-хендлер inquirer уронит процесс сырым стектрейсом.
|
|
53
|
+
try {
|
|
54
|
+
if (!fs.statSync(path.join(process.cwd(), answer)).isDirectory())
|
|
55
|
+
return 'указанный путь не является каталогом';
|
|
47
56
|
}
|
|
48
|
-
|
|
57
|
+
catch {
|
|
58
|
+
return 'папка не найдена';
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
49
61
|
}
|
|
50
62
|
});
|
|
51
63
|
conf.set('rootFolder', responses.rootFolder);
|
|
@@ -74,27 +86,9 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
74
86
|
currentConfiguration.GENERATE_WEBSITE === undefined ||
|
|
75
87
|
program.config) {
|
|
76
88
|
const defaults = [
|
|
77
|
-
currentConfiguration.GENERATE_MD
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
: null
|
|
81
|
-
: currentConfiguration.GENERATE_MD
|
|
82
|
-
? 'generateMD'
|
|
83
|
-
: null,
|
|
84
|
-
currentConfiguration.GENERATE_COMPLETE_MD_FILE === undefined
|
|
85
|
-
? defaultConfig.generateCompleteMD
|
|
86
|
-
? 'generateCompleteMD'
|
|
87
|
-
: null
|
|
88
|
-
: currentConfiguration.GENERATE_COMPLETE_MD_FILE
|
|
89
|
-
? 'generateCompleteMD'
|
|
90
|
-
: null,
|
|
91
|
-
currentConfiguration.GENERATE_WEBSITE === undefined
|
|
92
|
-
? defaultConfig.generateWEB
|
|
93
|
-
? 'generateWEB'
|
|
94
|
-
: null
|
|
95
|
-
: currentConfiguration.GENERATE_WEBSITE
|
|
96
|
-
? 'generateWEB'
|
|
97
|
-
: null
|
|
89
|
+
checkedKey(currentConfiguration.GENERATE_MD, defaultConfig.generateMD, 'generateMD'),
|
|
90
|
+
checkedKey(currentConfiguration.GENERATE_COMPLETE_MD_FILE, defaultConfig.generateCompleteMD, 'generateCompleteMD'),
|
|
91
|
+
checkedKey(currentConfiguration.GENERATE_WEBSITE, defaultConfig.generateWEB, 'generateWEB')
|
|
98
92
|
];
|
|
99
93
|
responses = await inquirer.prompt({
|
|
100
94
|
type: 'checkbox',
|
|
@@ -116,10 +110,10 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
116
110
|
}
|
|
117
111
|
]
|
|
118
112
|
});
|
|
119
|
-
conf.set('generateMD',
|
|
120
|
-
conf.set('generateCompleteMD',
|
|
121
|
-
conf.set('generateWEB',
|
|
122
|
-
if (responses.generate.
|
|
113
|
+
conf.set('generateMD', responses.generate.includes('generateMD'));
|
|
114
|
+
conf.set('generateCompleteMD', responses.generate.includes('generateCompleteMD'));
|
|
115
|
+
conf.set('generateWEB', responses.generate.includes('generateWEB'));
|
|
116
|
+
if (responses.generate.includes('generateMD')) {
|
|
123
117
|
let mdOptions = await inquirer.prompt({
|
|
124
118
|
type: 'confirm',
|
|
125
119
|
name: 'includeNavigation',
|
|
@@ -139,7 +133,7 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
139
133
|
});
|
|
140
134
|
conf.set('includeTableOfContents', mdOptions.includeTableOfContents);
|
|
141
135
|
}
|
|
142
|
-
if (responses.generate.
|
|
136
|
+
if (responses.generate.includes('generateWEB')) {
|
|
143
137
|
let webOptions = await inquirer.prompt({
|
|
144
138
|
type: 'input',
|
|
145
139
|
name: 'webTheme',
|
|
@@ -148,7 +142,9 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
148
142
|
});
|
|
149
143
|
conf.set('webTheme', webOptions.webTheme);
|
|
150
144
|
webOptions = await inquirer.prompt({
|
|
151
|
-
|
|
145
|
+
// confirm возвращает boolean — схема требует строгий z.boolean();
|
|
146
|
+
// input сохранял бы строку ('y') и валил последующий safeParse конфига.
|
|
147
|
+
type: 'confirm',
|
|
152
148
|
name: 'supportSearch',
|
|
153
149
|
message: 'Support search on navbar?',
|
|
154
150
|
default: defaultConfig.supportSearch
|
|
@@ -195,48 +191,12 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
195
191
|
currentConfiguration.EXCLUDE_OTHER_FILES === undefined ||
|
|
196
192
|
program.config) {
|
|
197
193
|
const defaults = [
|
|
198
|
-
currentConfiguration.INCLUDE_BREADCRUMBS
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
: null,
|
|
205
|
-
currentConfiguration.GENERATE_LOCAL_IMAGES === undefined
|
|
206
|
-
? defaultConfig.generateLocalImages
|
|
207
|
-
? 'generateLocalImages'
|
|
208
|
-
: null
|
|
209
|
-
: currentConfiguration.GENERATE_LOCAL_IMAGES
|
|
210
|
-
? 'generateLocalImages'
|
|
211
|
-
: null,
|
|
212
|
-
currentConfiguration.INCLUDE_LINK_TO_DIAGRAM === undefined
|
|
213
|
-
? defaultConfig.includeLinkToDiagram
|
|
214
|
-
? 'includeLinkToDiagram'
|
|
215
|
-
: null
|
|
216
|
-
: currentConfiguration.INCLUDE_LINK_TO_DIAGRAM
|
|
217
|
-
? 'includeLinkToDiagram'
|
|
218
|
-
: null,
|
|
219
|
-
currentConfiguration.DIAGRAMS_ON_TOP === undefined
|
|
220
|
-
? defaultConfig.diagramsOnTop
|
|
221
|
-
? 'diagramsOnTop'
|
|
222
|
-
: null
|
|
223
|
-
: currentConfiguration.DIAGRAMS_ON_TOP
|
|
224
|
-
? 'diagramsOnTop'
|
|
225
|
-
: null,
|
|
226
|
-
currentConfiguration.EMBED_DIAGRAM === undefined
|
|
227
|
-
? defaultConfig.embedDiagram
|
|
228
|
-
? 'embedDiagram'
|
|
229
|
-
: null
|
|
230
|
-
: currentConfiguration.EMBED_DIAGRAM
|
|
231
|
-
? 'embedDiagram'
|
|
232
|
-
: null,
|
|
233
|
-
currentConfiguration.EXCLUDE_OTHER_FILES === undefined
|
|
234
|
-
? defaultConfig.excludeOtherFiles
|
|
235
|
-
? 'excludeOtherFiles'
|
|
236
|
-
: null
|
|
237
|
-
: currentConfiguration.EXCLUDE_OTHER_FILES
|
|
238
|
-
? 'excludeOtherFiles'
|
|
239
|
-
: null
|
|
194
|
+
checkedKey(currentConfiguration.INCLUDE_BREADCRUMBS, defaultConfig.includeBreadcrumbs, 'includeBreadcrumbs'),
|
|
195
|
+
checkedKey(currentConfiguration.GENERATE_LOCAL_IMAGES, defaultConfig.generateLocalImages, 'generateLocalImages'),
|
|
196
|
+
checkedKey(currentConfiguration.INCLUDE_LINK_TO_DIAGRAM, defaultConfig.includeLinkToDiagram, 'includeLinkToDiagram'),
|
|
197
|
+
checkedKey(currentConfiguration.DIAGRAMS_ON_TOP, defaultConfig.diagramsOnTop, 'diagramsOnTop'),
|
|
198
|
+
checkedKey(currentConfiguration.EMBED_DIAGRAM, defaultConfig.embedDiagram, 'embedDiagram'),
|
|
199
|
+
checkedKey(currentConfiguration.EXCLUDE_OTHER_FILES, defaultConfig.excludeOtherFiles, 'excludeOtherFiles')
|
|
240
200
|
];
|
|
241
201
|
const choices = [
|
|
242
202
|
{
|
|
@@ -271,12 +231,12 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
271
231
|
default: defaults,
|
|
272
232
|
choices: choices
|
|
273
233
|
});
|
|
274
|
-
conf.set('includeBreadcrumbs',
|
|
275
|
-
conf.set('includeLinkToDiagram',
|
|
276
|
-
conf.set('diagramsOnTop',
|
|
277
|
-
conf.set('embedDiagram',
|
|
278
|
-
conf.set('excludeOtherFiles',
|
|
279
|
-
conf.set('generateLocalImages',
|
|
234
|
+
conf.set('includeBreadcrumbs', responses.generate.includes('includeBreadcrumbs'));
|
|
235
|
+
conf.set('includeLinkToDiagram', responses.generate.includes('includeLinkToDiagram'));
|
|
236
|
+
conf.set('diagramsOnTop', responses.generate.includes('diagramsOnTop'));
|
|
237
|
+
conf.set('embedDiagram', responses.generate.includes('embedDiagram'));
|
|
238
|
+
conf.set('excludeOtherFiles', responses.generate.includes('excludeOtherFiles'));
|
|
239
|
+
conf.set('generateLocalImages', responses.generate.includes('generateLocalImages'));
|
|
280
240
|
}
|
|
281
241
|
if (!currentConfiguration.PLANTUML_SERVER_URL || program.config) {
|
|
282
242
|
responses = await inquirer.prompt({
|
|
@@ -284,7 +244,7 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
284
244
|
name: 'plantumlServerUrl',
|
|
285
245
|
message: 'PlantUML Server URL',
|
|
286
246
|
default: currentConfiguration.PLANTUML_SERVER_URL || defaultConfig.plantumlServerUrl,
|
|
287
|
-
validate: validate(
|
|
247
|
+
validate: validate(nonEmptyString, 'укажите URL сервера PlantUML')
|
|
288
248
|
});
|
|
289
249
|
conf.set('plantumlServerUrl', responses.plantumlServerUrl);
|
|
290
250
|
}
|
|
@@ -294,7 +254,7 @@ export default async (currentConfiguration, conf, program) => {
|
|
|
294
254
|
name: 'diagramFormat',
|
|
295
255
|
message: 'Diagram Image Format',
|
|
296
256
|
default: currentConfiguration.DIAGRAM_FORMAT || defaultConfig.diagramFormat,
|
|
297
|
-
validate: validate(
|
|
257
|
+
validate: validate(nonEmptyString, 'укажите формат изображения диаграмм')
|
|
298
258
|
});
|
|
299
259
|
conf.set('diagramFormat', responses.diagramFormat);
|
|
300
260
|
}
|
package/dist/config/defaults.js
CHANGED
|
@@ -22,12 +22,14 @@ const defaultConfig = {
|
|
|
22
22
|
webPort: '3000',
|
|
23
23
|
includeBreadcrumbs: true,
|
|
24
24
|
includeLinkToDiagram: false,
|
|
25
|
-
diagramsOnTop:
|
|
25
|
+
diagramsOnTop: true,
|
|
26
26
|
embedDiagram: false,
|
|
27
27
|
excludeOtherFiles: false,
|
|
28
28
|
generateLocalImages: true,
|
|
29
29
|
plantumlServerUrl: 'https://www.plantuml.com/plantuml',
|
|
30
30
|
diagramFormat: 'svg',
|
|
31
|
-
|
|
31
|
+
d2Layout: 'dagre',
|
|
32
|
+
charset: 'UTF-8',
|
|
33
|
+
useSystemFonts: false
|
|
32
34
|
};
|
|
33
35
|
export { defaultConfig };
|
package/dist/config/options.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
// Опорные типы конфигурации c4builder. Объявлены здесь (первой группой порта),
|
|
2
2
|
// потому что результат getOptions() (BuildOptions) потребляет build() — он
|
|
3
3
|
// портируется раньше, чем cli/dispatch, где getOptions() физически живёт.
|
|
4
|
-
|
|
4
|
+
// Форма файла `.c4builder` (camelCase) — единый тип задаёт zod-схема (config/schema),
|
|
5
|
+
// её же валидацией опирается загрузка опций сборки. Ре-экспорт: потребители типа
|
|
6
|
+
// (defaults, dispatch) продолжают импортировать его отсюда.
|
|
7
|
+
import { configSchema } from "./schema.js";
|
|
8
|
+
/**
|
|
9
|
+
* Разбирает сырой объект `.c4builder`, разделяя строгий и щадящий пути. Сам НЕ печатает
|
|
10
|
+
* и НЕ завершает процесс — решение (ошибка+выход либо предупреждение) принимает CLI.
|
|
11
|
+
* Битые ключи определяются по первому сегменту `issue.path`; `salvaged` получается
|
|
12
|
+
* повторным разбором очищенного от них конфига — без дублирования схемы.
|
|
13
|
+
*/
|
|
14
|
+
export function parseConfig(raw) {
|
|
15
|
+
const parsed = configSchema.safeParse(raw);
|
|
16
|
+
if (parsed.success)
|
|
17
|
+
return { ok: true, value: parsed.data };
|
|
18
|
+
const issues = [];
|
|
19
|
+
const brokenKeys = new Set();
|
|
20
|
+
for (const issue of parsed.error.issues) {
|
|
21
|
+
const key = issue.path.length ? String(issue.path[0]) : '(корень)';
|
|
22
|
+
if (brokenKeys.has(key))
|
|
23
|
+
continue; // один ключ — одно сообщение (напр. массив с N битых элементов)
|
|
24
|
+
brokenKeys.add(key);
|
|
25
|
+
issues.push({ key, value: raw[key], message: issue.message });
|
|
26
|
+
}
|
|
27
|
+
const cleaned = Object.fromEntries(Object.entries(raw).filter(([k]) => !brokenKeys.has(k)));
|
|
28
|
+
const salvagedParse = configSchema.safeParse(cleaned);
|
|
29
|
+
// cleaned свободен от всех проблемных ключей → валиден; фолбэк на пустой конфиг — страховка.
|
|
30
|
+
const salvaged = salvagedParse.success ? salvagedParse.data : configSchema.parse({});
|
|
31
|
+
return { ok: false, issues, salvaged };
|
|
32
|
+
}
|
package/dist/config/schema.js
CHANGED
|
@@ -4,45 +4,66 @@
|
|
|
4
4
|
// валидируется `safeParse` — неверный тип значения отклоняется понятной ошибкой с
|
|
5
5
|
// путём ключа, отсутствующие поля дополняются дефолтами.
|
|
6
6
|
//
|
|
7
|
-
//
|
|
7
|
+
// Числа в строковых полях коэрсятся (`webPort: 8000` из рукописного конфига → строка),
|
|
8
8
|
// булевы поля строгие (`z.boolean`: строка вместо булева — ошибка, а не тихое truthy).
|
|
9
9
|
import { z } from 'zod';
|
|
10
10
|
import { defaultConfig } from "./defaults.js";
|
|
11
|
+
// В JSON-конфиге `null` означает «ключ не задан» (а не строку "null"): гоним его в
|
|
12
|
+
// `undefined` до внутренней схемы, чтобы срабатывал `.default`/`.optional`, а не строгий тип.
|
|
13
|
+
const nullish = (s) => z.preprocess((v) => (v === null ? undefined : v), s);
|
|
14
|
+
// Строковое поле: null И false = «не задано» → дефолт (false — легаси-идиома отключения,
|
|
15
|
+
// напр. `docsifyTemplate: false` в конфигах 0.2.x); число коэрсится в строку. Прочие типы
|
|
16
|
+
// (true, объекты) — понятная ошибка, а не тихая строка 'true' (z.coerce.string превращал
|
|
17
|
+
// false в 'false', и сборка падала на require('<cwd>/false')).
|
|
18
|
+
const strPre = (v) => v === null || v === false ? undefined : typeof v === 'number' ? String(v) : v;
|
|
19
|
+
const str = (def) => z.preprocess(strPre, z.string().default(def));
|
|
20
|
+
const optStr = () => z.preprocess(strPre, z.string().optional());
|
|
21
|
+
const bool = (def) => nullish(z.boolean().default(def));
|
|
22
|
+
const optBool = () => nullish(z.boolean().optional());
|
|
23
|
+
// TCP-порт: нечисловая строка ('30OO') иначе доехала бы до app.listen(), который
|
|
24
|
+
// трактует её как имя unix-сокета. Экспортируется для проверки CLI-флага -p.
|
|
25
|
+
export const isValidPort = (s) => /^\d+$/.test(s) && Number(s) >= 1 && Number(s) <= 65535;
|
|
11
26
|
export const configSchema = z.object({
|
|
12
27
|
// Базовые поля: опциональны в файле, но с дефолтом из defaultConfig — частичный
|
|
13
28
|
// или пустой конфиг остаётся валидным (недостающее берёт значение по умолчанию).
|
|
14
|
-
homepageName:
|
|
15
|
-
rootFolder:
|
|
16
|
-
distFolder:
|
|
17
|
-
generateMD:
|
|
18
|
-
generateCompleteMD:
|
|
19
|
-
generateWEB:
|
|
20
|
-
includeNavigation:
|
|
21
|
-
includeTableOfContents:
|
|
22
|
-
webTheme:
|
|
23
|
-
supportSearch:
|
|
24
|
-
repoUrl:
|
|
25
|
-
executeScript:
|
|
26
|
-
docsifyTemplate:
|
|
27
|
-
webPort: z.
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
29
|
+
homepageName: str(defaultConfig.homepageName),
|
|
30
|
+
rootFolder: str(defaultConfig.rootFolder),
|
|
31
|
+
distFolder: str(defaultConfig.distFolder),
|
|
32
|
+
generateMD: bool(defaultConfig.generateMD),
|
|
33
|
+
generateCompleteMD: bool(defaultConfig.generateCompleteMD),
|
|
34
|
+
generateWEB: bool(defaultConfig.generateWEB),
|
|
35
|
+
includeNavigation: bool(defaultConfig.includeNavigation),
|
|
36
|
+
includeTableOfContents: bool(defaultConfig.includeTableOfContents),
|
|
37
|
+
webTheme: str(defaultConfig.webTheme),
|
|
38
|
+
supportSearch: bool(defaultConfig.supportSearch),
|
|
39
|
+
repoUrl: str(defaultConfig.repoUrl),
|
|
40
|
+
executeScript: bool(defaultConfig.executeScript),
|
|
41
|
+
docsifyTemplate: str(defaultConfig.docsifyTemplate),
|
|
42
|
+
webPort: z.preprocess(strPre, z
|
|
43
|
+
.string()
|
|
44
|
+
.refine(isValidPort, 'ожидается TCP-порт: целое число 1..65535')
|
|
45
|
+
.default(defaultConfig.webPort)),
|
|
46
|
+
includeBreadcrumbs: bool(defaultConfig.includeBreadcrumbs),
|
|
47
|
+
includeLinkToDiagram: bool(defaultConfig.includeLinkToDiagram),
|
|
48
|
+
diagramsOnTop: bool(defaultConfig.diagramsOnTop),
|
|
49
|
+
embedDiagram: bool(defaultConfig.embedDiagram),
|
|
50
|
+
excludeOtherFiles: bool(defaultConfig.excludeOtherFiles),
|
|
51
|
+
generateLocalImages: bool(defaultConfig.generateLocalImages),
|
|
52
|
+
plantumlServerUrl: str(defaultConfig.plantumlServerUrl),
|
|
53
|
+
diagramFormat: str(defaultConfig.diagramFormat),
|
|
54
|
+
d2Layout: str(defaultConfig.d2Layout),
|
|
55
|
+
charset: str(defaultConfig.charset),
|
|
56
|
+
useSystemFonts: bool(defaultConfig.useSystemFonts),
|
|
37
57
|
// Задаются явно (флаг/промпт) либо тонкой правкой файла — в defaultConfig не входят.
|
|
38
|
-
projectName:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
58
|
+
projectName: optStr(),
|
|
59
|
+
webFileName: optStr(),
|
|
60
|
+
// Не-строки внутри массива молча отбрасываются (совместимость с master: null среди
|
|
61
|
+
// путей не валит сборку) — runtime дальше работает с чистым string[].
|
|
62
|
+
excludeSidebarFolderByPath: nullish(z.preprocess((v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : v), z.array(z.string()).optional())),
|
|
63
|
+
hasRun: optBool(),
|
|
43
64
|
// Легаси-ключи старых `.c4builder` — читаются лишь для однократного предупреждения.
|
|
44
|
-
plantumlVersion:
|
|
45
|
-
generatePDF:
|
|
46
|
-
generateCompletePDF:
|
|
65
|
+
plantumlVersion: optStr(),
|
|
66
|
+
generatePDF: optBool(),
|
|
67
|
+
generateCompletePDF: optBool(),
|
|
47
68
|
checksums: z.unknown().optional()
|
|
48
69
|
});
|
package/dist/core/build.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fsextra from 'fs-extra';
|
|
4
|
-
import { makeDirectory } from "../util/utils.js";
|
|
5
|
-
//
|
|
6
|
-
|
|
4
|
+
import { makeDirectory, writeOnSameLine } from "../util/utils.js";
|
|
5
|
+
// clearD2FileCache: сброс кеша графа D2-импортов на границах сборки (сам webworker
|
|
6
|
+
// D2 гасит CLI после одиночной сборки — в watch-режиме он переживает ребилды).
|
|
7
|
+
import { clearD2FileCache } from "./render/d2renderer.js";
|
|
7
8
|
// Фазы сборки: scan (дерево исходников) → render (диаграммы) → compose (markdown/сайт).
|
|
8
|
-
import { generateTree } from "./scan/tree.js";
|
|
9
|
+
import { generateTree, engineSupportsRemote, clearIncludeCache } from "./scan/tree.js";
|
|
9
10
|
import { generateImages } from "./render/diagrams.js";
|
|
10
|
-
import { generateMD, generateWebMD, generateCompleteMD } from "./compose/markdown.js";
|
|
11
|
+
import { generateMD, generateWebMD, generateCompleteMD, clearDiagramCache } from "./compose/markdown.js";
|
|
11
12
|
// Оркестратор владеет lifecycle выходного каталога: dist бэкапится в dist_bk, откуда
|
|
12
13
|
// generateImages восстанавливает неизменённые по чексумме картинки; в конце бэкап сносится.
|
|
13
14
|
const DIST_BACKUP_FOLDER_SUFFIX = '_bk';
|
|
@@ -26,50 +27,75 @@ const build = async (options, cacheConf) => {
|
|
|
26
27
|
await fsextra.emptyDir(options.DIST_FOLDER);
|
|
27
28
|
}
|
|
28
29
|
await makeDirectory(path.join(options.DIST_FOLDER));
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
30
|
+
// Сбросить пофайловые кеши прошлой сборки (актуально для watch-режима): base64
|
|
31
|
+
// диаграмм, граф D2-импортов, контент include-файлов PlantUML.
|
|
32
|
+
clearDiagramCache();
|
|
33
|
+
clearD2FileCache();
|
|
34
|
+
clearIncludeCache();
|
|
35
|
+
let ok = false;
|
|
36
|
+
try {
|
|
37
|
+
//actual build
|
|
38
|
+
console.log(chalk.green(`\nbuilding documentation in ./${options.DIST_FOLDER}`));
|
|
39
|
+
const tree = await generateTree(options.ROOT_FOLDER, options);
|
|
40
|
+
console.log(chalk.blue(`parsed ${tree.length} folders`));
|
|
41
|
+
// У движков без онлайн-рендера (D2, см. remoteRender в DIAGRAM_ENGINES) при
|
|
42
|
+
// выключенной локальной генерации диаграммы не во что превратить — ссылки на
|
|
43
|
+
// SVG вели бы в никуда. Падаем сразу с понятной ошибкой, а не молча битым выводом.
|
|
44
|
+
if (!options.GENERATE_LOCAL_IMAGES) {
|
|
45
|
+
const offline = new Set(tree.flatMap((item) => item.diagrams.filter((d) => !engineSupportsRemote(d.engine)).map((d) => d.engine)));
|
|
46
|
+
if (offline.size) {
|
|
47
|
+
throw new Error(`В проекте есть диаграммы без онлайн-сервера рендера (${[...offline].join(', ')}), ` +
|
|
48
|
+
'но generateLocalImages выключен — включите локальную генерацию изображений ' +
|
|
49
|
+
'(generateLocalImages).');
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (options.GENERATE_LOCAL_IMAGES) {
|
|
53
|
+
console.log(chalk.blue('generating images'));
|
|
54
|
+
await generateImages(tree, options, (count, total) => {
|
|
55
|
+
writeOnSameLine(`processed ${count}/${total} images`);
|
|
56
|
+
}, cacheConf, bkFolderName);
|
|
57
|
+
console.log('');
|
|
58
|
+
}
|
|
59
|
+
if (options.GENERATE_MD) {
|
|
60
|
+
console.log(chalk.blue('generating markdown files'));
|
|
61
|
+
await generateMD(tree, options, (count, total) => {
|
|
62
|
+
writeOnSameLine(`processed ${count}/${total} files`);
|
|
63
|
+
});
|
|
64
|
+
console.log('');
|
|
65
|
+
}
|
|
66
|
+
if (options.GENERATE_WEBSITE) {
|
|
67
|
+
console.log(chalk.blue('generating docsify site'));
|
|
68
|
+
await generateWebMD(tree, options);
|
|
69
|
+
}
|
|
70
|
+
if (options.GENERATE_COMPLETE_MD_FILE) {
|
|
71
|
+
console.log(chalk.blue('generating complete markdown file'));
|
|
72
|
+
await generateCompleteMD(tree, options);
|
|
73
|
+
}
|
|
74
|
+
// PDF-вывод удалён. Легаси-конфиг с truthy generatePDF/generateCompletePDF не
|
|
75
|
+
// роняем: печатаем предупреждение с реально присутствующими ключами и собираем
|
|
76
|
+
// остальные выходы (exit 0). Конфиг НЕ мутируем — только подсказка пользователю.
|
|
77
|
+
if (options.LEGACY_PDF_KEYS?.length) {
|
|
78
|
+
console.log(chalk.bold(chalk.yellow('WARNING:')));
|
|
79
|
+
console.log(chalk.yellow(`PDF-вывод больше не поддерживается. Удалите вручную из .c4builder: ${options.LEGACY_PDF_KEYS.join(', ')}.`));
|
|
80
|
+
}
|
|
81
|
+
console.log(chalk.green(`built in ${(Date.now() - start_date.getTime()) / 1000} seconds`));
|
|
82
|
+
ok = true;
|
|
61
83
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
84
|
+
finally {
|
|
85
|
+
// Бэкап dist сносим ТОЛЬКО при успехе: при падении фазы dist_bk — единственная
|
|
86
|
+
// полная копия прошлого удачного билда, её нельзя терять (иначе первый же
|
|
87
|
+
// сломавшийся рендер уничтожает рабочий вывод).
|
|
88
|
+
if (ok) {
|
|
89
|
+
await fsextra.removeSync(bkFolderName);
|
|
90
|
+
}
|
|
91
|
+
else if (options.GENERATE_LOCAL_IMAGES && fsextra.existsSync(bkFolderName)) {
|
|
92
|
+
console.log(chalk.yellow(`\nсборка прервана — бэкап предыдущего билда сохранён в ./${bkFolderName}`));
|
|
93
|
+
}
|
|
94
|
+
// Пофайловые кеши нужны только внутри сборки — чистим и на выходе, чтобы
|
|
95
|
+
// watch-процесс не держал base64 всех диаграмм в памяти между ребилдами.
|
|
96
|
+
clearDiagramCache();
|
|
97
|
+
clearD2FileCache();
|
|
98
|
+
clearIncludeCache();
|
|
68
99
|
}
|
|
69
|
-
// Remove image backup folder
|
|
70
|
-
await fsextra.removeSync(bkFolderName);
|
|
71
|
-
// Освободить D2-инстанс (webworker), иначе процесс не завершится. No-op, если .d2 не было.
|
|
72
|
-
await teardownD2();
|
|
73
|
-
console.log(chalk.green(`built in ${(Date.now() - start_date.getTime()) / 1000} seconds`));
|
|
74
100
|
};
|
|
75
101
|
export { build };
|
|
@@ -20,8 +20,8 @@ export default (options) => {
|
|
|
20
20
|
<script src="vendor/docsify.min.js"></script>
|
|
21
21
|
<script src="vendor/docsify-plantuml.min.js"></script>
|
|
22
22
|
<script src="vendor/zoom-image.min.js"></script>
|
|
23
|
-
${
|
|
24
|
-
${
|
|
23
|
+
${options.supportSearch ? `<script src="vendor/search.min.js"></script>` : ''}
|
|
24
|
+
${options.executeScript ? `<script src="vendor/swagger-ui-bundle.js"></script>` : ''}
|
|
25
25
|
</body>
|
|
26
26
|
|
|
27
27
|
</html>`;
|