@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 +74 -18
- package/dist/cli/dispatch.js +5 -1
- package/dist/config/defaults.js +2 -1
- package/dist/config/schema.js +1 -0
- package/dist/core/render/diagrams.js +29 -20
- package/dist/core/render/fonts.js +1 -1
- package/dist/core/render/pngraster.js +6 -9
- package/package.json +1 -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
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:**
|
|
37
|
-
|
|
38
|
-
|
|
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
|

|
|
@@ -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
|
-
|
|
67
|
+
$ c4builder --help
|
|
61
68
|
Usage: index [options]
|
|
62
69
|
|
|
63
70
|
Options:
|
|
64
|
-
-V, --version
|
|
65
|
-
new
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
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
|
|
package/dist/cli/dispatch.js
CHANGED
|
@@ -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) {
|
package/dist/config/defaults.js
CHANGED
package/dist/config/schema.js
CHANGED
|
@@ -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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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}
|
|
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
|
|
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 = '
|
|
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, но
|
|
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
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/vendor/fonts/README.md
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
# Вендорные шрифты
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Nimbus Sans
|
|
4
4
|
|
|
5
|
-
`
|
|
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=
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
<https://github.com/
|
|
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`).
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|