@alfacapital-tech/c4builder 0.3.0-rc.3 → 0.4.0-rc.1

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 CHANGED
@@ -33,9 +33,11 @@ I strongly encourage people to check them out first but if you want to dive righ
33
33
  `npm install` (no tag) always installs the latest stable release. Release candidates are
34
34
  opt-in only — see [Installing a release candidate](#installing-a-release-candidate).
35
35
 
36
- **Requirements:** local diagram generation needs a Java runtime (JRE 17+) on `PATH`.
37
- Graphviz is **not** required — layout is computed by PlantUML's built-in Java engine
38
- (Smetana). The PlantUML jar and the diagram font are bundled with c4builder.
36
+ **Requirements:** Node.js ≥ 20.19. Local diagram generation needs Java 17+: a system
37
+ `java` (`JAVA_HOME`/`PATH`) is used when present, otherwise c4builder downloads a private
38
+ JRE into its cache automatically (`c4builder jre install` pre-warms it, `jre info` shows
39
+ what would be used). Graphviz is **not** required — layout is computed by PlantUML's
40
+ built-in Java engine (Smetana). The PlantUML jar and the diagram font are bundled.
39
41
 
40
42
  With `diagramFormat: png`, diagrams are produced by rasterizing their SVG with
41
43
  [`@resvg/resvg-js`](https://github.com/thx/resvg-js) (a bundled dependency, prebuilt
@@ -62,22 +64,57 @@ A sample project is available at [C4Builder-Demo](https://github.com/adrianvlupu
62
64
  ## Available Commands
63
65
 
64
66
  ```bash
65
- PS C:\docprojects\Online Banking> c4builder --help
67
+ $ c4builder --help
66
68
  Usage: index [options]
67
69
 
68
70
  Options:
69
- -V, --version output the version number
70
- new create a new project from template
71
- config change configuration for the current directory
72
- list display the current configuration
73
- reset clear all configuration
74
- site serve the generated site
75
- -w, --watch watch for changes and rebuild
76
- -o, --open open the generated site in the browser (with --site)
77
- docs a brief explanation for the available configuration options
78
- -p, --port <n> port used for serving the generated site
79
- -c, --config-file <.c4builder> set the configuration file relative path
80
- -h, --help output usage information
71
+ -V, --version output the version number
72
+ --new create a new project from template
73
+ --name <name> project name for --new (skips the name prompt)
74
+ -y, --yes non-interactive --new: defaults, no prompts
75
+ (requires --name)
76
+ --force for `jre install`: force JRE download even if
77
+ system java is present
78
+ --config change configuration for the current directory
79
+ -c, --config-file <.c4builder> set the configuration file relative path
80
+ --list display the current configuration
81
+ --reset clear all configuration
82
+ --site serve the generated site
83
+ -w, --watch watch for changes and rebuild
84
+ -o, --open open the generated site in the browser (with
85
+ --site)
86
+ --docs a brief explanation for the available
87
+ configuration options
88
+ --system-fonts render with system fonts instead of the
89
+ bundled one (not deterministic)
90
+ -p, --port <n> port used for serving the generated site
91
+ -h, --help display help for command
92
+ ```
93
+
94
+ There is also a `jre` subcommand: `c4builder jre install [--force]` pre-downloads the
95
+ private JRE cache, `c4builder jre info` prints which Java would be used.
96
+
97
+ ### Validating single diagrams
98
+
99
+ `c4builder check <file...>` validates individual `.puml` / `.iuml` / `.d2` files with the
100
+ same engines the build uses (bundled PlantUML jar + system/managed Java, bundled D2) —
101
+ no project or `.c4builder` needed, nothing is written. Handy for pre-commit hooks and CI:
102
+ exit code 0 when every file compiles, 1 otherwise; errors are printed as
103
+ `✗ file: line N: <engine message>`.
104
+
105
+ ```bash
106
+ c4builder check src/c4/context.puml lib/shared.iuml diagrams/flow.d2
107
+ ```
108
+
109
+ `!include` paths resolve relative to the checked file's directory; a `.iuml` library is
110
+ validated by including it into an empty diagram.
111
+
112
+ A minimal pre-commit hook (`.git/hooks/pre-commit`) that validates staged diagrams:
113
+
114
+ ```bash
115
+ #!/bin/sh
116
+ files=$(git diff --cached --name-only --diff-filter=ACM -- '*.puml' '*.iuml' '*.d2')
117
+ [ -z "$files" ] || exec c4builder check $files
81
118
  ```
82
119
 
83
120
  By default, running `c4builder` will attempt to build the project.
@@ -217,7 +254,7 @@ The generated markdown file makes for an easy single page readme for the reposit
217
254
 
218
255
  ### Docsify site with a sidebar
219
256
 
220
- After choosing to generate a docsify site, it can be viewed locally by running `c4builder site` and accessing `http://localhost:3000`;
257
+ After choosing to generate a docsify site, it can be viewed locally by running `c4builder --site` and accessing `http://localhost:3000`;
221
258
 
222
259
  The site is minimalistic including only a sidebar with the System/Container/Components hierarchy.
223
260
 
@@ -264,24 +301,51 @@ Pre-release builds (`0.x.y-rc.N`) are published under the npm dist-tag `rc` and
264
301
 
265
302
  `npm install` without a tag keeps installing the latest stable version.
266
303
 
304
+ ## Docker
305
+
306
+ The image ships everything needed for a build (Node, JRE, PlantUML jar, fonts, D2) —
307
+ mount a project into `/pwd`:
308
+
309
+ ```bash
310
+ docker run --rm -v "$PWD:/pwd" ghcr.io/alfacapital-tech/c4builder:latest c4builder
311
+ ```
312
+
313
+ Tags (in sync with npm dist-tags):
314
+
315
+ | tag | meaning |
316
+ |---|---|
317
+ | `X.Y.Z`, `sha-…` | immutable releases / commits |
318
+ | `latest` | latest stable release |
319
+ | `rc` | latest release candidate |
320
+ | `edge` | last master commit |
321
+ | `pr-N` | pull request builds |
322
+
323
+ Use the image directly in CI (GitHub Actions, GitLab CI) as the job image — the
324
+ `c4builder` binary is on `PATH`.
325
+
267
326
  ## Releasing
268
327
 
269
328
  Publishing is triggered by pushing a git tag `v<version>` — pushes/merges to `master` alone
270
329
  do not publish.
271
330
 
272
- **Release candidate** (from `refactor/phoenix`, no merge to `master` needed):
331
+ Every user-facing PR adds a line to the `## Unreleased` section of the change log below;
332
+ the release commit renames it to `## vX.Y.Z`.
333
+
334
+ **Release candidate** (from `master`, as many as needed — consumers pinned to `rc`
335
+ pick each one up automatically):
273
336
 
274
337
  ```
275
- npm version 0.3.0-rc.1 -m "rc: %s"
338
+ npm version X.Y.Z-rc.1 -m "rc: %s" # first candidate
339
+ npm version prerelease -m "rc: %s" # next ones: rc.2, rc.3, …
276
340
  git push --follow-tags
277
341
  ```
278
342
 
279
- Publishes to dist-tag `rc`.
343
+ Publishes to npm dist-tag `rc` and docker tag `rc`; `latest` is untouched.
280
344
 
281
- **Final release:**
345
+ **Final release** (rename `## Unreleased` → `## vX.Y.Z` first):
282
346
 
283
347
  ```
284
- npm version 0.3.0 -m "release: %s"
348
+ npm version X.Y.Z -m "release: %s"
285
349
  git push --follow-tags
286
350
  ```
287
351
 
@@ -289,9 +353,31 @@ Publishes to dist-tag `latest`.
289
353
 
290
354
  # Change log
291
355
 
356
+ ## Unreleased
357
+
358
+ - `c4builder check <file...>` — validate individual `.puml` / `.iuml` / `.d2` files with the
359
+ bundled engines, without a project build (#13)
360
+ - D2 compile errors are printed as readable `file:line:col: message` lines instead of raw JSON
361
+ - Regression test for two adjacent `![..](x.puml)` embeds (#12 — the bug itself was fixed in 0.3.0)
362
+
292
363
  ## v0.3.0
293
364
 
294
- PDF output removed — use the docsify site or the single/collection markdown outputs instead (the `pdf`/`pdfCss` options no longer exist).
365
+ Full TypeScript rewrite (ESM, Node.js ≥ 20.19) with the same CLI and `.c4builder` format.
366
+
367
+ - PlantUML renders via a direct `java` call with the built-in Smetana layout — graphviz
368
+ is no longer needed; the jar is bundled (`vendor/`)
369
+ - Java itself is optional: system java ≥ 17 is used when present, otherwise a private
370
+ JRE is downloaded automatically (`c4builder jre install` / `jre info`)
371
+ - Second diagram backend: [D2](https://d2lang.com) (`.d2` files, optional
372
+ `@terrastruct/d2` dependency, imports supported)
373
+ - `diagramFormat: png` rasterizes SVG with `@resvg/resvg-js` — deterministic PNG for
374
+ PlantUML and D2 alike
375
+ - Bundled Nimbus Sans font for identical diagram geometry everywhere;
376
+ `useSystemFonts` / `--system-fonts` opts out
377
+ - Config is validated (zod): typos and wrong types fail with a clear message
378
+ - Docker image on GHCR with a tag scheme in sync with npm dist-tags (see [Docker](#docker))
379
+ - PDF output removed — use the docsify site or the single/collection markdown outputs
380
+ instead (the `pdf`/`pdfCss` options no longer exist)
295
381
 
296
382
  ## v0.2.20
297
383
 
@@ -0,0 +1,80 @@
1
+ import chalk from 'chalk';
2
+ import path from 'node:path';
3
+ import { readFile } from 'node:fs/promises';
4
+ import { VENDOR_DIR } from "../../util/paths.js";
5
+ import { VENDORED_JAR } from "../../util/utils.js";
6
+ import { resolveJava } from "../../core/render/jre.js";
7
+ import { renderDiagram } from "../../core/render/diagrams.js";
8
+ import { renderD2, teardownD2 } from "../../core/render/d2renderer.js";
9
+ // `c4builder check <file...>` — валидация отдельных диаграмм ТЕМ ЖЕ движком, что и
10
+ // сборка (вендорный PlantUML jar + системная/managed java, бандл D2), без проекта и
11
+ // `.c4builder`. Рендер идёт в память и выбрасывается: проверяем ровно то, что упало бы
12
+ // в сборке (системный `plantuml` может отличаться версией). Код выхода 0 — все файлы ок,
13
+ // 1 — есть ошибка (движка, неизвестного расширения или чтения файла).
14
+ // stderr PlantUML в -pipe: `ERROR` / позиция / описание. Позиция — 0-based строка
15
+ // диаграммы, для файла с одной диаграммой это file:N+1.
16
+ // ponytail: в файле с несколькими @startuml позиция считается от начала блока — смещение
17
+ // не учитываем, добавить, если такие файлы станут проверять.
18
+ const PUML_ERROR = /^ERROR\n(\d+)\n(.*)$/m;
19
+ const JAR_PATH = path.join(VENDOR_DIR, VENDORED_JAR.jar);
20
+ // JRE резолвится лениво и один раз: список только из .d2 java не трогает.
21
+ let javaPromise = null;
22
+ const getJava = () => {
23
+ javaPromise ??= resolveJava({ log: (m) => console.log(chalk.gray(m)) }).then((r) => r.path);
24
+ return javaPromise;
25
+ };
26
+ const checkPlantUml = async (abs) => {
27
+ // .iuml — библиотека без @startuml: сам по себе PlantUML «рендерит» его без ошибок,
28
+ // поэтому проверяем через включение в пустую диаграмму.
29
+ const isLib = path.extname(abs) === '.iuml';
30
+ const content = isLib ? `@startuml\n!include ${abs}\n@enduml\n` : await readFile(abs);
31
+ const isDitaa = /@startditaa/i.test(content.toString());
32
+ try {
33
+ await renderDiagram(content, {
34
+ javaBin: await getJava(),
35
+ jarPath: JAR_PATH,
36
+ includePath: path.dirname(abs),
37
+ format: isDitaa ? 'png' : 'svg',
38
+ charset: 'UTF-8',
39
+ isDitaa,
40
+ useSystemFonts: false
41
+ });
42
+ }
43
+ catch (e) {
44
+ const m = e.message.match(PUML_ERROR);
45
+ if (!m)
46
+ throw e;
47
+ // Для .iuml позиция относится к обёртке, а не к файлу — не показываем.
48
+ throw new Error(isLib ? m[2] : `строка ${Number(m[1]) + 1}: ${m[2]}`);
49
+ }
50
+ };
51
+ const CHECKERS = {
52
+ '.puml': checkPlantUml,
53
+ '.iuml': checkPlantUml,
54
+ '.d2': (abs) => renderD2(abs)
55
+ };
56
+ export default async (files) => {
57
+ if (files.length === 0) {
58
+ console.log(chalk.red('использование: c4builder check <file...> (.puml | .iuml | .d2)'));
59
+ process.exit(1);
60
+ }
61
+ let failed = 0;
62
+ // ponytail: последовательно — на сотнях файлов добавить пул как в generateImages.
63
+ for (const file of files) {
64
+ const abs = path.resolve(file);
65
+ const check = CHECKERS[path.extname(abs).toLowerCase()];
66
+ try {
67
+ if (!check)
68
+ throw new Error('неизвестное расширение (ожидается .puml, .iuml или .d2)');
69
+ await check(abs);
70
+ console.log(chalk.green(`✓ ${file}`));
71
+ }
72
+ catch (e) {
73
+ failed++;
74
+ console.error(chalk.red(`✗ ${file}: ${e.message}`));
75
+ }
76
+ }
77
+ await teardownD2();
78
+ if (failed)
79
+ process.exit(1);
80
+ };
@@ -6,6 +6,7 @@ import Configstore from 'configstore';
6
6
  import cmdHelp from "./commands/help.js";
7
7
  import cmdNewProject from "./commands/new.js";
8
8
  import cmdJre from "./commands/jre.js";
9
+ import cmdCheck from "./commands/check.js";
9
10
  import cmdList from "./commands/list.js";
10
11
  import cmdSite from "./commands/site.js";
11
12
  import cmdCollect from "./wizard/collect.js";
@@ -118,12 +119,15 @@ export default async () => {
118
119
  throw new InvalidArgumentError('ожидается TCP-порт: целое число 1..65535');
119
120
  return Number(v);
120
121
  })
121
- .allowExcessArguments() // позиционные аргументы подкоманды `jre <action>`
122
+ .allowExcessArguments() // позиционные аргументы подкоманд `jre <action>` и `check <file...>`
122
123
  .parse(process.argv);
123
124
  const opts = program.opts();
124
125
  // Прогрев JRE — до загрузки конфига проекта и intro: команда самостоятельна.
125
126
  if (program.args[0] === 'jre')
126
127
  return cmdJre(program.args.slice(1), { force: opts.force });
128
+ // Проверка отдельных диаграмм — тоже без конфига проекта.
129
+ if (program.args[0] === 'check')
130
+ return cmdCheck(program.args.slice(1));
127
131
  let conf = { get: () => { } };
128
132
  let cacheConf = { get: () => { }, set: () => { }, clear: () => { } };
129
133
  // Лок сборки — рядом с конфигом проекта (см. util/lock.ts). Заполняется вместе с conf.
@@ -159,6 +159,17 @@ const buildCompileRequest = (entryAbs) => {
159
159
  return { fs: fsMap, inputPath: toKey(path.resolve(entryAbs)) };
160
160
  };
161
161
  // Рендер .d2 → SVG (Buffer). Ошибки компиляции пробрасываются с путём файла.
162
+ // Ошибка компиляции движок отдаёт JSON-массивом [{range, errmsg}] — вытаскиваем
163
+ // человекочитаемые errmsg (`file:line:col: текст`); иной формат отдаём как есть.
164
+ const d2ErrorText = (message) => {
165
+ try {
166
+ const parsed = JSON.parse(message);
167
+ if (Array.isArray(parsed) && parsed.every((x) => typeof x?.errmsg === 'string'))
168
+ return parsed.map((x) => x.errmsg).join('\n');
169
+ }
170
+ catch { }
171
+ return message;
172
+ };
162
173
  const renderD2 = async (entryAbs, { layout = 'dagre' } = {}) => {
163
174
  const d2 = await getD2();
164
175
  const { fs: fsMap, inputPath } = buildCompileRequest(entryAbs);
@@ -168,7 +179,7 @@ const renderD2 = async (entryAbs, { layout = 'dagre' } = {}) => {
168
179
  }
169
180
  catch (err) {
170
181
  const e = err;
171
- throw new Error(`Ошибка компиляции D2 (${entryAbs}):\n${e.message || e}`);
182
+ throw new Error(`Ошибка компиляции D2 (${entryAbs}):\n${d2ErrorText(e.message || String(e))}`);
172
183
  }
173
184
  let svg;
174
185
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfacapital-tech/c4builder",
3
- "version": "0.3.0-rc.3",
3
+ "version": "0.4.0-rc.1",
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",