sfn-diagram 1.9.0 → 1.9.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/dist/bin.js CHANGED
@@ -6,7 +6,7 @@ import { parse } from "yaml";
6
6
  import dagre from "@dagrejs/dagre";
7
7
  import { curveBasis, line } from "d3-shape";
8
8
  import { minimatch } from "minimatch";
9
- import { homedir } from "node:os";
9
+ import { homedir, totalmem } from "node:os";
10
10
  import { execFileSync } from "node:child_process";
11
11
  // src/config/withoutUndefined.ts
12
12
  /**
@@ -2837,7 +2837,8 @@ function parseAsl(params) {
2837
2837
  });
2838
2838
  return {
2839
2839
  edges: assignEdgeIds({ edges }),
2840
- nodes
2840
+ nodes,
2841
+ startId: resolver.resolve("", definition.StartAt)
2841
2842
  };
2842
2843
  }
2843
2844
  /**
@@ -9081,11 +9082,15 @@ ${playbackRules(palette)}
9081
9082
  * Mermaid numeric-entity escapes for characters that are significant to
9082
9083
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
9083
9084
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
9084
- * syntax, and a backtick can break Markdown-flavoured label rendering.
9085
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
9086
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
9087
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
9088
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
9085
9089
  */
9086
9090
  const MERMAID_LABEL_ENTITIES = {
9087
9091
  "#": "#35;",
9088
9092
  "\"": "#quot;",
9093
+ ":": "#58;",
9089
9094
  ";": "#59;",
9090
9095
  "<": "#60;",
9091
9096
  ">": "#62;",
@@ -9280,7 +9285,7 @@ var MermaidRenderer = class {
9280
9285
  * a literal `-->` inside a label, so no separate arrow rule is needed.
9281
9286
  */
9282
9287
  escapeLabel(label) {
9283
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9288
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9284
9289
  }
9285
9290
  /**
9286
9291
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -9356,10 +9361,13 @@ var MermaidRenderer = class {
9356
9361
  /**
9357
9362
  * Find the start state from the ASL definition's StartAt or, without one, the
9358
9363
  * first top-level state nothing else transitions to.
9364
+ *
9365
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
9366
+ * can differ from the name (a sanitized character, or a discriminator).
9359
9367
  */
9360
9368
  findStartState(params) {
9361
9369
  const { asl, root } = params;
9362
- if (asl?.StartAt) return asl.StartAt;
9370
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
9363
9371
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
9364
9372
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
9365
9373
  }
@@ -9880,7 +9888,7 @@ function buildDiagramGraph(params) {
9880
9888
  edges: parsed.edges,
9881
9889
  mode: options.catchHandling,
9882
9890
  nodes: parsed.nodes,
9883
- startStateId: definition.StartAt
9891
+ startStateId: parsed.startId
9884
9892
  });
9885
9893
  return {
9886
9894
  edges,
@@ -15086,6 +15094,33 @@ function resolvePngFontOptions(params) {
15086
15094
  };
15087
15095
  }
15088
15096
  // src/exporters/resvgEngine.ts
15097
+ /** Bytes per pixel in resvg's RGBA output pixmap. */
15098
+ const PIXMAP_BYTES_PER_PIXEL = 4;
15099
+ /** Bytes in a GiB, for error messages. */
15100
+ const GIB = 2 ** 30;
15101
+ /**
15102
+ * Rounded intrinsic width below which {@link maxIntrinsicAspect} reads the exact size,
15103
+ * keeping the rounding bound within about 1% where it is used.
15104
+ */
15105
+ const EXACT_SIZE_BELOW_WIDTH = 100;
15106
+ /**
15107
+ * The largest height-to-width ratio the SVG's intrinsic size can have, or 0 when it has
15108
+ * no width.
15109
+ *
15110
+ * resvg's `width` and `height` getters round to whole pixels, so each true side lies
15111
+ * within 0.5 of its getter and `(height + 0.5) / (width - 0.5)` bounds the true ratio
15112
+ * from above. That bound is loose for a narrow SVG (3x at 1 x 1) and missing when the
15113
+ * width rounds to 0, so below {@link EXACT_SIZE_BELOW_WIDTH} the exact size is read
15114
+ * from the serialized root element instead. Every SVG this package generates is wider.
15115
+ */
15116
+ function maxIntrinsicAspect(params) {
15117
+ const { intrinsic } = params;
15118
+ if (intrinsic.width >= EXACT_SIZE_BELOW_WIDTH) return (intrinsic.height + .5) / (intrinsic.width - .5);
15119
+ const root = /<svg\b[^>]*>/.exec(intrinsic.toString())?.[0] ?? "";
15120
+ const exactWidth = Number(/\swidth="([^"]*)"/.exec(root)?.[1]);
15121
+ const exactHeight = Number(/\sheight="([^"]*)"/.exec(root)?.[1]);
15122
+ return exactWidth > 0 && exactHeight > 0 ? exactHeight / exactWidth : 0;
15123
+ }
15089
15124
  /**
15090
15125
  * Lazily load `@resvg/resvg-js`, which is an optional peer dependency. Keeping
15091
15126
  * it out of the static import graph means SVG/Mermaid-only consumers never
@@ -15113,7 +15148,14 @@ async function loadResvg() {
15113
15148
  async function renderResvgPng(params) {
15114
15149
  const { backgroundColor, fontDirs, fontFamily, fontFiles, loadSystemFonts, preferredFamily, scale = 1, svg, width } = params;
15115
15150
  if (!Number.isFinite(scale) || scale <= 0) throw new Error(`PNG export 'scale' must be a positive finite number, got ${scale}.`);
15116
- const { renderAsync } = await loadResvg();
15151
+ const { Resvg, renderAsync } = await loadResvg();
15152
+ const aspect = maxIntrinsicAspect({ intrinsic: new Resvg(svg, { font: { loadSystemFonts: false } }) });
15153
+ const targetWidth = Math.round(width * scale);
15154
+ const targetHeight = Math.ceil(targetWidth * aspect);
15155
+ const pixmapBytes = targetWidth * targetHeight * PIXMAP_BYTES_PER_PIXEL;
15156
+ const constrained = process.constrainedMemory?.() ?? 0;
15157
+ const memoryBytes = constrained > 0 ? Math.min(constrained, totalmem()) : totalmem();
15158
+ if (pixmapBytes > memoryBytes) throw new Error(`PNG export is too large: at scale ${scale} the image would be ${targetWidth} x ${targetHeight} pixels, which needs ${(pixmapBytes / GIB).toFixed(1)} GiB of memory, more than the ${(memoryBytes / GIB).toFixed(1)} GiB available.${scale > 1 ? " Use a smaller scale." : ""}`);
15117
15159
  const font = resolvePngFontOptions({
15118
15160
  fontDirs,
15119
15161
  fontFamily,
@@ -16214,7 +16256,7 @@ function isTerraformInput(params) {
16214
16256
  * as Terraform. Extraction warnings go to stderr.
16215
16257
  */
16216
16258
  function resolveDefinitionSource(params) {
16217
- const { path, resolveCfn, resource, source } = params;
16259
+ const { path, reportWarnings, resolveCfn, resource, source } = params;
16218
16260
  if (isTerraformInput({
16219
16261
  path,
16220
16262
  source
@@ -16223,7 +16265,7 @@ function resolveDefinitionSource(params) {
16223
16265
  config: source,
16224
16266
  resourceId: resource ?? void 0
16225
16267
  });
16226
- writeExtractionWarnings({ warnings });
16268
+ if (reportWarnings) writeExtractionWarnings({ warnings });
16227
16269
  return aslDefinition;
16228
16270
  }
16229
16271
  if (!resolveCfn && !isCfnTemplate(source)) return source;
@@ -16231,7 +16273,7 @@ function resolveDefinitionSource(params) {
16231
16273
  resourceId: resource ?? void 0,
16232
16274
  template: source
16233
16275
  });
16234
- writeExtractionWarnings({ warnings });
16276
+ if (reportWarnings) writeExtractionWarnings({ warnings });
16235
16277
  return aslDefinition;
16236
16278
  }
16237
16279
  /**
@@ -16502,7 +16544,7 @@ async function runCommentGitlab(argv) {
16502
16544
  * not be read, parsed, rendered or written, or if `--check` found an error.
16503
16545
  */
16504
16546
  async function renderOneInput(params) {
16505
- const { args, baselineIsLive, baselineSource, cache, definitionText, historySource, labelInput, options, outputPath, source, theme, writeAtomically } = params;
16547
+ const { args, baselineIsLive, baselineSource, cache, definitionText, historySource, labelInput, options, outputPath, reportStatic, source, theme, writeAtomically } = params;
16506
16548
  /** Write this render's output, honouring the atomic-write choice once. */
16507
16549
  const emit = (content) => writeOutput({
16508
16550
  atomic: writeAtomically,
@@ -16525,12 +16567,14 @@ async function renderOneInput(params) {
16525
16567
  try {
16526
16568
  definitionSource = resolveDefinitionSource({
16527
16569
  path: source.kind === "file" ? source.path : null,
16570
+ reportWarnings: reportStatic,
16528
16571
  resolveCfn: args.resolveCfn,
16529
16572
  resource: args.resource,
16530
16573
  source: aslSource
16531
16574
  });
16532
16575
  if (baselineSource !== null) baselineDefinition = resolveDefinitionSource({
16533
16576
  path: baselineIsLive ? null : args.diff,
16577
+ reportWarnings: reportStatic,
16534
16578
  resolveCfn: args.resolveCfn && !baselineIsLive,
16535
16579
  resource: baselineIsLive ? null : args.resource,
16536
16580
  source: baselineSource
@@ -16590,7 +16634,7 @@ async function renderOneInput(params) {
16590
16634
  ...redact ? { redact } : {},
16591
16635
  ...svgOptions
16592
16636
  });
16593
- if (result.metadata.diff) writeDiffSummary(result.metadata.diff);
16637
+ if (result.metadata.diff && reportStatic) writeDiffSummary(result.metadata.diff);
16594
16638
  if (result.metadata.execution) writeExecutionSummary(result.metadata.execution);
16595
16639
  emit(result.html);
16596
16640
  return 0;
@@ -16603,7 +16647,7 @@ async function renderOneInput(params) {
16603
16647
  layout: options.layout,
16604
16648
  theme
16605
16649
  });
16606
- writeDiffSummary(result.metadata);
16650
+ if (reportStatic) writeDiffSummary(result.metadata);
16607
16651
  emit(result.code);
16608
16652
  return 0;
16609
16653
  }
@@ -16612,7 +16656,7 @@ async function renderOneInput(params) {
16612
16656
  before: baselineDefinition,
16613
16657
  ...svgOptions
16614
16658
  });
16615
- writeDiffSummary(result.metadata);
16659
+ if (reportStatic) writeDiffSummary(result.metadata);
16616
16660
  emit(result.svg);
16617
16661
  return 0;
16618
16662
  }
@@ -17335,6 +17379,7 @@ async function renderResolvedBatch(params) {
17335
17379
  labelInput,
17336
17380
  options,
17337
17381
  outputPath: item.outputPath,
17382
+ reportStatic: true,
17338
17383
  source: item.source,
17339
17384
  theme,
17340
17385
  writeAtomically: false
@@ -17404,6 +17449,7 @@ async function runFollow(params) {
17404
17449
  labelInput: false,
17405
17450
  options,
17406
17451
  outputPath,
17452
+ reportStatic: ticks === 1,
17407
17453
  source,
17408
17454
  theme,
17409
17455
  writeAtomically: true
package/dist/ci.cjs CHANGED
@@ -2690,7 +2690,8 @@ function parseAsl(params) {
2690
2690
  });
2691
2691
  return {
2692
2692
  edges: assignEdgeIds({ edges }),
2693
- nodes
2693
+ nodes,
2694
+ startId: resolver.resolve("", definition.StartAt)
2694
2695
  };
2695
2696
  }
2696
2697
  /**
@@ -6043,11 +6044,15 @@ function resolveViewerTheme(params) {
6043
6044
  * Mermaid numeric-entity escapes for characters that are significant to
6044
6045
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
6045
6046
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
6046
- * syntax, and a backtick can break Markdown-flavoured label rendering.
6047
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
6048
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
6049
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
6050
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
6047
6051
  */
6048
6052
  const MERMAID_LABEL_ENTITIES = {
6049
6053
  "#": "#35;",
6050
6054
  "\"": "#quot;",
6055
+ ":": "#58;",
6051
6056
  ";": "#59;",
6052
6057
  "<": "#60;",
6053
6058
  ">": "#62;",
@@ -6242,7 +6247,7 @@ var MermaidRenderer = class {
6242
6247
  * a literal `-->` inside a label, so no separate arrow rule is needed.
6243
6248
  */
6244
6249
  escapeLabel(label) {
6245
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
6250
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
6246
6251
  }
6247
6252
  /**
6248
6253
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -6318,10 +6323,13 @@ var MermaidRenderer = class {
6318
6323
  /**
6319
6324
  * Find the start state from the ASL definition's StartAt or, without one, the
6320
6325
  * first top-level state nothing else transitions to.
6326
+ *
6327
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
6328
+ * can differ from the name (a sanitized character, or a discriminator).
6321
6329
  */
6322
6330
  findStartState(params) {
6323
6331
  const { asl, root } = params;
6324
- if (asl?.StartAt) return asl.StartAt;
6332
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
6325
6333
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
6326
6334
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
6327
6335
  }
@@ -6991,7 +6999,7 @@ function buildDiagramGraph(params) {
6991
6999
  edges: parsed.edges,
6992
7000
  mode: options.catchHandling,
6993
7001
  nodes: parsed.nodes,
6994
- startStateId: definition.StartAt
7002
+ startStateId: parsed.startId
6995
7003
  });
6996
7004
  return {
6997
7005
  edges,
package/dist/ci.js CHANGED
@@ -2667,7 +2667,8 @@ function parseAsl(params) {
2667
2667
  });
2668
2668
  return {
2669
2669
  edges: assignEdgeIds({ edges }),
2670
- nodes
2670
+ nodes,
2671
+ startId: resolver.resolve("", definition.StartAt)
2671
2672
  };
2672
2673
  }
2673
2674
  /**
@@ -6020,11 +6021,15 @@ function resolveViewerTheme(params) {
6020
6021
  * Mermaid numeric-entity escapes for characters that are significant to
6021
6022
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
6022
6023
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
6023
- * syntax, and a backtick can break Markdown-flavoured label rendering.
6024
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
6025
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
6026
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
6027
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
6024
6028
  */
6025
6029
  const MERMAID_LABEL_ENTITIES = {
6026
6030
  "#": "#35;",
6027
6031
  "\"": "#quot;",
6032
+ ":": "#58;",
6028
6033
  ";": "#59;",
6029
6034
  "<": "#60;",
6030
6035
  ">": "#62;",
@@ -6219,7 +6224,7 @@ var MermaidRenderer = class {
6219
6224
  * a literal `-->` inside a label, so no separate arrow rule is needed.
6220
6225
  */
6221
6226
  escapeLabel(label) {
6222
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
6227
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
6223
6228
  }
6224
6229
  /**
6225
6230
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -6295,10 +6300,13 @@ var MermaidRenderer = class {
6295
6300
  /**
6296
6301
  * Find the start state from the ASL definition's StartAt or, without one, the
6297
6302
  * first top-level state nothing else transitions to.
6303
+ *
6304
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
6305
+ * can differ from the name (a sanitized character, or a discriminator).
6298
6306
  */
6299
6307
  findStartState(params) {
6300
6308
  const { asl, root } = params;
6301
- if (asl?.StartAt) return asl.StartAt;
6309
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
6302
6310
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
6303
6311
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
6304
6312
  }
@@ -6968,7 +6976,7 @@ function buildDiagramGraph(params) {
6968
6976
  edges: parsed.edges,
6969
6977
  mode: options.catchHandling,
6970
6978
  nodes: parsed.nodes,
6971
- startStateId: definition.StartAt
6979
+ startStateId: parsed.startId
6972
6980
  });
6973
6981
  return {
6974
6982
  edges,
@@ -2851,7 +2851,8 @@ function parseAsl(params) {
2851
2851
  });
2852
2852
  return {
2853
2853
  edges: assignEdgeIds({ edges }),
2854
- nodes
2854
+ nodes,
2855
+ startId: resolver.resolve("", definition.StartAt)
2855
2856
  };
2856
2857
  }
2857
2858
  /**
@@ -9214,11 +9215,15 @@ function buildMermaidScopes(params) {
9214
9215
  * Mermaid numeric-entity escapes for characters that are significant to
9215
9216
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
9216
9217
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
9217
- * syntax, and a backtick can break Markdown-flavoured label rendering.
9218
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
9219
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
9220
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
9221
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
9218
9222
  */
9219
9223
  const MERMAID_LABEL_ENTITIES = {
9220
9224
  "#": "#35;",
9221
9225
  "\"": "#quot;",
9226
+ ":": "#58;",
9222
9227
  ";": "#59;",
9223
9228
  "<": "#60;",
9224
9229
  ">": "#62;",
@@ -9413,7 +9418,7 @@ var MermaidRenderer = class {
9413
9418
  * a literal `-->` inside a label, so no separate arrow rule is needed.
9414
9419
  */
9415
9420
  escapeLabel(label) {
9416
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9421
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9417
9422
  }
9418
9423
  /**
9419
9424
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -9489,10 +9494,13 @@ var MermaidRenderer = class {
9489
9494
  /**
9490
9495
  * Find the start state from the ASL definition's StartAt or, without one, the
9491
9496
  * first top-level state nothing else transitions to.
9497
+ *
9498
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
9499
+ * can differ from the name (a sanitized character, or a discriminator).
9492
9500
  */
9493
9501
  findStartState(params) {
9494
9502
  const { asl, root } = params;
9495
- if (asl?.StartAt) return asl.StartAt;
9503
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
9496
9504
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
9497
9505
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
9498
9506
  }
@@ -10162,7 +10170,7 @@ function buildDiagramGraph(params) {
10162
10170
  edges: parsed.edges,
10163
10171
  mode: options.catchHandling,
10164
10172
  nodes: parsed.nodes,
10165
- startStateId: definition.StartAt
10173
+ startStateId: parsed.startId
10166
10174
  });
10167
10175
  return {
10168
10176
  edges,
@@ -2829,7 +2829,8 @@ function parseAsl(params) {
2829
2829
  });
2830
2830
  return {
2831
2831
  edges: assignEdgeIds({ edges }),
2832
- nodes
2832
+ nodes,
2833
+ startId: resolver.resolve("", definition.StartAt)
2833
2834
  };
2834
2835
  }
2835
2836
  /**
@@ -9192,11 +9193,15 @@ function buildMermaidScopes(params) {
9192
9193
  * Mermaid numeric-entity escapes for characters that are significant to
9193
9194
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
9194
9195
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
9195
- * syntax, and a backtick can break Markdown-flavoured label rendering.
9196
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
9197
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
9198
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
9199
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
9196
9200
  */
9197
9201
  const MERMAID_LABEL_ENTITIES = {
9198
9202
  "#": "#35;",
9199
9203
  "\"": "#quot;",
9204
+ ":": "#58;",
9200
9205
  ";": "#59;",
9201
9206
  "<": "#60;",
9202
9207
  ">": "#62;",
@@ -9391,7 +9396,7 @@ var MermaidRenderer = class {
9391
9396
  * a literal `-->` inside a label, so no separate arrow rule is needed.
9392
9397
  */
9393
9398
  escapeLabel(label) {
9394
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9399
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9395
9400
  }
9396
9401
  /**
9397
9402
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -9467,10 +9472,13 @@ var MermaidRenderer = class {
9467
9472
  /**
9468
9473
  * Find the start state from the ASL definition's StartAt or, without one, the
9469
9474
  * first top-level state nothing else transitions to.
9475
+ *
9476
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
9477
+ * can differ from the name (a sanitized character, or a discriminator).
9470
9478
  */
9471
9479
  findStartState(params) {
9472
9480
  const { asl, root } = params;
9473
- if (asl?.StartAt) return asl.StartAt;
9481
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
9474
9482
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
9475
9483
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
9476
9484
  }
@@ -10140,7 +10148,7 @@ function buildDiagramGraph(params) {
10140
10148
  edges: parsed.edges,
10141
10149
  mode: options.catchHandling,
10142
10150
  nodes: parsed.nodes,
10143
- startStateId: definition.StartAt
10151
+ startStateId: parsed.startId
10144
10152
  });
10145
10153
  return {
10146
10154
  edges,
package/dist/element.cjs CHANGED
@@ -2852,7 +2852,8 @@ function parseAsl(params) {
2852
2852
  });
2853
2853
  return {
2854
2854
  edges: assignEdgeIds({ edges }),
2855
- nodes
2855
+ nodes,
2856
+ startId: resolver.resolve("", definition.StartAt)
2856
2857
  };
2857
2858
  }
2858
2859
  /**
@@ -9215,11 +9216,15 @@ function buildMermaidScopes(params) {
9215
9216
  * Mermaid numeric-entity escapes for characters that are significant to
9216
9217
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
9217
9218
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
9218
- * syntax, and a backtick can break Markdown-flavoured label rendering.
9219
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
9220
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
9221
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
9222
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
9219
9223
  */
9220
9224
  const MERMAID_LABEL_ENTITIES = {
9221
9225
  "#": "#35;",
9222
9226
  "\"": "#quot;",
9227
+ ":": "#58;",
9223
9228
  ";": "#59;",
9224
9229
  "<": "#60;",
9225
9230
  ">": "#62;",
@@ -9414,7 +9419,7 @@ var MermaidRenderer = class {
9414
9419
  * a literal `-->` inside a label, so no separate arrow rule is needed.
9415
9420
  */
9416
9421
  escapeLabel(label) {
9417
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9422
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9418
9423
  }
9419
9424
  /**
9420
9425
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -9490,10 +9495,13 @@ var MermaidRenderer = class {
9490
9495
  /**
9491
9496
  * Find the start state from the ASL definition's StartAt or, without one, the
9492
9497
  * first top-level state nothing else transitions to.
9498
+ *
9499
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
9500
+ * can differ from the name (a sanitized character, or a discriminator).
9493
9501
  */
9494
9502
  findStartState(params) {
9495
9503
  const { asl, root } = params;
9496
- if (asl?.StartAt) return asl.StartAt;
9504
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
9497
9505
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
9498
9506
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
9499
9507
  }
@@ -10163,7 +10171,7 @@ function buildDiagramGraph(params) {
10163
10171
  edges: parsed.edges,
10164
10172
  mode: options.catchHandling,
10165
10173
  nodes: parsed.nodes,
10166
- startStateId: definition.StartAt
10174
+ startStateId: parsed.startId
10167
10175
  });
10168
10176
  return {
10169
10177
  edges,
package/dist/element.js CHANGED
@@ -2829,7 +2829,8 @@ function parseAsl(params) {
2829
2829
  });
2830
2830
  return {
2831
2831
  edges: assignEdgeIds({ edges }),
2832
- nodes
2832
+ nodes,
2833
+ startId: resolver.resolve("", definition.StartAt)
2833
2834
  };
2834
2835
  }
2835
2836
  /**
@@ -9192,11 +9193,15 @@ function buildMermaidScopes(params) {
9192
9193
  * Mermaid numeric-entity escapes for characters that are significant to
9193
9194
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
9194
9195
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
9195
- * syntax, and a backtick can break Markdown-flavoured label rendering.
9196
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
9197
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
9198
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
9199
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
9196
9200
  */
9197
9201
  const MERMAID_LABEL_ENTITIES = {
9198
9202
  "#": "#35;",
9199
9203
  "\"": "#quot;",
9204
+ ":": "#58;",
9200
9205
  ";": "#59;",
9201
9206
  "<": "#60;",
9202
9207
  ">": "#62;",
@@ -9391,7 +9396,7 @@ var MermaidRenderer = class {
9391
9396
  * a literal `-->` inside a label, so no separate arrow rule is needed.
9392
9397
  */
9393
9398
  escapeLabel(label) {
9394
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9399
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
9395
9400
  }
9396
9401
  /**
9397
9402
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -9467,10 +9472,13 @@ var MermaidRenderer = class {
9467
9472
  /**
9468
9473
  * Find the start state from the ASL definition's StartAt or, without one, the
9469
9474
  * first top-level state nothing else transitions to.
9475
+ *
9476
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
9477
+ * can differ from the name (a sanitized character, or a discriminator).
9470
9478
  */
9471
9479
  findStartState(params) {
9472
9480
  const { asl, root } = params;
9473
- if (asl?.StartAt) return asl.StartAt;
9481
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
9474
9482
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
9475
9483
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
9476
9484
  }
@@ -10140,7 +10148,7 @@ function buildDiagramGraph(params) {
10140
10148
  edges: parsed.edges,
10141
10149
  mode: options.catchHandling,
10142
10150
  nodes: parsed.nodes,
10143
- startStateId: definition.StartAt
10151
+ startStateId: parsed.startId
10144
10152
  });
10145
10153
  return {
10146
10154
  edges,
package/dist/index.cjs CHANGED
@@ -2878,7 +2878,8 @@ function parseAsl(params) {
2878
2878
  });
2879
2879
  return {
2880
2880
  edges: assignEdgeIds({ edges }),
2881
- nodes
2881
+ nodes,
2882
+ startId: resolver.resolve("", definition.StartAt)
2882
2883
  };
2883
2884
  }
2884
2885
  /**
@@ -6604,11 +6605,15 @@ ${playbackRules(palette)}
6604
6605
  * Mermaid numeric-entity escapes for characters that are significant to
6605
6606
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
6606
6607
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
6607
- * syntax, and a backtick can break Markdown-flavoured label rendering.
6608
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
6609
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
6610
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
6611
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
6608
6612
  */
6609
6613
  const MERMAID_LABEL_ENTITIES = {
6610
6614
  "#": "#35;",
6611
6615
  "\"": "#quot;",
6616
+ ":": "#58;",
6612
6617
  ";": "#59;",
6613
6618
  "<": "#60;",
6614
6619
  ">": "#62;",
@@ -6803,7 +6808,7 @@ var MermaidRenderer = class {
6803
6808
  * a literal `-->` inside a label, so no separate arrow rule is needed.
6804
6809
  */
6805
6810
  escapeLabel(label) {
6806
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
6811
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
6807
6812
  }
6808
6813
  /**
6809
6814
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -6879,10 +6884,13 @@ var MermaidRenderer = class {
6879
6884
  /**
6880
6885
  * Find the start state from the ASL definition's StartAt or, without one, the
6881
6886
  * first top-level state nothing else transitions to.
6887
+ *
6888
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
6889
+ * can differ from the name (a sanitized character, or a discriminator).
6882
6890
  */
6883
6891
  findStartState(params) {
6884
6892
  const { asl, root } = params;
6885
- if (asl?.StartAt) return asl.StartAt;
6893
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
6886
6894
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
6887
6895
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
6888
6896
  }
@@ -8308,7 +8316,7 @@ function buildDiagramGraph(params) {
8308
8316
  edges: parsed.edges,
8309
8317
  mode: options.catchHandling,
8310
8318
  nodes: parsed.nodes,
8311
- startStateId: definition.StartAt
8319
+ startStateId: parsed.startId
8312
8320
  });
8313
8321
  return {
8314
8322
  edges,
package/dist/index.js CHANGED
@@ -2855,7 +2855,8 @@ function parseAsl(params) {
2855
2855
  });
2856
2856
  return {
2857
2857
  edges: assignEdgeIds({ edges }),
2858
- nodes
2858
+ nodes,
2859
+ startId: resolver.resolve("", definition.StartAt)
2859
2860
  };
2860
2861
  }
2861
2862
  /**
@@ -6581,11 +6582,15 @@ ${playbackRules(palette)}
6581
6582
  * Mermaid numeric-entity escapes for characters that are significant to
6582
6583
  * `stateDiagram-v2` syntax: `#` introduces an entity itself, `"` and `;` end a
6583
6584
  * label or statement early, `<`/`>` and `{`/`}` are HTML/composite-state
6584
- * syntax, and a backtick can break Markdown-flavoured label rendering.
6585
+ * syntax, and a backtick can break Markdown-flavoured label rendering. Mermaid
6586
+ * up to at least 11.4.1 also rejects a `:` inside a state description or an
6587
+ * edge label (`A: Rejected (error: X)`), and renderers that bundle an older
6588
+ * Mermaid (a self-managed GitLab, an editor preview) are still in use.
6585
6589
  */
6586
6590
  const MERMAID_LABEL_ENTITIES = {
6587
6591
  "#": "#35;",
6588
6592
  "\"": "#quot;",
6593
+ ":": "#58;",
6589
6594
  ";": "#59;",
6590
6595
  "<": "#60;",
6591
6596
  ">": "#62;",
@@ -6780,7 +6785,7 @@ var MermaidRenderer = class {
6780
6785
  * a literal `-->` inside a label, so no separate arrow rule is needed.
6781
6786
  */
6782
6787
  escapeLabel(label) {
6783
- return label.replace(/[#";<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
6788
+ return label.replace(/[#":;<>{}`]/g, (character) => MERMAID_LABEL_ENTITIES[character]).replace(/\n/g, " ");
6784
6789
  }
6785
6790
  /**
6786
6791
  * Build the lines of one scope: its `[*]` entry, state label lines, nested
@@ -6856,10 +6861,13 @@ var MermaidRenderer = class {
6856
6861
  /**
6857
6862
  * Find the start state from the ASL definition's StartAt or, without one, the
6858
6863
  * first top-level state nothing else transitions to.
6864
+ *
6865
+ * StartAt is resolved to a node id the way the parser resolved it, because the id
6866
+ * can differ from the name (a sanitized character, or a discriminator).
6859
6867
  */
6860
6868
  findStartState(params) {
6861
6869
  const { asl, root } = params;
6862
- if (asl?.StartAt) return asl.StartAt;
6870
+ if (asl?.StartAt) return buildIdResolver({ definition: asl }).resolve("", asl.StartAt);
6863
6871
  const targetIds = new Set(root.transitions.filter((edge) => edge.from !== edge.to).map((edge) => edge.to));
6864
6872
  return root.memberIds.find((id) => !targetIds.has(id)) ?? root.memberIds[0];
6865
6873
  }
@@ -8285,7 +8293,7 @@ function buildDiagramGraph(params) {
8285
8293
  edges: parsed.edges,
8286
8294
  mode: options.catchHandling,
8287
8295
  nodes: parsed.nodes,
8288
- startStateId: definition.StartAt
8296
+ startStateId: parsed.startId
8289
8297
  });
8290
8298
  return {
8291
8299
  edges,
package/dist/png.cjs CHANGED
@@ -23,6 +23,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
23
  let d3_shape = require("d3-shape");
24
24
  let _dagrejs_dagre = require("@dagrejs/dagre");
25
25
  _dagrejs_dagre = __toESM(_dagrejs_dagre, 1);
26
+ let node_os = require("node:os");
26
27
  let node_fs = require("node:fs");
27
28
  let node_path = require("node:path");
28
29
  // src/config/withoutUndefined.ts
@@ -2634,7 +2635,8 @@ function parseAsl(params) {
2634
2635
  });
2635
2636
  return {
2636
2637
  edges: assignEdgeIds({ edges }),
2637
- nodes
2638
+ nodes,
2639
+ startId: resolver.resolve("", definition.StartAt)
2638
2640
  };
2639
2641
  }
2640
2642
  /**
@@ -6337,7 +6339,7 @@ function buildDiagramGraph(params) {
6337
6339
  edges: parsed.edges,
6338
6340
  mode: options.catchHandling,
6339
6341
  nodes: parsed.nodes,
6340
- startStateId: definition.StartAt
6342
+ startStateId: parsed.startId
6341
6343
  });
6342
6344
  return {
6343
6345
  edges,
@@ -7330,6 +7332,33 @@ function resolvePngFontOptions(params) {
7330
7332
  };
7331
7333
  }
7332
7334
  // src/exporters/resvgEngine.ts
7335
+ /** Bytes per pixel in resvg's RGBA output pixmap. */
7336
+ const PIXMAP_BYTES_PER_PIXEL = 4;
7337
+ /** Bytes in a GiB, for error messages. */
7338
+ const GIB = 2 ** 30;
7339
+ /**
7340
+ * Rounded intrinsic width below which {@link maxIntrinsicAspect} reads the exact size,
7341
+ * keeping the rounding bound within about 1% where it is used.
7342
+ */
7343
+ const EXACT_SIZE_BELOW_WIDTH = 100;
7344
+ /**
7345
+ * The largest height-to-width ratio the SVG's intrinsic size can have, or 0 when it has
7346
+ * no width.
7347
+ *
7348
+ * resvg's `width` and `height` getters round to whole pixels, so each true side lies
7349
+ * within 0.5 of its getter and `(height + 0.5) / (width - 0.5)` bounds the true ratio
7350
+ * from above. That bound is loose for a narrow SVG (3x at 1 x 1) and missing when the
7351
+ * width rounds to 0, so below {@link EXACT_SIZE_BELOW_WIDTH} the exact size is read
7352
+ * from the serialized root element instead. Every SVG this package generates is wider.
7353
+ */
7354
+ function maxIntrinsicAspect(params) {
7355
+ const { intrinsic } = params;
7356
+ if (intrinsic.width >= EXACT_SIZE_BELOW_WIDTH) return (intrinsic.height + .5) / (intrinsic.width - .5);
7357
+ const root = /<svg\b[^>]*>/.exec(intrinsic.toString())?.[0] ?? "";
7358
+ const exactWidth = Number(/\swidth="([^"]*)"/.exec(root)?.[1]);
7359
+ const exactHeight = Number(/\sheight="([^"]*)"/.exec(root)?.[1]);
7360
+ return exactWidth > 0 && exactHeight > 0 ? exactHeight / exactWidth : 0;
7361
+ }
7333
7362
  /**
7334
7363
  * Lazily load `@resvg/resvg-js`, which is an optional peer dependency. Keeping
7335
7364
  * it out of the static import graph means SVG/Mermaid-only consumers never
@@ -7357,7 +7386,14 @@ async function loadResvg() {
7357
7386
  async function renderResvgPng(params) {
7358
7387
  const { backgroundColor, fontDirs, fontFamily, fontFiles, loadSystemFonts, preferredFamily, scale = 1, svg, width } = params;
7359
7388
  if (!Number.isFinite(scale) || scale <= 0) throw new Error(`PNG export 'scale' must be a positive finite number, got ${scale}.`);
7360
- const { renderAsync } = await loadResvg();
7389
+ const { Resvg, renderAsync } = await loadResvg();
7390
+ const aspect = maxIntrinsicAspect({ intrinsic: new Resvg(svg, { font: { loadSystemFonts: false } }) });
7391
+ const targetWidth = Math.round(width * scale);
7392
+ const targetHeight = Math.ceil(targetWidth * aspect);
7393
+ const pixmapBytes = targetWidth * targetHeight * PIXMAP_BYTES_PER_PIXEL;
7394
+ const constrained = process.constrainedMemory?.() ?? 0;
7395
+ const memoryBytes = constrained > 0 ? Math.min(constrained, (0, node_os.totalmem)()) : (0, node_os.totalmem)();
7396
+ if (pixmapBytes > memoryBytes) throw new Error(`PNG export is too large: at scale ${scale} the image would be ${targetWidth} x ${targetHeight} pixels, which needs ${(pixmapBytes / GIB).toFixed(1)} GiB of memory, more than the ${(memoryBytes / GIB).toFixed(1)} GiB available.${scale > 1 ? " Use a smaller scale." : ""}`);
7361
7397
  const font = resolvePngFontOptions({
7362
7398
  fontDirs,
7363
7399
  fontFamily,
package/dist/png.d.cts CHANGED
@@ -779,7 +779,9 @@ interface PngExportOptions {
779
779
  loadSystemFonts?: boolean;
780
780
  /**
781
781
  * Multiplier applied to the diagram's rendered size. `resvg` only. Must be a
782
- * positive finite number; anything else throws.
782
+ * positive finite number; anything else throws. Also throws when the resulting
783
+ * image would need more memory than the process can have (the machine's, or a
784
+ * container's limit), since resvg would otherwise abort the process.
783
785
  * @default 1
784
786
  */
785
787
  scale?: number;
package/dist/png.d.ts CHANGED
@@ -779,7 +779,9 @@ interface PngExportOptions {
779
779
  loadSystemFonts?: boolean;
780
780
  /**
781
781
  * Multiplier applied to the diagram's rendered size. `resvg` only. Must be a
782
- * positive finite number; anything else throws.
782
+ * positive finite number; anything else throws. Also throws when the resulting
783
+ * image would need more memory than the process can have (the machine's, or a
784
+ * container's limit), since resvg would otherwise abort the process.
783
785
  * @default 1
784
786
  */
785
787
  scale?: number;
package/dist/png.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { curveBasis, line } from "d3-shape";
2
2
  import dagre from "@dagrejs/dagre";
3
+ import { totalmem } from "node:os";
3
4
  import { closeSync, fstatSync, openSync, readSync, readdirSync, statSync } from "node:fs";
4
5
  import { delimiter, join } from "node:path";
5
6
  // src/config/withoutUndefined.ts
@@ -2611,7 +2612,8 @@ function parseAsl(params) {
2611
2612
  });
2612
2613
  return {
2613
2614
  edges: assignEdgeIds({ edges }),
2614
- nodes
2615
+ nodes,
2616
+ startId: resolver.resolve("", definition.StartAt)
2615
2617
  };
2616
2618
  }
2617
2619
  /**
@@ -6314,7 +6316,7 @@ function buildDiagramGraph(params) {
6314
6316
  edges: parsed.edges,
6315
6317
  mode: options.catchHandling,
6316
6318
  nodes: parsed.nodes,
6317
- startStateId: definition.StartAt
6319
+ startStateId: parsed.startId
6318
6320
  });
6319
6321
  return {
6320
6322
  edges,
@@ -7307,6 +7309,33 @@ function resolvePngFontOptions(params) {
7307
7309
  };
7308
7310
  }
7309
7311
  // src/exporters/resvgEngine.ts
7312
+ /** Bytes per pixel in resvg's RGBA output pixmap. */
7313
+ const PIXMAP_BYTES_PER_PIXEL = 4;
7314
+ /** Bytes in a GiB, for error messages. */
7315
+ const GIB = 2 ** 30;
7316
+ /**
7317
+ * Rounded intrinsic width below which {@link maxIntrinsicAspect} reads the exact size,
7318
+ * keeping the rounding bound within about 1% where it is used.
7319
+ */
7320
+ const EXACT_SIZE_BELOW_WIDTH = 100;
7321
+ /**
7322
+ * The largest height-to-width ratio the SVG's intrinsic size can have, or 0 when it has
7323
+ * no width.
7324
+ *
7325
+ * resvg's `width` and `height` getters round to whole pixels, so each true side lies
7326
+ * within 0.5 of its getter and `(height + 0.5) / (width - 0.5)` bounds the true ratio
7327
+ * from above. That bound is loose for a narrow SVG (3x at 1 x 1) and missing when the
7328
+ * width rounds to 0, so below {@link EXACT_SIZE_BELOW_WIDTH} the exact size is read
7329
+ * from the serialized root element instead. Every SVG this package generates is wider.
7330
+ */
7331
+ function maxIntrinsicAspect(params) {
7332
+ const { intrinsic } = params;
7333
+ if (intrinsic.width >= EXACT_SIZE_BELOW_WIDTH) return (intrinsic.height + .5) / (intrinsic.width - .5);
7334
+ const root = /<svg\b[^>]*>/.exec(intrinsic.toString())?.[0] ?? "";
7335
+ const exactWidth = Number(/\swidth="([^"]*)"/.exec(root)?.[1]);
7336
+ const exactHeight = Number(/\sheight="([^"]*)"/.exec(root)?.[1]);
7337
+ return exactWidth > 0 && exactHeight > 0 ? exactHeight / exactWidth : 0;
7338
+ }
7310
7339
  /**
7311
7340
  * Lazily load `@resvg/resvg-js`, which is an optional peer dependency. Keeping
7312
7341
  * it out of the static import graph means SVG/Mermaid-only consumers never
@@ -7334,7 +7363,14 @@ async function loadResvg() {
7334
7363
  async function renderResvgPng(params) {
7335
7364
  const { backgroundColor, fontDirs, fontFamily, fontFiles, loadSystemFonts, preferredFamily, scale = 1, svg, width } = params;
7336
7365
  if (!Number.isFinite(scale) || scale <= 0) throw new Error(`PNG export 'scale' must be a positive finite number, got ${scale}.`);
7337
- const { renderAsync } = await loadResvg();
7366
+ const { Resvg, renderAsync } = await loadResvg();
7367
+ const aspect = maxIntrinsicAspect({ intrinsic: new Resvg(svg, { font: { loadSystemFonts: false } }) });
7368
+ const targetWidth = Math.round(width * scale);
7369
+ const targetHeight = Math.ceil(targetWidth * aspect);
7370
+ const pixmapBytes = targetWidth * targetHeight * PIXMAP_BYTES_PER_PIXEL;
7371
+ const constrained = process.constrainedMemory?.() ?? 0;
7372
+ const memoryBytes = constrained > 0 ? Math.min(constrained, totalmem()) : totalmem();
7373
+ if (pixmapBytes > memoryBytes) throw new Error(`PNG export is too large: at scale ${scale} the image would be ${targetWidth} x ${targetHeight} pixels, which needs ${(pixmapBytes / GIB).toFixed(1)} GiB of memory, more than the ${(memoryBytes / GIB).toFixed(1)} GiB available.${scale > 1 ? " Use a smaller scale." : ""}`);
7338
7374
  const font = resolvePngFontOptions({
7339
7375
  fontDirs,
7340
7376
  fontFamily,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sfn-diagram",
3
- "version": "1.9.0",
3
+ "version": "1.9.1",
4
4
  "description": "Generate SVG, Mermaid, HTML, and PNG diagrams from AWS Step Functions ASL definitions, CloudFormation/CDK templates, Terraform configs, or live AWS state machines",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@9.15.9",