rshow 0.0.8 → 0.0.9

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
@@ -26,8 +26,8 @@
26
26
  */
27
27
 
28
28
  // src/main.ts
29
- import path26 from "node:path";
30
- import process23 from "node:process";
29
+ import path28 from "node:path";
30
+ import process25 from "node:process";
31
31
 
32
32
  // src/binary.ts
33
33
  import { mkdir, mkdtemp, rename, rm, stat, writeFile } from "node:fs/promises";
@@ -38,7 +38,7 @@ import process from "node:process";
38
38
  // src/version.ts
39
39
  import { readFileSync } from "node:fs";
40
40
  import { fileURLToPath } from "node:url";
41
- var VERSION = true ? "0.0.8" : versionFromPackage();
41
+ var VERSION = true ? "0.0.9" : versionFromPackage();
42
42
 
43
43
  // src/binary.ts
44
44
  var ASSET_MANIFEST = "assets.json";
@@ -998,6 +998,7 @@ commands:
998
998
  pin <slug> <n|live> serve version n, or live for the newest
999
999
  delete <slug> [--version n] [--force] --yes
1000
1000
  delete an artifact, or one version of it
1001
+ upgrade [--check] update rshow to the newest release
1001
1002
  login [--url u] save the API token for this account
1002
1003
  logout [--url u] remove the saved token for a server
1003
1004
  completion <shell> print a completion script for bash, zsh or fish
@@ -1275,6 +1276,31 @@ A theme or a theme version also needs --force, because pages link to it.
1275
1276
  A pinned version and the only version of an artifact cannot be deleted.
1276
1277
  A public version can stay visible for up to 5 minutes after the delete, and a
1277
1278
  theme file for longer.`;
1279
+ var UPGRADE_USAGE = `usage: rshow upgrade [--check] [--version <x.y.z>] [--force]
1280
+
1281
+ Updates rshow to the newest release, or to the release --version names, which
1282
+ may be an older one.
1283
+ A single executable is replaced in place, and an npm global install runs
1284
+ npm install -g rshow@<version>.
1285
+ Any other install, such as a source checkout, an npx cache or a local
1286
+ dependency, is left alone and named with the tool that owns it.
1287
+ Running the command is the consent: there is no --yes.
1288
+ No config and no token are read, so it works before rshow init.
1289
+
1290
+ --check prints the current and the newest version and changes nothing, and
1291
+ exits 0 either way; --json prints {current, latest, upgrade_available}.
1292
+ A run prints upgraded <old> -> <new>, or already at <version>. --json prints
1293
+ {previous, version, upgraded, kind}, where kind is binary or npm.
1294
+ --version takes x.y.z with an optional leading v; --force reinstalls the same
1295
+ version, which is how a broken binary comes back.
1296
+ Skills that rshow installed are refreshed by the new rshow, and a bash, zsh or
1297
+ fish completion that is already there is replaced; RSHOW_NO_COMPLETIONS=1
1298
+ leaves the completions alone.
1299
+ RSHOW_DOWNLOAD_BASE downloads from a mirror instead of the release, and takes
1300
+ an http or https url; it cannot be used with --check.
1301
+
1302
+ A release older than this one has no upgrade command, so after a downgrade
1303
+ come back with curl -fsSL https://rslib.github.io/rshow/install.sh | sh.`;
1278
1304
  var LOGIN_USAGE = `usage: rshow login [--url u]
1279
1305
 
1280
1306
  Reads the token from stdin, checks it with the API, then saves it in the macOS
@@ -1968,27 +1994,27 @@ var sqrtTall = function sqrtTall2(extraVinculum, hLinePad2, viewBoxHeight) {
1968
1994
  };
1969
1995
  var sqrtPath = function sqrtPath2(size, extraVinculum, viewBoxHeight) {
1970
1996
  extraVinculum = 1e3 * extraVinculum;
1971
- var path27 = "";
1997
+ var path29 = "";
1972
1998
  switch (size) {
1973
1999
  case "sqrtMain":
1974
- path27 = sqrtMain(extraVinculum, hLinePad);
2000
+ path29 = sqrtMain(extraVinculum, hLinePad);
1975
2001
  break;
1976
2002
  case "sqrtSize1":
1977
- path27 = sqrtSize1(extraVinculum, hLinePad);
2003
+ path29 = sqrtSize1(extraVinculum, hLinePad);
1978
2004
  break;
1979
2005
  case "sqrtSize2":
1980
- path27 = sqrtSize2(extraVinculum, hLinePad);
2006
+ path29 = sqrtSize2(extraVinculum, hLinePad);
1981
2007
  break;
1982
2008
  case "sqrtSize3":
1983
- path27 = sqrtSize3(extraVinculum, hLinePad);
2009
+ path29 = sqrtSize3(extraVinculum, hLinePad);
1984
2010
  break;
1985
2011
  case "sqrtSize4":
1986
- path27 = sqrtSize4(extraVinculum, hLinePad);
2012
+ path29 = sqrtSize4(extraVinculum, hLinePad);
1987
2013
  break;
1988
2014
  case "sqrtTall":
1989
- path27 = sqrtTall(extraVinculum, hLinePad, viewBoxHeight);
2015
+ path29 = sqrtTall(extraVinculum, hLinePad, viewBoxHeight);
1990
2016
  }
1991
- return path27;
2017
+ return path29;
1992
2018
  };
1993
2019
  var innerPath = function innerPath2(name, height) {
1994
2020
  switch (name) {
@@ -6202,8 +6228,8 @@ var svgData = {
6202
6228
  };
6203
6229
  var staticSvg = function staticSvg2(value, options) {
6204
6230
  var _svgData$value = svgData[value], pathName = _svgData$value[0], width = _svgData$value[1], height = _svgData$value[2];
6205
- var path27 = new PathNode(pathName);
6206
- var svgNode = new SvgNode([path27], {
6231
+ var path29 = new PathNode(pathName);
6232
+ var svgNode = new SvgNode([path29], {
6207
6233
  "width": makeEm(width),
6208
6234
  "height": makeEm(height),
6209
6235
  // Override CSS rule `.katex svg { width: 100% }`
@@ -7346,8 +7372,8 @@ var stretchySvg = function stretchySvg2(group, options) {
7346
7372
  pathName = "tilde" + imgIndex;
7347
7373
  }
7348
7374
  }
7349
- var path27 = new PathNode(pathName);
7350
- var svgNode = new SvgNode([path27], {
7375
+ var path29 = new PathNode(pathName);
7376
+ var svgNode = new SvgNode([path29], {
7351
7377
  "width": "100%",
7352
7378
  "height": makeEm(_height),
7353
7379
  "viewBox": "0 0 " + viewBoxWidth + " " + viewBoxHeight,
@@ -8561,8 +8587,8 @@ var makeGlyphSpan = function makeGlyphSpan2(symbol, font, mode, options) {
8561
8587
  };
8562
8588
  var makeInner = function makeInner2(ch2, height, options) {
8563
8589
  var width = fontMetricsData["Size4-Regular"][ch2.charCodeAt(0)] ? fontMetricsData["Size4-Regular"][ch2.charCodeAt(0)][4] : fontMetricsData["Size1-Regular"][ch2.charCodeAt(0)][4];
8564
- var path27 = new PathNode("inner", innerPath(ch2, Math.round(1e3 * height)));
8565
- var svgNode = new SvgNode([path27], {
8590
+ var path29 = new PathNode("inner", innerPath(ch2, Math.round(1e3 * height)));
8591
+ var svgNode = new SvgNode([path29], {
8566
8592
  "width": makeEm(width),
8567
8593
  "height": makeEm(height),
8568
8594
  // Override CSS rule `.katex svg { width: 100% }`
@@ -8731,10 +8757,10 @@ var makeStackedDelim = function makeStackedDelim2(delim, heightTotal, center, op
8731
8757
  var midHeight = realHeightTotal - topHeightTotal - bottomHeightTotal;
8732
8758
  var viewBoxHeight = Math.round(realHeightTotal * 1e3);
8733
8759
  var pathStr = tallDelim(svgLabel, Math.round(midHeight * 1e3));
8734
- var path27 = new PathNode(svgLabel, pathStr);
8760
+ var path29 = new PathNode(svgLabel, pathStr);
8735
8761
  var width = makeEm(viewBoxWidth / 1e3);
8736
8762
  var height = makeEm(viewBoxHeight / 1e3);
8737
- var svg = new SvgNode([path27], {
8763
+ var svg = new SvgNode([path29], {
8738
8764
  "width": width,
8739
8765
  "height": height,
8740
8766
  "viewBox": "0 0 " + viewBoxWidth + " " + viewBoxHeight
@@ -8775,8 +8801,8 @@ var makeStackedDelim = function makeStackedDelim2(delim, heightTotal, center, op
8775
8801
  var vbPad = 80;
8776
8802
  var emPad = 0.08;
8777
8803
  var sqrtSvg = function sqrtSvg2(sqrtName, height, viewBoxHeight, extraVinculum, options) {
8778
- var path27 = sqrtPath(sqrtName, extraVinculum, viewBoxHeight);
8779
- var pathNode = new PathNode(sqrtName, path27);
8804
+ var path29 = sqrtPath(sqrtName, extraVinculum, viewBoxHeight);
8805
+ var pathNode = new PathNode(sqrtName, path29);
8780
8806
  var svg = new SvgNode([pathNode], {
8781
8807
  // Note: 1000:1 ratio of viewBox to document em width.
8782
8808
  "width": "400em",
@@ -9275,8 +9301,8 @@ var htmlBuilder$7 = (group, options) => {
9275
9301
  var angleHeight = inner2.height + inner2.depth + lineWeight + clearance;
9276
9302
  inner2.style.paddingLeft = makeEm(angleHeight / 2 + lineWeight);
9277
9303
  var viewBoxHeight = Math.floor(1e3 * angleHeight * scale);
9278
- var path27 = phasePath(viewBoxHeight);
9279
- var svgNode = new SvgNode([new PathNode("phase", path27)], {
9304
+ var path29 = phasePath(viewBoxHeight);
9305
+ var svgNode = new SvgNode([new PathNode("phase", path29)], {
9280
9306
  "width": "400em",
9281
9307
  "height": makeEm(viewBoxHeight / 1e3),
9282
9308
  "viewBox": "0 0 400000 " + viewBoxHeight,
@@ -13305,11 +13331,11 @@ var newcommand = (context, existsOK, nonexistsOK, skipIfExists) => {
13305
13331
  throw new ParseError("\\newcommand's first argument must be a macro name");
13306
13332
  }
13307
13333
  var name = arg[0].text;
13308
- var exists2 = context.isDefined(name);
13309
- if (exists2 && !existsOK) {
13334
+ var exists3 = context.isDefined(name);
13335
+ if (exists3 && !existsOK) {
13310
13336
  throw new ParseError("\\newcommand{" + name + "} attempting to redefine " + (name + "; use \\renewcommand"));
13311
13337
  }
13312
- if (!exists2 && !nonexistsOK) {
13338
+ if (!exists3 && !nonexistsOK) {
13313
13339
  throw new ParseError("\\renewcommand{" + name + "} when command " + name + " does not yet exist; use \\newcommand");
13314
13340
  }
13315
13341
  var numArgs = 0;
@@ -13327,7 +13353,7 @@ var newcommand = (context, existsOK, nonexistsOK, skipIfExists) => {
13327
13353
  numArgs = parseInt(argText);
13328
13354
  arg = context.consumeArg().tokens;
13329
13355
  }
13330
- if (!(exists2 && skipIfExists)) {
13356
+ if (!(exists3 && skipIfExists)) {
13331
13357
  context.macros.set(name, {
13332
13358
  tokens: arg,
13333
13359
  numArgs
@@ -17046,7 +17072,7 @@ function sameFiles(local, remote) {
17046
17072
  const paths = Object.keys(local);
17047
17073
  const other = remote;
17048
17074
  if (paths.length !== Object.keys(other).length) return false;
17049
- return paths.every((path27) => other[path27]?.hash === local[path27]?.hash);
17075
+ return paths.every((path29) => other[path29]?.hash === local[path29]?.hash);
17050
17076
  }
17051
17077
  function reportUnchanged(out, result) {
17052
17078
  out.data({ unchanged: true, slug: result.slug, version: result.version.n });
@@ -17784,8 +17810,8 @@ function keyLine(text2, key2, mode) {
17784
17810
  }
17785
17811
  return 0;
17786
17812
  }
17787
- function finding(path27, line, severity, message) {
17788
- return { path: path27, line: Math.max(1, line), severity, message };
17813
+ function finding(path29, line, severity, message) {
17814
+ return { path: path29, line: Math.max(1, line), severity, message };
17789
17815
  }
17790
17816
  function resolveValue(name, map, fallback) {
17791
17817
  let current = map[name] ?? fallback[name];
@@ -19171,8 +19197,8 @@ async function create(argv, ctx) {
19171
19197
  if (baseDir === void 0) throw new UsageError(`no built-in theme: ${from}`);
19172
19198
  const parent = path18.resolve(stringValue(args, "dir") ?? ".");
19173
19199
  const target = path18.join(parent, name);
19174
- const exists2 = await stat13(target).catch(() => void 0);
19175
- if (exists2 !== void 0) throw new UsageError(`${target} already exists`);
19200
+ const exists3 = await stat13(target).catch(() => void 0);
19201
+ if (exists3 !== void 0) throw new UsageError(`${target} already exists`);
19176
19202
  const values = await baseRoleValues(baseDir, ROLE_TOKENS);
19177
19203
  const doc = { name, extends: from, tokens: {}, modeTokens: values };
19178
19204
  await mkdir6(target, { recursive: true });
@@ -22729,6 +22755,7 @@ var USAGE = {
22729
22755
  delete: DELETE_USAGE,
22730
22756
  login: LOGIN_USAGE,
22731
22757
  logout: LOGOUT_USAGE,
22758
+ upgrade: UPGRADE_USAGE,
22732
22759
  completion: COMPLETION_USAGE
22733
22760
  };
22734
22761
  var WHEN = {
@@ -23105,6 +23132,7 @@ var SPECS = {
23105
23132
  delete: { usage: DELETE_USAGE },
23106
23133
  login: { usage: LOGIN_USAGE },
23107
23134
  logout: { usage: LOGOUT_USAGE },
23135
+ upgrade: { usage: UPGRADE_USAGE },
23108
23136
  completion: { usage: COMPLETION_USAGE, choices: [...SHELLS] }
23109
23137
  };
23110
23138
  function optionsOf(usage) {
@@ -23470,15 +23498,507 @@ async function run20(argv, ctx, platform = process21.platform) {
23470
23498
  ctx.out.line(`logged in as ${email} (scope ${scope}) for ${url}, saved in ${store2.description}`);
23471
23499
  }
23472
23500
 
23473
- // src/commands/logout.ts
23501
+ // src/commands/upgrade.ts
23502
+ import { realpath as realpath2 } from "node:fs/promises";
23503
+ import path27 from "node:path";
23504
+ import process23 from "node:process";
23505
+
23506
+ // src/upgrade.ts
23507
+ import { execFile, spawn as spawn6 } from "node:child_process";
23508
+ import { createHash as createHash3 } from "node:crypto";
23509
+ import { createReadStream, createWriteStream } from "node:fs";
23510
+ import { chmod as chmod2, copyFile as copyFile2, lstat as lstat2, mkdtemp as mkdtemp5, readFile as readFile19, realpath, rename as rename3, rm as rm8, stat as stat15 } from "node:fs/promises";
23511
+ import path26 from "node:path";
23474
23512
  import process22 from "node:process";
23475
- async function run21(argv, ctx, platform = process22.platform) {
23513
+ import { Readable } from "node:stream";
23514
+ import { pipeline } from "node:stream/promises";
23515
+ var REPO = "rslib/rshow";
23516
+ var NPM_HINT = "npm i -g rshow";
23517
+ var INSTALLER_ELSEWHERE = `curl -fsSL https://rslib.github.io/rshow/install.sh | RSHOW_INSTALL_DIR=$HOME/.local/bin sh`;
23518
+ var LATEST_URL = `https://github.com/${REPO}/releases/latest`;
23519
+ var RELEASES = `https://github.com/${REPO}/releases/download/v`;
23520
+ var TARGETS = ["linux-x64", "linux-arm64", "darwin-arm64"];
23521
+ var VERSION_PATTERN = /^v?([0-9]+\.[0-9]+\.[0-9]+)$/;
23522
+ var TAG_PATH = /\/tag\/v([0-9]+\.[0-9]+\.[0-9]+)$/;
23523
+ var LOOKUP_TIMEOUT_MS = 15e3;
23524
+ var DOWNLOAD_TIMEOUT_MS = 6e5;
23525
+ var UNPACK_TIMEOUT_MS = 12e4;
23526
+ var RUN_TIMEOUT_MS = 2e4;
23527
+ var NPM_TIMEOUT_MS = 3e5;
23528
+ var SKILLS_TIMEOUT_MS = 12e4;
23529
+ var UpgradeFailure = class extends Error {
23530
+ exit;
23531
+ constructor(message, exit = EXIT.api) {
23532
+ super(message);
23533
+ this.name = "UpgradeFailure";
23534
+ this.exit = exit;
23535
+ }
23536
+ };
23537
+ function messageOf12(err) {
23538
+ return err instanceof Error ? err.message : String(err);
23539
+ }
23540
+ function codeOf(err) {
23541
+ const code = typeof err === "object" && err !== null ? err.code : void 0;
23542
+ if (typeof code === "number") return `exit ${code}`;
23543
+ if (typeof code === "string" && code !== "") return code;
23544
+ return messageOf12(err);
23545
+ }
23546
+ function runProgram(file, args, options = {}) {
23547
+ return new Promise((resolve2, reject) => {
23548
+ const run23 = { timeout: options.timeoutMs, maxBuffer: 16 * 1024 * 1024 };
23549
+ execFile(file, [...args], run23, (err, stdout) => {
23550
+ if (err !== null) {
23551
+ reject(err);
23552
+ return;
23553
+ }
23554
+ resolve2(stdout);
23555
+ });
23556
+ });
23557
+ }
23558
+ function parseVersion(raw) {
23559
+ const found = VERSION_PATTERN.exec(raw.trim());
23560
+ if (found === null) throw new UsageError(`--version needs x.y.z, got: ${raw}`);
23561
+ return found[1];
23562
+ }
23563
+ function compareVersions(left, right) {
23564
+ const a = left.split(".").map(Number);
23565
+ const b = right.split(".").map(Number);
23566
+ for (let index = 0; index < 3; index += 1) {
23567
+ const diff = (a[index] ?? 0) - (b[index] ?? 0);
23568
+ if (diff !== 0) return diff < 0 ? -1 : 1;
23569
+ }
23570
+ return 0;
23571
+ }
23572
+ function targetOf2(platform, arch) {
23573
+ const name = `${platform}-${arch}`;
23574
+ return TARGETS.includes(name) ? name : void 0;
23575
+ }
23576
+ function releaseTagOf(location) {
23577
+ if (location === null || location.trim() === "") return void 0;
23578
+ let pathname;
23579
+ try {
23580
+ pathname = new URL(location, LATEST_URL).pathname;
23581
+ } catch {
23582
+ return void 0;
23583
+ }
23584
+ return TAG_PATH.exec(pathname)?.[1];
23585
+ }
23586
+ async function latestVersion(options) {
23587
+ const reply = await sendRequest(
23588
+ options.fetch,
23589
+ LATEST_URL,
23590
+ { redirect: "manual", signal: AbortSignal.timeout(LOOKUP_TIMEOUT_MS) },
23591
+ options.env
23592
+ );
23593
+ const location = reply.headers.get("location");
23594
+ const version2 = releaseTagOf(location);
23595
+ if (version2 === void 0) {
23596
+ throw new UpgradeFailure(`${LATEST_URL} did not name a release (location: ${location ?? "none"})`);
23597
+ }
23598
+ return version2;
23599
+ }
23600
+ function mirrorOf(options) {
23601
+ const raw = (options.env.RSHOW_DOWNLOAD_BASE ?? "").trim();
23602
+ if (raw === "") return void 0;
23603
+ const base = raw.replace(/\/+$/, "");
23604
+ let protocol;
23605
+ try {
23606
+ protocol = new URL(base).protocol;
23607
+ } catch {
23608
+ throw new UsageError(`RSHOW_DOWNLOAD_BASE is not a url: ${raw}`);
23609
+ }
23610
+ if (protocol !== "http:" && protocol !== "https:") {
23611
+ throw new UsageError(`RSHOW_DOWNLOAD_BASE needs an http or https url, got: ${raw}`);
23612
+ }
23613
+ return base;
23614
+ }
23615
+ function otherError(options) {
23616
+ const from = options.install.argv1 ?? options.install.execPath;
23617
+ const version2 = options.wanted ?? "<version>";
23618
+ const where = from.split(path26.sep).join("/");
23619
+ if (where.includes("/_npx/")) {
23620
+ return new UsageError(`rshow runs from ${from}, an npx cache that npm owns; run npx rshow@${version2}`);
23621
+ }
23622
+ if (where.includes("/node_modules/")) {
23623
+ return new UsageError(
23624
+ `rshow runs from ${from}, a local dependency that npm owns; run npm install rshow@${version2} to update it`
23625
+ );
23626
+ }
23627
+ return new UsageError(`rshow runs from ${from}, a source checkout; update it with git pull and npm run build`);
23628
+ }
23629
+ function dataHome(env) {
23630
+ const xdg = (env.XDG_DATA_HOME ?? "").trim();
23631
+ return xdg !== "" ? xdg : path26.join(homeDir(env), ".local", "share");
23632
+ }
23633
+ function configHome(env) {
23634
+ const xdg = (env.XDG_CONFIG_HOME ?? "").trim();
23635
+ return xdg !== "" ? xdg : path26.join(homeDir(env), ".config");
23636
+ }
23637
+ function completionPaths(env) {
23638
+ const data = dataHome(env);
23639
+ const config = configHome(env);
23640
+ return [
23641
+ { name: "rshow.bash", dst: path26.join(data, "bash-completion", "completions", "rshow") },
23642
+ { name: "_rshow", dst: path26.join(data, "zsh", "site-functions", "_rshow") },
23643
+ { name: "rshow.fish", dst: path26.join(config, "fish", "completions", "rshow.fish") }
23644
+ ];
23645
+ }
23646
+ async function exists2(file) {
23647
+ return await stat15(file).catch(() => void 0) !== void 0;
23648
+ }
23649
+ function missingFile(name, url, named) {
23650
+ if (named === void 0) {
23651
+ return new UpgradeFailure(
23652
+ `this release has no ${name} (${url}); a new release attaches its binaries a few minutes after it appears, so try again`
23653
+ );
23654
+ }
23655
+ return new UpgradeFailure(
23656
+ `release v${named} has no ${name} (${url}); it may not exist, and a release before 0.0.6 has no tarball, so use the installer with RSHOW_VERSION=${named}`
23657
+ );
23658
+ }
23659
+ async function download(options, url, file, name, named) {
23660
+ const reply = await sendRequest(options.fetch, url, { signal: AbortSignal.timeout(DOWNLOAD_TIMEOUT_MS) }, options.env);
23661
+ if (reply.status === 404) throw missingFile(name, url, named);
23662
+ if (reply.status !== 200 || reply.body === null) {
23663
+ throw new UpgradeFailure(`cannot download ${url}: HTTP ${reply.status}`);
23664
+ }
23665
+ try {
23666
+ await pipeline(Readable.fromWeb(reply.body), createWriteStream(file));
23667
+ } catch (err) {
23668
+ throw new UpgradeFailure(`cannot write ${file}: ${messageOf12(err)}`);
23669
+ }
23670
+ }
23671
+ function sumOf(text2, name) {
23672
+ for (const line of text2.split("\n")) {
23673
+ const parts2 = line.trim().split(/\s+/);
23674
+ if (parts2.length < 2) continue;
23675
+ if (parts2[1].replace(/^\*/, "") === name) return parts2[0];
23676
+ }
23677
+ return void 0;
23678
+ }
23679
+ async function sha256Of(file) {
23680
+ const hash = createHash3("sha256");
23681
+ await pipeline(createReadStream(file), hash);
23682
+ return hash.digest("hex");
23683
+ }
23684
+ async function checkSum(file, name, sumsFile) {
23685
+ const expected = sumOf(await readFile19(sumsFile, "utf8"), name);
23686
+ const actual = await sha256Of(file);
23687
+ if (expected === void 0) {
23688
+ throw new UpgradeFailure(`SHA256SUMS holds no line for ${name}, and the download is ${actual}`);
23689
+ }
23690
+ if (expected !== actual) {
23691
+ throw new UpgradeFailure(`checksum mismatch for ${name}: SHA256SUMS says ${expected}, the download is ${actual}`);
23692
+ }
23693
+ }
23694
+ async function unpack2(file, dir, tarball) {
23695
+ try {
23696
+ await runProgram("tar", ["-xzf", file, "-C", dir], { timeoutMs: UNPACK_TIMEOUT_MS });
23697
+ } catch (err) {
23698
+ throw new UpgradeFailure(`cannot unpack ${tarball}: ${codeOf(err)}`);
23699
+ }
23700
+ }
23701
+ async function printedVersion(file) {
23702
+ try {
23703
+ return (await runProgram(file, ["--version"], { timeoutMs: RUN_TIMEOUT_MS })).trim();
23704
+ } catch (err) {
23705
+ throw new UpgradeFailure(`the new rshow does not run (${codeOf(err)}); the old rshow is still in place`);
23706
+ }
23707
+ }
23708
+ async function replaceCompletions(options, dir, target) {
23709
+ if ((options.env.RSHOW_NO_COMPLETIONS ?? "").trim() === "1") return;
23710
+ for (const entry of completionPaths(options.env)) {
23711
+ const src = path26.join(dir, `rshow-${target}`, "completions", entry.name);
23712
+ try {
23713
+ if (!await exists2(entry.dst) || !await exists2(src)) continue;
23714
+ await copyFile2(src, entry.dst);
23715
+ } catch (err) {
23716
+ warn(`cannot replace the completion ${entry.dst}: ${messageOf12(err)}`);
23717
+ }
23718
+ }
23719
+ }
23720
+ function modeOf(target, statuses) {
23721
+ const kinds = statuses.filter((entry) => entry.target === target).map((entry) => entry.state.kind);
23722
+ if (kinds.includes("copy")) return "copy";
23723
+ if (kinds.includes("link")) return "link";
23724
+ return void 0;
23725
+ }
23726
+ async function refreshSkills(options, file, prefix) {
23727
+ const targets = await detectedTargets(options.home);
23728
+ if (targets.length === 0) return;
23729
+ let source;
23730
+ try {
23731
+ source = await skillsSource(options.env);
23732
+ } catch {
23733
+ return;
23734
+ }
23735
+ if (source === void 0) return;
23736
+ const statuses = await skillsStatus({ home: options.home, source, targets });
23737
+ const groups = { copy: [], link: [] };
23738
+ for (const target of targets) {
23739
+ const mode = modeOf(target, statuses);
23740
+ if (mode !== void 0) groups[mode].push(target);
23741
+ }
23742
+ for (const mode of ["copy", "link"]) {
23743
+ if (groups[mode].length === 0) continue;
23744
+ try {
23745
+ await runProgram(file, [...prefix, "skills", "install", `--${mode}`, ...groups[mode]], {
23746
+ timeoutMs: SKILLS_TIMEOUT_MS
23747
+ });
23748
+ } catch (err) {
23749
+ warn(`the new rshow could not refresh the skills (${codeOf(err)}); run rshow skills install`);
23750
+ }
23751
+ }
23752
+ }
23753
+ function versionIn(printed) {
23754
+ return /^rshow ([0-9]+\.[0-9]+\.[0-9]+)/.exec(printed)?.[1];
23755
+ }
23756
+ async function replaceBinary(job, dest) {
23757
+ const dir = path26.dirname(dest);
23758
+ let tmp;
23759
+ try {
23760
+ tmp = await mkdtemp5(path26.join(dir, ".rshow-upgrade-"));
23761
+ } catch (err) {
23762
+ const code = err.code;
23763
+ if (code === "EACCES" || code === "EPERM" || code === "EROFS") {
23764
+ throw new UpgradeFailure(`${dir} is not writable; install elsewhere instead: ${INSTALLER_ELSEWHERE}`);
23765
+ }
23766
+ throw new UpgradeFailure(`cannot write next to ${dest}: ${messageOf12(err)}`);
23767
+ }
23768
+ try {
23769
+ const tarball = `rshow-${job.target}.tar.gz`;
23770
+ const file = path26.join(tmp, tarball);
23771
+ const sums = path26.join(tmp, "SHA256SUMS");
23772
+ const url = `${job.base}/${tarball}`;
23773
+ process22.stderr.write(`rshow: downloading ${url}
23774
+ `);
23775
+ await download(job.options, url, file, tarball, job.named);
23776
+ await download(job.options, `${job.base}/SHA256SUMS`, sums, "SHA256SUMS", job.named);
23777
+ await checkSum(file, tarball, sums);
23778
+ await unpack2(file, tmp, tarball);
23779
+ const folder = `rshow-${job.target}`;
23780
+ const unpacked = path26.join(tmp, folder, "rshow");
23781
+ const info = await lstat2(unpacked).catch(() => void 0);
23782
+ if (info === void 0) {
23783
+ throw new UpgradeFailure(`${tarball} holds no ${folder}/rshow; the old rshow is still in place`);
23784
+ }
23785
+ if (!info.isFile()) {
23786
+ throw new UpgradeFailure(`${tarball} holds a ${folder}/rshow that is not a file; the old rshow is still in place`);
23787
+ }
23788
+ await chmod2(unpacked, 493);
23789
+ const printed = await printedVersion(unpacked);
23790
+ let version2;
23791
+ if (job.version === void 0) {
23792
+ const found = versionIn(printed);
23793
+ if (found === void 0) {
23794
+ throw new UpgradeFailure(`the new rshow printed ${printed || "no version"}; the old rshow is still in place`);
23795
+ }
23796
+ if (!job.force && found === job.options.current) return { replaced: false, version: found };
23797
+ version2 = found;
23798
+ } else {
23799
+ if (!printed.startsWith(`rshow ${job.version}`)) {
23800
+ throw new UpgradeFailure(
23801
+ `the new rshow printed ${printed || "no version"}, not rshow ${job.version}; the old rshow is still in place`
23802
+ );
23803
+ }
23804
+ version2 = job.version;
23805
+ }
23806
+ await rename3(unpacked, dest);
23807
+ await replaceCompletions(job.options, tmp, job.target);
23808
+ return { replaced: true, version: version2 };
23809
+ } finally {
23810
+ await rm8(tmp, { recursive: true, force: true });
23811
+ }
23812
+ }
23813
+ async function installPath(options) {
23814
+ try {
23815
+ return await realpath(options.install.execPath);
23816
+ } catch (err) {
23817
+ throw new UpgradeFailure(`cannot read ${options.install.execPath}: ${messageOf12(err)}`);
23818
+ }
23819
+ }
23820
+ function targetOrFail(options) {
23821
+ const platform = options.platform ?? process22.platform;
23822
+ const arch = options.arch ?? process22.arch;
23823
+ const target = targetOf2(platform, arch);
23824
+ if (target === void 0) {
23825
+ throw new UsageError(`no rshow single executable for ${platform}-${arch}; run ${NPM_HINT}`);
23826
+ }
23827
+ return target;
23828
+ }
23829
+ async function installBinary(job) {
23830
+ const dest = await installPath(job.options);
23831
+ const outcome = await replaceBinary(job, dest);
23832
+ if (!outcome.replaced) return { status: "current", kind: "binary", version: outcome.version };
23833
+ await refreshSkills(job.options, dest, []);
23834
+ return { status: "upgraded", kind: "binary", previous: job.options.current, version: outcome.version };
23835
+ }
23836
+ function runNpm(args) {
23837
+ return new Promise((resolve2, reject) => {
23838
+ const child = spawn6("npm", [...args], { stdio: ["ignore", 2, 2], timeout: NPM_TIMEOUT_MS });
23839
+ child.on("error", (err) => {
23840
+ child.emit("exit", null, null);
23841
+ reject(err);
23842
+ });
23843
+ child.on("close", (code, signal) => {
23844
+ if (code === 0) {
23845
+ resolve2();
23846
+ return;
23847
+ }
23848
+ if (code !== null) {
23849
+ const err = Object.assign(new Error(`npm exited ${code}`), { code });
23850
+ reject(err);
23851
+ return;
23852
+ }
23853
+ reject(new Error(`npm stopped on ${signal ?? "a timeout"}`));
23854
+ });
23855
+ });
23856
+ }
23857
+ async function npmInstall(version2) {
23858
+ const command = `npm install -g rshow@${version2}`;
23859
+ try {
23860
+ await runNpm(["install", "-g", `rshow@${version2}`]);
23861
+ } catch (err) {
23862
+ const code = err.code;
23863
+ if (typeof code === "number") throw new UpgradeFailure(`${command} failed (exit ${code})`, code);
23864
+ throw new UpgradeFailure(`cannot run npm (${codeOf(err)}); run ${command}`, EXIT.usage);
23865
+ }
23866
+ }
23867
+ async function upgrade(options) {
23868
+ const mirror = mirrorOf(options);
23869
+ const force = options.force === true;
23870
+ const kind = options.install.kind;
23871
+ if (options.check === true) {
23872
+ if (mirror !== void 0) {
23873
+ throw new UsageError(`--check cannot be used with RSHOW_DOWNLOAD_BASE (${mirror}), which names no release`);
23874
+ }
23875
+ const latest = options.wanted ?? await latestVersion(options);
23876
+ return {
23877
+ status: "check",
23878
+ current: options.current,
23879
+ latest,
23880
+ upgrade: compareVersions(latest, options.current) > 0
23881
+ };
23882
+ }
23883
+ if (kind === "other") throw otherError(options);
23884
+ if (kind === "binary" && mirror !== void 0 && options.wanted === void 0) {
23885
+ return installBinary({ options, base: mirror, target: targetOrFail(options), force });
23886
+ }
23887
+ let version2 = options.wanted;
23888
+ if (version2 === void 0) {
23889
+ const latest = await latestVersion(options);
23890
+ if (compareVersions(latest, options.current) < 0) {
23891
+ return { status: "newer", kind, current: options.current, latest };
23892
+ }
23893
+ version2 = latest;
23894
+ }
23895
+ if (version2 === options.current && !force) {
23896
+ return { status: "current", kind, version: version2 };
23897
+ }
23898
+ if (kind === "binary") {
23899
+ const base = mirror ?? `${RELEASES}${version2}`;
23900
+ return installBinary({ options, base, target: targetOrFail(options), version: version2, named: options.wanted, force });
23901
+ }
23902
+ await npmInstall(version2);
23903
+ const argv1 = options.install.argv1;
23904
+ if (argv1 !== void 0) await refreshSkills(options, process22.execPath, [argv1]);
23905
+ return { status: "upgraded", kind: "npm", previous: options.current, version: version2 };
23906
+ }
23907
+ function reportOf(result) {
23908
+ if (result.status === "check") {
23909
+ return {
23910
+ data: { current: result.current, latest: result.latest, upgrade_available: result.upgrade },
23911
+ lines: [`current: ${result.current}`, `latest: ${result.latest}`]
23912
+ };
23913
+ }
23914
+ if (result.status === "newer") {
23915
+ return {
23916
+ data: { previous: result.current, version: result.current, upgraded: false, kind: result.kind },
23917
+ lines: [`rshow: ${result.current} is newer than the newest release, ${result.latest}`]
23918
+ };
23919
+ }
23920
+ if (result.status === "current") {
23921
+ return {
23922
+ data: { previous: result.version, version: result.version, upgraded: false, kind: result.kind },
23923
+ lines: [`rshow: already at ${result.version}`]
23924
+ };
23925
+ }
23926
+ return {
23927
+ data: { previous: result.previous, version: result.version, upgraded: true, kind: result.kind },
23928
+ lines: [`rshow: upgraded ${result.previous} -> ${result.version}`]
23929
+ };
23930
+ }
23931
+
23932
+ // src/commands/upgrade.ts
23933
+ var TIMEOUT_MS2 = 6e4;
23934
+ function isUnder2(file, dir) {
23935
+ const rel2 = path27.relative(dir, file);
23936
+ return rel2 !== "" && rel2 !== ".." && !rel2.startsWith(`..${path27.sep}`) && !path27.isAbsolute(rel2);
23937
+ }
23938
+ async function npmRoot() {
23939
+ try {
23940
+ const root2 = (await runProgram("npm", ["root", "-g"], { timeoutMs: TIMEOUT_MS2 })).trim();
23941
+ return root2 === "" ? void 0 : await realpath2(root2);
23942
+ } catch {
23943
+ return void 0;
23944
+ }
23945
+ }
23946
+ async function installKind() {
23947
+ if (await isBinary()) return { kind: "binary", execPath: process23.execPath };
23948
+ const argv1 = process23.argv[1];
23949
+ const root2 = await npmRoot();
23950
+ if (root2 !== void 0 && argv1 !== void 0) {
23951
+ const script3 = await realpath2(argv1).catch(() => void 0);
23952
+ if (script3 !== void 0 && isUnder2(script3, root2)) {
23953
+ return { kind: "npm", execPath: process23.execPath, argv1: script3 };
23954
+ }
23955
+ }
23956
+ return { kind: "other", execPath: process23.execPath, argv1 };
23957
+ }
23958
+ async function run21(argv, ctx) {
23959
+ const args = parseCommandArgs(
23960
+ argv,
23961
+ { check: { type: "boolean" }, version: { type: "string" }, force: { type: "boolean" } },
23962
+ 0,
23963
+ 0,
23964
+ UPGRADE_USAGE
23965
+ );
23966
+ const raw = stringValue(args, "version");
23967
+ const wanted = raw === void 0 ? void 0 : parseVersion(raw);
23968
+ const env = process23.env;
23969
+ let result;
23970
+ try {
23971
+ result = await upgrade({
23972
+ current: VERSION,
23973
+ wanted,
23974
+ check: boolValue(args, "check"),
23975
+ force: boolValue(args, "force"),
23976
+ install: await installKind(),
23977
+ fetch,
23978
+ env,
23979
+ home: homeDir(env)
23980
+ });
23981
+ } catch (err) {
23982
+ if (!(err instanceof UpgradeFailure)) throw err;
23983
+ process23.stderr.write(`rshow: ${err.message}
23984
+ `);
23985
+ return err.exit;
23986
+ }
23987
+ const report = reportOf(result);
23988
+ ctx.out.data(report.data);
23989
+ for (const line of report.lines) ctx.out.line(line);
23990
+ return EXIT.ok;
23991
+ }
23992
+
23993
+ // src/commands/logout.ts
23994
+ import process24 from "node:process";
23995
+ async function run22(argv, ctx, platform = process24.platform) {
23476
23996
  const args = parseCommandArgs(argv, { url: { type: "string" } }, 0, 0, LOGOUT_USAGE);
23477
23997
  const settings = await ctx.config();
23478
23998
  const given = (stringValue(args, "url") ?? "").trim();
23479
23999
  const url = (given === "" ? requireUrl(settings) : given).replace(/\/+$/, "");
23480
24000
  const removed = [];
23481
- for (const store2 of await every(process22.env, platform)) {
24001
+ for (const store2 of await every(process24.env, platform)) {
23482
24002
  if (await store2.remove(url)) removed.push(store2.description);
23483
24003
  }
23484
24004
  if (removed.length === 0) {
@@ -23508,14 +24028,15 @@ var COMMANDS = {
23508
24028
  pin: { usage: PIN_USAGE, run: run12 },
23509
24029
  delete: { usage: DELETE_USAGE, run: run13 },
23510
24030
  login: { usage: LOGIN_USAGE, run: run20 },
23511
- logout: { usage: LOGOUT_USAGE, run: run21 },
24031
+ logout: { usage: LOGOUT_USAGE, run: run22 },
24032
+ upgrade: { usage: UPGRADE_USAGE, run: run21 },
23512
24033
  completion: { usage: COMPLETION_USAGE, run: run19 },
23513
24034
  // Kept out of the general help: the docs site runs it, a person rarely does.
23514
24035
  docs: { usage: DOCS_USAGE, run: run18 }
23515
24036
  };
23516
24037
  function fail(err) {
23517
24038
  const message = err instanceof Error ? err.message : String(err);
23518
- process23.stderr.write(`rshow: ${message}
24039
+ process25.stderr.write(`rshow: ${message}
23519
24040
  `);
23520
24041
  if (err instanceof UsageError) return EXIT.usage;
23521
24042
  if (err instanceof CheckError) return EXIT.check;
@@ -23535,17 +24056,17 @@ async function main2(argv) {
23535
24056
  const wantsVersion = versionAt >= 0 && (commandAt < 0 || versionAt < commandAt);
23536
24057
  if (wantsVersion) {
23537
24058
  const suffix = await isBinary() ? " (binary)" : "";
23538
- process23.stdout.write(`rshow ${VERSION}${suffix}
24059
+ process25.stdout.write(`rshow ${VERSION}${suffix}
23539
24060
  `);
23540
24061
  return EXIT.ok;
23541
24062
  }
23542
24063
  if (wantsHelp) {
23543
- process23.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
24064
+ process25.stdout.write(`${spec === void 0 ? GENERAL_USAGE : spec.usage}
23544
24065
  `);
23545
24066
  return EXIT.ok;
23546
24067
  }
23547
24068
  if (name === void 0) {
23548
- process23.stderr.write(`${GENERAL_USAGE}
24069
+ process25.stderr.write(`${GENERAL_USAGE}
23549
24070
  `);
23550
24071
  return EXIT.usage;
23551
24072
  }
@@ -23555,12 +24076,12 @@ async function main2(argv) {
23555
24076
  const rest = [...args.slice(0, commandAt), ...args.slice(commandAt + 1)];
23556
24077
  const configs = /* @__PURE__ */ new Map();
23557
24078
  const clients = /* @__PURE__ */ new Map();
23558
- const keyOf = (dir) => dir === void 0 ? "" : path26.resolve(dir);
24079
+ const keyOf = (dir) => dir === void 0 ? "" : path28.resolve(dir);
23559
24080
  const configFor = (dir) => {
23560
24081
  const key2 = keyOf(dir);
23561
24082
  let pending = configs.get(key2);
23562
24083
  if (pending === void 0) {
23563
- pending = loadConfig(process23.env, dir);
24084
+ pending = loadConfig(process25.env, dir);
23564
24085
  configs.set(key2, pending);
23565
24086
  }
23566
24087
  return pending;
@@ -23573,7 +24094,7 @@ async function main2(argv) {
23573
24094
  if (cached !== void 0) return cached;
23574
24095
  const settings = await configFor(dir);
23575
24096
  const url = requireUrl(settings);
23576
- const client = new Client(url, await readToken(process23.env, url));
24097
+ const client = new Client(url, await readToken(process25.env, url));
23577
24098
  clients.set(key2, client);
23578
24099
  return client;
23579
24100
  },
@@ -23582,11 +24103,11 @@ async function main2(argv) {
23582
24103
  const code = await spec.run(rest, ctx);
23583
24104
  return code ?? EXIT.ok;
23584
24105
  }
23585
- main2(process23.argv.slice(2)).then(
24106
+ main2(process25.argv.slice(2)).then(
23586
24107
  (code) => {
23587
- process23.exitCode = code;
24108
+ process25.exitCode = code;
23588
24109
  },
23589
24110
  (err) => {
23590
- process23.exitCode = fail(err);
24111
+ process25.exitCode = fail(err);
23591
24112
  }
23592
24113
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rshow",
3
- "version": "0.0.8",
3
+ "version": "0.0.9",
4
4
  "description": "Publish static artifacts to rshow",
5
5
  "license": "MIT",
6
6
  "homepage": "https://rslib.github.io/rshow",
@@ -28,6 +28,7 @@ This file is the whole surface, both config files and the exit codes.
28
28
  | `rshow config [dir] [--offline]` | print the merged config as JSON, with `theme` as `<name>@latest` and, with the network, `theme_resolved` as the version it resolves to |
29
29
  | `rshow init --url <admin-url> --art <viewer-url>` | write the global config file, with `--theme` and `--force` |
30
30
  | `rshow doctor [dir]` | check node, config, token, api, art, theme and skills, one `ok\|warn\|fail` line each |
31
+ | `rshow upgrade [--check] [--version x.y.z] [--force]` | replace the single executable, or run `npm install -g` on an npm install, with another release |
31
32
  | `rshow list` | list artifacts you own or that are shared with you, with `--project`, `--tag` and `--q` filters |
32
33
  | `rshow show <slug>` | print the artifact, its versions, its shares, its project and its tags |
33
34
  | `rshow share <slug>` | set the mode, or add and remove shares |
@@ -51,6 +52,10 @@ This file is the whole surface, both config files and the exit codes.
51
52
  `--help` on its own prints the general usage, and `--help` after a command prints that command.
52
53
  `--json` is global and makes stdout the API reply, which is how a script reads a version number.
53
54
 
55
+ `rshow upgrade` moves an installed rshow to the newest release, or to the one `--version` names, and `--check` prints both versions and changes nothing.
56
+ It needs no config and no token, and a source checkout or an npx cache is named with the tool that owns it instead of being touched.
57
+ Run `rshow upgrade` only when the person asks for it.
58
+
54
59
  Every flag takes `--flag value` or `--flag=value`.
55
60
  `--add`, `--editor`, `--remove` and `--tag` accept several values in a row after one flag.
56
61
 
package/worker/index.js CHANGED
@@ -3277,18 +3277,15 @@ __name(etagMatches, "etagMatches");
3277
3277
 
3278
3278
  // src/cache.ts
3279
3279
  var CACHE_ORIGIN = "https://rshow.internal";
3280
- var ACCESS_TTL_MS = 15e3;
3281
- var ACCESS_MAX_ENTRIES = 500;
3282
3280
  var MANIFEST_MAX_ENTRIES = 200;
3283
3281
  var VERSION_MAX_ENTRIES = 500;
3284
- var ACCESS_CACHE_CONTROL = "max-age=15";
3282
+ var ACCESS_CACHE_CONTROL = "max-age=2";
3285
3283
  var MANIFEST_CACHE_CONTROL = "public, max-age=31536000, immutable";
3286
3284
  var VERSION_CACHE_CONTROL = "public, max-age=31536000, immutable";
3287
3285
  var JSON_CONTENT_TYPE = "application/json";
3288
3286
  var BLOB_CACHE_CONTROL = "public, max-age=31536000, immutable";
3289
3287
  var ARTIFACT_COLUMNS2 = "slug, title, owner, mode, pinned, latest, last_n, version_gen, project, created_at, updated_at";
3290
3288
  var VERSION_COLUMNS = "slug, n, label, manifest_key, files, bytes, created_by, created_at";
3291
- var accessMemory = /* @__PURE__ */ new Map();
3292
3289
  var manifestMemory = /* @__PURE__ */ new Map();
3293
3290
  var versionMemory = /* @__PURE__ */ new Map();
3294
3291
  var versionKeys = /* @__PURE__ */ new Map();
@@ -3313,16 +3310,6 @@ function versionMemoryKey(slug, n, gen) {
3313
3310
  return `${slug}/${n}/${gen}`;
3314
3311
  }
3315
3312
  __name(versionMemoryKey, "versionMemoryKey");
3316
- function rememberAccess(slug, value, expires) {
3317
- accessMemory.delete(slug);
3318
- accessMemory.set(slug, { value, expires });
3319
- while (accessMemory.size > ACCESS_MAX_ENTRIES) {
3320
- const oldest = accessMemory.keys().next().value;
3321
- if (oldest === void 0) break;
3322
- accessMemory.delete(oldest);
3323
- }
3324
- }
3325
- __name(rememberAccess, "rememberAccess");
3326
3313
  function rememberManifest(key, value) {
3327
3314
  manifestMemory.delete(key);
3328
3315
  manifestMemory.set(key, value);
@@ -3453,30 +3440,18 @@ async function loadAccess(env, slug) {
3453
3440
  }
3454
3441
  __name(loadAccess, "loadAccess");
3455
3442
  async function accessFor(env, slug) {
3456
- const now = Date.now();
3457
- const memory = accessMemory.get(slug);
3458
- if (memory && memory.expires > now) {
3459
- rememberAccess(slug, memory.value, memory.expires);
3460
- return memory.value;
3461
- }
3462
- accessMemory.delete(slug);
3463
3443
  const key = accessCacheKey(slug);
3464
3444
  const cached = await cacheGet(key);
3465
3445
  if (cached) {
3466
3446
  const decoded = decodeAccess(await cached.text());
3467
- if (decoded.ok) {
3468
- rememberAccess(slug, decoded.value, now + ACCESS_TTL_MS);
3469
- return decoded.value;
3470
- }
3447
+ if (decoded.ok) return decoded.value;
3471
3448
  }
3472
3449
  const value = await loadAccess(env, slug);
3473
- rememberAccess(slug, value, now + ACCESS_TTL_MS);
3474
3450
  await cachePut(key, JSON.stringify(value), ACCESS_CACHE_CONTROL);
3475
3451
  return value;
3476
3452
  }
3477
3453
  __name(accessFor, "accessFor");
3478
3454
  async function invalidateAccess(slug) {
3479
- accessMemory.delete(slug);
3480
3455
  const key = accessCacheKey(slug);
3481
3456
  trackedKeys.delete(key);
3482
3457
  try {
@@ -4036,10 +4011,11 @@ function registerArtifactCreate(app) {
4036
4011
  const existing = await getArtifact(c.env, slug);
4037
4012
  if (existing) return jsonError("slug is already taken", 409);
4038
4013
  const now = (/* @__PURE__ */ new Date()).toISOString();
4014
+ let counters;
4039
4015
  try {
4040
- await c.env.DB.prepare(
4041
- "INSERT INTO artifacts (slug, title, owner, mode, pinned, latest, project, created_at, updated_at) VALUES (?1, ?2, ?3, ?4, NULL, NULL, ?5, ?6, ?6)"
4042
- ).bind(slug, title2, email, MODE.private, project, now).run();
4016
+ counters = await c.env.DB.prepare(
4017
+ "INSERT INTO artifacts (slug, title, owner, mode, pinned, latest, last_n, version_gen, project, created_at, updated_at) SELECT ?1, ?2, ?3, ?4, NULL, NULL, COALESCE((SELECT last_n FROM retired_slugs WHERE slug = ?1), 0), COALESCE((SELECT version_gen FROM retired_slugs WHERE slug = ?1), 0), ?5, ?6, ?6 RETURNING last_n, version_gen"
4018
+ ).bind(slug, title2, email, MODE.private, project, now).first();
4043
4019
  } catch {
4044
4020
  return jsonError("slug is already taken", 409);
4045
4021
  }
@@ -4055,8 +4031,8 @@ function registerArtifactCreate(app) {
4055
4031
  mode: MODE.private,
4056
4032
  pinned: null,
4057
4033
  latest: null,
4058
- last_n: 0,
4059
- version_gen: 0,
4034
+ last_n: counters?.last_n ?? 0,
4035
+ version_gen: counters?.version_gen ?? 0,
4060
4036
  project,
4061
4037
  created_at: now,
4062
4038
  updated_at: now
@@ -4210,6 +4186,9 @@ function registerArtifactItem(app) {
4210
4186
  if (keys.length > 0) await c.env.BUCKET.delete(keys);
4211
4187
  await deleteArtifactData(c.env, access.artifact.slug);
4212
4188
  await c.env.DB.batch([
4189
+ c.env.DB.prepare(
4190
+ "INSERT INTO retired_slugs (slug, last_n, version_gen) SELECT slug, MAX(last_n, COALESCE(latest, 0)), version_gen + 1 FROM artifacts WHERE slug = ?1 ON CONFLICT(slug) DO UPDATE SET last_n = excluded.last_n, version_gen = excluded.version_gen"
4191
+ ).bind(access.artifact.slug),
4213
4192
  c.env.DB.prepare("DELETE FROM page_data WHERE slug = ?1").bind(access.artifact.slug),
4214
4193
  c.env.DB.prepare("DELETE FROM artifact_tags WHERE slug = ?1").bind(access.artifact.slug),
4215
4194
  c.env.DB.prepare("DELETE FROM versions WHERE slug = ?1").bind(access.artifact.slug),
@@ -0,0 +1,5 @@
1
+ CREATE TABLE retired_slugs (
2
+ slug TEXT PRIMARY KEY,
3
+ last_n INTEGER NOT NULL,
4
+ version_gen INTEGER NOT NULL
5
+ );