visigraph 0.0.4 → 0.0.7

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/main.js CHANGED
@@ -2856,10 +2856,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2856
2856
  * @param {string} flag
2857
2857
  * @private
2858
2858
  */
2859
- unknownOption(flag) {
2859
+ unknownOption(flag2) {
2860
2860
  if (this._allowUnknownOption) return;
2861
2861
  let suggestion = "";
2862
- if (flag.startsWith("--") && this._showSuggestionAfterError) {
2862
+ if (flag2.startsWith("--") && this._showSuggestionAfterError) {
2863
2863
  let candidateFlags = [];
2864
2864
  let command = this;
2865
2865
  do {
@@ -2867,9 +2867,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
2867
2867
  candidateFlags = candidateFlags.concat(moreFlags);
2868
2868
  command = command.parent;
2869
2869
  } while (command && !command._enablePositionalOptions);
2870
- suggestion = suggestSimilar(flag, candidateFlags);
2870
+ suggestion = suggestSimilar(flag2, candidateFlags);
2871
2871
  }
2872
- const message = `error: unknown option '${flag}'${suggestion}`;
2872
+ const message = `error: unknown option '${flag2}'${suggestion}`;
2873
2873
  this.error(message, { code: "commander.unknownOption" });
2874
2874
  }
2875
2875
  /**
@@ -3662,13 +3662,13 @@ var require_directives = __commonJS({
3662
3662
  onError("Verbatim tags must end with a >");
3663
3663
  return verbatim;
3664
3664
  }
3665
- const [, handle, suffix] = source.match(/^(.*!)([^!]*)$/s);
3666
- if (!suffix)
3665
+ const [, handle, suffix2] = source.match(/^(.*!)([^!]*)$/s);
3666
+ if (!suffix2)
3667
3667
  onError(`The ${source} tag has no suffix`);
3668
3668
  const prefix = this.tags[handle];
3669
3669
  if (prefix) {
3670
3670
  try {
3671
- return prefix + decodeURIComponent(suffix);
3671
+ return prefix + decodeURIComponent(suffix2);
3672
3672
  } catch (error) {
3673
3673
  onError(String(error));
3674
3674
  return null;
@@ -3956,36 +3956,38 @@ var require_Alias = __commonJS({
3956
3956
  if (node.anchor === this.source)
3957
3957
  found = node;
3958
3958
  }
3959
+ if (found && ctx) {
3960
+ const { anchors: anchors2, doc: doc2, maxAliasCount } = ctx;
3961
+ let data = anchors2.get(found);
3962
+ if (!data) {
3963
+ toJS.toJS(found, null, ctx);
3964
+ data = anchors2.get(found);
3965
+ }
3966
+ if (data?.res === void 0) {
3967
+ const msg = "This should not happen: Alias anchor was not resolved?";
3968
+ throw new ReferenceError(msg);
3969
+ }
3970
+ if (maxAliasCount >= 0) {
3971
+ data.count += 1;
3972
+ if (data.aliasCount === 0)
3973
+ data.aliasCount = getAliasCount(doc2, found, anchors2);
3974
+ if (data.count * data.aliasCount > maxAliasCount) {
3975
+ const msg = "Excessive alias count indicates a resource exhaustion attack";
3976
+ throw new ReferenceError(msg);
3977
+ }
3978
+ }
3979
+ }
3959
3980
  return found;
3960
3981
  }
3961
3982
  toJSON(_arg, ctx) {
3962
3983
  if (!ctx)
3963
3984
  return { source: this.source };
3964
- const { anchors: anchors2, doc, maxAliasCount } = ctx;
3965
- const source = this.resolve(doc, ctx);
3985
+ const source = this.resolve(ctx.doc, ctx);
3966
3986
  if (!source) {
3967
3987
  const msg = `Unresolved alias (the anchor must be set before the alias): ${this.source}`;
3968
3988
  throw new ReferenceError(msg);
3969
3989
  }
3970
- let data = anchors2.get(source);
3971
- if (!data) {
3972
- toJS.toJS(source, null, ctx);
3973
- data = anchors2.get(source);
3974
- }
3975
- if (data?.res === void 0) {
3976
- const msg = "This should not happen: Alias anchor was not resolved?";
3977
- throw new ReferenceError(msg);
3978
- }
3979
- if (maxAliasCount >= 0) {
3980
- data.count += 1;
3981
- if (data.aliasCount === 0)
3982
- data.aliasCount = getAliasCount(doc, source, anchors2);
3983
- if (data.count * data.aliasCount > maxAliasCount) {
3984
- const msg = "Excessive alias count indicates a resource exhaustion attack";
3985
- throw new ReferenceError(msg);
3986
- }
3987
- }
3988
- return data.res;
3990
+ return ctx.anchors.get(source).res;
3989
3991
  }
3990
3992
  toString(ctx, _onComment, _onChompKeep) {
3991
3993
  const src = `*${this.source}`;
@@ -7987,37 +7989,38 @@ var require_resolve_flow_scalar = __commonJS({
7987
7989
  }
7988
7990
  if (badChar)
7989
7991
  onError(0, "BAD_SCALAR_START", `Plain value cannot start with ${badChar}`);
7990
- return foldLines(source);
7992
+ return unfoldLines(source);
7991
7993
  }
7992
7994
  function singleQuotedValue(source, onError) {
7993
7995
  if (source[source.length - 1] !== "'" || source.length === 1)
7994
7996
  onError(source.length, "MISSING_CHAR", "Missing closing 'quote");
7995
- return foldLines(source.slice(1, -1)).replace(/''/g, "'");
7997
+ return unfoldLines(source.slice(1, -1)).replace(/''/g, "'");
7996
7998
  }
7997
- function foldLines(source) {
7998
- let first, line;
7999
+ function unfoldLines(source) {
8000
+ const line = /(.*?)\r?\n/sy;
8001
+ let match = line.exec(source);
8002
+ if (!match)
8003
+ return source;
8004
+ let trimEnd, trimBoth;
7999
8005
  try {
8000
- first = new RegExp("(.*?)(?<![ ])[ ]*\r?\n", "sy");
8001
- line = new RegExp("[ ]*(.*?)(?:(?<![ ])[ ]*)?\r?\n", "sy");
8006
+ trimEnd = new RegExp("(?<![ ])[ ]+$");
8007
+ trimBoth = new RegExp("^[ ]+|(?<![ ])[ ]+$", "g");
8002
8008
  } catch {
8003
- first = /(.*?)[ \t]*\r?\n/sy;
8004
- line = /[ \t]*(.*?)[ \t]*\r?\n/sy;
8009
+ trimEnd = /[ \t]+$/;
8010
+ trimBoth = /^[ \t]+|[ \t]+$/g;
8005
8011
  }
8006
- let match = first.exec(source);
8007
- if (!match)
8008
- return source;
8009
- let res = match[1];
8012
+ let res = match[1].replace(trimEnd, "");
8010
8013
  let sep = " ";
8011
- let pos = first.lastIndex;
8012
- line.lastIndex = pos;
8014
+ let pos = line.lastIndex;
8013
8015
  while (match = line.exec(source)) {
8014
- if (match[1] === "") {
8016
+ const lm = match[1].replace(trimBoth, "");
8017
+ if (lm === "") {
8015
8018
  if (sep === "\n")
8016
8019
  res += sep;
8017
8020
  else
8018
8021
  sep = "\n";
8019
8022
  } else {
8020
- res += sep + match[1];
8023
+ res += sep + lm;
8021
8024
  sep = " ";
8022
8025
  }
8023
8026
  pos = line.lastIndex;
@@ -10804,16 +10807,86 @@ function lineOf(range, lines) {
10804
10807
  return `line ${line}, column ${col}`;
10805
10808
  }
10806
10809
 
10810
+ // ../core/dist/kinds.js
10811
+ var FAMILIES = ["container", "endpoint", "entity"];
10812
+ var KindRegistry = class _KindRegistry {
10813
+ families = /* @__PURE__ */ new Map();
10814
+ static of(...extensions) {
10815
+ const r = new _KindRegistry();
10816
+ for (const e of extensions)
10817
+ r.register(e);
10818
+ return r;
10819
+ }
10820
+ register(extension) {
10821
+ for (const definition of extension.kinds) {
10822
+ if (!FAMILIES.includes(definition.family)) {
10823
+ throw new Error(`extension '${extension.name}': unknown family '${definition.family}'`);
10824
+ }
10825
+ const table = this.families.get(definition.family) ?? /* @__PURE__ */ new Map();
10826
+ const existing = table.get(definition.kind);
10827
+ if (existing) {
10828
+ throw new Error(`extension '${extension.name}': ${definition.family} kind '${definition.kind}' is already registered by '${existing.extension}'`);
10829
+ }
10830
+ table.set(definition.kind, { definition, extension: extension.name });
10831
+ this.families.set(definition.family, table);
10832
+ }
10833
+ return this;
10834
+ }
10835
+ /** True when at least one kind is registered for the family, so kinds in it are checked. */
10836
+ governs(family) {
10837
+ return (this.families.get(family)?.size ?? 0) > 0;
10838
+ }
10839
+ get(family, kind) {
10840
+ return this.families.get(family)?.get(kind)?.definition;
10841
+ }
10842
+ kinds(family) {
10843
+ return [...this.families.get(family)?.keys() ?? []].sort();
10844
+ }
10845
+ };
10846
+ function checkKind(registry, family, element) {
10847
+ if (!registry || !registry.governs(family))
10848
+ return [];
10849
+ const kind = element.kind;
10850
+ if (kind === void 0)
10851
+ return [];
10852
+ if (typeof kind !== "string")
10853
+ return [["kind", `${family} kind must be a string`]];
10854
+ const def = registry.get(family, kind);
10855
+ if (!def)
10856
+ return [["kind", `unknown ${family} kind '${kind}'; registered: ${registry.kinds(family).join(", ")}`]];
10857
+ const out = [];
10858
+ for (const [name, decl] of Object.entries(def.properties)) {
10859
+ const value = element[name];
10860
+ if (value === void 0) {
10861
+ if (decl.required)
10862
+ out.push([name, `${family} kind '${kind}' requires '${name}'`]);
10863
+ continue;
10864
+ }
10865
+ if (typeof value !== decl.type) {
10866
+ out.push([name, `'${name}' on ${family} kind '${kind}' must be a ${decl.type}`]);
10867
+ continue;
10868
+ }
10869
+ if (decl.enum && !decl.enum.includes(value)) {
10870
+ out.push([name, `'${name}' on ${family} kind '${kind}' must be one of ${decl.enum.join(", ")}; got '${String(value)}'`]);
10871
+ }
10872
+ }
10873
+ return out;
10874
+ }
10875
+
10807
10876
  // ../core/dist/resolve.js
10808
10877
  function resolve(model, options = {}) {
10809
- const r = new Resolver();
10878
+ const r = new Resolver(options.kinds);
10810
10879
  const root = r.declare(model, model.system, void 0, { file: options.file ?? "", prefix: "" });
10811
10880
  r.bind(root, void 0);
10812
10881
  r.check(root);
10813
10882
  return { root, diagnostics: r.diagnostics };
10814
10883
  }
10815
10884
  var Resolver = class {
10885
+ kinds;
10816
10886
  diagnostics = [];
10887
+ constructor(kinds2) {
10888
+ this.kinds = kinds2;
10889
+ }
10817
10890
  /** Scopes by interior identity, so a shared file is one scope. */
10818
10891
  scopes = /* @__PURE__ */ new Map();
10819
10892
  locs = /* @__PURE__ */ new Map();
@@ -10852,6 +10925,8 @@ var Resolver = class {
10852
10925
  return;
10853
10926
  }
10854
10927
  scope.entities.set(entity.id, entity);
10928
+ for (const [suffix2, message] of checkKind(this.kinds, "entity", entity))
10929
+ err(`${path}.${suffix2}`, message);
10855
10930
  });
10856
10931
  eachListed(node.journeys, "journeys", err, (journey, path) => {
10857
10932
  if (scope.journeys.has(journey.id)) {
@@ -10865,6 +10940,8 @@ var Resolver = class {
10865
10940
  err(`${path}.id`, `duplicate container id '${child.id}'`);
10866
10941
  return;
10867
10942
  }
10943
+ for (const [suffix2, message] of checkKind(this.kinds, "container", child))
10944
+ err(`${path}.${suffix2}`, message);
10868
10945
  const link = child[LINKED];
10869
10946
  const childLoc = link ? { file: link.file, prefix: "" } : { file: loc.file, prefix: `${loc.prefix}${path}.` };
10870
10947
  scope.children.set(child.id, this.declare(child, child.id, child, childLoc, scope.path));
@@ -10878,6 +10955,8 @@ var Resolver = class {
10878
10955
  err(`${path}.id`, `duplicate endpoint id '${endpoint.id}' in container '${container.id}'`);
10879
10956
  }
10880
10957
  endpointIds.add(endpoint.id);
10958
+ for (const [suffix2, message] of checkKind(this.kinds, "endpoint", endpoint))
10959
+ err(`${path}.${suffix2}`, message);
10881
10960
  });
10882
10961
  const stateIds = /* @__PURE__ */ new Set();
10883
10962
  eachListed(container.state, "state", err, (state, path) => {
@@ -10934,6 +11013,21 @@ var Resolver = class {
10934
11013
  const loc = this.locs.get(scope);
10935
11014
  const err = (path, message) => this.err(loc, path, message);
10936
11015
  const node = scope.container ?? this.rootNode(scope);
11016
+ eachListed(node.containers, "containers", () => {
11017
+ }, (container, cPath) => {
11018
+ if (container[LINKED])
11019
+ return;
11020
+ eachListed(container.endpoints, `${cPath}.endpoints`, () => {
11021
+ }, (endpoint, ePath) => {
11022
+ for (const key of ["accepts", "returns"]) {
11023
+ if (endpoint[key] === void 0)
11024
+ continue;
11025
+ const entity = ref(endpoint[key], `${ePath}.${key}`, err);
11026
+ if (entity)
11027
+ this.lookupMember(scope, entity, "entity", `${ePath}.${key}`, err);
11028
+ }
11029
+ });
11030
+ });
10937
11031
  eachListed(node.journeys, "journeys", err, (journey, path) => {
10938
11032
  const hasSteps = journey.steps !== void 0;
10939
11033
  const hasScenarios = journey.scenarios !== void 0;
@@ -10982,20 +11076,30 @@ var Resolver = class {
10982
11076
  this.lookupContainer(scope, from, `${p}.from`, err);
10983
11077
  const to = ref(step.to, `${p}.to`, err);
10984
11078
  const target = to ? this.lookupContainer(scope, to, `${p}.to`, err) : void 0;
11079
+ let endpointDecl;
10985
11080
  if (step.endpoint !== void 0) {
10986
11081
  const endpoint = ref(step.endpoint, `${p}.endpoint`, err);
10987
11082
  if (endpoint && target) {
10988
11083
  if (endpoint.includes(".")) {
10989
11084
  err(`${p}.endpoint`, `endpoint '${endpoint}' must be a bare id on the 'to' container, not a path`);
10990
- } else if (!(target.container?.endpoints ?? []).some((e) => e.id === endpoint)) {
10991
- err(`${p}.endpoint`, `container '${to}' has no endpoint '${endpoint}'`);
11085
+ } else {
11086
+ endpointDecl = (target.container?.endpoints ?? []).find((e) => e.id === endpoint);
11087
+ if (!endpointDecl)
11088
+ err(`${p}.endpoint`, `container '${to}' has no endpoint '${endpoint}'`);
10992
11089
  }
10993
11090
  }
10994
11091
  }
10995
11092
  if (step.payload !== void 0) {
10996
- const entity = isRecord(step.payload) ? ref(step.payload.entity, `${p}.payload.entity`, err) : ref(step.payload, `${p}.payload`, err);
10997
- if (entity)
10998
- this.lookupMember(scope, entity, "entity", `${p}.payload`, err);
11093
+ const entityRef = isRecord(step.payload) ? ref(step.payload.entity, `${p}.payload.entity`, err) : ref(step.payload, `${p}.payload`, err);
11094
+ const payload = entityRef ? this.lookupMember(scope, entityRef, "entity", `${p}.payload`, err) : void 0;
11095
+ if (payload && endpointDecl?.accepts !== void 0 && target) {
11096
+ const owner = this.ownerScope(target);
11097
+ const accepted = owner ? this.lookupMember(owner, endpointDecl.accepts, "entity", `${p}.payload`, () => {
11098
+ }) : void 0;
11099
+ if (accepted && accepted !== payload) {
11100
+ err(`${p}.payload`, `endpoint '${step.endpoint}' on '${to}' accepts '${endpointDecl.accepts}', not '${entityRef}'`);
11101
+ }
11102
+ }
10999
11103
  }
11000
11104
  if (!STEP_KINDS.includes(step.kind)) {
11001
11105
  err(`${p}.kind`, `kind must be one of ${STEP_KINDS.join(", ")}; got '${String(step.kind)}'`);
@@ -11026,19 +11130,27 @@ var Resolver = class {
11026
11130
  }
11027
11131
  return cur;
11028
11132
  }
11029
- /** Resolve `a.b.name` as container path `a.b` plus a member id. */
11030
11133
  lookupMember(scope, pathStr, kind, at, err) {
11031
11134
  const segments = pathStr.split(".");
11032
11135
  const name = segments.pop();
11033
11136
  const owner = segments.length === 0 ? scope : this.lookupContainer(scope, segments.join("."), at, err);
11034
11137
  if (!owner)
11035
- return false;
11036
- const table = kind === "entity" ? owner.entities : owner.journeys;
11037
- if (!table.has(name)) {
11138
+ return void 0;
11139
+ const found = kind === "entity" ? owner.entities.get(name) : owner.journeys.get(name);
11140
+ if (!found) {
11038
11141
  err(at, segments.length === 0 ? `unknown ${kind} '${name}'` : `unknown ${kind} '${name}' in '${segments.join(".")}'`);
11039
- return false;
11142
+ return void 0;
11040
11143
  }
11041
- return true;
11144
+ return found;
11145
+ }
11146
+ /** The scope a container is declared in: where its endpoint contracts resolve from. */
11147
+ ownerScope(target) {
11148
+ for (const s of this.scopes.values()) {
11149
+ for (const child of s.children.values())
11150
+ if (child === target)
11151
+ return s;
11152
+ }
11153
+ return void 0;
11042
11154
  }
11043
11155
  };
11044
11156
  function loadedFile(req, loc) {
@@ -11095,6 +11207,36 @@ var EXIT = {
11095
11207
  usage: 2
11096
11208
  };
11097
11209
 
11210
+ // ../ext-http/dist/index.js
11211
+ var http = {
11212
+ name: "@visigraph/ext-http",
11213
+ kinds: [
11214
+ {
11215
+ family: "endpoint",
11216
+ kind: "http",
11217
+ description: "An HTTP endpoint: a method and a path on a container.",
11218
+ properties: {
11219
+ method: {
11220
+ type: "string",
11221
+ tier: "advanced",
11222
+ required: true,
11223
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"],
11224
+ category: "transport"
11225
+ },
11226
+ path: {
11227
+ type: "string",
11228
+ tier: "advanced",
11229
+ required: true,
11230
+ category: "transport"
11231
+ }
11232
+ }
11233
+ }
11234
+ ]
11235
+ };
11236
+
11237
+ // src/kinds.ts
11238
+ var kinds = KindRegistry.of(http);
11239
+
11098
11240
  // src/commands/validate.ts
11099
11241
  function registerValidate(program3) {
11100
11242
  program3.command("validate").argument("<model>", "path to a model file (YAML)").description("check a model and every model it links or requires; every identifier must resolve").addHelpText(
@@ -11112,7 +11254,7 @@ async function validate(file) {
11112
11254
  const parsed = await loadModel(file);
11113
11255
  let diagnostics = parsed.diagnostics;
11114
11256
  if (parsed.model) {
11115
- diagnostics = diagnostics.concat(resolve(parsed.model, { file }).diagnostics);
11257
+ diagnostics = diagnostics.concat(resolve(parsed.model, { file, kinds }).diagnostics);
11116
11258
  }
11117
11259
  for (const d of diagnostics) {
11118
11260
  const where = d.path ? `${d.file ?? file}:${d.path}` : d.file ?? file;
@@ -11126,6 +11268,101 @@ async function validate(file) {
11126
11268
  return EXIT.ok;
11127
11269
  }
11128
11270
 
11271
+ // src/update.ts
11272
+ var import_yaml2 = __toESM(require_dist(), 1);
11273
+ import { spawn } from "child_process";
11274
+ import { mkdir, readFile as readFile2, writeFile } from "fs/promises";
11275
+ import { homedir } from "os";
11276
+ import { join as join2 } from "path";
11277
+ import readline from "readline";
11278
+
11279
+ // src/progress.ts
11280
+ var CELLS = 6;
11281
+ var GLYPHS = {
11282
+ unicode: { filled: "\u25B0", empty: "\u25B1" },
11283
+ // ▰ ▱ black / white parallelogram
11284
+ ascii: { filled: "#", empty: ".", open: "[", close: "]" }
11285
+ };
11286
+ function renderProgress(done, total, opts = {}) {
11287
+ const cells = opts.cells ?? CELLS;
11288
+ const safeTotal = Math.max(total, 0);
11289
+ const safeDone = Math.min(Math.max(done, 0), safeTotal);
11290
+ const filled = safeTotal === 0 ? 0 : Math.round(safeDone / safeTotal * cells);
11291
+ return `${bar(filled, cells, opts.unicode ?? true)} ${safeDone}/${safeTotal}${suffix(opts.label)}`;
11292
+ }
11293
+ function renderSweep(tick, opts = {}) {
11294
+ const cells = opts.cells ?? CELLS;
11295
+ const unicode = opts.unicode ?? true;
11296
+ const g = unicode ? GLYPHS.unicode : GLYPHS.ascii;
11297
+ const pos = (tick % cells + cells) % cells;
11298
+ const body = Array.from({ length: cells }, (_, i) => i === pos ? g.filled : g.empty).join("");
11299
+ return `${unicode ? body : `${GLYPHS.ascii.open}${body}${GLYPHS.ascii.close}`}${suffix(opts.label)}`;
11300
+ }
11301
+ function bar(filled, cells, unicode) {
11302
+ const g = unicode ? GLYPHS.unicode : GLYPHS.ascii;
11303
+ const body = g.filled.repeat(filled) + g.empty.repeat(cells - filled);
11304
+ return unicode ? body : `${GLYPHS.ascii.open}${body}${GLYPHS.ascii.close}`;
11305
+ }
11306
+ function suffix(label) {
11307
+ return label ? ` ${label}` : "";
11308
+ }
11309
+ function supportsUnicode(env = process.env, platform = process.platform) {
11310
+ if (platform === "win32") {
11311
+ return Boolean(env.WT_SESSION || env.TERM_PROGRAM === "vscode" || env.ConEmuANSI === "ON");
11312
+ }
11313
+ if (env.TERM === "dumb") return false;
11314
+ const locale = env.LC_ALL || env.LC_CTYPE || env.LANG || "";
11315
+ return /utf-?8/i.test(locale) || locale === "";
11316
+ }
11317
+ var ProgressLine = class {
11318
+ constructor(out = process.stderr) {
11319
+ this.out = out;
11320
+ this.enabled = Boolean(out.isTTY);
11321
+ this.unicode = supportsUnicode();
11322
+ }
11323
+ out;
11324
+ timer;
11325
+ tick = 0;
11326
+ enabled;
11327
+ unicode;
11328
+ /** Animate a sweep with a label until `stop` or `finish` is called. */
11329
+ sweep(label, intervalMs = 120) {
11330
+ if (!this.enabled) return;
11331
+ this.clearTimer();
11332
+ this.draw(renderSweep(this.tick, { unicode: this.unicode, label }));
11333
+ this.timer = setInterval(() => {
11334
+ this.tick += 1;
11335
+ this.draw(renderSweep(this.tick, { unicode: this.unicode, label }));
11336
+ }, intervalMs);
11337
+ }
11338
+ /** Show `done` of `total` with a label. */
11339
+ update(done, total, label) {
11340
+ if (!this.enabled) return;
11341
+ this.clearTimer();
11342
+ this.draw(renderProgress(done, total, label === void 0 ? { unicode: this.unicode } : { unicode: this.unicode, label }));
11343
+ }
11344
+ /** Fill the bar, print the final label, and move to a new line. */
11345
+ finish(label) {
11346
+ if (!this.enabled) return;
11347
+ this.clearTimer();
11348
+ this.draw(renderProgress(CELLS, CELLS, { unicode: this.unicode, label }));
11349
+ this.out.write("\n");
11350
+ }
11351
+ /** Erase the line without a final message. */
11352
+ stop() {
11353
+ if (!this.enabled) return;
11354
+ this.clearTimer();
11355
+ this.out.write("\r\x1B[2K");
11356
+ }
11357
+ draw(text) {
11358
+ this.out.write(`\r\x1B[2K${text}`);
11359
+ }
11360
+ clearTimer() {
11361
+ if (this.timer) clearInterval(this.timer);
11362
+ this.timer = void 0;
11363
+ }
11364
+ };
11365
+
11129
11366
  // src/version.ts
11130
11367
  import { createRequire } from "module";
11131
11368
  var load = createRequire(import.meta.url);
@@ -11133,15 +11370,184 @@ var pkg = load("../package.json");
11133
11370
  var VERSION = pkg.version;
11134
11371
  var DESCRIPTION = pkg.description;
11135
11372
 
11373
+ // src/update.ts
11374
+ var PKG_NAME = "visigraph";
11375
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org";
11376
+ var FETCH_TIMEOUT_MS = 2e3;
11377
+ var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
11378
+ var NO_UPDATE_ENV = "VISIGRAPH_NO_UPDATE_CHECK";
11379
+ var configDir = () => process.env.VISIGRAPH_HOME ?? join2(homedir(), ".visigraph");
11380
+ async function maybeUpdate() {
11381
+ const gate = shouldCheck({
11382
+ noUpdateEnv: process.env[NO_UPDATE_ENV] === "1",
11383
+ stdinTTY: Boolean(process.stdin.isTTY),
11384
+ stdoutTTY: Boolean(process.stdout.isTTY),
11385
+ configCheckDisabled: await configDisablesCheck()
11386
+ });
11387
+ if (!gate) return;
11388
+ const latest = await latestVersion();
11389
+ if (!latest || !isNewerVersion(latest, VERSION)) return;
11390
+ const viaNpx = invokedViaNpx({ npmCommand: process.env.npm_command, script: process.argv[1] ?? "" });
11391
+ console.log(`visigraph ${latest} is available; you have ${VERSION}.`);
11392
+ if (viaNpx) {
11393
+ console.log(`Run it with: npx ${PKG_NAME}@latest`);
11394
+ return;
11395
+ }
11396
+ if (!await confirm("Update now?")) return;
11397
+ const progress = new ProgressLine();
11398
+ progress.sweep(`installing visigraph ${latest}`);
11399
+ const result = await runInstall();
11400
+ if (!result.ok) {
11401
+ progress.stop();
11402
+ console.error("Update failed. Continuing with the current version.");
11403
+ if (result.output.trim()) console.error(result.output.trim());
11404
+ return;
11405
+ }
11406
+ progress.finish(`updated to ${latest}, relaunching`);
11407
+ await relaunch();
11408
+ }
11409
+ function shouldCheck(input) {
11410
+ if (input.noUpdateEnv) return false;
11411
+ if (input.configCheckDisabled) return false;
11412
+ if (!input.stdinTTY || !input.stdoutTTY) return false;
11413
+ return true;
11414
+ }
11415
+ function compareVersions(a, b) {
11416
+ const pa = a.split("-")[0].split(".").map(Number);
11417
+ const pb = b.split("-")[0].split(".").map(Number);
11418
+ for (let i = 0; i < 3; i++) {
11419
+ const d = (pa[i] ?? 0) - (pb[i] ?? 0);
11420
+ if (d !== 0) return d;
11421
+ }
11422
+ return 0;
11423
+ }
11424
+ function isNewerVersion(latest, current) {
11425
+ return compareVersions(latest, current) > 0;
11426
+ }
11427
+ function invokedViaNpx(input) {
11428
+ return input.npmCommand === "exec" || /[\\/]_npx[\\/]/.test(input.script);
11429
+ }
11430
+ function registryFrom(input) {
11431
+ const fromEnv = input.env?.trim();
11432
+ if (fromEnv) return fromEnv.replace(/\/+$/, "");
11433
+ const line = input.npmrc?.split(/\r?\n/).find((l) => /^\s*registry\s*=/.test(l));
11434
+ const fromRc = line?.split("=").slice(1).join("=").trim();
11435
+ if (fromRc) return fromRc.replace(/\/+$/, "");
11436
+ return DEFAULT_REGISTRY;
11437
+ }
11438
+ function cacheIsFresh(entry, now, ttl = CACHE_TTL_MS) {
11439
+ return entry !== void 0 && typeof entry.latest === "string" && now - entry.checkedAt < ttl;
11440
+ }
11441
+ async function fetchLatestVersion(registry, fetchImpl = fetch) {
11442
+ try {
11443
+ const ctrl = new AbortController();
11444
+ const timer = setTimeout(() => ctrl.abort(), FETCH_TIMEOUT_MS);
11445
+ const res = await fetchImpl(`${registry}/${PKG_NAME}/latest`, {
11446
+ signal: ctrl.signal,
11447
+ headers: { "User-Agent": `${PKG_NAME}/${VERSION}`, accept: "application/json" }
11448
+ });
11449
+ clearTimeout(timer);
11450
+ if (!res.ok) return null;
11451
+ const data = await res.json();
11452
+ return typeof data.version === "string" ? data.version : null;
11453
+ } catch {
11454
+ return null;
11455
+ }
11456
+ }
11457
+ async function configDisablesCheck() {
11458
+ try {
11459
+ const text = await readFile2(join2(configDir(), "config.yaml"), "utf8");
11460
+ const cfg = (0, import_yaml2.parse)(text);
11461
+ return cfg?.update?.check === false;
11462
+ } catch {
11463
+ return false;
11464
+ }
11465
+ }
11466
+ async function latestVersion() {
11467
+ const cachePath = join2(configDir(), "update-check.json");
11468
+ const now = Date.now();
11469
+ try {
11470
+ const cached = JSON.parse(await readFile2(cachePath, "utf8"));
11471
+ if (cacheIsFresh(cached, now)) return cached.latest;
11472
+ } catch {
11473
+ }
11474
+ let npmrc;
11475
+ try {
11476
+ npmrc = await readFile2(join2(homedir(), ".npmrc"), "utf8");
11477
+ } catch {
11478
+ npmrc = void 0;
11479
+ }
11480
+ const registry = registryFrom({ env: process.env.npm_config_registry, npmrc });
11481
+ const latest = await fetchLatestVersion(registry);
11482
+ if (latest) {
11483
+ try {
11484
+ await mkdir(configDir(), { recursive: true });
11485
+ await writeFile(cachePath, JSON.stringify({ checkedAt: now, latest }));
11486
+ } catch {
11487
+ }
11488
+ }
11489
+ return latest;
11490
+ }
11491
+ function confirm(question) {
11492
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
11493
+ return new Promise((resolve2) => {
11494
+ rl.on("close", () => resolve2(false));
11495
+ rl.question(`${question} (y/N) `, (answer) => {
11496
+ resolve2(/^y(es)?$/i.test(answer.trim()));
11497
+ rl.close();
11498
+ });
11499
+ });
11500
+ }
11501
+ function runInstall() {
11502
+ const npm = process.platform === "win32" ? "npm.cmd" : "npm";
11503
+ return new Promise((resolve2) => {
11504
+ const chunks = [];
11505
+ const child = spawn(npm, ["install", "-g", `${PKG_NAME}@latest`, "--no-fund", "--no-audit"], {
11506
+ stdio: ["ignore", "pipe", "pipe"],
11507
+ shell: false
11508
+ });
11509
+ child.stdout.on("data", (d) => chunks.push(d.toString()));
11510
+ child.stderr.on("data", (d) => chunks.push(d.toString()));
11511
+ child.on("exit", (code) => resolve2({ ok: code === 0, output: chunks.join("") }));
11512
+ child.on("error", (e) => resolve2({ ok: false, output: e.message }));
11513
+ });
11514
+ }
11515
+ function relaunch() {
11516
+ const script = process.argv[1];
11517
+ const args = process.argv.slice(2);
11518
+ return new Promise((_, reject) => {
11519
+ const child = spawn(process.execPath, [script, ...args], {
11520
+ stdio: "inherit",
11521
+ shell: false,
11522
+ env: { ...process.env, [NO_UPDATE_ENV]: "1" }
11523
+ });
11524
+ child.on("exit", (code) => process.exit(code ?? 0));
11525
+ child.on("error", reject);
11526
+ });
11527
+ }
11528
+
11136
11529
  // src/main.ts
11530
+ for (const stream of [process.stdout, process.stderr]) {
11531
+ stream.on("error", (e) => {
11532
+ if (e.code === "EPIPE") process.exit(process.exitCode ?? 0);
11533
+ throw e;
11534
+ });
11535
+ }
11137
11536
  var program2 = new Command();
11138
- program2.name("visigraph").description(DESCRIPTION).version(VERSION, "-v, --version", "print the version and exit").helpOption("-h, --help", "print help and exit").helpCommand("help [command]", "print help for a command").showHelpAfterError("(run with --help for usage)").exitOverride();
11537
+ program2.name("visigraph").addHelpText("before", `${DESCRIPTION}
11538
+ `).version(VERSION, "-v, --version", "print the version and exit").helpOption("-h, --help", "print help and exit").helpCommand("help [command]", "print help for a command").showHelpAfterError("(run with --help for usage)").exitOverride();
11139
11539
  registerValidate(program2);
11540
+ var INSTANT = /* @__PURE__ */ new Set(["-v", "--version", "-h", "--help", "help"]);
11541
+ var [flag, maybeCommand] = process.argv.slice(2);
11542
+ if ((flag === "-h" || flag === "--help") && maybeCommand && program2.commands.some((c) => c.name() === maybeCommand)) {
11543
+ process.argv.splice(2, 2, "help", maybeCommand);
11544
+ }
11140
11545
  try {
11141
11546
  if (process.argv.length <= 2) {
11142
11547
  program2.outputHelp();
11143
11548
  process.exitCode = EXIT.usage;
11144
11549
  } else {
11550
+ if (!process.argv.slice(2).some((a) => INSTANT.has(a))) await maybeUpdate();
11145
11551
  await program2.parseAsync(process.argv);
11146
11552
  }
11147
11553
  } catch (e) {