@alfacapital-tech/c4builder 0.3.0-rc.1 → 0.3.0-rc.2
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 +6 -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 +130 -36
- package/dist/cli/wizard/collect.js +51 -91
- package/dist/config/defaults.js +2 -1
- package/dist/config/options.js +29 -1
- package/dist/config/schema.js +52 -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 +232 -85
- package/dist/core/render/fonts.js +8 -0
- package/dist/core/render/jre.js +155 -51
- package/dist/core/render/pngraster.js +14 -17
- 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/dist/config/defaults.js
CHANGED
|
@@ -22,12 +22,13 @@ 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
|
+
d2Layout: 'dagre',
|
|
31
32
|
charset: 'UTF-8'
|
|
32
33
|
};
|
|
33
34
|
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,65 @@
|
|
|
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),
|
|
37
56
|
// Задаются явно (флаг/промпт) либо тонкой правкой файла — в defaultConfig не входят.
|
|
38
|
-
projectName:
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
57
|
+
projectName: optStr(),
|
|
58
|
+
webFileName: optStr(),
|
|
59
|
+
// Не-строки внутри массива молча отбрасываются (совместимость с master: null среди
|
|
60
|
+
// путей не валит сборку) — runtime дальше работает с чистым string[].
|
|
61
|
+
excludeSidebarFolderByPath: nullish(z.preprocess((v) => (Array.isArray(v) ? v.filter((x) => typeof x === 'string') : v), z.array(z.string()).optional())),
|
|
62
|
+
hasRun: optBool(),
|
|
43
63
|
// Легаси-ключи старых `.c4builder` — читаются лишь для однократного предупреждения.
|
|
44
|
-
plantumlVersion:
|
|
45
|
-
generatePDF:
|
|
46
|
-
generateCompletePDF:
|
|
64
|
+
plantumlVersion: optStr(),
|
|
65
|
+
generatePDF: optBool(),
|
|
66
|
+
generateCompletePDF: optBool(),
|
|
47
67
|
checksums: z.unknown().optional()
|
|
48
68
|
});
|
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>`;
|