@qpfai/pf-gate-cli 1.0.26 → 2.0.0-darwin-x64
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 +6 -33
- package/package.json +9 -14
- package/vendor/x86_64-apple-darwin/path/.keep +0 -0
- package/vendor/x86_64-apple-darwin/pf-gate/pf-gate +31 -0
- package/artifacts/persons_field-1.0.0-py3-none-any.whl +0 -0
- package/artifacts/python-wheel.json +0 -8
- package/bin/pf.mjs +0 -10
- package/lib/main.mjs +0 -786
package/README.md
CHANGED
|
@@ -1,36 +1,9 @@
|
|
|
1
|
-
# @qpfai/pf-gate-cli
|
|
1
|
+
# @qpfai/pf-gate-cli (darwin-x64)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Platform runtime payload for PF Gate CLI.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The bundled launcher at `vendor/<target-triple>/pf-gate/` runs PF Gate through Python:
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
## Use
|
|
12
|
-
|
|
13
|
-
Open the PF Gate terminal UX:
|
|
14
|
-
|
|
15
|
-
```bash
|
|
16
|
-
pf gate
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
Run direct CLI commands:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
pf --version
|
|
23
|
-
pf gate --selftest
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
On first run, the launcher creates `~/.pf-gate/runtime/.venv` and installs PF Gate.
|
|
27
|
-
|
|
28
|
-
## Maintainer release flow
|
|
29
|
-
|
|
30
|
-
From repo root:
|
|
31
|
-
|
|
32
|
-
```bash
|
|
33
|
-
cd npm/pf-gate-cli
|
|
34
|
-
npm run prepare:python
|
|
35
|
-
npm publish --access public
|
|
36
|
-
```
|
|
7
|
+
- Uses `PF_GATE_PYTHON` when set, else probes `python3` then `python` (Windows also tries `py -3`).
|
|
8
|
+
- Uses `PF_GATE_PIP_SPEC` when set, else installs `persons-field==1.0.0` if missing.
|
|
9
|
+
- Starts `persons_field.terminal.launcher` with forwarded arguments.
|
package/package.json
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qpfai/pf-gate-cli",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "PF Gate
|
|
3
|
+
"version": "2.0.0-darwin-x64",
|
|
4
|
+
"description": "PF Gate runtime payload for darwin x64.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
"os": [
|
|
8
|
+
"darwin"
|
|
9
|
+
],
|
|
10
|
+
"cpu": [
|
|
11
|
+
"x64"
|
|
12
|
+
],
|
|
11
13
|
"files": [
|
|
12
|
-
"
|
|
13
|
-
"lib",
|
|
14
|
-
"artifacts",
|
|
14
|
+
"vendor",
|
|
15
15
|
"README.md"
|
|
16
16
|
],
|
|
17
|
-
"scripts": {
|
|
18
|
-
"prepare:python": "node scripts/prepare-python-wheel.mjs",
|
|
19
|
-
"prepack": "npm run prepare:python",
|
|
20
|
-
"test": "node --test tests/*.test.mjs"
|
|
21
|
-
},
|
|
22
17
|
"engines": {
|
|
23
18
|
"node": ">=18"
|
|
24
19
|
},
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
PYTHON_BIN="${PF_GATE_PYTHON:-}"
|
|
5
|
+
if [ -z "$PYTHON_BIN" ]; then
|
|
6
|
+
if command -v python3 >/dev/null 2>&1; then
|
|
7
|
+
PYTHON_BIN="python3"
|
|
8
|
+
elif command -v python >/dev/null 2>&1; then
|
|
9
|
+
PYTHON_BIN="python"
|
|
10
|
+
else
|
|
11
|
+
echo "PF Gate runtime error: Python 3.13+ not found. Install Python or set PF_GATE_PYTHON." >&2
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
fi
|
|
15
|
+
|
|
16
|
+
PIP_SPEC="${PF_GATE_PIP_SPEC:-persons-field==1.0.0}"
|
|
17
|
+
|
|
18
|
+
if ! "$PYTHON_BIN" -c "import persons_field" >/dev/null 2>&1; then
|
|
19
|
+
echo "PF Gate runtime: persons_field is missing; installing ${PIP_SPEC}..." >&2
|
|
20
|
+
if ! "$PYTHON_BIN" -m pip --version >/dev/null 2>&1; then
|
|
21
|
+
"$PYTHON_BIN" -m ensurepip --upgrade >/dev/null 2>&1 || true
|
|
22
|
+
fi
|
|
23
|
+
if ! "$PYTHON_BIN" -m pip install --upgrade --disable-pip-version-check "$PIP_SPEC"; then
|
|
24
|
+
if ! "$PYTHON_BIN" -m pip install --user --upgrade --disable-pip-version-check "$PIP_SPEC"; then
|
|
25
|
+
echo "PF Gate runtime error: failed to install ${PIP_SPEC}. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry." >&2
|
|
26
|
+
exit 1
|
|
27
|
+
fi
|
|
28
|
+
fi
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
exec "$PYTHON_BIN" -m persons_field.terminal.launcher "$@"
|
|
Binary file
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-02-17T03:13:25.424Z",
|
|
4
|
-
"pythonPackage": "persons-field",
|
|
5
|
-
"pythonPackageVersion": "1.0.0",
|
|
6
|
-
"wheel": "persons_field-1.0.0-py3-none-any.whl",
|
|
7
|
-
"sha256": "0270434fae7427fdc6fc46be62a5cabe660b212bbb4b5c1fb894141a97c88303"
|
|
8
|
-
}
|
package/bin/pf.mjs
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { runCli } from "../lib/main.mjs";
|
|
4
|
-
|
|
5
|
-
runCli(process.argv.slice(2)).catch((error) => {
|
|
6
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
7
|
-
console.error(`PF Gate launcher error: ${message}`);
|
|
8
|
-
process.exit(1);
|
|
9
|
-
});
|
|
10
|
-
|
package/lib/main.mjs
DELETED
|
@@ -1,786 +0,0 @@
|
|
|
1
|
-
import { spawn, spawnSync } from "node:child_process";
|
|
2
|
-
import { createHash } from "node:crypto";
|
|
3
|
-
import fs from "node:fs";
|
|
4
|
-
import https from "node:https";
|
|
5
|
-
import os from "node:os";
|
|
6
|
-
import path from "node:path";
|
|
7
|
-
import process from "node:process";
|
|
8
|
-
import readline from "node:readline/promises";
|
|
9
|
-
import { fileURLToPath } from "node:url";
|
|
10
|
-
|
|
11
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
-
const __dirname = path.dirname(__filename);
|
|
13
|
-
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
14
|
-
const ARTIFACT_MANIFEST_PATH = path.join(PACKAGE_ROOT, "artifacts", "python-wheel.json");
|
|
15
|
-
const INSTALL_STATE_NAME = "install-state.json";
|
|
16
|
-
const PYTHON_CANDIDATES = ["python3.13", "python3", "python"];
|
|
17
|
-
const DEFAULT_NPM_REGISTRY = "https://registry.npmjs.org";
|
|
18
|
-
const UPDATE_CHECK_TIMEOUT_MS = 6000;
|
|
19
|
-
const UPDATE_INSTALL_MAX_RETRIES = 10;
|
|
20
|
-
const UPDATE_INSTALL_RETRY_MS = 3000;
|
|
21
|
-
|
|
22
|
-
function isWindows() {
|
|
23
|
-
return process.platform === "win32";
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function runtimePythonPath(runtimeRoot) {
|
|
27
|
-
if (isWindows()) {
|
|
28
|
-
return path.join(runtimeRoot, ".venv", "Scripts", "python.exe");
|
|
29
|
-
}
|
|
30
|
-
return path.join(runtimeRoot, ".venv", "bin", "python");
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function runtimeVenvPath(runtimeRoot) {
|
|
34
|
-
return path.join(runtimeRoot, ".venv");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function runtimeBinDir(runtimeRoot) {
|
|
38
|
-
if (isWindows()) {
|
|
39
|
-
return path.join(runtimeRoot, ".venv", "Scripts");
|
|
40
|
-
}
|
|
41
|
-
return path.join(runtimeRoot, ".venv", "bin");
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function removeStaleRuntimeEntryPoints(runtimeRoot) {
|
|
45
|
-
const binDir = runtimeBinDir(runtimeRoot);
|
|
46
|
-
const candidates = isWindows()
|
|
47
|
-
? ["pf.exe", "pf.cmd", "PF.exe", "PF.cmd", "pf-gate.exe", "pf-gate.cmd"]
|
|
48
|
-
: ["pf", "PF", "pf-gate"];
|
|
49
|
-
for (const name of candidates) {
|
|
50
|
-
try {
|
|
51
|
-
fs.rmSync(path.join(binDir, name), { force: true });
|
|
52
|
-
} catch (_error) {
|
|
53
|
-
// best-effort cleanup only.
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function runtimePipArgs(runtimeRoot) {
|
|
59
|
-
return ["-m", "pip"];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function runtimeLauncherArgs(args) {
|
|
63
|
-
return ["-m", "persons_field.terminal.launcher", ...args];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function shouldOpenUx(args) {
|
|
67
|
-
if (args.length === 0) {
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
70
|
-
if (args.length !== 1) {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
const first = String(args[0] || "").trim().toLowerCase();
|
|
74
|
-
return first === "gate" || first === "ux" || first === "shell";
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function loadPackageMetadata() {
|
|
78
|
-
const raw = fs.readFileSync(path.join(PACKAGE_ROOT, "package.json"), "utf-8");
|
|
79
|
-
const payload = JSON.parse(raw);
|
|
80
|
-
return {
|
|
81
|
-
name: String(payload.name || "@pfgate/cli"),
|
|
82
|
-
version: String(payload.version || "0.0.0"),
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function ensureDir(dirPath) {
|
|
87
|
-
fs.mkdirSync(dirPath, { recursive: true });
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function runChecked(command, args, options = {}) {
|
|
91
|
-
const result = spawnSync(command, args, {
|
|
92
|
-
stdio: "inherit",
|
|
93
|
-
...options,
|
|
94
|
-
});
|
|
95
|
-
if (result.error) {
|
|
96
|
-
throw result.error;
|
|
97
|
-
}
|
|
98
|
-
if ((result.status ?? 1) !== 0) {
|
|
99
|
-
throw new Error(`Command failed (${result.status}): ${command} ${args.join(" ")}`);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function runCheckedCapture(command, args, options = {}) {
|
|
104
|
-
const result = spawnSync(command, args, {
|
|
105
|
-
stdio: "pipe",
|
|
106
|
-
encoding: "utf-8",
|
|
107
|
-
...options,
|
|
108
|
-
});
|
|
109
|
-
if (result.stdout) {
|
|
110
|
-
process.stdout.write(String(result.stdout));
|
|
111
|
-
}
|
|
112
|
-
if (result.stderr) {
|
|
113
|
-
process.stderr.write(String(result.stderr));
|
|
114
|
-
}
|
|
115
|
-
if (result.error) {
|
|
116
|
-
throw result.error;
|
|
117
|
-
}
|
|
118
|
-
if ((result.status ?? 1) !== 0) {
|
|
119
|
-
const combined = `${String(result.stdout || "")}\n${String(result.stderr || "")}`;
|
|
120
|
-
const error = new Error(
|
|
121
|
-
`Command failed (${result.status}): ${command} ${args.join(" ")}`
|
|
122
|
-
);
|
|
123
|
-
error.commandOutput = combined;
|
|
124
|
-
throw error;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function runCapture(command, args) {
|
|
129
|
-
const result = spawnSync(command, args, {
|
|
130
|
-
stdio: "pipe",
|
|
131
|
-
encoding: "utf-8",
|
|
132
|
-
});
|
|
133
|
-
if (result.error) {
|
|
134
|
-
return { ok: false, output: "" };
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
ok: (result.status ?? 1) === 0,
|
|
138
|
-
output: String(result.stdout || "").trim(),
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
function sleep(ms) {
|
|
143
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function startSphereSpinner(message) {
|
|
147
|
-
if (process.stdout.isTTY) {
|
|
148
|
-
process.stdout.write(`${message}\n`);
|
|
149
|
-
}
|
|
150
|
-
return (doneMessage = `✓ ${message}`) => {
|
|
151
|
-
if (process.stdout.isTTY && doneMessage) {
|
|
152
|
-
process.stdout.write(`${doneMessage}\n`);
|
|
153
|
-
}
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
function spawnCapture(command, args, options = {}) {
|
|
158
|
-
return new Promise((resolve) => {
|
|
159
|
-
const child = spawn(command, args, {
|
|
160
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
161
|
-
...options,
|
|
162
|
-
});
|
|
163
|
-
let stdout = "";
|
|
164
|
-
let stderr = "";
|
|
165
|
-
if (child.stdout) {
|
|
166
|
-
child.stdout.on("data", (chunk) => {
|
|
167
|
-
stdout += String(chunk);
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
if (child.stderr) {
|
|
171
|
-
child.stderr.on("data", (chunk) => {
|
|
172
|
-
stderr += String(chunk);
|
|
173
|
-
});
|
|
174
|
-
}
|
|
175
|
-
child.on("error", (error) => {
|
|
176
|
-
resolve({ status: 1, error, stdout, stderr });
|
|
177
|
-
});
|
|
178
|
-
child.on("close", (code) => {
|
|
179
|
-
resolve({ status: code ?? 1, error: null, stdout, stderr });
|
|
180
|
-
});
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
async function runCommandWithSphereSpinner(command, args, message, options = {}) {
|
|
185
|
-
const { failureMessage, successMessage, ...spawnOptions } = options;
|
|
186
|
-
const stop = startSphereSpinner(message);
|
|
187
|
-
const result = await spawnCapture(command, args, spawnOptions);
|
|
188
|
-
if (result.error || (result.status ?? 1) !== 0) {
|
|
189
|
-
stop(failureMessage || `… ${message}`);
|
|
190
|
-
const error =
|
|
191
|
-
result.error ||
|
|
192
|
-
new Error(`Command failed (${result.status}): ${command} ${args.join(" ")}`);
|
|
193
|
-
error.commandOutput = `${String(result.stdout || "")}\n${String(result.stderr || "")}`;
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
196
|
-
stop(successMessage || `✓ ${message}`);
|
|
197
|
-
return result;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function parseVersionTriplet(version) {
|
|
201
|
-
const text = String(version || "").trim();
|
|
202
|
-
const [core] = text.split("-");
|
|
203
|
-
const pieces = core.split(".");
|
|
204
|
-
const numbers = [];
|
|
205
|
-
for (let index = 0; index < 3; index += 1) {
|
|
206
|
-
const value = Number.parseInt(pieces[index] || "0", 10);
|
|
207
|
-
numbers.push(Number.isFinite(value) ? value : 0);
|
|
208
|
-
}
|
|
209
|
-
return numbers;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
export function compareVersions(left, right) {
|
|
213
|
-
const lhs = parseVersionTriplet(left);
|
|
214
|
-
const rhs = parseVersionTriplet(right);
|
|
215
|
-
for (let index = 0; index < 3; index += 1) {
|
|
216
|
-
if (lhs[index] > rhs[index]) {
|
|
217
|
-
return 1;
|
|
218
|
-
}
|
|
219
|
-
if (lhs[index] < rhs[index]) {
|
|
220
|
-
return -1;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return 0;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
function configuredNpmRegistry() {
|
|
227
|
-
const fromEnv = String(process.env.PF_GATE_NPM_REGISTRY || "").trim();
|
|
228
|
-
if (fromEnv) {
|
|
229
|
-
return fromEnv.replace(/\/+$/, "");
|
|
230
|
-
}
|
|
231
|
-
return DEFAULT_NPM_REGISTRY;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function debugUpdateEnabled() {
|
|
235
|
-
return String(process.env.PF_GATE_DEBUG_UPDATE_CHECK || "").trim() === "1";
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function debugUpdate(message) {
|
|
239
|
-
if (debugUpdateEnabled()) {
|
|
240
|
-
console.log(`[PF Gate update-check] ${message}`);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
function fetchJson(url, timeoutMs = UPDATE_CHECK_TIMEOUT_MS) {
|
|
245
|
-
return new Promise((resolve) => {
|
|
246
|
-
const request = https.get(
|
|
247
|
-
url,
|
|
248
|
-
{
|
|
249
|
-
headers: {
|
|
250
|
-
"User-Agent": "pf-gate-cli",
|
|
251
|
-
Accept: "application/json",
|
|
252
|
-
},
|
|
253
|
-
},
|
|
254
|
-
(response) => {
|
|
255
|
-
let body = "";
|
|
256
|
-
response.on("data", (chunk) => {
|
|
257
|
-
body += String(chunk);
|
|
258
|
-
});
|
|
259
|
-
response.on("end", () => {
|
|
260
|
-
if ((response.statusCode || 500) >= 400) {
|
|
261
|
-
resolve(null);
|
|
262
|
-
return;
|
|
263
|
-
}
|
|
264
|
-
try {
|
|
265
|
-
resolve(JSON.parse(body));
|
|
266
|
-
} catch (_error) {
|
|
267
|
-
resolve(null);
|
|
268
|
-
}
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
);
|
|
272
|
-
request.setTimeout(timeoutMs, () => {
|
|
273
|
-
request.destroy(new Error("timeout"));
|
|
274
|
-
resolve(null);
|
|
275
|
-
});
|
|
276
|
-
request.on("error", () => resolve(null));
|
|
277
|
-
});
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
export function latestFromDistTags(payload) {
|
|
281
|
-
if (!payload || typeof payload !== "object") {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
const latest = String(payload.latest || "").trim();
|
|
285
|
-
if (!latest) {
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
return latest;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export function parseNpmViewVersion(raw) {
|
|
292
|
-
const text = String(raw || "").trim();
|
|
293
|
-
if (!text) {
|
|
294
|
-
return null;
|
|
295
|
-
}
|
|
296
|
-
try {
|
|
297
|
-
const parsed = JSON.parse(text);
|
|
298
|
-
if (typeof parsed === "string" && parsed.trim()) {
|
|
299
|
-
return parsed.trim();
|
|
300
|
-
}
|
|
301
|
-
if (Array.isArray(parsed) && parsed.length > 0) {
|
|
302
|
-
const first = String(parsed[0] || "").trim();
|
|
303
|
-
return first || null;
|
|
304
|
-
}
|
|
305
|
-
} catch (_error) {
|
|
306
|
-
// npm can return plain text; fall through.
|
|
307
|
-
}
|
|
308
|
-
const firstLine = text.split(/\r?\n/, 1)[0] || "";
|
|
309
|
-
const candidate = firstLine.replace(/^["']|["']$/g, "").trim();
|
|
310
|
-
return candidate || null;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
function fetchLatestVersionFromNpmCli(packageName, registry) {
|
|
314
|
-
const probe = runCapture("npm", [
|
|
315
|
-
"view",
|
|
316
|
-
`${packageName}@latest`,
|
|
317
|
-
"version",
|
|
318
|
-
"--json",
|
|
319
|
-
"--registry",
|
|
320
|
-
registry,
|
|
321
|
-
]);
|
|
322
|
-
if (!probe.ok) {
|
|
323
|
-
return null;
|
|
324
|
-
}
|
|
325
|
-
return parseNpmViewVersion(probe.output);
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
async function fetchLatestVersion(packageName, registry) {
|
|
329
|
-
const encoded = encodeURIComponent(packageName);
|
|
330
|
-
const url = `${registry}/-/package/${encoded}/dist-tags`;
|
|
331
|
-
debugUpdate(`fetch ${url}`);
|
|
332
|
-
const payload = await fetchJson(url);
|
|
333
|
-
const latest = latestFromDistTags(payload);
|
|
334
|
-
if (latest) {
|
|
335
|
-
debugUpdate(`dist-tags latest=${latest}`);
|
|
336
|
-
return latest;
|
|
337
|
-
}
|
|
338
|
-
debugUpdate("dist-tags unavailable; falling back to npm view");
|
|
339
|
-
const fallback = fetchLatestVersionFromNpmCli(packageName, registry);
|
|
340
|
-
if (fallback) {
|
|
341
|
-
debugUpdate(`npm view latest=${fallback}`);
|
|
342
|
-
}
|
|
343
|
-
return fallback;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
function canPrompt() {
|
|
347
|
-
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async function promptYesNo(question, defaultYes = true, promptSuffix = null) {
|
|
351
|
-
if (!canPrompt()) {
|
|
352
|
-
return false;
|
|
353
|
-
}
|
|
354
|
-
let prompt = defaultYes ? `${question} [Y/n] ` : `${question} [y/N] `;
|
|
355
|
-
if (typeof promptSuffix === "string" && promptSuffix.trim()) {
|
|
356
|
-
prompt = `${question} ${promptSuffix.trim()} `;
|
|
357
|
-
}
|
|
358
|
-
const rl = readline.createInterface({
|
|
359
|
-
input: process.stdin,
|
|
360
|
-
output: process.stdout,
|
|
361
|
-
});
|
|
362
|
-
try {
|
|
363
|
-
const answer = (await rl.question(prompt)).trim().toLowerCase();
|
|
364
|
-
if (!answer) {
|
|
365
|
-
return defaultYes;
|
|
366
|
-
}
|
|
367
|
-
return answer === "y" || answer === "yes";
|
|
368
|
-
} finally {
|
|
369
|
-
rl.close();
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
async function promptCliUpdate(metadata, latestVersion) {
|
|
374
|
-
if (!canPrompt()) {
|
|
375
|
-
return false;
|
|
376
|
-
}
|
|
377
|
-
console.clear();
|
|
378
|
-
console.log(
|
|
379
|
-
`Update available: ${metadata.name} ${metadata.version} -> ${latestVersion}`
|
|
380
|
-
);
|
|
381
|
-
return promptYesNo("Install update now?", false, "[Y/N]");
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
function npmGlobalBinDir() {
|
|
385
|
-
const prefix = runCapture("npm", ["prefix", "-g"]);
|
|
386
|
-
if (!prefix.ok) {
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
const root = String(prefix.output || "").trim();
|
|
390
|
-
if (!root) {
|
|
391
|
-
return null;
|
|
392
|
-
}
|
|
393
|
-
return path.join(root, "bin");
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function maybeHealBinCollision(output) {
|
|
397
|
-
const text = String(output || "");
|
|
398
|
-
if (!text.includes("EEXIST")) {
|
|
399
|
-
return false;
|
|
400
|
-
}
|
|
401
|
-
const binDir = npmGlobalBinDir();
|
|
402
|
-
if (!binDir) {
|
|
403
|
-
return false;
|
|
404
|
-
}
|
|
405
|
-
let removed = false;
|
|
406
|
-
for (const name of ["PF", "pf", "pf-gate"]) {
|
|
407
|
-
const candidate = path.join(binDir, name);
|
|
408
|
-
try {
|
|
409
|
-
if (fs.existsSync(candidate)) {
|
|
410
|
-
fs.rmSync(candidate, { force: true });
|
|
411
|
-
removed = true;
|
|
412
|
-
}
|
|
413
|
-
} catch (_error) {
|
|
414
|
-
// keep going and try remaining bins
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
if (removed) {
|
|
418
|
-
console.log("PF Gate updater: removed stale global CLI bin links; retrying install...");
|
|
419
|
-
}
|
|
420
|
-
return removed;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function isEtargetPublishRace(output) {
|
|
424
|
-
const text = String(output || "").toLowerCase();
|
|
425
|
-
return text.includes("code etarget") || text.includes("notarget no matching version");
|
|
426
|
-
}
|
|
427
|
-
|
|
428
|
-
async function npmVersionExists(packageName, version, registry) {
|
|
429
|
-
const result = await spawnCapture(
|
|
430
|
-
"npm",
|
|
431
|
-
["view", `${packageName}@${version}`, "version", "--json", "--registry", registry],
|
|
432
|
-
{ encoding: "utf-8" }
|
|
433
|
-
);
|
|
434
|
-
if (result.error || (result.status ?? 1) !== 0) {
|
|
435
|
-
return false;
|
|
436
|
-
}
|
|
437
|
-
const parsed = parseNpmViewVersion(String(result.stdout || "").trim());
|
|
438
|
-
return parsed === String(version || "").trim();
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
async function installLatestCliPackage(packageName, targetVersion, registry) {
|
|
442
|
-
const args = ["i", "-g", `${packageName}@${targetVersion}`, "--registry", registry];
|
|
443
|
-
const runOnce = async () => {
|
|
444
|
-
const stop = startSphereSpinner("Installing PF Gate update...");
|
|
445
|
-
const result = await spawnCapture("npm", args, { encoding: "utf-8" });
|
|
446
|
-
if (!result.error && (result.status ?? 1) === 0) {
|
|
447
|
-
stop("✓ Installing PF Gate update...");
|
|
448
|
-
} else {
|
|
449
|
-
stop("✗ Installing PF Gate update...");
|
|
450
|
-
}
|
|
451
|
-
return result;
|
|
452
|
-
};
|
|
453
|
-
|
|
454
|
-
let attempts = 0;
|
|
455
|
-
let healedCollision = false;
|
|
456
|
-
let result = null;
|
|
457
|
-
|
|
458
|
-
while (attempts < UPDATE_INSTALL_MAX_RETRIES) {
|
|
459
|
-
attempts += 1;
|
|
460
|
-
const versionReady = await npmVersionExists(packageName, targetVersion, registry);
|
|
461
|
-
if (!versionReady) {
|
|
462
|
-
if (attempts < UPDATE_INSTALL_MAX_RETRIES) {
|
|
463
|
-
const delayMs = UPDATE_INSTALL_RETRY_MS * attempts;
|
|
464
|
-
const seconds = Math.ceil(delayMs / 1000);
|
|
465
|
-
const stopWait = startSphereSpinner(
|
|
466
|
-
`Waiting for ${packageName}@${targetVersion} to be available (${seconds}s)...`
|
|
467
|
-
);
|
|
468
|
-
await sleep(delayMs);
|
|
469
|
-
stopWait("✓ Retrying update install...");
|
|
470
|
-
continue;
|
|
471
|
-
}
|
|
472
|
-
break;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
result = await runOnce();
|
|
476
|
-
if (!result.error && (result.status ?? 1) === 0) {
|
|
477
|
-
return true;
|
|
478
|
-
}
|
|
479
|
-
|
|
480
|
-
const combined = `${String(result.stdout || "")}\n${String(result.stderr || "")}`;
|
|
481
|
-
if (!healedCollision && maybeHealBinCollision(combined)) {
|
|
482
|
-
healedCollision = true;
|
|
483
|
-
continue;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
if (attempts < UPDATE_INSTALL_MAX_RETRIES && isEtargetPublishRace(combined)) {
|
|
487
|
-
const delayMs = UPDATE_INSTALL_RETRY_MS * attempts;
|
|
488
|
-
const seconds = Math.ceil(delayMs / 1000);
|
|
489
|
-
const stopWait = startSphereSpinner(
|
|
490
|
-
`Waiting for npm registry propagation (${seconds}s)...`
|
|
491
|
-
);
|
|
492
|
-
await sleep(delayMs);
|
|
493
|
-
stopWait("✓ Retrying update install...");
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
break;
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
if (result && result.stdout) {
|
|
500
|
-
process.stdout.write(String(result.stdout));
|
|
501
|
-
}
|
|
502
|
-
if (result && result.stderr) {
|
|
503
|
-
process.stderr.write(String(result.stderr));
|
|
504
|
-
}
|
|
505
|
-
return false;
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
async function maybeOfferCliUpdate(metadata) {
|
|
509
|
-
if (String(process.env.PF_GATE_DISABLE_UPDATE_CHECK || "").trim() === "1") {
|
|
510
|
-
debugUpdate("disabled via PF_GATE_DISABLE_UPDATE_CHECK=1");
|
|
511
|
-
return false;
|
|
512
|
-
}
|
|
513
|
-
const registry = configuredNpmRegistry();
|
|
514
|
-
debugUpdate(`current=${metadata.version}, package=${metadata.name}, registry=${registry}`);
|
|
515
|
-
const stopCheck = startSphereSpinner("Checking for updates...");
|
|
516
|
-
const latestVersion = await fetchLatestVersion(metadata.name, registry);
|
|
517
|
-
stopCheck("✓ Checking for updates...");
|
|
518
|
-
if (!latestVersion) {
|
|
519
|
-
debugUpdate("unable to resolve latest version");
|
|
520
|
-
return false;
|
|
521
|
-
}
|
|
522
|
-
if (compareVersions(latestVersion, metadata.version) <= 0) {
|
|
523
|
-
debugUpdate(`up-to-date (latest=${latestVersion})`);
|
|
524
|
-
return false;
|
|
525
|
-
}
|
|
526
|
-
const wantsUpdate = await promptCliUpdate(metadata, latestVersion);
|
|
527
|
-
if (!wantsUpdate) {
|
|
528
|
-
console.log(`Continue with ${metadata.version}.`);
|
|
529
|
-
return false;
|
|
530
|
-
}
|
|
531
|
-
console.log(`Updating ${metadata.name}...`);
|
|
532
|
-
const installed = await installLatestCliPackage(metadata.name, latestVersion, registry);
|
|
533
|
-
if (!installed) {
|
|
534
|
-
console.log("Update failed. Continuing with current version.");
|
|
535
|
-
return false;
|
|
536
|
-
}
|
|
537
|
-
console.log("Update installed. Restart PF Gate to use the new version.");
|
|
538
|
-
return true;
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function resolvePython() {
|
|
542
|
-
const envPython = (process.env.PF_GATE_PYTHON || "").trim();
|
|
543
|
-
const candidates = envPython ? [envPython, ...PYTHON_CANDIDATES] : PYTHON_CANDIDATES;
|
|
544
|
-
for (const candidate of candidates) {
|
|
545
|
-
const probe = runCapture(candidate, ["--version"]);
|
|
546
|
-
if (probe.ok) {
|
|
547
|
-
return candidate;
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
throw new Error(
|
|
551
|
-
"No Python runtime found. Install Python 3.13+ or set PF_GATE_PYTHON to an explicit executable."
|
|
552
|
-
);
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
function sha256File(filePath) {
|
|
556
|
-
const data = fs.readFileSync(filePath);
|
|
557
|
-
return createHash("sha256").update(data).digest("hex");
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
function parseManifest() {
|
|
561
|
-
if (!fs.existsSync(ARTIFACT_MANIFEST_PATH)) {
|
|
562
|
-
return null;
|
|
563
|
-
}
|
|
564
|
-
const raw = fs.readFileSync(ARTIFACT_MANIFEST_PATH, "utf-8");
|
|
565
|
-
const payload = JSON.parse(raw);
|
|
566
|
-
const wheelName = String(payload.wheel || "").trim();
|
|
567
|
-
if (!wheelName) {
|
|
568
|
-
return null;
|
|
569
|
-
}
|
|
570
|
-
const wheelPath = path.join(PACKAGE_ROOT, "artifacts", wheelName);
|
|
571
|
-
if (!fs.existsSync(wheelPath)) {
|
|
572
|
-
return null;
|
|
573
|
-
}
|
|
574
|
-
const expectedSha = String(payload.sha256 || "").trim().toLowerCase();
|
|
575
|
-
if (expectedSha) {
|
|
576
|
-
const actualSha = sha256File(wheelPath);
|
|
577
|
-
if (actualSha !== expectedSha) {
|
|
578
|
-
throw new Error(
|
|
579
|
-
`Wheel checksum mismatch for ${wheelName}. Expected ${expectedSha}, got ${actualSha}.`
|
|
580
|
-
);
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
return {
|
|
584
|
-
kind: "wheel",
|
|
585
|
-
wheelPath,
|
|
586
|
-
wheelName,
|
|
587
|
-
sha256: expectedSha || null,
|
|
588
|
-
packageVersion: String(payload.pythonPackageVersion || "").trim() || null,
|
|
589
|
-
};
|
|
590
|
-
}
|
|
591
|
-
|
|
592
|
-
function resolveInstallSource(cliVersion) {
|
|
593
|
-
const manifestSource = parseManifest();
|
|
594
|
-
if (manifestSource) {
|
|
595
|
-
return manifestSource;
|
|
596
|
-
}
|
|
597
|
-
const pipSpec = (process.env.PF_GATE_PIP_SPEC || `persons-field==${cliVersion}`).trim();
|
|
598
|
-
return {
|
|
599
|
-
kind: "pip",
|
|
600
|
-
pipSpec,
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
function installStatePath(runtimeRoot) {
|
|
605
|
-
return path.join(runtimeRoot, INSTALL_STATE_NAME);
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
function loadInstallState(runtimeRoot) {
|
|
609
|
-
const statePath = installStatePath(runtimeRoot);
|
|
610
|
-
if (!fs.existsSync(statePath)) {
|
|
611
|
-
return null;
|
|
612
|
-
}
|
|
613
|
-
try {
|
|
614
|
-
return JSON.parse(fs.readFileSync(statePath, "utf-8"));
|
|
615
|
-
} catch (_error) {
|
|
616
|
-
return null;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function writeInstallState(runtimeRoot, payload) {
|
|
621
|
-
fs.writeFileSync(
|
|
622
|
-
installStatePath(runtimeRoot),
|
|
623
|
-
JSON.stringify(payload, null, 2) + "\n",
|
|
624
|
-
"utf-8"
|
|
625
|
-
);
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
function desiredStateFingerprint(source, cliVersion) {
|
|
629
|
-
if (source.kind === "wheel") {
|
|
630
|
-
return `wheel:${source.wheelName}:${source.sha256 || "nosha"}:${cliVersion}`;
|
|
631
|
-
}
|
|
632
|
-
return `pip:${source.pipSpec}:${cliVersion}`;
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
function isRuntimeHealthy(runtimeRoot, source, cliVersion) {
|
|
636
|
-
const pythonPath = runtimePythonPath(runtimeRoot);
|
|
637
|
-
if (!fs.existsSync(pythonPath)) {
|
|
638
|
-
return false;
|
|
639
|
-
}
|
|
640
|
-
const state = loadInstallState(runtimeRoot);
|
|
641
|
-
const fingerprint = desiredStateFingerprint(source, cliVersion);
|
|
642
|
-
if (!state || String(state.fingerprint || "") !== fingerprint) {
|
|
643
|
-
return false;
|
|
644
|
-
}
|
|
645
|
-
const probe = runCapture(pythonPath, ["-c", "import persons_field"]);
|
|
646
|
-
return probe.ok;
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
async function ensureVirtualenv(runtimeRoot, bootstrapPython) {
|
|
650
|
-
const pythonPath = runtimePythonPath(runtimeRoot);
|
|
651
|
-
if (fs.existsSync(pythonPath)) {
|
|
652
|
-
return;
|
|
653
|
-
}
|
|
654
|
-
await runCommandWithSphereSpinner(
|
|
655
|
-
bootstrapPython,
|
|
656
|
-
["-m", "venv", path.join(runtimeRoot, ".venv")],
|
|
657
|
-
"Creating runtime environment..."
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
async function recreateVirtualenv(runtimeRoot, bootstrapPython) {
|
|
662
|
-
const venvPath = runtimeVenvPath(runtimeRoot);
|
|
663
|
-
if (fs.existsSync(venvPath)) {
|
|
664
|
-
fs.rmSync(venvPath, { recursive: true, force: true });
|
|
665
|
-
}
|
|
666
|
-
await runCommandWithSphereSpinner(
|
|
667
|
-
bootstrapPython,
|
|
668
|
-
["-m", "venv", venvPath],
|
|
669
|
-
"Recreating runtime environment..."
|
|
670
|
-
);
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
async function installRuntime(runtimeRoot, source, cliVersion) {
|
|
674
|
-
const pythonPath = runtimePythonPath(runtimeRoot);
|
|
675
|
-
const skipPipUpgrade = String(process.env.PF_GATE_SKIP_PIP_UPGRADE || "").trim() === "1";
|
|
676
|
-
removeStaleRuntimeEntryPoints(runtimeRoot);
|
|
677
|
-
if (!skipPipUpgrade) {
|
|
678
|
-
await runCommandWithSphereSpinner(
|
|
679
|
-
pythonPath,
|
|
680
|
-
[...runtimePipArgs(runtimeRoot), "install", "--upgrade", "pip"],
|
|
681
|
-
"Preparing runtime dependencies..."
|
|
682
|
-
);
|
|
683
|
-
}
|
|
684
|
-
if (source.kind === "wheel") {
|
|
685
|
-
await runCommandWithSphereSpinner(
|
|
686
|
-
pythonPath,
|
|
687
|
-
[
|
|
688
|
-
...runtimePipArgs(runtimeRoot),
|
|
689
|
-
"install",
|
|
690
|
-
"--upgrade",
|
|
691
|
-
"--force-reinstall",
|
|
692
|
-
"--ignore-installed",
|
|
693
|
-
source.wheelPath,
|
|
694
|
-
],
|
|
695
|
-
"Installing PF Gate runtime..."
|
|
696
|
-
);
|
|
697
|
-
} else {
|
|
698
|
-
await runCommandWithSphereSpinner(
|
|
699
|
-
pythonPath,
|
|
700
|
-
[
|
|
701
|
-
...runtimePipArgs(runtimeRoot),
|
|
702
|
-
"install",
|
|
703
|
-
"--upgrade",
|
|
704
|
-
"--force-reinstall",
|
|
705
|
-
"--ignore-installed",
|
|
706
|
-
source.pipSpec,
|
|
707
|
-
],
|
|
708
|
-
"Installing PF Gate runtime..."
|
|
709
|
-
);
|
|
710
|
-
}
|
|
711
|
-
await runCommandWithSphereSpinner(
|
|
712
|
-
pythonPath,
|
|
713
|
-
[
|
|
714
|
-
...runtimePipArgs(runtimeRoot),
|
|
715
|
-
"install",
|
|
716
|
-
"--upgrade",
|
|
717
|
-
"--force-reinstall",
|
|
718
|
-
"--ignore-installed",
|
|
719
|
-
"prompt_toolkit>=3.0.39",
|
|
720
|
-
],
|
|
721
|
-
"Installing terminal UX dependencies..."
|
|
722
|
-
);
|
|
723
|
-
const fingerprint = desiredStateFingerprint(source, cliVersion);
|
|
724
|
-
writeInstallState(runtimeRoot, {
|
|
725
|
-
installedAt: new Date().toISOString(),
|
|
726
|
-
cliVersion,
|
|
727
|
-
fingerprint,
|
|
728
|
-
});
|
|
729
|
-
console.log("✓ PF Gate runtime ready.");
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
function resolveRuntimeRoot() {
|
|
733
|
-
const appHome = (process.env.PF_GATE_HOME || "").trim() || path.join(os.homedir(), ".pf-gate");
|
|
734
|
-
return path.join(appHome, "runtime");
|
|
735
|
-
}
|
|
736
|
-
|
|
737
|
-
function dryRunEnabled() {
|
|
738
|
-
return String(process.env.PF_GATE_BOOTSTRAP_DRY_RUN || "").trim() === "1";
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
async function ensureRuntimeInstalled(cliVersion) {
|
|
742
|
-
const runtimeRoot = resolveRuntimeRoot();
|
|
743
|
-
ensureDir(runtimeRoot);
|
|
744
|
-
const source = resolveInstallSource(cliVersion);
|
|
745
|
-
if (!isRuntimeHealthy(runtimeRoot, source, cliVersion)) {
|
|
746
|
-
const bootstrapPython = resolvePython();
|
|
747
|
-
await ensureVirtualenv(runtimeRoot, bootstrapPython);
|
|
748
|
-
try {
|
|
749
|
-
await installRuntime(runtimeRoot, source, cliVersion);
|
|
750
|
-
} catch (error) {
|
|
751
|
-
console.log("PF Gate bootstrap: runtime install failed. Retrying with a clean environment...");
|
|
752
|
-
await recreateVirtualenv(runtimeRoot, bootstrapPython);
|
|
753
|
-
await installRuntime(runtimeRoot, source, cliVersion);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return runtimeRoot;
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
export async function runCli(args) {
|
|
760
|
-
const metadata = loadPackageMetadata();
|
|
761
|
-
if (dryRunEnabled()) {
|
|
762
|
-
const uiMode = shouldOpenUx(args) ? "ux" : "passthrough";
|
|
763
|
-
console.log(`PF Gate launcher dry-run (${uiMode})`);
|
|
764
|
-
console.log(`args: ${JSON.stringify(args)}`);
|
|
765
|
-
return;
|
|
766
|
-
}
|
|
767
|
-
const updated = await maybeOfferCliUpdate(metadata);
|
|
768
|
-
if (updated) {
|
|
769
|
-
process.exit(0);
|
|
770
|
-
}
|
|
771
|
-
const runtimeRoot = await ensureRuntimeInstalled(metadata.version);
|
|
772
|
-
const pythonPath = runtimePythonPath(runtimeRoot);
|
|
773
|
-
const resolvedArgs = args.length === 0 ? ["Gate"] : args;
|
|
774
|
-
const childEnv = {
|
|
775
|
-
...process.env,
|
|
776
|
-
PF_GATE_CLI_VERSION: metadata.version,
|
|
777
|
-
};
|
|
778
|
-
const child = spawnSync(pythonPath, runtimeLauncherArgs(resolvedArgs), {
|
|
779
|
-
stdio: "inherit",
|
|
780
|
-
env: childEnv,
|
|
781
|
-
});
|
|
782
|
-
if (child.error) {
|
|
783
|
-
throw child.error;
|
|
784
|
-
}
|
|
785
|
-
process.exit(child.status ?? 1);
|
|
786
|
-
}
|