@alfacapital-tech/c4builder 0.3.0-rc.2 → 0.3.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 CHANGED
@@ -33,15 +33,22 @@ 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
42
44
  native binary, no browser). This gives a single deterministic PNG for both PlantUML and
43
45
  D2, using the bundled font. ditaa diagrams stay native PlantUML PNG.
44
46
 
47
+ **Fonts.** Diagrams use the bundled Nimbus Sans (Helvetica metrics, Cyrillic and Greek —
48
+ see `vendor/fonts/README.md`), so diagram geometry is identical on any machine.
49
+ `useSystemFonts: true` in `.c4builder` (or `--system-fonts` for one run) renders with the
50
+ machine's fonts instead — machine-dependent by design.
51
+
45
52
  The easiest way to get started is with the _new project_ template.
46
53
 
47
54
  ![new](docs/images/new.gif)
@@ -57,24 +64,36 @@ A sample project is available at [C4Builder-Demo](https://github.com/adrianvlupu
57
64
  ## Available Commands
58
65
 
59
66
  ```bash
60
- PS C:\docprojects\Online Banking> c4builder --help
67
+ $ c4builder --help
61
68
  Usage: index [options]
62
69
 
63
70
  Options:
64
- -V, --version output the version number
65
- new create a new project from template
66
- config change configuration for the current directory
67
- list display the current configuration
68
- reset clear all configuration
69
- site serve the generated site
70
- -w, --watch watch for changes and rebuild
71
- -o, --open open the generated site in the browser (with --site)
72
- docs a brief explanation for the available configuration options
73
- -p, --port <n> port used for serving the generated site
74
- -c, --config-file <.c4builder> set the configuration file relative path
75
- -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
76
92
  ```
77
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
+
78
97
  By default, running `c4builder` will attempt to build the project.
79
98
 
80
99
  If no configuration was set it will ask for it and then do the actual build. Running just `c4builder` after a configuration will no longer show the wizard.
@@ -212,7 +231,7 @@ The generated markdown file makes for an easy single page readme for the reposit
212
231
 
213
232
  ### Docsify site with a sidebar
214
233
 
215
- After choosing to generate a docsify site, it can be viewed locally by running `c4builder site` and accessing `http://localhost:3000`;
234
+ After choosing to generate a docsify site, it can be viewed locally by running `c4builder --site` and accessing `http://localhost:3000`;
216
235
 
217
236
  The site is minimalistic including only a sidebar with the System/Container/Components hierarchy.
218
237
 
@@ -259,6 +278,28 @@ Pre-release builds (`0.x.y-rc.N`) are published under the npm dist-tag `rc` and
259
278
 
260
279
  `npm install` without a tag keeps installing the latest stable version.
261
280
 
281
+ ## Docker
282
+
283
+ The image ships everything needed for a build (Node, JRE, PlantUML jar, fonts, D2) —
284
+ mount a project into `/pwd`:
285
+
286
+ ```bash
287
+ docker run --rm -v "$PWD:/pwd" ghcr.io/alfacapital-tech/c4builder:latest c4builder
288
+ ```
289
+
290
+ Tags (in sync with npm dist-tags):
291
+
292
+ | tag | meaning |
293
+ |---|---|
294
+ | `X.Y.Z`, `sha-…` | immutable releases / commits |
295
+ | `latest` | latest stable release |
296
+ | `rc` | latest release candidate |
297
+ | `edge` | last master commit |
298
+ | `pr-N` | pull request builds |
299
+
300
+ Use the image directly in CI (GitHub Actions, GitLab CI) as the job image — the
301
+ `c4builder` binary is on `PATH`.
302
+
262
303
  ## Releasing
263
304
 
264
305
  Publishing is triggered by pushing a git tag `v<version>` — pushes/merges to `master` alone
@@ -286,7 +327,22 @@ Publishes to dist-tag `latest`.
286
327
 
287
328
  ## v0.3.0
288
329
 
289
- PDF output removed — use the docsify site or the single/collection markdown outputs instead (the `pdf`/`pdfCss` options no longer exist).
330
+ Full TypeScript rewrite (ESM, Node.js ≥ 20.19) with the same CLI and `.c4builder` format.
331
+
332
+ - PlantUML renders via a direct `java` call with the built-in Smetana layout — graphviz
333
+ is no longer needed; the jar is bundled (`vendor/`)
334
+ - Java itself is optional: system java ≥ 17 is used when present, otherwise a private
335
+ JRE is downloaded automatically (`c4builder jre install` / `jre info`)
336
+ - Second diagram backend: [D2](https://d2lang.com) (`.d2` files, optional
337
+ `@terrastruct/d2` dependency, imports supported)
338
+ - `diagramFormat: png` rasterizes SVG with `@resvg/resvg-js` — deterministic PNG for
339
+ PlantUML and D2 alike
340
+ - Bundled Nimbus Sans font for identical diagram geometry everywhere;
341
+ `useSystemFonts` / `--system-fonts` opts out
342
+ - Config is validated (zod): typos and wrong types fail with a clear message
343
+ - Docker image on GHCR with a tag scheme in sync with npm dist-tags (see [Docker](#docker))
344
+ - PDF output removed — use the docsify site or the single/collection markdown outputs
345
+ instead (the `pdf`/`pdfCss` options no longer exist)
290
346
 
291
347
  ## v0.2.20
292
348
 
@@ -92,7 +92,8 @@ function getOptions(c) {
92
92
  WEB_FILE_NAME: c.webFileName,
93
93
  SUPPORT_SEARCH: c.supportSearch,
94
94
  EXECUTE_SCRIPT: c.executeScript,
95
- EXCLUDE_OTHER_FILES: c.excludeOtherFiles
95
+ EXCLUDE_OTHER_FILES: c.excludeOtherFiles,
96
+ USE_SYSTEM_FONTS: c.useSystemFonts
96
97
  };
97
98
  }
98
99
  export default async () => {
@@ -110,6 +111,7 @@ export default async () => {
110
111
  .option('-w, --watch', 'watch for changes and rebuild')
111
112
  .option('-o, --open', 'open the generated site in the browser (with --site)')
112
113
  .option('--docs', 'a brief explanation for the available configuration options')
114
+ .option('--system-fonts', 'render with system fonts instead of the bundled one (not deterministic)')
113
115
  .option('-p, --port <n>', 'port used for serving the generated site', (v) => {
114
116
  // parseInt молча резал бы опечатку ('30OO' → 30) — валидируем строго.
115
117
  if (!isValidPort(v))
@@ -202,6 +204,8 @@ export default async () => {
202
204
  // уводил бы последующие запуски на строгий путь (exit 1 до визарда).
203
205
  conf.set('hasRun', true);
204
206
  const options = getOptions(built.value);
207
+ if (opts.systemFonts)
208
+ options.USE_SYSTEM_FONTS = true; // флаг сильнее ключа конфига
205
209
  const reloadEmitter = new EventEmitter();
206
210
  reloadEmitter.setMaxListeners(0);
207
211
  if (opts.watch) {
@@ -29,6 +29,7 @@ const defaultConfig = {
29
29
  plantumlServerUrl: 'https://www.plantuml.com/plantuml',
30
30
  diagramFormat: 'svg',
31
31
  d2Layout: 'dagre',
32
- charset: 'UTF-8'
32
+ charset: 'UTF-8',
33
+ useSystemFonts: false
33
34
  };
34
35
  export { defaultConfig };
@@ -53,6 +53,7 @@ export const configSchema = z.object({
53
53
  diagramFormat: str(defaultConfig.diagramFormat),
54
54
  d2Layout: str(defaultConfig.d2Layout),
55
55
  charset: str(defaultConfig.charset),
56
+ useSystemFonts: bool(defaultConfig.useSystemFonts),
56
57
  // Задаются явно (флаг/промпт) либо тонкой правкой файла — в defaultConfig не входят.
57
58
  projectName: optStr(),
58
59
  webFileName: optStr(),
@@ -78,6 +78,9 @@ const d2EngineVersion = () => {
78
78
  }
79
79
  return d2VersionCache;
80
80
  };
81
+ // В ключ кеша: смена режима не должна отдавать картинку, отрисованную другим шрифтом
82
+ // (D2 тоже — его PNG растрирует общий resvg).
83
+ export const fontCacheTag = (options) => options.USE_SYSTEM_FONTS ? 'system' : DEFAULT_FONT_NAME;
81
84
  export const getMime = (format) => {
82
85
  if (format === 'svg')
83
86
  return `image/svg+xml`;
@@ -102,22 +105,24 @@ export const httpGet = async (url) => (await httpGetBuffer(url)).toString('base6
102
105
  // расходятся между ОС. С пином шрифт берётся из vendor/fonts (JRE своих не несёт,
103
106
  // freetype у Temurin бандлед), и SVG получается одинаковым везде.
104
107
  // ditaa этим не лечится: его движок берёт AWT-шрифт мимо обеих опций.
105
- export const renderDiagram = (content, { javaBin, jarPath, includePath, format, charset, isDitaa }) => new Promise((resolve, reject) => {
106
- const argv = [
107
- '-Djava.awt.headless=true',
108
- `-Dplantuml.include.path=${includePath}`,
109
- `-Dsun.java2d.fontpath=prepend:${FONTS_DIR}`,
110
- '-jar',
111
- jarPath,
112
- ...(isDitaa ? [] : ['-Playout=smetana']),
113
- `-SdefaultFontName=${DEFAULT_FONT_NAME}`,
114
- `-SCircledCharacterFontName=${DEFAULT_FONT_NAME}`,
115
- '-charset',
116
- charset,
117
- `-t${format}`,
118
- '-pipe'
119
- ];
120
- const child = spawn(javaBin, argv);
108
+ // useSystemFonts убирает все три шрифтовых аргумента — рендер как до пина (машинозависим).
109
+ export const renderArgv = ({ jarPath, includePath, format, charset, isDitaa, useSystemFonts }) => [
110
+ '-Djava.awt.headless=true',
111
+ `-Dplantuml.include.path=${includePath}`,
112
+ ...(useSystemFonts ? [] : [`-Dsun.java2d.fontpath=prepend:${FONTS_DIR}`]),
113
+ '-jar',
114
+ jarPath,
115
+ ...(isDitaa ? [] : ['-Playout=smetana']),
116
+ ...(useSystemFonts
117
+ ? []
118
+ : [`-SdefaultFontName=${DEFAULT_FONT_NAME}`, `-SCircledCharacterFontName=${DEFAULT_FONT_NAME}`]),
119
+ '-charset',
120
+ charset,
121
+ `-t${format}`,
122
+ '-pipe'
123
+ ];
124
+ export const renderDiagram = (content, options) => new Promise((resolve, reject) => {
125
+ const child = spawn(options.javaBin, renderArgv(options));
121
126
  const stdout = [];
122
127
  const stderr = [];
123
128
  // stdio по умолчанию 'pipe' → потоки заведомо не null; берём их деструктуризацией
@@ -253,9 +258,10 @@ export const generateImages = async (tree, options, onImageGenerated, cacheConf,
253
258
  // чексумму → из бэкапа копировалась старая картинка (новые параметры молча
254
259
  // игнорировались, кэш чистился только через --reset). Формат/движок/версия
255
260
  // движка тоже в ключе: их смена обязана приводить к перерендеру.
256
- const renderKey = entryPath !== null
261
+ const renderKey = (entryPath !== null
257
262
  ? `d2\0${d2EngineVersion()}\0layout=${options.D2_LAYOUT}\0fmt=${outFormat}`
258
- : `puml\0${VENDORED_JAR.version}\0charset=${options.CHARSET}\0fmt=${outFormat}\0ditaa=${diagram.isDitaa}\0font=${DEFAULT_FONT_NAME}`;
263
+ : `puml\0${VENDORED_JAR.version}\0charset=${options.CHARSET}\0fmt=${outFormat}\0ditaa=${diagram.isDitaa}`) +
264
+ `\0font=${fontCacheTag(options)}`;
259
265
  const cksum = crypto
260
266
  .createHash('sha256')
261
267
  .update(body + includes + renderKey, 'utf-8')
@@ -305,10 +311,13 @@ export const generateImages = async (tree, options, onImageGenerated, cacheConf,
305
311
  includePath,
306
312
  format: needsRaster ? 'svg' : outFormat,
307
313
  charset: options.CHARSET,
308
- isDitaa
314
+ isDitaa,
315
+ useSystemFonts: options.USE_SYSTEM_FONTS
309
316
  });
310
317
  }
311
- const image = needsRaster ? await rasterizeSvgToPng(rendered) : rendered;
318
+ const image = needsRaster
319
+ ? await rasterizeSvgToPng(rendered, options.USE_SYSTEM_FONTS)
320
+ : rendered;
312
321
  await writeFile(filePath, image);
313
322
  cksumEntry.ok = true;
314
323
  }
@@ -5,4 +5,4 @@ import { VENDOR_DIR } from "../../util/paths.js";
5
5
  // (`fontDirs`, `defaultFontFamily`). Значения обязаны совпадать — иначе метрики
6
6
  // текста разъедутся и PNG-golden покраснеют. НЕ менять без пересбора эталонов.
7
7
  export const FONTS_DIR = path.join(VENDOR_DIR, 'fonts');
8
- export const DEFAULT_FONT_NAME = 'Liberation Sans';
8
+ export const DEFAULT_FONT_NAME = 'Nimbus Sans';
@@ -20,21 +20,18 @@ const getResvg = () => {
20
20
  }
21
21
  return resvgMod;
22
22
  };
23
+ export const resvgFontOptions = (useSystemFonts) => useSystemFonts
24
+ ? { loadSystemFonts: true }
25
+ : { fontDirs: [FONTS_DIR], defaultFontFamily: DEFAULT_FONT_NAME, loadSystemFonts: false };
23
26
  // SVG (Buffer или строка) → PNG (Buffer), масштаб 1:1 к размеру SVG. Вход — валидный
24
27
  // SVG движка: PlantUML ссылается на шрифт по имени (грузим из vendor/fonts), D2 несёт
25
- // шрифт во вшитом @font-face, но loadSystemFonts:false фиксируем в обоих случаях.
28
+ // шрифт во вшитом @font-face, но пин по умолчанию фиксируем в обоих случаях.
26
29
  // renderAsync считает в napi-пуле тредов: JS-поток не блокируется, PlantUML-пул и
27
30
  // D2-очередь продолжают крутиться параллельно растеризации (прежний sync render()
28
31
  // останавливал весь event loop на каждую картинку).
29
- const rasterizeSvgToPng = async (svg) => {
32
+ const rasterizeSvgToPng = async (svg, useSystemFonts = false) => {
30
33
  const { renderAsync } = getResvg();
31
- const rendered = await renderAsync(svg, {
32
- font: {
33
- fontDirs: [FONTS_DIR],
34
- defaultFontFamily: DEFAULT_FONT_NAME,
35
- loadSystemFonts: false
36
- }
37
- });
34
+ const rendered = await renderAsync(svg, { font: resvgFontOptions(useSystemFonts) });
38
35
  return rendered.asPng();
39
36
  };
40
37
  export { rasterizeSvgToPng };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@alfacapital-tech/c4builder",
3
- "version": "0.3.0-rc.2",
3
+ "version": "0.3.0",
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",
Binary file
@@ -1,17 +1,20 @@
1
1
  # Вендорные шрифты
2
2
 
3
- ## Liberation Sans
3
+ ## Nimbus Sans
4
4
 
5
- `LiberationSans-{Regular,Bold,Italic,BoldItalic}.ttf`
5
+ `NimbusSans-{Regular,Bold,Italic,BoldItalic}.otf`
6
6
 
7
7
  - **Назначение:** детерминизм метрик текста в SVG. Ширина текста (`textLength`)
8
8
  считается PlantUML из AWT-метрик шрифта; системный `sans-serif` резолвится
9
9
  по-разному на разных машинах. Шрифт отдаётся JVM через
10
- `-Dsun.java2d.fontpath=prepend:` + `-SdefaultFontName=Liberation Sans`, чтобы
11
- метрики не зависели от машины (см. `build.js`).
12
- - **Происхождение:** пакет `ttf-liberation` / `fonts-liberation`, версия 2.1.5
13
- (font version 2.1). Метрически совместим с Arial; та же версия поставляется на
14
- CI-раннере `ubuntu-24.04`.
15
- - **Лицензия:** SIL Open Font License 1.1 (разрешает вендоринг и распространение).
16
- Полный текст: <https://openfontlicense.org/> · апстрим:
17
- <https://github.com/liberationfonts/liberation-fonts>.
10
+ `-Dsun.java2d.fontpath=prepend:` + `-SdefaultFontName=Nimbus Sans` и resvg через
11
+ `fontDirs` (см. `src/core/render/fonts.ts`). Пин снимается ключом `useSystemFonts`
12
+ в `.c4builder` / флагом `--system-fonts` — тогда рендер идёт системным шрифтом и
13
+ перестаёт быть машинонезависимым.
14
+ - **Происхождение:** URW base35 (Artifex), релиз 20200910 — файлы побайтно совпадают
15
+ с пакетом `gsfonts` этой версии. Метрически совместим с Helvetica, содержит
16
+ кириллицу и греческий. Апстрим:
17
+ <https://github.com/ArtifexSoftware/urw-base35-fonts>.
18
+ - **Лицензия:** AGPL-3.0-only с шрифтовым исключением
19
+ (`PS-or-PDF-font-exception-20170817`): встраивание шрифта в документы не делает их
20
+ производными работами. Полный текст — в апстрим-репозитории (`LICENSE`).