@verentis/cli 0.2.2 → 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;
@@ -893,14 +894,49 @@ function validateManifest(manifest) {
893
894
  if (!manifestPermissions(manifest).length) warning("spec.permissions is empty \u2014 the run token will carry no VFS scopes");
894
895
  }
895
896
  if (kind === "Application") {
896
- if (!spec.entry && !spec["entry-points"]?.length) {
897
- error("applications need spec.entry or spec.entry-points[]");
897
+ const hasIframe = !!spec.entry || !!spec["entry-points"]?.length;
898
+ const hasInstall = Array.isArray(spec.install) && spec.install.length > 0;
899
+ const hasScopes = Array.isArray(spec.scopes) && spec.scopes.length > 0;
900
+ if (!hasIframe && !hasInstall && !hasScopes) {
901
+ error(
902
+ "an Application must declare at least one contribution: spec.entry / spec.entry-points[] (iframe UI), spec.install[] (installed files), or spec.scopes[] (identity)"
903
+ );
898
904
  }
899
905
  }
906
+ validateInstallDirectives(spec.install, issues);
900
907
  const packaging = manifest.packaging ?? {};
901
908
  if (!packaging.publisher) warning("packaging.publisher is not set \u2014 `verentis pack`/`publish` will refuse the package");
902
909
  return issues;
903
910
  }
911
+ function validateInstallDirectives(install, issues) {
912
+ if (install === void 0) return;
913
+ const error = (message) => issues.push({ level: "error", message });
914
+ if (!Array.isArray(install)) {
915
+ error("spec.install must be a list of install directives");
916
+ return;
917
+ }
918
+ install.forEach((directive, index) => {
919
+ const at = `spec.install[${index}]`;
920
+ if (!directive || typeof directive !== "object") {
921
+ error(`${at} must be an object with 'from' and 'to'`);
922
+ return;
923
+ }
924
+ if (!directive.from || typeof directive.from !== "string") {
925
+ error(`${at}.from is required (a path/glob relative to the package payload)`);
926
+ } else if (directive.from.startsWith("/") || directive.from.split("/").includes("..")) {
927
+ error(`${at}.from must be a relative path inside the package payload (no leading '/' or '..')`);
928
+ }
929
+ if (!directive.to || typeof directive.to !== "string") {
930
+ error(`${at}.to is required (a workspace-absolute destination path)`);
931
+ } else {
932
+ if (!directive.to.startsWith("/")) error(`${at}.to must be a workspace-absolute path (start with '/')`);
933
+ if (directive.to.split("/").includes("..")) error(`${at}.to must not contain '..'`);
934
+ }
935
+ if (directive.mode !== void 0 && directive.mode !== "seed" && directive.mode !== "managed") {
936
+ error(`${at}.mode must be 'seed' or 'managed' (got '${directive.mode}')`);
937
+ }
938
+ });
939
+ }
904
940
  var KIND_MAP = {
905
941
  application: "Application",
906
942
  executionengine: "ExecutionEngine",
@@ -1974,9 +2010,21 @@ async function resolvePayloadFiles(projectDir, manifestPath, manifest) {
1974
2010
  function defaultIncludes(manifest) {
1975
2011
  const sources = /* @__PURE__ */ new Set();
1976
2012
  collectSchemaSources(manifest, sources);
2013
+ collectInstallSources(manifest, sources);
1977
2014
  if (sources.size === 0) return [];
1978
2015
  return [...sources];
1979
2016
  }
2017
+ function collectInstallSources(manifest, sources) {
2018
+ const spec = manifest.spec;
2019
+ const install = spec?.install;
2020
+ if (!Array.isArray(install)) return;
2021
+ for (const directive of install) {
2022
+ const from = directive?.from;
2023
+ if (typeof from === "string" && from.length > 0 && !from.startsWith("/")) {
2024
+ sources.add(from);
2025
+ }
2026
+ }
2027
+ }
1980
2028
  function collectSchemaSources(node, sources) {
1981
2029
  if (Array.isArray(node)) {
1982
2030
  for (const item of node) collectSchemaSources(item, sources);
@@ -2435,6 +2483,271 @@ function registerSettings(program2) {
2435
2483
  console.log(`Setting ${key} deleted.`);
2436
2484
  });
2437
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
+ }
2438
2751
 
2439
2752
  // src/commands/use.ts
2440
2753
  function registerUse(program2) {
@@ -2631,7 +2944,7 @@ function registerWorkspace(program2) {
2631
2944
  }
2632
2945
 
2633
2946
  // src/index.ts
2634
- 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());
2635
2948
  registerLogin(program);
2636
2949
  registerUse(program);
2637
2950
  registerAccount(program);
@@ -2650,13 +2963,20 @@ registerPack(program);
2650
2963
  registerRegistry(program);
2651
2964
  registerPackage(program);
2652
2965
  registerApi(program);
2653
- program.parseAsync().catch((error) => {
2654
- if (error instanceof ApiError || error instanceof Error) {
2655
- console.error(`Error: ${error.message}`);
2656
- } else {
2657
- 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;
2658
2977
  }
2659
- process.exitCode = 1;
2660
- });
2978
+ await notifyOnExit(process.argv);
2979
+ }
2980
+ void main();
2661
2981
  //# sourceMappingURL=index.js.map
2662
2982
  //# sourceMappingURL=index.js.map