@qpfai/pf-gate-cli 1.0.0 → 1.0.2

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "generatedAt": "2026-02-16T18:51:30.931Z",
3
+ "generatedAt": "2026-02-16T19:11:19.187Z",
4
4
  "pythonPackage": "persons-field",
5
5
  "pythonPackageVersion": "1.0.0",
6
6
  "wheel": "persons_field-1.0.0-py3-none-any.whl",
7
- "sha256": "f16ad52f913ea806e8d40a4f49c3a92f50f2708ecce67e7ceaf37b492ed0d793"
7
+ "sha256": "30ef4c0fdd6ada01a9edcdffbacf3a6a3d7c0e676ab1e13670362594caa84b7f"
8
8
  }
package/lib/main.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  import { spawnSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
  import fs from "node:fs";
4
+ import https from "node:https";
4
5
  import os from "node:os";
5
6
  import path from "node:path";
6
7
  import process from "node:process";
8
+ import readline from "node:readline/promises";
7
9
  import { fileURLToPath } from "node:url";
8
10
 
9
11
  const __filename = fileURLToPath(import.meta.url);
@@ -12,6 +14,7 @@ const PACKAGE_ROOT = path.resolve(__dirname, "..");
12
14
  const ARTIFACT_MANIFEST_PATH = path.join(PACKAGE_ROOT, "artifacts", "python-wheel.json");
13
15
  const INSTALL_STATE_NAME = "install-state.json";
14
16
  const PYTHON_CANDIDATES = ["python3.13", "python3", "python"];
17
+ const NPM_REGISTRY = "https://registry.npmjs.org";
15
18
 
16
19
  function isWindows() {
17
20
  return process.platform === "win32";
@@ -83,6 +86,139 @@ function runCapture(command, args) {
83
86
  };
84
87
  }
85
88
 
89
+ function parseVersionTriplet(version) {
90
+ const text = String(version || "").trim();
91
+ const [core] = text.split("-");
92
+ const pieces = core.split(".");
93
+ const numbers = [];
94
+ for (let index = 0; index < 3; index += 1) {
95
+ const value = Number.parseInt(pieces[index] || "0", 10);
96
+ numbers.push(Number.isFinite(value) ? value : 0);
97
+ }
98
+ return numbers;
99
+ }
100
+
101
+ export function compareVersions(left, right) {
102
+ const lhs = parseVersionTriplet(left);
103
+ const rhs = parseVersionTriplet(right);
104
+ for (let index = 0; index < 3; index += 1) {
105
+ if (lhs[index] > rhs[index]) {
106
+ return 1;
107
+ }
108
+ if (lhs[index] < rhs[index]) {
109
+ return -1;
110
+ }
111
+ }
112
+ return 0;
113
+ }
114
+
115
+ function fetchJson(url, timeoutMs = 2500) {
116
+ return new Promise((resolve) => {
117
+ const request = https.get(
118
+ url,
119
+ {
120
+ headers: {
121
+ "User-Agent": "pf-gate-cli",
122
+ Accept: "application/json",
123
+ },
124
+ },
125
+ (response) => {
126
+ let body = "";
127
+ response.on("data", (chunk) => {
128
+ body += String(chunk);
129
+ });
130
+ response.on("end", () => {
131
+ if ((response.statusCode || 500) >= 400) {
132
+ resolve(null);
133
+ return;
134
+ }
135
+ try {
136
+ resolve(JSON.parse(body));
137
+ } catch (_error) {
138
+ resolve(null);
139
+ }
140
+ });
141
+ }
142
+ );
143
+ request.setTimeout(timeoutMs, () => {
144
+ request.destroy(new Error("timeout"));
145
+ resolve(null);
146
+ });
147
+ request.on("error", () => resolve(null));
148
+ });
149
+ }
150
+
151
+ async function fetchLatestVersion(packageName) {
152
+ const encoded = encodeURIComponent(packageName);
153
+ const url = `${NPM_REGISTRY}/-/package/${encoded}/dist-tags`;
154
+ const payload = await fetchJson(url);
155
+ if (!payload || typeof payload !== "object") {
156
+ return null;
157
+ }
158
+ const latest = String(payload.latest || "").trim();
159
+ if (!latest) {
160
+ return null;
161
+ }
162
+ return latest;
163
+ }
164
+
165
+ function canPrompt() {
166
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
167
+ }
168
+
169
+ async function promptYesNo(question, defaultYes = true) {
170
+ if (!canPrompt()) {
171
+ return false;
172
+ }
173
+ const prompt = defaultYes ? `${question} [Y/n] ` : `${question} [y/N] `;
174
+ const rl = readline.createInterface({
175
+ input: process.stdin,
176
+ output: process.stdout,
177
+ });
178
+ try {
179
+ const answer = (await rl.question(prompt)).trim().toLowerCase();
180
+ if (!answer) {
181
+ return defaultYes;
182
+ }
183
+ return answer === "y" || answer === "yes";
184
+ } finally {
185
+ rl.close();
186
+ }
187
+ }
188
+
189
+ async function maybeOfferCliUpdate(metadata) {
190
+ if (String(process.env.PF_GATE_DISABLE_UPDATE_CHECK || "").trim() === "1") {
191
+ return false;
192
+ }
193
+ const latestVersion = await fetchLatestVersion(metadata.name);
194
+ if (!latestVersion) {
195
+ return false;
196
+ }
197
+ if (compareVersions(latestVersion, metadata.version) <= 0) {
198
+ return false;
199
+ }
200
+ console.log(
201
+ `Update available: ${metadata.name} ${metadata.version} -> ${latestVersion}`
202
+ );
203
+ const wantsUpdate = await promptYesNo("Install update now?", true);
204
+ if (!wantsUpdate) {
205
+ console.log(`Continue with ${metadata.version}.`);
206
+ return false;
207
+ }
208
+ console.log(`Updating ${metadata.name}...`);
209
+ const result = spawnSync(
210
+ "npm",
211
+ ["i", "-g", `${metadata.name}@latest`, "--registry", NPM_REGISTRY],
212
+ { stdio: "inherit" }
213
+ );
214
+ if (result.error || (result.status ?? 1) !== 0) {
215
+ console.log("Update failed. Continuing with current version.");
216
+ return false;
217
+ }
218
+ console.log("Update installed. Restart PF Gate to use the new version.");
219
+ return true;
220
+ }
221
+
86
222
  function resolvePython() {
87
223
  const envPython = (process.env.PF_GATE_PYTHON || "").trim();
88
224
  const candidates = envPython ? [envPython, ...PYTHON_CANDIDATES] : PYTHON_CANDIDATES;
@@ -213,6 +349,7 @@ function installRuntime(runtimeRoot, source, cliVersion) {
213
349
  ...runtimePipArgs(runtimeRoot),
214
350
  "install",
215
351
  "--upgrade",
352
+ "--force-reinstall",
216
353
  source.wheelPath,
217
354
  ]);
218
355
  } else {
@@ -261,6 +398,10 @@ export async function runCli(args) {
261
398
  console.log(`args: ${JSON.stringify(args)}`);
262
399
  return;
263
400
  }
401
+ const updated = await maybeOfferCliUpdate(metadata);
402
+ if (updated) {
403
+ process.exit(0);
404
+ }
264
405
  const runtimeRoot = ensureRuntimeInstalled(metadata.version);
265
406
  const pythonPath = runtimePythonPath(runtimeRoot);
266
407
  const resolvedArgs = args.length === 0 ? ["Gate"] : args;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qpfai/pf-gate-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "PF Gate terminal launcher with first-run runtime bootstrap.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",