@diagrammo/dgmo 0.1.0 → 0.1.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 CHANGED
@@ -6,12 +6,53 @@ Write simple, readable `.dgmo` text files and render them as charts, diagrams, a
6
6
 
7
7
  ## Install
8
8
 
9
+ ### As a library
10
+
9
11
  ```bash
10
12
  npm install @diagrammo/dgmo
11
13
  # or
12
14
  pnpm add @diagrammo/dgmo
13
15
  ```
14
16
 
17
+ ### As a CLI
18
+
19
+ ```bash
20
+ # via Homebrew (macOS)
21
+ brew tap diagrammo/dgmo
22
+ brew install dgmo
23
+
24
+ # or run directly via npx
25
+ npx @diagrammo/dgmo diagram.dgmo
26
+ ```
27
+
28
+ ## CLI usage
29
+
30
+ ```bash
31
+ # First time with no args? Creates a sample.dgmo to get you started
32
+ dgmo
33
+
34
+ # Render to PNG (default)
35
+ dgmo diagram.dgmo # → diagram.png
36
+
37
+ # Render to SVG
38
+ dgmo diagram.dgmo -o output.svg
39
+
40
+ # Explicit PNG
41
+ dgmo diagram.dgmo -o output.png
42
+
43
+ # Pipe from stdin
44
+ cat diagram.dgmo | dgmo -o out.png
45
+ cat diagram.dgmo | dgmo > out.png # PNG to stdout
46
+
47
+ # With theme and palette options
48
+ dgmo diagram.dgmo --theme dark --palette catppuccin
49
+ ```
50
+
51
+ | Option | Values | Default |
52
+ |---|---|---|
53
+ | `--theme` | `light`, `dark`, `transparent` | `light` |
54
+ | `--palette` | `nord`, `solarized`, `catppuccin`, `rose-pine`, `gruvbox`, `tokyo-night`, `one-dark`, `bold` | `nord` |
55
+
15
56
  ## How it works
16
57
 
17
58
  Every `.dgmo` file is plain text with a `chart: <type>` header followed by metadata and data. The library routes each chart type to the right framework and gives you either:
package/dist/cli.cjs CHANGED
@@ -2459,6 +2459,7 @@ var init_renderer = __esm({
2459
2459
  var import_node_fs = require("fs");
2460
2460
  var import_node_path = require("path");
2461
2461
  var import_jsdom = require("jsdom");
2462
+ var import_resvg_js = require("@resvg/resvg-js");
2462
2463
 
2463
2464
  // src/d3.ts
2464
2465
  var d3Scale = __toESM(require("d3-scale"), 1);
@@ -5056,13 +5057,15 @@ var PALETTES = [
5056
5057
  ];
5057
5058
  var THEMES = ["light", "dark", "transparent"];
5058
5059
  function printHelp() {
5059
- console.log(`Usage: dgmo render <input> [options]
5060
+ console.log(`Usage: dgmo <input> [options]
5061
+ cat input.dgmo | dgmo [options]
5060
5062
 
5061
- Commands:
5062
- render <input> Render a .dgmo file to SVG
5063
+ Render a .dgmo file to PNG (default) or SVG.
5063
5064
 
5064
5065
  Options:
5065
- -o <file> Write SVG to file (default: stdout)
5066
+ -o <file> Output file (default: <input>.png in cwd)
5067
+ Format inferred from extension: .svg \u2192 SVG, else PNG
5068
+ With stdin and no -o, PNG is written to stdout
5066
5069
  --theme <theme> Theme: ${THEMES.join(", ")} (default: light)
5067
5070
  --palette <name> Palette: ${PALETTES.join(", ")} (default: nord)
5068
5071
  --help Show this help
@@ -5076,7 +5079,6 @@ function printVersion() {
5076
5079
  }
5077
5080
  function parseArgs(argv) {
5078
5081
  const result = {
5079
- command: void 0,
5080
5082
  input: void 0,
5081
5083
  output: void 0,
5082
5084
  theme: "light",
@@ -5117,9 +5119,6 @@ function parseArgs(argv) {
5117
5119
  }
5118
5120
  result.palette = val;
5119
5121
  i++;
5120
- } else if (!result.command) {
5121
- result.command = arg;
5122
- i++;
5123
5122
  } else if (!result.input) {
5124
5123
  result.input = arg;
5125
5124
  i++;
@@ -5139,6 +5138,53 @@ function setupDom() {
5139
5138
  Object.defineProperty(globalThis, "HTMLElement", { value: win.HTMLElement, configurable: true });
5140
5139
  Object.defineProperty(globalThis, "SVGElement", { value: win.SVGElement, configurable: true });
5141
5140
  }
5141
+ function inferFormat(outputPath) {
5142
+ if (outputPath && (0, import_node_path.extname)(outputPath).toLowerCase() === ".svg") {
5143
+ return "svg";
5144
+ }
5145
+ return "png";
5146
+ }
5147
+ function svgToPng(svg, background) {
5148
+ const resvg = new import_resvg_js.Resvg(svg, {
5149
+ fitTo: { mode: "zoom", value: 2 },
5150
+ ...background ? { background } : {}
5151
+ });
5152
+ const rendered = resvg.render();
5153
+ return rendered.asPng();
5154
+ }
5155
+ function noInput() {
5156
+ const samplePath = (0, import_node_path.resolve)("sample.dgmo");
5157
+ if ((0, import_node_fs.existsSync)(samplePath)) {
5158
+ console.error("Error: No input file specified");
5159
+ console.error(`Try: dgmo ${(0, import_node_path.basename)(samplePath)}`);
5160
+ process.exit(1);
5161
+ }
5162
+ (0, import_node_fs.writeFileSync)(
5163
+ samplePath,
5164
+ [
5165
+ "chart: sequence",
5166
+ "activations: off",
5167
+ "",
5168
+ " Client -> API: POST /login",
5169
+ " API -> Auth: validate credentials",
5170
+ " Auth -> DB: SELECT user",
5171
+ " DB -> Auth: user record",
5172
+ " Auth -> API: JWT token",
5173
+ " API -> Client: 200 OK { token }",
5174
+ ""
5175
+ ].join("\n"),
5176
+ "utf-8"
5177
+ );
5178
+ console.error(`Created ${samplePath}`);
5179
+ console.error("");
5180
+ console.error(" Render it: dgmo sample.dgmo");
5181
+ console.error(" As SVG: dgmo sample.dgmo -o sample.svg");
5182
+ console.error("");
5183
+ console.error(
5184
+ "Edit sample.dgmo to make it your own, or run dgmo --help for all options."
5185
+ );
5186
+ process.exit(0);
5187
+ }
5142
5188
  async function main() {
5143
5189
  const opts = parseArgs(process.argv);
5144
5190
  if (opts.help) {
@@ -5149,27 +5195,28 @@ async function main() {
5149
5195
  printVersion();
5150
5196
  return;
5151
5197
  }
5152
- if (opts.command !== "render") {
5153
- if (opts.command) {
5154
- console.error(`Error: Unknown command "${opts.command}"`);
5155
- } else {
5156
- console.error("Error: No command specified");
5157
- }
5158
- console.error('Run "dgmo --help" for usage');
5159
- process.exit(1);
5160
- }
5161
- if (!opts.input) {
5162
- console.error("Error: No input file specified");
5163
- console.error("Usage: dgmo render <input> [-o output.svg]");
5164
- process.exit(1);
5165
- }
5166
- const inputPath = (0, import_node_path.resolve)(opts.input);
5167
5198
  let content;
5168
- try {
5169
- content = (0, import_node_fs.readFileSync)(inputPath, "utf-8");
5170
- } catch {
5171
- console.error(`Error: Cannot read file "${inputPath}"`);
5172
- process.exit(1);
5199
+ let inputBasename;
5200
+ const stdinIsPiped = !process.stdin.isTTY;
5201
+ if (opts.input) {
5202
+ const inputPath = (0, import_node_path.resolve)(opts.input);
5203
+ try {
5204
+ content = (0, import_node_fs.readFileSync)(inputPath, "utf-8");
5205
+ } catch {
5206
+ console.error(`Error: Cannot read file "${inputPath}"`);
5207
+ process.exit(1);
5208
+ }
5209
+ const name = (0, import_node_path.basename)(opts.input);
5210
+ const ext = (0, import_node_path.extname)(name);
5211
+ inputBasename = ext ? name.slice(0, -ext.length) : name;
5212
+ } else if (stdinIsPiped) {
5213
+ try {
5214
+ content = (0, import_node_fs.readFileSync)(0, "utf-8");
5215
+ } catch {
5216
+ noInput();
5217
+ }
5218
+ } else {
5219
+ noInput();
5173
5220
  }
5174
5221
  setupDom();
5175
5222
  const isDark = opts.theme === "dark";
@@ -5181,12 +5228,22 @@ async function main() {
5181
5228
  );
5182
5229
  process.exit(1);
5183
5230
  }
5231
+ const format = inferFormat(opts.output);
5232
+ const pngBg = opts.theme === "transparent" ? void 0 : paletteColors.bg;
5184
5233
  if (opts.output) {
5185
5234
  const outputPath = (0, import_node_path.resolve)(opts.output);
5186
- (0, import_node_fs.writeFileSync)(outputPath, svg, "utf-8");
5235
+ if (format === "svg") {
5236
+ (0, import_node_fs.writeFileSync)(outputPath, svg, "utf-8");
5237
+ } else {
5238
+ (0, import_node_fs.writeFileSync)(outputPath, svgToPng(svg, pngBg));
5239
+ }
5240
+ console.error(`Wrote ${outputPath}`);
5241
+ } else if (inputBasename) {
5242
+ const outputPath = (0, import_node_path.resolve)(`${inputBasename}.png`);
5243
+ (0, import_node_fs.writeFileSync)(outputPath, svgToPng(svg, pngBg));
5187
5244
  console.error(`Wrote ${outputPath}`);
5188
5245
  } else {
5189
- process.stdout.write(svg);
5246
+ process.stdout.write(svgToPng(svg, pngBg));
5190
5247
  }
5191
5248
  }
5192
5249
  main().catch((err) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@diagrammo/dgmo",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "DGMO diagram markup language — parser, renderer, and color system",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,6 +33,7 @@
33
33
  "dev": "tsup --watch"
34
34
  },
35
35
  "dependencies": {
36
+ "@resvg/resvg-js": "^2.6.2",
36
37
  "chart.js": "^4.4.8",
37
38
  "chartjs-plugin-datalabels": "^2.2.0",
38
39
  "d3-array": "^3.2.4",
@@ -44,12 +45,12 @@
44
45
  "jsdom": "^26.0.0"
45
46
  },
46
47
  "devDependencies": {
47
- "@types/jsdom": "^21.1.7",
48
48
  "@types/d3-array": "^3.2.1",
49
49
  "@types/d3-cloud": "^1.2.9",
50
50
  "@types/d3-scale": "^4.0.8",
51
51
  "@types/d3-selection": "^3.0.11",
52
52
  "@types/d3-shape": "^3.1.7",
53
+ "@types/jsdom": "^21.1.7",
53
54
  "tsup": "^8.5.1",
54
55
  "typescript": "^5.7.3"
55
56
  }
package/src/cli.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { readFileSync, writeFileSync } from 'node:fs';
2
- import { resolve } from 'node:path';
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { resolve, basename, extname } from 'node:path';
3
3
  import { JSDOM } from 'jsdom';
4
+ import { Resvg } from '@resvg/resvg-js';
4
5
  import { renderD3ForExport } from './d3';
5
6
  import { getPalette } from './palettes/registry';
6
7
 
@@ -18,13 +19,15 @@ const PALETTES = [
18
19
  const THEMES = ['light', 'dark', 'transparent'] as const;
19
20
 
20
21
  function printHelp(): void {
21
- console.log(`Usage: dgmo render <input> [options]
22
+ console.log(`Usage: dgmo <input> [options]
23
+ cat input.dgmo | dgmo [options]
22
24
 
23
- Commands:
24
- render <input> Render a .dgmo file to SVG
25
+ Render a .dgmo file to PNG (default) or SVG.
25
26
 
26
27
  Options:
27
- -o <file> Write SVG to file (default: stdout)
28
+ -o <file> Output file (default: <input>.png in cwd)
29
+ Format inferred from extension: .svg → SVG, else PNG
30
+ With stdin and no -o, PNG is written to stdout
28
31
  --theme <theme> Theme: ${THEMES.join(', ')} (default: light)
29
32
  --palette <name> Palette: ${PALETTES.join(', ')} (default: nord)
30
33
  --help Show this help
@@ -39,7 +42,6 @@ function printVersion(): void {
39
42
  }
40
43
 
41
44
  function parseArgs(argv: string[]): {
42
- command: string | undefined;
43
45
  input: string | undefined;
44
46
  output: string | undefined;
45
47
  theme: (typeof THEMES)[number];
@@ -48,7 +50,6 @@ function parseArgs(argv: string[]): {
48
50
  version: boolean;
49
51
  } {
50
52
  const result = {
51
- command: undefined as string | undefined,
52
53
  input: undefined as string | undefined,
53
54
  output: undefined as string | undefined,
54
55
  theme: 'light' as (typeof THEMES)[number],
@@ -92,9 +93,6 @@ function parseArgs(argv: string[]): {
92
93
  }
93
94
  result.palette = val;
94
95
  i++;
95
- } else if (!result.command) {
96
- result.command = arg;
97
- i++;
98
96
  } else if (!result.input) {
99
97
  result.input = arg;
100
98
  i++;
@@ -119,6 +117,56 @@ function setupDom(): void {
119
117
  Object.defineProperty(globalThis, 'SVGElement', { value: win.SVGElement, configurable: true });
120
118
  }
121
119
 
120
+ function inferFormat(outputPath: string | undefined): 'svg' | 'png' {
121
+ if (outputPath && extname(outputPath).toLowerCase() === '.svg') {
122
+ return 'svg';
123
+ }
124
+ return 'png';
125
+ }
126
+
127
+ function svgToPng(svg: string, background?: string): Buffer {
128
+ const resvg = new Resvg(svg, {
129
+ fitTo: { mode: 'zoom', value: 2 },
130
+ ...(background ? { background } : {}),
131
+ });
132
+ const rendered = resvg.render();
133
+ return rendered.asPng();
134
+ }
135
+
136
+ function noInput(): never {
137
+ const samplePath = resolve('sample.dgmo');
138
+ if (existsSync(samplePath)) {
139
+ console.error('Error: No input file specified');
140
+ console.error(`Try: dgmo ${basename(samplePath)}`);
141
+ process.exit(1);
142
+ }
143
+ writeFileSync(
144
+ samplePath,
145
+ [
146
+ 'chart: sequence',
147
+ 'activations: off',
148
+ '',
149
+ ' Client -> API: POST /login',
150
+ ' API -> Auth: validate credentials',
151
+ ' Auth -> DB: SELECT user',
152
+ ' DB -> Auth: user record',
153
+ ' Auth -> API: JWT token',
154
+ ' API -> Client: 200 OK { token }',
155
+ '',
156
+ ].join('\n'),
157
+ 'utf-8'
158
+ );
159
+ console.error(`Created ${samplePath}`);
160
+ console.error('');
161
+ console.error(' Render it: dgmo sample.dgmo');
162
+ console.error(' As SVG: dgmo sample.dgmo -o sample.svg');
163
+ console.error('');
164
+ console.error(
165
+ 'Edit sample.dgmo to make it your own, or run dgmo --help for all options.'
166
+ );
167
+ process.exit(0);
168
+ }
169
+
122
170
  async function main(): Promise<void> {
123
171
  const opts = parseArgs(process.argv);
124
172
 
@@ -132,29 +180,33 @@ async function main(): Promise<void> {
132
180
  return;
133
181
  }
134
182
 
135
- if (opts.command !== 'render') {
136
- if (opts.command) {
137
- console.error(`Error: Unknown command "${opts.command}"`);
138
- } else {
139
- console.error('Error: No command specified');
140
- }
141
- console.error('Run "dgmo --help" for usage');
142
- process.exit(1);
143
- }
144
-
145
- if (!opts.input) {
146
- console.error('Error: No input file specified');
147
- console.error('Usage: dgmo render <input> [-o output.svg]');
148
- process.exit(1);
149
- }
150
-
151
- const inputPath = resolve(opts.input);
183
+ // Determine input source
152
184
  let content: string;
153
- try {
154
- content = readFileSync(inputPath, 'utf-8');
155
- } catch {
156
- console.error(`Error: Cannot read file "${inputPath}"`);
157
- process.exit(1);
185
+ let inputBasename: string | undefined;
186
+ const stdinIsPiped = !process.stdin.isTTY;
187
+
188
+ if (opts.input) {
189
+ // File argument provided
190
+ const inputPath = resolve(opts.input);
191
+ try {
192
+ content = readFileSync(inputPath, 'utf-8');
193
+ } catch {
194
+ console.error(`Error: Cannot read file "${inputPath}"`);
195
+ process.exit(1);
196
+ }
197
+ // Strip extension for default output name
198
+ const name = basename(opts.input);
199
+ const ext = extname(name);
200
+ inputBasename = ext ? name.slice(0, -ext.length) : name;
201
+ } else if (stdinIsPiped) {
202
+ // Read from stdin
203
+ try {
204
+ content = readFileSync(0, 'utf-8');
205
+ } catch {
206
+ noInput();
207
+ }
208
+ } else {
209
+ noInput();
158
210
  }
159
211
 
160
212
  // Set up jsdom before any d3/renderer code runs
@@ -174,12 +226,27 @@ async function main(): Promise<void> {
174
226
  process.exit(1);
175
227
  }
176
228
 
229
+ // Determine output format and destination
230
+ const format = inferFormat(opts.output);
231
+ const pngBg = opts.theme === 'transparent' ? undefined : paletteColors.bg;
232
+
177
233
  if (opts.output) {
234
+ // Explicit output path
178
235
  const outputPath = resolve(opts.output);
179
- writeFileSync(outputPath, svg, 'utf-8');
236
+ if (format === 'svg') {
237
+ writeFileSync(outputPath, svg, 'utf-8');
238
+ } else {
239
+ writeFileSync(outputPath, svgToPng(svg, pngBg));
240
+ }
241
+ console.error(`Wrote ${outputPath}`);
242
+ } else if (inputBasename) {
243
+ // File input, no -o → write <basename>.png in cwd
244
+ const outputPath = resolve(`${inputBasename}.png`);
245
+ writeFileSync(outputPath, svgToPng(svg, pngBg));
180
246
  console.error(`Wrote ${outputPath}`);
181
247
  } else {
182
- process.stdout.write(svg);
248
+ // Stdin input, no -o → write PNG to stdout
249
+ process.stdout.write(svgToPng(svg, pngBg));
183
250
  }
184
251
  }
185
252