@cartanova/qgrid-cli 2.3.9 → 2.4.0

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/cli.js CHANGED
@@ -1,9 +1,58 @@
1
1
  #!/usr/bin/env node
2
2
  import { execFileSync, execSync, spawnSync } from "node:child_process";
3
3
  import { existsSync, readFileSync } from "node:fs";
4
- import { dirname, join } from "node:path";
4
+ import { delimiter, dirname, join } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { Command } from "commander";
7
+ //#region src/self-update.ts
8
+ function detectSelfUpdatePackageManager(qgridPath, userAgent = process.env.npm_config_user_agent) {
9
+ return userAgent?.includes("pnpm") || qgridPath.includes("pnpm") ? "pnpm" : "npm";
10
+ }
11
+ function parsePnpmGlobalBinDir(output) {
12
+ return output.match(/configured global bin directory "([^"]+)"/)?.[1] ?? output.match(/global bin directory "([^"]+)"/)?.[1] ?? null;
13
+ }
14
+ function pnpmGlobalBinDir() {
15
+ const result = spawnSync("pnpm", ["bin", "-g"], { encoding: "utf-8" });
16
+ if (result.status === 0) {
17
+ const value = result.stdout.trim();
18
+ return value.length > 0 ? value : null;
19
+ }
20
+ return parsePnpmGlobalBinDir(`${result.stdout}\n${result.stderr}`);
21
+ }
22
+ function withPrependedPath(env, pathEntry) {
23
+ if (!pathEntry) return env;
24
+ const parts = (env.PATH ?? "").split(delimiter).filter(Boolean);
25
+ if (parts.includes(pathEntry)) return env;
26
+ return {
27
+ ...env,
28
+ PATH: [pathEntry, ...parts].join(delimiter)
29
+ };
30
+ }
31
+ function selfUpdateInstallCommand(packageManager, packageName, version) {
32
+ const exactPackage = `${packageName}@${version}`;
33
+ return packageManager === "pnpm" ? {
34
+ command: "pnpm",
35
+ args: [
36
+ "add",
37
+ "-g",
38
+ exactPackage,
39
+ "--save-exact"
40
+ ]
41
+ } : {
42
+ command: "npm",
43
+ args: [
44
+ "i",
45
+ "-g",
46
+ exactPackage
47
+ ]
48
+ };
49
+ }
50
+ function resolveQgridRestartCommand(packageManager, globalBinDir) {
51
+ if (packageManager !== "pnpm" || !globalBinDir) return "qgrid";
52
+ const candidate = join(globalBinDir, process.platform === "win32" ? "qgrid.cmd" : "qgrid");
53
+ return existsSync(candidate) ? candidate : "qgrid";
54
+ }
55
+ //#endregion
7
56
  //#region src/cli.ts
8
57
  const __dirname = dirname(fileURLToPath(import.meta.url));
9
58
  const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
@@ -18,10 +67,11 @@ const RUNTIME_CLI_DEPENDENCIES = [{
18
67
  label: "Claude Code",
19
68
  missingReason: "Anthropic tokens require Claude Code."
20
69
  }];
21
- function commandVersion(command) {
70
+ function commandVersion(command, env = process.env) {
22
71
  try {
23
72
  return execFileSync(command, ["--version"], {
24
73
  encoding: "utf-8",
74
+ env,
25
75
  stdio: "pipe"
26
76
  }).trim();
27
77
  } catch {
@@ -41,6 +91,16 @@ function packageLatestVersion(packageName) {
41
91
  function parseVersion(output) {
42
92
  return output?.match(/\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?/)?.[0] ?? null;
43
93
  }
94
+ function commandPath(command) {
95
+ try {
96
+ return execFileSync("which", [command], {
97
+ encoding: "utf-8",
98
+ stdio: "pipe"
99
+ }).trim();
100
+ } catch {
101
+ return "";
102
+ }
103
+ }
44
104
  function warnMissingRuntimeCliDependency(dep) {
45
105
  console.warn(`Warning: ${dep.command} CLI not found. ${dep.missingReason}`);
46
106
  console.warn(`Install: npm i -g ${dep.packageName}`);
@@ -97,11 +157,32 @@ program.name("qgrid").version(pkg.version).description("Qgrid — LLM subscripti
97
157
  if (!opts.skipUpdate) {
98
158
  const latest = packageLatestVersion("@cartanova/qgrid-cli");
99
159
  if (latest !== pkg.version) {
100
- const installCmd = process.env.npm_config_user_agent?.includes("pnpm") || execSync("which qgrid", { encoding: "utf-8" }).includes("pnpm") ? "pnpm add -g @cartanova/qgrid-cli@latest" : "npm i -g @cartanova/qgrid-cli@latest";
160
+ const packageManager = detectSelfUpdatePackageManager(commandPath("qgrid"));
161
+ const globalBinDir = packageManager === "pnpm" ? pnpmGlobalBinDir() : null;
162
+ const updateEnv = withPrependedPath(process.env, globalBinDir);
163
+ const installCmd = selfUpdateInstallCommand(packageManager, "@cartanova/qgrid-cli", latest);
101
164
  console.log(`Updating qgrid-cli: ${pkg.version} → ${latest}`);
102
- execSync(installCmd, { stdio: "inherit" });
165
+ const updated = spawnSync(installCmd.command, installCmd.args, {
166
+ env: updateEnv,
167
+ stdio: "inherit"
168
+ });
169
+ if (updated.status !== 0) {
170
+ console.error(`Failed to update qgrid-cli to ${latest}.`);
171
+ console.error(`Fix the global ${packageManager} installation and run qgrid again.`);
172
+ process.exit(updated.status ?? 1);
173
+ }
174
+ const restartCommand = resolveQgridRestartCommand(packageManager, globalBinDir);
175
+ const updatedVersion = parseVersion(commandVersion(restartCommand, updateEnv));
176
+ if (updatedVersion !== latest) {
177
+ console.error(`Failed to run updated qgrid-cli. Expected ${latest}, found ${updatedVersion ?? "unknown"}.`);
178
+ console.error(`Check PATH and global ${packageManager} bin configuration.`);
179
+ process.exit(1);
180
+ }
103
181
  console.log("Updated. Restarting...\n");
104
- const restarted = spawnSync("qgrid", process.argv.slice(2).concat("--skip-update"), { stdio: "inherit" });
182
+ const restarted = spawnSync(restartCommand, process.argv.slice(2).concat("--skip-update"), {
183
+ env: updateEnv,
184
+ stdio: "inherit"
185
+ });
105
186
  process.exit(restarted.status ?? 0);
106
187
  }
107
188
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-cli",
3
- "version": "2.3.9",
3
+ "version": "2.4.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cartanova-ai/qgrid"
@@ -8,6 +8,8 @@ Use this reference for qgrid startup, configuration, and runtime-facing environm
8
8
  - `-p, --port <port>`: server port. Default `44900`. The CLI validates the port and refuses to start if it is already in use.
9
9
  - `--skip-update`: skip qgrid CLI self-update check.
10
10
 
11
+ The self-update check is strict: when npm reports a newer `@cartanova/qgrid-cli`, including patch releases, the CLI installs that exact resolved version and restarts from the updated binary. Do not use a moving `@latest` install target for this path; exact versions avoid stale pnpm global project ranges.
12
+
11
13
  The CLI also ensures runtime CLIs are installed/current enough:
12
14
 
13
15
  - `codex` from `@openai/codex`, required for OpenAI tokens.