@verentis/cli 0.2.3 → 0.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -10,6 +10,21 @@ The Verentis CLI is three tools in one:
10
10
  npm install -g @verentis/cli
11
11
  ```
12
12
 
13
+ ## Staying up to date
14
+
15
+ The CLI checks the npm registry for a newer release at most once a day (in the background, so it never
16
+ slows a command down) and prints a one-line notice when one is available. Update in place with:
17
+
18
+ ```bash
19
+ verentis update # install the latest published version
20
+ verentis update --check # just report whether a newer version exists
21
+ verentis update --to 0.3.0 # install a specific version
22
+ ```
23
+
24
+ `update` detects your global package manager (npm/pnpm/yarn/bun). Silence the notice with
25
+ `VERENTIS_NO_UPDATE_CHECK=1` (it is also skipped in CI, on non-interactive output, and with `--json`).
26
+ Point `VERENTIS_UPDATE_REGISTRY` at a mirror if you install from a private registry.
27
+
13
28
  ## Sign in
14
29
 
15
30
  ```bash
package/dist/index.js CHANGED
@@ -3,13 +3,14 @@ import { Command } from 'commander';
3
3
  import { mkdir, stat, writeFile, readFile, readdir, mkdtemp, rm, glob } from 'fs/promises';
4
4
  import { tmpdir, homedir } from 'os';
5
5
  import { resolve, basename, dirname, posix, join, sep, relative } from 'path';
6
- import { spawn } from 'child_process';
6
+ import { spawn, spawnSync } from 'child_process';
7
7
  import { generateKeyPairSync, createHash, randomUUID, randomBytes, createCipheriv, createPublicKey, verify, diffieHellman, hkdfSync, sign, createPrivateKey } from 'crypto';
8
8
  import { parse } from 'yaml';
9
- import { createWriteStream, createReadStream } from 'fs';
9
+ import { createWriteStream, readFileSync, createReadStream } from 'fs';
10
10
  import { Readable } from 'stream';
11
11
  import { pipeline } from 'stream/promises';
12
12
  import * as tar from 'tar';
13
+ import { fileURLToPath } from 'url';
13
14
 
14
15
  var configDir = () => process.env.VERENTIS_CONFIG_DIR ?? join(homedir(), ".verentis");
15
16
  var configPath = () => join(configDir(), "config.json");
@@ -192,8 +193,8 @@ async function createApiClient(overrides) {
192
193
  }
193
194
  async function credentialFor(scopes, workspaceId) {
194
195
  const cacheKey = `${workspaceId ?? ""}:${[...scopes].sort().join(" ")}`;
195
- const cached = tokenCache.get(cacheKey);
196
- if (cached && !isExpired(cached)) return cached;
196
+ const cached2 = tokenCache.get(cacheKey);
197
+ if (cached2 && !isExpired(cached2)) return cached2;
197
198
  const token = await mintAccessToken({ scopes, workspaceId });
198
199
  tokenCache.set(cacheKey, token);
199
200
  return token;
@@ -2482,6 +2483,271 @@ function registerSettings(program2) {
2482
2483
  console.log(`Setting ${key} deleted.`);
2483
2484
  });
2484
2485
  }
2486
+ var PACKAGE_NAME = "@verentis/cli";
2487
+ var cached;
2488
+ function getCliVersion() {
2489
+ if (cached) return cached;
2490
+ cached = readVersion() ?? "0.0.0";
2491
+ return cached;
2492
+ }
2493
+ function readVersion() {
2494
+ const candidates = ["../package.json", "../../package.json"];
2495
+ for (const relative2 of candidates) {
2496
+ try {
2497
+ const raw = readFileSync(fileURLToPath(new URL(relative2, import.meta.url)), "utf8");
2498
+ const pkg = JSON.parse(raw);
2499
+ if (pkg.version && (relative2 === "../package.json" || pkg.name === PACKAGE_NAME)) return pkg.version;
2500
+ } catch {
2501
+ }
2502
+ }
2503
+ return void 0;
2504
+ }
2505
+
2506
+ // src/update/registry.ts
2507
+ var DEFAULT_REGISTRY = "https://registry.npmjs.org";
2508
+ function registryUrl() {
2509
+ const configured = process.env.VERENTIS_UPDATE_REGISTRY ?? process.env.npm_config_registry ?? DEFAULT_REGISTRY;
2510
+ return configured.replace(/\/+$/, "");
2511
+ }
2512
+ async function fetchLatestVersion(options = {}) {
2513
+ const url = `${registryUrl()}/${PACKAGE_NAME.replace("/", "%2F")}`;
2514
+ const controller = new AbortController();
2515
+ const timer = setTimeout(() => controller.abort(), options.timeoutMs ?? 3e3);
2516
+ try {
2517
+ const res = await fetch(url, {
2518
+ // The abbreviated packument is far smaller than the full manifest but still carries dist-tags.
2519
+ headers: { Accept: "application/vnd.npm.install-v1+json" },
2520
+ signal: controller.signal
2521
+ });
2522
+ if (!res.ok) return null;
2523
+ const body = await res.json();
2524
+ return body["dist-tags"]?.latest ?? null;
2525
+ } catch {
2526
+ return null;
2527
+ } finally {
2528
+ clearTimeout(timer);
2529
+ }
2530
+ }
2531
+
2532
+ // src/update/semver.ts
2533
+ function isNewerVersion(candidate, base) {
2534
+ return compareVersions(candidate, base) > 0;
2535
+ }
2536
+ function compareVersions(a, b) {
2537
+ const pa = parse4(a);
2538
+ const pb = parse4(b);
2539
+ for (let i = 0; i < 3; i++) {
2540
+ if (pa.main[i] !== pb.main[i]) return pa.main[i] < pb.main[i] ? -1 : 1;
2541
+ }
2542
+ if (pa.pre.length === 0 && pb.pre.length === 0) return 0;
2543
+ if (pa.pre.length === 0) return 1;
2544
+ if (pb.pre.length === 0) return -1;
2545
+ const len = Math.max(pa.pre.length, pb.pre.length);
2546
+ for (let i = 0; i < len; i++) {
2547
+ if (i >= pa.pre.length) return -1;
2548
+ if (i >= pb.pre.length) return 1;
2549
+ const x = pa.pre[i];
2550
+ const y = pb.pre[i];
2551
+ const xNum = /^\d+$/.test(x);
2552
+ const yNum = /^\d+$/.test(y);
2553
+ if (xNum && yNum) {
2554
+ const nx = Number(x);
2555
+ const ny = Number(y);
2556
+ if (nx !== ny) return nx < ny ? -1 : 1;
2557
+ } else if (xNum !== yNum) {
2558
+ return xNum ? -1 : 1;
2559
+ } else if (x !== y) {
2560
+ return x < y ? -1 : 1;
2561
+ }
2562
+ }
2563
+ return 0;
2564
+ }
2565
+ function parse4(version) {
2566
+ const cleaned = version.trim().replace(/^v/i, "").split("+", 1)[0];
2567
+ const dash = cleaned.indexOf("-");
2568
+ const core = dash === -1 ? cleaned : cleaned.slice(0, dash);
2569
+ const pre = dash === -1 ? "" : cleaned.slice(dash + 1);
2570
+ const parts = core.split(".").map((n) => Number.parseInt(n, 10) || 0);
2571
+ return { main: [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0], pre: pre ? pre.split(".") : [] };
2572
+ }
2573
+ var DEFAULT_MAX_AGE_MS = 24 * 60 * 60 * 1e3;
2574
+ var statePath = () => join(configDir(), "update-check.json");
2575
+ async function readUpdateState() {
2576
+ try {
2577
+ return JSON.parse(await readFile(statePath(), "utf8"));
2578
+ } catch {
2579
+ return null;
2580
+ }
2581
+ }
2582
+ async function writeUpdateState(state) {
2583
+ await mkdir(configDir(), { recursive: true, mode: 448 });
2584
+ await writeFile(statePath(), JSON.stringify(state, null, 2) + "\n", { mode: 384 });
2585
+ }
2586
+ function isStale(state, maxAgeMs = DEFAULT_MAX_AGE_MS) {
2587
+ if (!state?.lastCheckAt) return true;
2588
+ const checkedAt = Date.parse(state.lastCheckAt);
2589
+ return !Number.isFinite(checkedAt) || Date.now() - checkedAt > maxAgeMs;
2590
+ }
2591
+
2592
+ // src/update/notifier.ts
2593
+ var SUPPRESSED_COMMANDS = /* @__PURE__ */ new Set(["update", "__update-check", "help"]);
2594
+ async function notifyOnExit(argv) {
2595
+ if (isUpdateCheckSuppressed(argv)) return;
2596
+ try {
2597
+ const current = getCliVersion();
2598
+ const state = await readUpdateState();
2599
+ if (state?.latestVersion && isNewerVersion(state.latestVersion, current)) {
2600
+ printUpdateNotice(current, state.latestVersion);
2601
+ }
2602
+ if (isStale(state)) spawnBackgroundRefresh();
2603
+ } catch {
2604
+ }
2605
+ }
2606
+ async function runUpdateCheckRefresh() {
2607
+ const latest = await fetchLatestVersion({ timeoutMs: 4e3 }).catch(() => null);
2608
+ await writeUpdateState({ lastCheckAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion: latest ?? void 0 }).catch(() => {
2609
+ });
2610
+ }
2611
+ function isUpdateCheckSuppressed(argv) {
2612
+ if (isEnvDisabled()) return true;
2613
+ if (process.env.CI) return true;
2614
+ if (!process.stderr.isTTY) return true;
2615
+ const args = argv.slice(2);
2616
+ if (args.length === 0) return true;
2617
+ if (args.some((a) => a === "--json" || a === "-h" || a === "--help" || a === "-V" || a === "--version")) return true;
2618
+ const command = args.find((a) => !a.startsWith("-"));
2619
+ return command !== void 0 && SUPPRESSED_COMMANDS.has(command);
2620
+ }
2621
+ function isEnvDisabled() {
2622
+ const off = /* @__PURE__ */ new Set(["0", "false", "no", "off"]);
2623
+ const noCheck = process.env.VERENTIS_NO_UPDATE_CHECK;
2624
+ if (noCheck !== void 0 && noCheck !== "" && !off.has(noCheck.toLowerCase())) return true;
2625
+ const explicit = process.env.VERENTIS_UPDATE_CHECK;
2626
+ return explicit !== void 0 && off.has(explicit.toLowerCase());
2627
+ }
2628
+ function printUpdateNotice(current, latest) {
2629
+ const message = [
2630
+ `Update available ${current} \u2192 ${latest}`,
2631
+ "Run `verentis update` to install the latest version."
2632
+ ];
2633
+ const width = Math.max(...message.map((line) => line.length));
2634
+ const border = "\u2500".repeat(width + 2);
2635
+ const lines = ["", `\u256D${border}\u256E`, ...message.map((line) => `\u2502 ${line.padEnd(width)} \u2502`), `\u2570${border}\u256F`, ""];
2636
+ process.stderr.write(lines.join("\n") + "\n");
2637
+ }
2638
+ function spawnBackgroundRefresh() {
2639
+ try {
2640
+ const entry = process.argv[1];
2641
+ if (!entry) return;
2642
+ const child = spawn(process.execPath, [entry, "__update-check"], {
2643
+ detached: true,
2644
+ stdio: "ignore",
2645
+ windowsHide: true
2646
+ });
2647
+ child.on("error", () => {
2648
+ });
2649
+ child.unref();
2650
+ } catch {
2651
+ }
2652
+ }
2653
+
2654
+ // src/commands/update.ts
2655
+ function registerUpdate(program2) {
2656
+ program2.command("update").description("Update the Verentis CLI to the latest published version").option("--check", "report whether a newer version is available without installing").option("--to <version>", "install a specific version instead of the latest").action(async (options) => {
2657
+ const current = getCliVersion();
2658
+ const target = await resolveTarget(options.to);
2659
+ if (options.check) {
2660
+ reportCheck(current, target, Boolean(options.to));
2661
+ return;
2662
+ }
2663
+ if (!options.to && !isNewerVersion(target, current)) {
2664
+ console.log(`Already on the latest version (${current}).`);
2665
+ return;
2666
+ }
2667
+ const install = resolveInstallCommand(`${PACKAGE_NAME}@${target}`);
2668
+ if (!install) {
2669
+ throw new Error(
2670
+ `Automatic update is only supported for a global install of ${PACKAGE_NAME}.
2671
+ Update manually, for example:
2672
+ npm install -g ${PACKAGE_NAME}@${target}`
2673
+ );
2674
+ }
2675
+ console.log(`Updating ${PACKAGE_NAME}: ${current} \u2192 ${target}`);
2676
+ console.log(`$ ${install.command} ${install.args.join(" ")}`);
2677
+ const result = spawnSync(install.command, install.args, { stdio: "inherit" });
2678
+ if (result.error) {
2679
+ const code = result.error.code;
2680
+ const hint = code === "ENOENT" ? ` ('${install.command}' was not found on PATH).` : `: ${result.error.message}`;
2681
+ throw new Error(`Could not run the update${hint}
2682
+ Update manually:
2683
+ ${install.command} ${install.args.join(" ")}`);
2684
+ }
2685
+ if (typeof result.status === "number" && result.status !== 0) {
2686
+ throw new Error(
2687
+ `Update failed (${install.command} exited with code ${result.status}).
2688
+ You can retry manually:
2689
+ ${install.command} ${install.args.join(" ")}`
2690
+ );
2691
+ }
2692
+ console.log(`Updated to ${target}. Run \`verentis --version\` to confirm.`);
2693
+ });
2694
+ program2.command("__update-check", { hidden: true }).description("(internal) refresh the cached latest-version marker").action(async () => {
2695
+ await runUpdateCheckRefresh();
2696
+ });
2697
+ }
2698
+ async function resolveTarget(requested) {
2699
+ if (requested) return requested.replace(/^v/i, "");
2700
+ const latest = await fetchLatestVersion({ timeoutMs: 8e3 });
2701
+ if (!latest) {
2702
+ throw new Error(
2703
+ "Could not reach the package registry to determine the latest version. Check your connection, or point VERENTIS_UPDATE_REGISTRY at a reachable registry."
2704
+ );
2705
+ }
2706
+ await writeUpdateState({ lastCheckAt: (/* @__PURE__ */ new Date()).toISOString(), latestVersion: latest }).catch(() => {
2707
+ });
2708
+ return latest;
2709
+ }
2710
+ function reportCheck(current, target, pinned) {
2711
+ if (pinned) {
2712
+ console.log(`Current version: ${current}`);
2713
+ console.log(`Requested version: ${target}`);
2714
+ return;
2715
+ }
2716
+ if (isNewerVersion(target, current)) {
2717
+ console.log(`A new version is available: ${current} \u2192 ${target}`);
2718
+ console.log("Run `verentis update` to install it.");
2719
+ } else {
2720
+ console.log(`You are on the latest version (${current}).`);
2721
+ }
2722
+ }
2723
+ function resolveInstallCommand(spec) {
2724
+ const location = packageLocation();
2725
+ if (!location || !location.replace(/\\/g, "/").includes("/node_modules/")) return null;
2726
+ switch (detectPackageManager(location)) {
2727
+ case "pnpm":
2728
+ return { command: "pnpm", args: ["add", "-g", spec] };
2729
+ case "yarn":
2730
+ return { command: "yarn", args: ["global", "add", spec] };
2731
+ case "bun":
2732
+ return { command: "bun", args: ["add", "-g", spec] };
2733
+ default:
2734
+ return { command: "npm", args: ["install", "-g", spec] };
2735
+ }
2736
+ }
2737
+ function packageLocation() {
2738
+ try {
2739
+ return fileURLToPath(import.meta.url);
2740
+ } catch {
2741
+ return process.argv[1];
2742
+ }
2743
+ }
2744
+ function detectPackageManager(location) {
2745
+ const path = location.replace(/\\/g, "/");
2746
+ if (/\/(\.pnpm|pnpm)\//.test(path)) return "pnpm";
2747
+ if (/\/(\.yarn|yarn)\//.test(path)) return "yarn";
2748
+ if (/\/\.bun\//.test(path)) return "bun";
2749
+ return "npm";
2750
+ }
2485
2751
 
2486
2752
  // src/commands/use.ts
2487
2753
  function registerUse(program2) {
@@ -2678,7 +2944,7 @@ function registerWorkspace(program2) {
2678
2944
  }
2679
2945
 
2680
2946
  // src/index.ts
2681
- var program = new Command().name("verentis").description("Verentis CLI \u2014 admin, marketplace publishing, and the engine/app developer loop").version("0.2.0");
2947
+ var program = new Command().name("verentis").description("Verentis CLI \u2014 admin, marketplace publishing, and the engine/app developer loop").version(getCliVersion());
2682
2948
  registerLogin(program);
2683
2949
  registerUse(program);
2684
2950
  registerAccount(program);
@@ -2697,13 +2963,20 @@ registerPack(program);
2697
2963
  registerRegistry(program);
2698
2964
  registerPackage(program);
2699
2965
  registerApi(program);
2700
- program.parseAsync().catch((error) => {
2701
- if (error instanceof ApiError || error instanceof Error) {
2702
- console.error(`Error: ${error.message}`);
2703
- } else {
2704
- console.error(error);
2966
+ registerUpdate(program);
2967
+ async function main() {
2968
+ try {
2969
+ await program.parseAsync();
2970
+ } catch (error) {
2971
+ if (error instanceof ApiError || error instanceof Error) {
2972
+ console.error(`Error: ${error.message}`);
2973
+ } else {
2974
+ console.error(error);
2975
+ }
2976
+ process.exitCode = 1;
2705
2977
  }
2706
- process.exitCode = 1;
2707
- });
2978
+ await notifyOnExit(process.argv);
2979
+ }
2980
+ void main();
2708
2981
  //# sourceMappingURL=index.js.map
2709
2982
  //# sourceMappingURL=index.js.map