@diagrammo/dgmo 0.1.0 → 0.1.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
@@ -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,52 @@ 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) {
5148
+ const resvg = new import_resvg_js.Resvg(svg, {
5149
+ fitTo: { mode: "zoom", value: 2 }
5150
+ });
5151
+ const rendered = resvg.render();
5152
+ return rendered.asPng();
5153
+ }
5154
+ function noInput() {
5155
+ const samplePath = (0, import_node_path.resolve)("sample.dgmo");
5156
+ if ((0, import_node_fs.existsSync)(samplePath)) {
5157
+ console.error("Error: No input file specified");
5158
+ console.error(`Try: dgmo ${(0, import_node_path.basename)(samplePath)}`);
5159
+ process.exit(1);
5160
+ }
5161
+ (0, import_node_fs.writeFileSync)(
5162
+ samplePath,
5163
+ [
5164
+ "chart: sequence",
5165
+ "activations: off",
5166
+ "",
5167
+ " Client -> API: POST /login",
5168
+ " API -> Auth: validate credentials",
5169
+ " Auth -> DB: SELECT user",
5170
+ " DB -> Auth: user record",
5171
+ " Auth -> API: JWT token",
5172
+ " API -> Client: 200 OK { token }",
5173
+ ""
5174
+ ].join("\n"),
5175
+ "utf-8"
5176
+ );
5177
+ console.error(`Created ${samplePath}`);
5178
+ console.error("");
5179
+ console.error(" Render it: dgmo sample.dgmo");
5180
+ console.error(" As SVG: dgmo sample.dgmo -o sample.svg");
5181
+ console.error("");
5182
+ console.error(
5183
+ "Edit sample.dgmo to make it your own, or run dgmo --help for all options."
5184
+ );
5185
+ process.exit(0);
5186
+ }
5142
5187
  async function main() {
5143
5188
  const opts = parseArgs(process.argv);
5144
5189
  if (opts.help) {
@@ -5149,27 +5194,28 @@ async function main() {
5149
5194
  printVersion();
5150
5195
  return;
5151
5196
  }
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
5197
  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);
5198
+ let inputBasename;
5199
+ const stdinIsPiped = !process.stdin.isTTY;
5200
+ if (opts.input) {
5201
+ const inputPath = (0, import_node_path.resolve)(opts.input);
5202
+ try {
5203
+ content = (0, import_node_fs.readFileSync)(inputPath, "utf-8");
5204
+ } catch {
5205
+ console.error(`Error: Cannot read file "${inputPath}"`);
5206
+ process.exit(1);
5207
+ }
5208
+ const name = (0, import_node_path.basename)(opts.input);
5209
+ const ext = (0, import_node_path.extname)(name);
5210
+ inputBasename = ext ? name.slice(0, -ext.length) : name;
5211
+ } else if (stdinIsPiped) {
5212
+ try {
5213
+ content = (0, import_node_fs.readFileSync)(0, "utf-8");
5214
+ } catch {
5215
+ noInput();
5216
+ }
5217
+ } else {
5218
+ noInput();
5173
5219
  }
5174
5220
  setupDom();
5175
5221
  const isDark = opts.theme === "dark";
@@ -5181,12 +5227,21 @@ async function main() {
5181
5227
  );
5182
5228
  process.exit(1);
5183
5229
  }
5230
+ const format = inferFormat(opts.output);
5184
5231
  if (opts.output) {
5185
5232
  const outputPath = (0, import_node_path.resolve)(opts.output);
5186
- (0, import_node_fs.writeFileSync)(outputPath, svg, "utf-8");
5233
+ if (format === "svg") {
5234
+ (0, import_node_fs.writeFileSync)(outputPath, svg, "utf-8");
5235
+ } else {
5236
+ (0, import_node_fs.writeFileSync)(outputPath, svgToPng(svg));
5237
+ }
5238
+ console.error(`Wrote ${outputPath}`);
5239
+ } else if (inputBasename) {
5240
+ const outputPath = (0, import_node_path.resolve)(`${inputBasename}.png`);
5241
+ (0, import_node_fs.writeFileSync)(outputPath, svgToPng(svg));
5187
5242
  console.error(`Wrote ${outputPath}`);
5188
5243
  } else {
5189
- process.stdout.write(svg);
5244
+ process.stdout.write(svgToPng(svg));
5190
5245
  }
5191
5246
  }
5192
5247
  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.1",
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,55 @@ 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): Buffer {
128
+ const resvg = new Resvg(svg, {
129
+ fitTo: { mode: 'zoom', value: 2 },
130
+ });
131
+ const rendered = resvg.render();
132
+ return rendered.asPng();
133
+ }
134
+
135
+ function noInput(): never {
136
+ const samplePath = resolve('sample.dgmo');
137
+ if (existsSync(samplePath)) {
138
+ console.error('Error: No input file specified');
139
+ console.error(`Try: dgmo ${basename(samplePath)}`);
140
+ process.exit(1);
141
+ }
142
+ writeFileSync(
143
+ samplePath,
144
+ [
145
+ 'chart: sequence',
146
+ 'activations: off',
147
+ '',
148
+ ' Client -> API: POST /login',
149
+ ' API -> Auth: validate credentials',
150
+ ' Auth -> DB: SELECT user',
151
+ ' DB -> Auth: user record',
152
+ ' Auth -> API: JWT token',
153
+ ' API -> Client: 200 OK { token }',
154
+ '',
155
+ ].join('\n'),
156
+ 'utf-8'
157
+ );
158
+ console.error(`Created ${samplePath}`);
159
+ console.error('');
160
+ console.error(' Render it: dgmo sample.dgmo');
161
+ console.error(' As SVG: dgmo sample.dgmo -o sample.svg');
162
+ console.error('');
163
+ console.error(
164
+ 'Edit sample.dgmo to make it your own, or run dgmo --help for all options.'
165
+ );
166
+ process.exit(0);
167
+ }
168
+
122
169
  async function main(): Promise<void> {
123
170
  const opts = parseArgs(process.argv);
124
171
 
@@ -132,29 +179,33 @@ async function main(): Promise<void> {
132
179
  return;
133
180
  }
134
181
 
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);
182
+ // Determine input source
152
183
  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);
184
+ let inputBasename: string | undefined;
185
+ const stdinIsPiped = !process.stdin.isTTY;
186
+
187
+ if (opts.input) {
188
+ // File argument provided
189
+ const inputPath = resolve(opts.input);
190
+ try {
191
+ content = readFileSync(inputPath, 'utf-8');
192
+ } catch {
193
+ console.error(`Error: Cannot read file "${inputPath}"`);
194
+ process.exit(1);
195
+ }
196
+ // Strip extension for default output name
197
+ const name = basename(opts.input);
198
+ const ext = extname(name);
199
+ inputBasename = ext ? name.slice(0, -ext.length) : name;
200
+ } else if (stdinIsPiped) {
201
+ // Read from stdin
202
+ try {
203
+ content = readFileSync(0, 'utf-8');
204
+ } catch {
205
+ noInput();
206
+ }
207
+ } else {
208
+ noInput();
158
209
  }
159
210
 
160
211
  // Set up jsdom before any d3/renderer code runs
@@ -174,12 +225,26 @@ async function main(): Promise<void> {
174
225
  process.exit(1);
175
226
  }
176
227
 
228
+ // Determine output format and destination
229
+ const format = inferFormat(opts.output);
230
+
177
231
  if (opts.output) {
232
+ // Explicit output path
178
233
  const outputPath = resolve(opts.output);
179
- writeFileSync(outputPath, svg, 'utf-8');
234
+ if (format === 'svg') {
235
+ writeFileSync(outputPath, svg, 'utf-8');
236
+ } else {
237
+ writeFileSync(outputPath, svgToPng(svg));
238
+ }
239
+ console.error(`Wrote ${outputPath}`);
240
+ } else if (inputBasename) {
241
+ // File input, no -o → write <basename>.png in cwd
242
+ const outputPath = resolve(`${inputBasename}.png`);
243
+ writeFileSync(outputPath, svgToPng(svg));
180
244
  console.error(`Wrote ${outputPath}`);
181
245
  } else {
182
- process.stdout.write(svg);
246
+ // Stdin input, no -o → write PNG to stdout
247
+ process.stdout.write(svgToPng(svg));
183
248
  }
184
249
  }
185
250