@alfacapital-tech/c4builder 0.10.0-rc.1 → 0.10.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/dist/cli/commands/site.js +11 -4
- package/dist/cli/dispatch.js +47 -6
- package/dist/core/build.js +3 -7
- package/dist/index.js +9 -1
- package/dist/util/utils.js +9 -3
- package/package.json +1 -1
- package/skills/c4builder/SKILL.md +2 -2
|
@@ -100,7 +100,7 @@ pages = () => new Map()) => {
|
|
|
100
100
|
const distFolder = path.resolve(currentConfiguration.DIST_FOLDER);
|
|
101
101
|
const liveReloadEnabled = !!(program.watch && reloadEmitter);
|
|
102
102
|
const agent = { ...(program.agent ?? { on: false, name: '', explicit: false }) };
|
|
103
|
-
// Кеш index.html — отдаём его из памяти, если на диске его временно нет
|
|
103
|
+
// Кеш корневого index.html — отдаём его из памяти, если на диске его временно нет
|
|
104
104
|
// (в окне build.js → emptyDir(DIST_FOLDER) перед записью).
|
|
105
105
|
let lastIndexHtml = null;
|
|
106
106
|
let sweeper;
|
|
@@ -421,6 +421,9 @@ pages = () => new Map()) => {
|
|
|
421
421
|
}
|
|
422
422
|
let filePath;
|
|
423
423
|
const isIndex = urlPath.endsWith('/');
|
|
424
|
+
// docsify — один корневой index.html с hash-роутингом. Фолбэк окна ребилда только
|
|
425
|
+
// для корня: иначе любой несуществующий `…/` отвечал бы 200 главной, а не 404.
|
|
426
|
+
const isRoot = urlPath === '/';
|
|
424
427
|
if (isIndex) {
|
|
425
428
|
filePath = path.join(distFolder, urlPath, 'index.html');
|
|
426
429
|
}
|
|
@@ -436,14 +439,14 @@ pages = () => new Map()) => {
|
|
|
436
439
|
fs.readFile(resolved, 'utf8', (err, data) => {
|
|
437
440
|
if (err) {
|
|
438
441
|
// ENOENT в окне ребилда (emptyDir → запись) — отдаём последний известный index.html.
|
|
439
|
-
if (
|
|
442
|
+
if (isRoot && lastIndexHtml) {
|
|
440
443
|
res.set('Cache-Control', 'no-store').type('html').send(lastIndexHtml);
|
|
441
444
|
return;
|
|
442
445
|
}
|
|
443
446
|
return next();
|
|
444
447
|
}
|
|
445
448
|
const page = inject(data);
|
|
446
|
-
if (
|
|
449
|
+
if (isRoot)
|
|
447
450
|
lastIndexHtml = page;
|
|
448
451
|
res.set('Cache-Control', 'no-store').type('html').send(page);
|
|
449
452
|
});
|
|
@@ -453,7 +456,11 @@ pages = () => new Map()) => {
|
|
|
453
456
|
// статику через app.use (тот же эффект: fallthrough-обработчик всех GET).
|
|
454
457
|
app.use(express.static(distFolder));
|
|
455
458
|
return new Promise((resolve, reject) => {
|
|
456
|
-
|
|
459
|
+
// Express 5 зовёт колбэк listen и с ошибкой (занятый порт) — её разбирает
|
|
460
|
+
// server.on('error') ниже, а «serving» печатаем только после настоящего listen.
|
|
461
|
+
const server = app.listen(port, '127.0.0.1', (err) => {
|
|
462
|
+
if (err)
|
|
463
|
+
return;
|
|
457
464
|
const url = `http://localhost:${port}`;
|
|
458
465
|
console.log('serving your docsify site');
|
|
459
466
|
console.log(`go to ${chalk.green(url)}`);
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -31,7 +31,25 @@ import { packageJson as pkg } from "../util/paths.js";
|
|
|
31
31
|
import { outputDirs, parseConfig } from "../config/options.js";
|
|
32
32
|
import { isValidPort } from "../config/schema.js";
|
|
33
33
|
// Позиционные подкоманды: allowExcessArguments пропускает любые, список — единственный фильтр.
|
|
34
|
-
|
|
34
|
+
// Commander о них не знает — аргументы и описания здесь же, для блока команд в --help.
|
|
35
|
+
const COMMANDS = {
|
|
36
|
+
check: ['<files...>', 'check diagrams without a build, exit code 0 or 1'],
|
|
37
|
+
jre: ['info|install', 'which Java will be used | download a JRE in advance'],
|
|
38
|
+
site: ['', 'build and serve the site locally (same as --site)'],
|
|
39
|
+
new: ['', 'create a project from the template (same as --new)'],
|
|
40
|
+
config: ['', 'setup wizard with the current values (same as --config)'],
|
|
41
|
+
update: ['', 'update a global npm install to the newest version'],
|
|
42
|
+
feedback: ['', 'wait for page feedback from site -w (--wait --as <name>)'],
|
|
43
|
+
skills: ['', 'install or update the agent skills in the current folder']
|
|
44
|
+
};
|
|
45
|
+
const commandsHelp = () => {
|
|
46
|
+
const rows = Object.entries(COMMANDS).map(([name, [args, about]]) => [
|
|
47
|
+
`${name} ${args}`.trim(),
|
|
48
|
+
about
|
|
49
|
+
]);
|
|
50
|
+
const width = Math.max(...rows.map(([usage]) => usage.length));
|
|
51
|
+
return `\nCommands:\n${rows.map(([usage, about]) => ` ${usage.padEnd(width)} ${about}`).join('\n')}`;
|
|
52
|
+
};
|
|
35
53
|
const intro = () => {
|
|
36
54
|
console.log(chalk.blue(figlet.textSync('c4builder')));
|
|
37
55
|
console.log(chalk.gray('Blow up your software documentation writing skills'));
|
|
@@ -113,6 +131,8 @@ function getOptions(c) {
|
|
|
113
131
|
}
|
|
114
132
|
export default async () => {
|
|
115
133
|
program
|
|
134
|
+
.name('c4builder')
|
|
135
|
+
.usage('[command] [options]')
|
|
116
136
|
.version(pkg.version)
|
|
117
137
|
.option('--new', 'create a new project from template')
|
|
118
138
|
.option('--name <name>', 'project name for --new (skips the name prompt)')
|
|
@@ -138,14 +158,15 @@ export default async () => {
|
|
|
138
158
|
return Number(v);
|
|
139
159
|
})
|
|
140
160
|
.allowExcessArguments() // позиционные аргументы подкоманд `jre <action>` и `check <file...>`
|
|
161
|
+
.addHelpText('after', commandsHelp())
|
|
141
162
|
.parse(process.argv);
|
|
142
163
|
const opts = program.opts();
|
|
143
164
|
const [cmd, ...cmdArgs] = program.args;
|
|
144
165
|
// allowExcessArguments глотает любой позиционный аргумент — без этой проверки
|
|
145
166
|
// опечатка (`c4builder nwe`) молча превращалась бы в сборку.
|
|
146
|
-
if (cmd !== undefined && !
|
|
167
|
+
if (cmd !== undefined && !Object.hasOwn(COMMANDS, cmd)) {
|
|
147
168
|
console.error(chalk.red(`unknown command: ${cmd}`));
|
|
148
|
-
console.error(`available commands: ${COMMANDS.join(', ')} (see c4builder --help for options)`);
|
|
169
|
+
console.error(`available commands: ${Object.keys(COMMANDS).join(', ')} (see c4builder --help for options)`);
|
|
149
170
|
process.exit(1);
|
|
150
171
|
}
|
|
151
172
|
// --version/--help уже вышли в parse. Уведомление — по кэшу прошлой проверки, до её
|
|
@@ -184,6 +205,11 @@ export default async () => {
|
|
|
184
205
|
console.error(chalk.red(`no .c4builder in ${process.cwd()} — run inside a project or create one with c4builder new`));
|
|
185
206
|
process.exit(1);
|
|
186
207
|
}
|
|
208
|
+
// Без терминала мастеру неоткуда взять ответы: inquirer падал со стектрейсом и кодом 0.
|
|
209
|
+
if (!opts.new && !process.stdin.isTTY && !fs.existsSync(configPath)) {
|
|
210
|
+
console.error(chalk.red(`no ${opts.configFile ?? '.c4builder'} in ${process.cwd()} — run c4builder --new, or c4builder --config in a terminal`));
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
187
213
|
let conf = { get: () => { } };
|
|
188
214
|
let cacheConf = { get: () => { }, set: () => { }, clear: () => { } };
|
|
189
215
|
// Лок сборки — рядом с конфигом проекта (см. util/lock.ts). Заполняется вместе с conf.
|
|
@@ -191,7 +217,15 @@ export default async () => {
|
|
|
191
217
|
if (!opts.new) {
|
|
192
218
|
const projectKey = process.cwd().split(path.sep).splice(1).join('_');
|
|
193
219
|
lockPath = `${configPath}.lock`;
|
|
194
|
-
|
|
220
|
+
try {
|
|
221
|
+
// По умолчанию Configstore молча затирает битый JSON пустым конфигом — настройки
|
|
222
|
+
// терялись, а сборка уходила в мастер. --reset стирает конфиг и так.
|
|
223
|
+
conf = new Configstore(projectKey, {}, { configPath, clearInvalidConfig: !!opts.reset });
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
console.error(chalk.red(`.c4builder error — cannot read ${configPath}: ${e.message}`));
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
195
229
|
cacheConf = new Configstore(`${projectKey}_cache`, {}, { configPath: `${configPath}.cache` });
|
|
196
230
|
// Миграция: чексуммы раньше жили в .c4builder, переносим их в .c4builder.cache,
|
|
197
231
|
// чтобы рабочий конфиг перестал «дёргаться» в git при каждой сборке.
|
|
@@ -367,8 +401,15 @@ export default async () => {
|
|
|
367
401
|
}
|
|
368
402
|
}
|
|
369
403
|
isBuilding = false;
|
|
370
|
-
if (opts.site)
|
|
371
|
-
|
|
404
|
+
if (opts.site) {
|
|
405
|
+
try {
|
|
406
|
+
return await cmdSite(options, { ...opts, agent: detectAgent(process.env, opts) }, reloadEmitter, () => pages);
|
|
407
|
+
}
|
|
408
|
+
catch {
|
|
409
|
+
// Ошибку запуска сервера (занятый порт) cmdSite уже напечатал — остаётся код выхода.
|
|
410
|
+
process.exit(1);
|
|
411
|
+
}
|
|
412
|
+
}
|
|
372
413
|
if (options.GENERATE_WEBSITE && !opts.watch) {
|
|
373
414
|
console.log(chalk.gray('\nto view the generated website run'));
|
|
374
415
|
console.log(`> c4builder site`);
|
package/dist/core/build.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import fsextra from 'fs-extra';
|
|
4
|
-
import { makeDirectory, writeFile,
|
|
4
|
+
import { makeDirectory, writeFile, writeProgress } from "../util/utils.js";
|
|
5
5
|
// clearD2FileCache: сброс кеша графа D2-импортов на границах сборки (сам webworker
|
|
6
6
|
// D2 гасит CLI после одиночной сборки — в watch-режиме он переживает ребилды).
|
|
7
7
|
import { clearD2FileCache } from "./render/d2renderer.js";
|
|
@@ -57,16 +57,12 @@ const build = async (options, cacheConf, plugins = []) => {
|
|
|
57
57
|
}
|
|
58
58
|
if (options.GENERATE_LOCAL_IMAGES) {
|
|
59
59
|
console.log(chalk.blue('generating images'));
|
|
60
|
-
await generateImages(tree, options, (count, total) =>
|
|
61
|
-
writeOnSameLine(`processed ${count}/${total} images`);
|
|
62
|
-
}, cacheConf, bkFolderName);
|
|
60
|
+
await generateImages(tree, options, (count, total) => writeProgress(count, total, 'images'), cacheConf, bkFolderName);
|
|
63
61
|
console.log('');
|
|
64
62
|
}
|
|
65
63
|
if (options.GENERATE_MD) {
|
|
66
64
|
console.log(chalk.blue('generating markdown files'));
|
|
67
|
-
await generateMD(tree, options, (count, total) =>
|
|
68
|
-
writeOnSameLine(`processed ${count}/${total} files`);
|
|
69
|
-
});
|
|
65
|
+
await generateMD(tree, options, (count, total) => writeProgress(count, total, 'files'));
|
|
70
66
|
console.log('');
|
|
71
67
|
}
|
|
72
68
|
if (options.GENERATE_WEBSITE) {
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
//entry point
|
|
3
3
|
import cli from "./cli/dispatch.js";
|
|
4
|
-
|
|
4
|
+
// Закрытый промпт (Ctrl+C или EOF без терминала) — отмена, а не сбой: одна строка вместо
|
|
5
|
+
// стектрейса inquirer. Код выставляем сами: на EOF процесс уже выходил с кодом 0, и CI
|
|
6
|
+
// принимал несобранный сайт за успех.
|
|
7
|
+
cli().catch((e) => {
|
|
8
|
+
if (!(e instanceof Error && e.name === 'ExitPromptError'))
|
|
9
|
+
throw e;
|
|
10
|
+
console.error(`aborted: the prompt was closed${process.stdin.isTTY ? '' : ' (no terminal for the wizard)'}`);
|
|
11
|
+
process.exit(1);
|
|
12
|
+
});
|
package/dist/util/utils.js
CHANGED
|
@@ -9,8 +9,14 @@ const makeDirectory = async (path) => {
|
|
|
9
9
|
// Кодировка не передаётся ни одним вызовом → всегда Buffer (потребители зовут .toString()).
|
|
10
10
|
const readFile = (path) => fs.promises.readFile(path);
|
|
11
11
|
const writeFile = (path, data) => fs.promises.writeFile(path, data);
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
// Вне терминала (лог фонового `--site -w`, CI) `\r` не перетирает строку, а склеивает
|
|
13
|
+
// все обновления в одну гигантскую — там печатаем только итог фазы. Перевод строки
|
|
14
|
+
// после прогресса ставит вызывающий.
|
|
15
|
+
const writeProgress = (count, total, unit) => {
|
|
16
|
+
if (process.stdout.isTTY)
|
|
17
|
+
process.stdout.write(`processed ${count}/${total} ${unit}\r`);
|
|
18
|
+
else if (count === total)
|
|
19
|
+
process.stdout.write(`processed ${total} ${unit}`);
|
|
14
20
|
};
|
|
15
21
|
const encodeURIPath = (path) => {
|
|
16
22
|
path = path.split('\\').join('/');
|
|
@@ -110,4 +116,4 @@ const VENDORED_JAR = {
|
|
|
110
116
|
version: '1.2026.8',
|
|
111
117
|
jar: 'plantuml-1.2026.8.jar'
|
|
112
118
|
};
|
|
113
|
-
export { makeDirectory, readFile, writeFile, encodeURIPath,
|
|
119
|
+
export { makeDirectory, readFile, writeFile, encodeURIPath, writeProgress, clearConsole, plantUmlServerUrl, VENDORED_JAR };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alfacapital-tech/c4builder",
|
|
3
|
-
"version": "0.10.0-rc.
|
|
3
|
+
"version": "0.10.0-rc.2",
|
|
4
4
|
"description": "A CLI tool designed to compile a folder structure of markdowns and plant uml files into a site, single file markdown or a collection of markdowns with links",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -72,8 +72,8 @@ How the source tree becomes pages:
|
|
|
72
72
|
|
|
73
73
|
With a complete `.c4builder` (written by `c4builder --new --name <name> -y` or by the
|
|
74
74
|
first interactive run) this is a one-off, non-interactive build. A missing or partial
|
|
75
|
-
config
|
|
76
|
-
config (references/config.md). A diagram that fails here aborts the build with a long
|
|
75
|
+
config needs the interactive wizard; without a terminal the run stops with exit code 1.
|
|
76
|
+
Do not drive the wizard: ask the user or write the full config (references/config.md). A diagram that fails here aborts the build with a long
|
|
77
77
|
Java stack trace and keeps the previous output in `docs_bk/`; run `c4builder check` on
|
|
78
78
|
the changed files to get the short message.
|
|
79
79
|
4. **Look at the result** in `docs/`: `docs/<folder>/README.md` (markdown collection),
|