@qpfai/pf-gate-cli 1.0.65-win32-arm64 → 1.0.65

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
@@ -1,15 +1,81 @@
1
- # @qpfai/pf-gate-cli (win32-arm64)
1
+ # @qpfai/pf-gate-cli
2
2
 
3
- Platform runtime payload for PF Gate CLI.
3
+ Global CLI launcher for PF Gate with Codex-style platform runtime packages.
4
4
 
5
- The bundled launcher at `vendor/<target-triple>/pf-gate/` runs PF Gate through Python.
5
+ ## Install
6
6
 
7
- Default install source:
8
- - bundled wheel: `vendor/<target-triple>/python/persons_field-1.0.0-py3-none-any.whl`
7
+ ```bash
8
+ npm i -g @qpfai/pf-gate-cli
9
+ ```
9
10
 
10
- Runtime behavior:
11
- - Uses `PF_GATE_PYTHON` when set.
12
- - Otherwise creates/uses a per-user runtime venv at `~/.pf-gate/runtime/.venv` (or `PF_GATE_RUNTIME_HOME/.venv`).
13
- - Installs from bundled wheel by default.
14
- - Optional override: set `PF_GATE_PIP_SPEC` to force a different install source.
15
- - Starts `persons_field.terminal.launcher` with forwarded arguments.
11
+ ## Use
12
+
13
+ Open the PF Gate terminal UX:
14
+
15
+ ```bash
16
+ pf gate
17
+ ```
18
+
19
+ `pf gate` checks npm for a newer `@qpfai/pf-gate-cli` release on startup and
20
+ auto-installs updates before launching UX.
21
+ To disable this behavior, set `PF_GATE_DISABLE_AUTO_UPDATE=1`.
22
+
23
+ Run direct CLI commands:
24
+
25
+ ```bash
26
+ pf --version
27
+ pf gate --selftest
28
+ ```
29
+
30
+ The launcher resolves a platform package at install time and executes a bundled runtime binary at run time.
31
+ On first run, it bootstraps a per-user runtime environment under `~/.pf-gate/runtime/.venv`.
32
+
33
+ ## Maintainer release flow
34
+
35
+ Set the release version for all npm package manifests:
36
+
37
+ ```bash
38
+ node npm/tools/set-version.mjs 1.0.0
39
+ ```
40
+
41
+ Validate release topology without publishing:
42
+
43
+ ```bash
44
+ node npm/tools/publish-all.mjs --dry-run
45
+ ```
46
+
47
+ `publish-all.mjs` rebuilds `persons_field-1.0.0-py3-none-any.whl` from current source
48
+ and syncs it into every platform package before validation/publish.
49
+
50
+ The dry run warns on placeholder runtime payloads by default.
51
+ To fail dry run on placeholders, use:
52
+
53
+ ```bash
54
+ node npm/tools/publish-all.mjs --dry-run --strict-placeholder-check
55
+ ```
56
+
57
+ Publish all platform variants, then the meta package:
58
+
59
+ ```bash
60
+ node npm/tools/publish-all.mjs --registry=https://registry.npmjs.org/
61
+ ```
62
+
63
+ Release tags used by default:
64
+ - platform payload packages: `platform`
65
+ - meta package: `latest`
66
+
67
+ With npm passkey/WebAuthn 2FA:
68
+
69
+ ```bash
70
+ cd "/Users/nicholashuunguyen/Documents/PF Gate/PF Gate V7"
71
+ node npm/tools/publish-all.mjs --registry=https://registry.npmjs.org/
72
+ npm view @qpfai/pf-gate-cli version --registry=https://registry.npmjs.org/
73
+ ```
74
+
75
+ If npm prompts with `Authenticate your account at ... Press ENTER to open in the browser...`,
76
+ press Enter and approve with your passkey in the browser.
77
+
78
+ Each platform package must contain:
79
+
80
+ - `vendor/<target-triple>/pf-gate/<binary>`
81
+ - optional helper tools in `vendor/<target-triple>/path/`
package/bin/pf.mjs ADDED
@@ -0,0 +1,10 @@
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 ADDED
@@ -0,0 +1,648 @@
1
+ import { spawn, spawnSync } from "node:child_process";
2
+ import { existsSync } from "node:fs";
3
+ import path from "node:path";
4
+ import process from "node:process";
5
+ import { createInterface } from "node:readline";
6
+ import { createRequire } from "node:module";
7
+ import { fileURLToPath } from "node:url";
8
+
9
+ const __filename = fileURLToPath(import.meta.url);
10
+ const __dirname = path.dirname(__filename);
11
+ const PACKAGE_ROOT = path.resolve(__dirname, "..");
12
+ const require = createRequire(import.meta.url);
13
+ const NPM_REGISTRY = "https://registry.npmjs.org/";
14
+ const CLI_PACKAGE_NAME = "@qpfai/pf-gate-cli";
15
+ const CLI_PACKAGE_VERSION = (() => {
16
+ try {
17
+ const packageJson = require(path.join(PACKAGE_ROOT, "package.json"));
18
+ return String(packageJson?.version || "").trim();
19
+ } catch {
20
+ return "";
21
+ }
22
+ })();
23
+
24
+ export function resolveCliPackageVersion() {
25
+ return CLI_PACKAGE_VERSION;
26
+ }
27
+
28
+ function envFlagEnabled(value) {
29
+ return /^(1|true|yes|on)$/i.test(String(value || "").trim());
30
+ }
31
+
32
+ function parseVersion(rawVersion) {
33
+ const normalized = String(rawVersion || "")
34
+ .trim()
35
+ .replace(/^v/i, "");
36
+ const [coreRaw, prereleaseRaw = ""] = normalized.split("-", 2);
37
+ const core = coreRaw
38
+ .split(".")
39
+ .map((segment) => Number.parseInt(segment, 10))
40
+ .map((value) => (Number.isFinite(value) ? value : 0))
41
+ .slice(0, 3);
42
+ while (core.length < 3) {
43
+ core.push(0);
44
+ }
45
+ return {
46
+ core,
47
+ prerelease: prereleaseRaw.trim(),
48
+ };
49
+ }
50
+
51
+ export function compareCliVersions(left, right) {
52
+ const a = parseVersion(left);
53
+ const b = parseVersion(right);
54
+ for (let index = 0; index < 3; index += 1) {
55
+ if (a.core[index] > b.core[index]) {
56
+ return 1;
57
+ }
58
+ if (a.core[index] < b.core[index]) {
59
+ return -1;
60
+ }
61
+ }
62
+ if (!a.prerelease && !b.prerelease) {
63
+ return 0;
64
+ }
65
+ if (!a.prerelease) {
66
+ return 1;
67
+ }
68
+ if (!b.prerelease) {
69
+ return -1;
70
+ }
71
+ return a.prerelease.localeCompare(b.prerelease, undefined, {
72
+ numeric: true,
73
+ sensitivity: "base",
74
+ });
75
+ }
76
+
77
+ function normalizeVersionValue(raw) {
78
+ if (typeof raw === "string") {
79
+ return raw.trim();
80
+ }
81
+ if (Array.isArray(raw) && raw.length > 0 && typeof raw[0] === "string") {
82
+ return raw[0].trim();
83
+ }
84
+ return "";
85
+ }
86
+
87
+ function normalizeDistTagsValue(raw) {
88
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
89
+ return {};
90
+ }
91
+ const parsed = {};
92
+ for (const [key, value] of Object.entries(raw)) {
93
+ const tag = String(key || "").trim();
94
+ const version = String(value || "").trim();
95
+ if (!tag || !version) {
96
+ continue;
97
+ }
98
+ parsed[tag] = version;
99
+ }
100
+ return parsed;
101
+ }
102
+
103
+ export function resolveLatestPublishedVersion(options = {}) {
104
+ const packageManager = options.packageManager || "npm";
105
+ if (packageManager !== "npm") {
106
+ return "";
107
+ }
108
+ const env = options.env || process.env;
109
+ const runSync = options.runSync || spawnSync;
110
+ const timeoutMs = Number(options.timeoutMs || 5000);
111
+ const requestedTag = String(options.tag || "latest").trim() || "latest";
112
+ const packageSpecifier =
113
+ requestedTag === "latest" ? CLI_PACKAGE_NAME : `${CLI_PACKAGE_NAME}@${requestedTag}`;
114
+ const result = runSync(
115
+ "npm",
116
+ ["view", packageSpecifier, "version", "--json", "--registry", NPM_REGISTRY],
117
+ {
118
+ env,
119
+ encoding: "utf-8",
120
+ timeout: timeoutMs,
121
+ },
122
+ );
123
+ if (result.error || (result.status ?? 1) !== 0) {
124
+ return "";
125
+ }
126
+ const stdout = String(result.stdout || "").trim();
127
+ if (!stdout) {
128
+ return "";
129
+ }
130
+ try {
131
+ return normalizeVersionValue(JSON.parse(stdout));
132
+ } catch {
133
+ return normalizeVersionValue(stdout.replace(/^"+|"+$/g, ""));
134
+ }
135
+ }
136
+
137
+ export function resolvePublishedDistTags(options = {}) {
138
+ const packageManager = options.packageManager || "npm";
139
+ if (packageManager !== "npm") {
140
+ return {};
141
+ }
142
+ const env = options.env || process.env;
143
+ const runSync = options.runSync || spawnSync;
144
+ const timeoutMs = Number(options.timeoutMs || 5000);
145
+ const result = runSync(
146
+ "npm",
147
+ ["view", CLI_PACKAGE_NAME, "dist-tags", "--json", "--registry", NPM_REGISTRY],
148
+ {
149
+ env,
150
+ encoding: "utf-8",
151
+ timeout: timeoutMs,
152
+ },
153
+ );
154
+ if (result.error || (result.status ?? 1) !== 0) {
155
+ return {};
156
+ }
157
+ const stdout = String(result.stdout || "").trim();
158
+ if (!stdout) {
159
+ return {};
160
+ }
161
+ try {
162
+ return normalizeDistTagsValue(JSON.parse(stdout));
163
+ } catch {
164
+ return {};
165
+ }
166
+ }
167
+
168
+ export function resolveAutoUpdateTag(options = {}) {
169
+ const env = options.env || process.env;
170
+ const forcedTag = String(options.updateTag || env.PF_GATE_UPDATE_TAG || "").trim();
171
+ if (forcedTag) {
172
+ return forcedTag;
173
+ }
174
+
175
+ const distTags = normalizeDistTagsValue(options.distTags || {});
176
+ const currentVersion = String(options.currentVersion || "").trim();
177
+ if (!currentVersion || !distTags.latest) {
178
+ return "latest";
179
+ }
180
+
181
+ for (const [tag, version] of Object.entries(distTags)) {
182
+ if (version === currentVersion) {
183
+ return tag;
184
+ }
185
+ }
186
+
187
+ if (compareCliVersions(currentVersion, distTags.latest) <= 0) {
188
+ return "latest";
189
+ }
190
+
191
+ let selectedTag = "latest";
192
+ let selectedVersion = "";
193
+ for (const [tag, version] of Object.entries(distTags)) {
194
+ if (tag === "latest") {
195
+ continue;
196
+ }
197
+ if (compareCliVersions(version, currentVersion) <= 0) {
198
+ continue;
199
+ }
200
+ if (!selectedVersion || compareCliVersions(version, selectedVersion) < 0) {
201
+ selectedTag = tag;
202
+ selectedVersion = version;
203
+ }
204
+ }
205
+ return selectedTag;
206
+ }
207
+
208
+ export function maybeAutoUpdateCli(options = {}) {
209
+ const env = options.env || process.env;
210
+ const log =
211
+ typeof options.log === "function"
212
+ ? options.log
213
+ : (message) => process.stderr.write(String(message));
214
+ if (envFlagEnabled(env.PF_GATE_DISABLE_AUTO_UPDATE)) {
215
+ return { updated: false, latestVersion: "" };
216
+ }
217
+ if (envFlagEnabled(env.PF_GATE_AUTO_UPDATE_APPLIED)) {
218
+ return { updated: false, latestVersion: "" };
219
+ }
220
+
221
+ const packageManager = options.packageManager || detectPackageManager(env) || "npm";
222
+ if (packageManager !== "npm") {
223
+ return { updated: false, latestVersion: "" };
224
+ }
225
+
226
+ const currentVersion = String(options.currentVersion || CLI_PACKAGE_VERSION).trim();
227
+ if (!currentVersion) {
228
+ return { updated: false, latestVersion: "" };
229
+ }
230
+
231
+ const runSync = options.runSync || spawnSync;
232
+ const latestVersionOverride = String(options.latestVersionOverride || "").trim();
233
+ const latestVersion =
234
+ latestVersionOverride ||
235
+ resolveLatestPublishedVersion({
236
+ packageManager,
237
+ env,
238
+ runSync,
239
+ timeoutMs: options.timeoutMs,
240
+ tag: options.updateTag,
241
+ });
242
+ if (!latestVersion) {
243
+ return { updated: false, latestVersion: "" };
244
+ }
245
+ if (compareCliVersions(latestVersion, currentVersion) <= 0) {
246
+ return { updated: false, latestVersion };
247
+ }
248
+
249
+ if (typeof options.shouldInstall === "function") {
250
+ const shouldInstall = Boolean(
251
+ options.shouldInstall({
252
+ currentVersion,
253
+ latestVersion,
254
+ packageName: CLI_PACKAGE_NAME,
255
+ }),
256
+ );
257
+ if (!shouldInstall) {
258
+ return { updated: false, latestVersion };
259
+ }
260
+ }
261
+
262
+ const installTarget =
263
+ String(options.installTarget || "").trim() || CLI_PACKAGE_NAME;
264
+ log(`PF Gate launcher: updating ${CLI_PACKAGE_NAME} ${currentVersion} -> ${latestVersion}...\n`);
265
+ const installResult = runSync(
266
+ "npm",
267
+ ["install", "-g", installTarget, "--registry", NPM_REGISTRY],
268
+ {
269
+ env,
270
+ stdio: "inherit",
271
+ },
272
+ );
273
+ if (installResult.error || (installResult.status ?? 1) !== 0) {
274
+ log("PF Gate launcher warning: auto-update failed; continuing with installed version.\n");
275
+ return { updated: false, latestVersion };
276
+ }
277
+ return { updated: true, latestVersion };
278
+ }
279
+
280
+ export function resolveEffectiveCliVersion(options = {}) {
281
+ const env = options.env || process.env;
282
+ const currentVersion = String(options.currentVersion || CLI_PACKAGE_VERSION).trim();
283
+ const existingVersion = String(env.PF_GATE_CLI_VERSION || "").trim();
284
+ if (existingVersion) {
285
+ return existingVersion;
286
+ }
287
+
288
+ const updateResult = options.updateResult || {};
289
+ const updated = Boolean(updateResult.updated);
290
+ const latestVersion = String(updateResult.latestVersion || "").trim();
291
+ if (updated && latestVersion) {
292
+ return latestVersion;
293
+ }
294
+ return currentVersion;
295
+ }
296
+
297
+ export function promptOutputStream(primaryOut = process.stderr, secondaryOut = process.stdout) {
298
+ if (primaryOut?.isTTY) {
299
+ return primaryOut;
300
+ }
301
+ if (secondaryOut?.isTTY) {
302
+ return secondaryOut;
303
+ }
304
+ return primaryOut || secondaryOut;
305
+ }
306
+
307
+ export function isInteractivePrompt(
308
+ streamIn = process.stdin,
309
+ primaryOut = process.stderr,
310
+ secondaryOut = process.stdout,
311
+ ) {
312
+ const output = promptOutputStream(primaryOut, secondaryOut);
313
+ return Boolean(streamIn?.isTTY && output?.isTTY);
314
+ }
315
+
316
+ async function promptYesNo(
317
+ message,
318
+ { streamIn = process.stdin, streamOut = process.stderr, fallbackOut = process.stdout } = {},
319
+ ) {
320
+ const output = promptOutputStream(streamOut, fallbackOut);
321
+ if (!isInteractivePrompt(streamIn, output)) {
322
+ return false;
323
+ }
324
+ return await new Promise((resolve) => {
325
+ const rl = createInterface({
326
+ input: streamIn,
327
+ output,
328
+ });
329
+
330
+ const ask = () => {
331
+ rl.question(`${message} [Y/N]: `, (rawAnswer) => {
332
+ const answer = String(rawAnswer || "").trim().toLowerCase();
333
+ if (answer === "y" || answer === "yes") {
334
+ rl.close();
335
+ resolve(true);
336
+ return;
337
+ }
338
+ if (answer === "n" || answer === "no") {
339
+ rl.close();
340
+ resolve(false);
341
+ return;
342
+ }
343
+ output.write("Please enter Y or N.\n");
344
+ ask();
345
+ });
346
+ };
347
+
348
+ ask();
349
+ });
350
+ }
351
+
352
+ function shouldAutoUpdateForArgs(args) {
353
+ if (!Array.isArray(args) || args.length === 0) {
354
+ return true;
355
+ }
356
+ const command = String(args[0] || "")
357
+ .trim()
358
+ .toLowerCase();
359
+ return command === "gate" || command === "ux" || command === "shell";
360
+ }
361
+
362
+ const PLATFORM_PACKAGE_BY_TARGET = Object.freeze({
363
+ "x86_64-unknown-linux-musl": "@qpfai/pf-gate-cli-linux-x64",
364
+ "aarch64-unknown-linux-musl": "@qpfai/pf-gate-cli-linux-arm64",
365
+ "x86_64-apple-darwin": "@qpfai/pf-gate-cli-darwin-x64",
366
+ "aarch64-apple-darwin": "@qpfai/pf-gate-cli-darwin-arm64",
367
+ "x86_64-pc-windows-msvc": "@qpfai/pf-gate-cli-win32-x64",
368
+ "aarch64-pc-windows-msvc": "@qpfai/pf-gate-cli-win32-arm64",
369
+ });
370
+
371
+ function commandForPackageManager(packageManager) {
372
+ if (packageManager === "bun") {
373
+ return "bun install -g @qpfai/pf-gate-cli";
374
+ }
375
+ return "npm install -g @qpfai/pf-gate-cli";
376
+ }
377
+
378
+ function firstExisting(paths, existsSyncFn) {
379
+ for (const candidate of paths) {
380
+ if (existsSyncFn(candidate)) {
381
+ return candidate;
382
+ }
383
+ }
384
+ return null;
385
+ }
386
+
387
+ export function resolveTargetTriple(platform = process.platform, arch = process.arch) {
388
+ switch (platform) {
389
+ case "linux":
390
+ case "android":
391
+ if (arch === "x64") {
392
+ return "x86_64-unknown-linux-musl";
393
+ }
394
+ if (arch === "arm64") {
395
+ return "aarch64-unknown-linux-musl";
396
+ }
397
+ return null;
398
+ case "darwin":
399
+ if (arch === "x64") {
400
+ return "x86_64-apple-darwin";
401
+ }
402
+ if (arch === "arm64") {
403
+ return "aarch64-apple-darwin";
404
+ }
405
+ return null;
406
+ case "win32":
407
+ if (arch === "x64") {
408
+ return "x86_64-pc-windows-msvc";
409
+ }
410
+ if (arch === "arm64") {
411
+ return "aarch64-pc-windows-msvc";
412
+ }
413
+ return null;
414
+ default:
415
+ return null;
416
+ }
417
+ }
418
+
419
+ export function packageAliasForTarget(targetTriple) {
420
+ return PLATFORM_PACKAGE_BY_TARGET[targetTriple] || null;
421
+ }
422
+
423
+ export function binaryNameCandidates(platform = process.platform) {
424
+ if (platform === "win32") {
425
+ return ["pf-gate.exe", "pf-gate.cmd", "pf-gate.bat"];
426
+ }
427
+ return ["pf-gate"];
428
+ }
429
+
430
+ export function prependPath(existingPath, newDirs, platform = process.platform) {
431
+ const separator = platform === "win32" ? ";" : ":";
432
+ const current = String(existingPath || "")
433
+ .split(separator)
434
+ .filter(Boolean);
435
+ return [...newDirs, ...current].join(separator);
436
+ }
437
+
438
+ export function detectPackageManager(env = process.env, dirnameHint = __dirname) {
439
+ const userAgent = String(env.npm_config_user_agent || "");
440
+ if (/\bbun\//.test(userAgent)) {
441
+ return "bun";
442
+ }
443
+ const execPath = String(env.npm_execpath || "");
444
+ if (execPath.includes("bun")) {
445
+ return "bun";
446
+ }
447
+ if (
448
+ dirnameHint.includes(".bun/install/global") ||
449
+ dirnameHint.includes(".bun\\install\\global")
450
+ ) {
451
+ return "bun";
452
+ }
453
+ if (userAgent) {
454
+ return "npm";
455
+ }
456
+ return null;
457
+ }
458
+
459
+ export function shouldUseShellForBinary(binaryPath, platform = process.platform) {
460
+ if (platform !== "win32") {
461
+ return false;
462
+ }
463
+ return /\.(cmd|bat)$/i.test(binaryPath);
464
+ }
465
+
466
+ export function resolveRuntimeBinary(options = {}) {
467
+ const platform = options.platform || process.platform;
468
+ const arch = options.arch || process.arch;
469
+ const packageRoot = options.packageRoot || PACKAGE_ROOT;
470
+ const existsSyncFn = options.existsSync || existsSync;
471
+ const requireResolve = options.requireResolve || ((specifier) => require.resolve(specifier));
472
+
473
+ const targetTriple = resolveTargetTriple(platform, arch);
474
+ if (!targetTriple) {
475
+ throw new Error(`Unsupported platform: ${platform} (${arch})`);
476
+ }
477
+
478
+ const packageAlias = packageAliasForTarget(targetTriple);
479
+ if (!packageAlias) {
480
+ throw new Error(`Unsupported target triple: ${targetTriple}`);
481
+ }
482
+
483
+ const names = binaryNameCandidates(platform);
484
+ const localVendorRoot = path.join(packageRoot, "vendor");
485
+ const localBinaryCandidates = names.map((name) =>
486
+ path.join(localVendorRoot, targetTriple, "pf-gate", name),
487
+ );
488
+
489
+ let vendorRoot = null;
490
+ try {
491
+ const packageJsonPath = requireResolve(`${packageAlias}/package.json`);
492
+ vendorRoot = path.join(path.dirname(packageJsonPath), "vendor");
493
+ } catch {
494
+ const localBinary = firstExisting(localBinaryCandidates, existsSyncFn);
495
+ if (localBinary) {
496
+ vendorRoot = localVendorRoot;
497
+ }
498
+ }
499
+
500
+ if (!vendorRoot) {
501
+ const packageManager = detectPackageManager();
502
+ const reinstallCommand = commandForPackageManager(packageManager);
503
+ throw new Error(
504
+ `Missing optional dependency ${packageAlias}. Reinstall PF Gate CLI: ${reinstallCommand}`,
505
+ );
506
+ }
507
+
508
+ const binaryCandidates = names.map((name) => path.join(vendorRoot, targetTriple, "pf-gate", name));
509
+ const binaryPath = firstExisting(binaryCandidates, existsSyncFn);
510
+ if (!binaryPath) {
511
+ throw new Error(
512
+ `PF Gate binary missing for ${targetTriple}. Expected one of: ${binaryCandidates.join(", ")}`,
513
+ );
514
+ }
515
+
516
+ const pathDir = path.join(vendorRoot, targetTriple, "path");
517
+ const additionalPathDirs = existsSyncFn(pathDir) ? [pathDir] : [];
518
+ return {
519
+ targetTriple,
520
+ packageAlias,
521
+ vendorRoot,
522
+ binaryPath,
523
+ additionalPathDirs,
524
+ };
525
+ }
526
+
527
+ function forwardSignal(child, signal) {
528
+ if (child.killed) {
529
+ return;
530
+ }
531
+ try {
532
+ child.kill(signal);
533
+ } catch {
534
+ // Ignore child process signaling failures.
535
+ }
536
+ }
537
+
538
+ export async function runCli(args) {
539
+ const env = { ...process.env };
540
+ const packageManager = detectPackageManager(env);
541
+ const effectivePackageManager = packageManager || "npm";
542
+ const shouldAutoUpdate = shouldAutoUpdateForArgs(args);
543
+ const currentVersion = String(CLI_PACKAGE_VERSION || "").trim();
544
+ let updateTag = "latest";
545
+
546
+ let approvedUpdate = true;
547
+ let latestVersionHint = "";
548
+ if (
549
+ shouldAutoUpdate &&
550
+ effectivePackageManager === "npm" &&
551
+ currentVersion &&
552
+ !envFlagEnabled(env.PF_GATE_DISABLE_AUTO_UPDATE) &&
553
+ !envFlagEnabled(env.PF_GATE_AUTO_UPDATE_APPLIED)
554
+ ) {
555
+ const distTags = resolvePublishedDistTags({
556
+ packageManager: effectivePackageManager,
557
+ env,
558
+ runSync: spawnSync,
559
+ });
560
+ updateTag = resolveAutoUpdateTag({
561
+ currentVersion,
562
+ distTags,
563
+ env,
564
+ });
565
+ latestVersionHint = resolveLatestPublishedVersion({
566
+ packageManager: effectivePackageManager,
567
+ env,
568
+ runSync: spawnSync,
569
+ tag: updateTag,
570
+ });
571
+ if (!latestVersionHint && updateTag !== "latest") {
572
+ updateTag = "latest";
573
+ latestVersionHint = resolveLatestPublishedVersion({
574
+ packageManager: effectivePackageManager,
575
+ env,
576
+ runSync: spawnSync,
577
+ tag: updateTag,
578
+ });
579
+ }
580
+ if (latestVersionHint && compareCliVersions(latestVersionHint, currentVersion) > 0) {
581
+ const tagHint = updateTag === "latest" ? "" : ` [channel: ${updateTag}]`;
582
+ approvedUpdate = await promptYesNo(
583
+ `PF Gate launcher: update available ${CLI_PACKAGE_NAME} ${currentVersion} -> ${latestVersionHint}${tagHint}. Update now?`,
584
+ );
585
+ }
586
+ }
587
+
588
+ const installTarget =
589
+ updateTag === "latest" ? CLI_PACKAGE_NAME : `${CLI_PACKAGE_NAME}@${updateTag}`;
590
+ const updateResult = shouldAutoUpdate
591
+ ? maybeAutoUpdateCli({
592
+ currentVersion: CLI_PACKAGE_VERSION,
593
+ packageManager: effectivePackageManager,
594
+ env,
595
+ latestVersionOverride: latestVersionHint,
596
+ updateTag,
597
+ installTarget,
598
+ shouldInstall: () => approvedUpdate,
599
+ })
600
+ : { updated: false, latestVersion: "" };
601
+
602
+ if (updateResult.updated) {
603
+ env.PF_GATE_AUTO_UPDATE_APPLIED = "1";
604
+ }
605
+ env.PF_GATE_CLI_VERSION = resolveEffectiveCliVersion({
606
+ env,
607
+ currentVersion: CLI_PACKAGE_VERSION,
608
+ updateResult,
609
+ });
610
+
611
+ const runtime = resolveRuntimeBinary();
612
+ if (runtime.additionalPathDirs.length > 0) {
613
+ env.PATH = prependPath(env.PATH, runtime.additionalPathDirs);
614
+ }
615
+ const managedByVar = packageManager === "bun" ? "PF_GATE_MANAGED_BY_BUN" : "PF_GATE_MANAGED_BY_NPM";
616
+ env[managedByVar] = "1";
617
+
618
+ const child = spawn(runtime.binaryPath, args, {
619
+ stdio: "inherit",
620
+ env,
621
+ shell: shouldUseShellForBinary(runtime.binaryPath),
622
+ });
623
+
624
+ child.on("error", (error) => {
625
+ console.error(error);
626
+ process.exit(1);
627
+ });
628
+
629
+ for (const signal of ["SIGINT", "SIGTERM", "SIGHUP"]) {
630
+ process.on(signal, () => forwardSignal(child, signal));
631
+ }
632
+
633
+ const childResult = await new Promise((resolve) => {
634
+ child.on("exit", (code, signal) => {
635
+ if (signal) {
636
+ resolve({ type: "signal", signal });
637
+ return;
638
+ }
639
+ resolve({ type: "code", exitCode: code ?? 1 });
640
+ });
641
+ });
642
+
643
+ if (childResult.type === "signal") {
644
+ process.kill(process.pid, childResult.signal);
645
+ return;
646
+ }
647
+ process.exit(childResult.exitCode);
648
+ }
package/package.json CHANGED
@@ -1,22 +1,33 @@
1
1
  {
2
2
  "name": "@qpfai/pf-gate-cli",
3
- "version": "1.0.65-win32-arm64",
4
- "description": "PF Gate runtime payload for win32 arm64.",
3
+ "version": "1.0.65",
4
+ "description": "PF Gate platform launcher with optional native runtime packages.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
7
- "os": [
8
- "win32"
9
- ],
10
- "cpu": [
11
- "arm64"
12
- ],
7
+ "bin": {
8
+ "pf": "bin/pf.mjs",
9
+ "pf-gate": "bin/pf.mjs"
10
+ },
13
11
  "files": [
12
+ "bin",
13
+ "lib",
14
14
  "vendor",
15
15
  "README.md"
16
16
  ],
17
+ "scripts": {
18
+ "test": "node --test tests/*.test.mjs"
19
+ },
17
20
  "engines": {
18
21
  "node": ">=18"
19
22
  },
23
+ "optionalDependencies": {
24
+ "@qpfai/pf-gate-cli-linux-x64": "npm:@qpfai/pf-gate-cli@1.0.65-linux-x64",
25
+ "@qpfai/pf-gate-cli-linux-arm64": "npm:@qpfai/pf-gate-cli@1.0.65-linux-arm64",
26
+ "@qpfai/pf-gate-cli-darwin-x64": "npm:@qpfai/pf-gate-cli@1.0.65-darwin-x64",
27
+ "@qpfai/pf-gate-cli-darwin-arm64": "npm:@qpfai/pf-gate-cli@1.0.65-darwin-arm64",
28
+ "@qpfai/pf-gate-cli-win32-x64": "npm:@qpfai/pf-gate-cli@1.0.65-win32-x64",
29
+ "@qpfai/pf-gate-cli-win32-arm64": "npm:@qpfai/pf-gate-cli@1.0.65-win32-arm64"
30
+ },
20
31
  "publishConfig": {
21
32
  "access": "public"
22
33
  }
@@ -1,145 +0,0 @@
1
- @echo off
2
- setlocal EnableExtensions EnableDelayedExpansion
3
-
4
- set "SCRIPT_DIR=%~dp0"
5
- set "BUNDLED_WHEEL=%SCRIPT_DIR%..\python\persons_field-1.0.0-py3-none-any.whl"
6
- set "RUNTIME_HOME=%PF_GATE_RUNTIME_HOME%"
7
- if "%RUNTIME_HOME%"=="" set "RUNTIME_HOME=%USERPROFILE%\.pf-gate\runtime"
8
- set "WHEEL_STAMP_FILE="
9
- if not "%RUNTIME_HOME%"=="" set "WHEEL_STAMP_FILE=%RUNTIME_HOME%\.bundled-wheel.sha256"
10
- set "SYSTEM_PYTHON="
11
- set "VENV_DIR="
12
- set "IS_MANAGED_VENV=0"
13
-
14
- set "PYTHON_BIN=%PF_GATE_PYTHON%"
15
- if "%PYTHON_BIN%"=="" (
16
- where python >nul 2>nul
17
- if not errorlevel 1 (
18
- set "SYSTEM_PYTHON=python"
19
- ) else (
20
- where py >nul 2>nul
21
- if not errorlevel 1 (
22
- set "SYSTEM_PYTHON=py -3"
23
- )
24
- )
25
-
26
- if "%SYSTEM_PYTHON%"=="" (
27
- echo PF Gate runtime error: Python 3.13+ not found. Install Python or set PF_GATE_PYTHON. 1>&2
28
- exit /b 1
29
- )
30
-
31
- set "VENV_DIR=%RUNTIME_HOME%\.venv"
32
- set "IS_MANAGED_VENV=1"
33
- set "PYTHON_BIN=%VENV_DIR%\Scripts\python.exe"
34
-
35
- if not exist "%PYTHON_BIN%" (
36
- echo PF Gate runtime: creating runtime environment at %VENV_DIR% 1>&2
37
- if not exist "%RUNTIME_HOME%" mkdir "%RUNTIME_HOME%"
38
- call %SYSTEM_PYTHON% -m venv "%VENV_DIR%"
39
- if errorlevel 1 (
40
- echo PF Gate runtime error: failed to create runtime environment at %VENV_DIR% 1>&2
41
- exit /b 1
42
- )
43
- )
44
- )
45
-
46
- set "PIP_TARGET=%PF_GATE_PIP_SPEC%"
47
- if "%PIP_TARGET%"=="" set "PIP_TARGET=%BUNDLED_WHEEL%"
48
- if "%PF_GATE_PIP_SPEC%"=="" (
49
- if not exist "%BUNDLED_WHEEL%" (
50
- echo PF Gate runtime error: missing bundled wheel at %BUNDLED_WHEEL% 1>&2
51
- exit /b 1
52
- )
53
- )
54
-
55
- set "BUNDLED_HASH="
56
- if "%PF_GATE_PIP_SPEC%"=="" (
57
- for /f "skip=1 tokens=* delims=" %%H in ('certutil -hashfile "%BUNDLED_WHEEL%" SHA256 ^| findstr /R /V /C:"hash of file" /C:"CertUtil"') do (
58
- set "line=%%H"
59
- set "line=!line: =!"
60
- if not "!line!"=="" (
61
- set "BUNDLED_HASH=!line!"
62
- goto :hash_done
63
- )
64
- )
65
- )
66
- :hash_done
67
-
68
- set "NEEDS_INSTALL=0"
69
- set "FORCE_REINSTALL=0"
70
- set "INSTALL_REASON=persons_field is missing"
71
-
72
- call %PYTHON_BIN% -c "import persons_field.terminal.launcher" >nul 2>nul
73
- if errorlevel 1 (
74
- set "NEEDS_INSTALL=1"
75
- ) else (
76
- if "%PF_GATE_PIP_SPEC%"=="" if defined BUNDLED_HASH (
77
- set "STORED_HASH="
78
- if defined WHEEL_STAMP_FILE if exist "%WHEEL_STAMP_FILE%" (
79
- set /p STORED_HASH=<"%WHEEL_STAMP_FILE%"
80
- set "STORED_HASH=!STORED_HASH: =!"
81
- )
82
- if /I not "!STORED_HASH!"=="!BUNDLED_HASH!" (
83
- set "NEEDS_INSTALL=1"
84
- set "FORCE_REINSTALL=1"
85
- set "INSTALL_REASON=bundled runtime payload changed"
86
- )
87
- )
88
- )
89
-
90
- if "%NEEDS_INSTALL%"=="1" (
91
- echo PF Gate runtime: !INSTALL_REASON!; installing %PIP_TARGET%... 1>&2
92
- set "FORCE_PIP_REINSTALL=%FORCE_REINSTALL%"
93
- if "!FORCE_REINSTALL!"=="1" if "!IS_MANAGED_VENV!"=="1" (
94
- echo PF Gate runtime: refreshing runtime environment at !VENV_DIR! 1>&2
95
- if exist "!VENV_DIR!" rmdir /s /q "!VENV_DIR!"
96
- if not exist "!RUNTIME_HOME!" mkdir "!RUNTIME_HOME!"
97
- call !SYSTEM_PYTHON! -m venv "!VENV_DIR!"
98
- if errorlevel 1 (
99
- echo PF Gate runtime error: failed to refresh runtime environment at !VENV_DIR! 1>&2
100
- exit /b 1
101
- )
102
- set "PYTHON_BIN=!VENV_DIR!\Scripts\python.exe"
103
- set "FORCE_PIP_REINSTALL=0"
104
- )
105
- call %PYTHON_BIN% -m pip --version >nul 2>nul
106
- if errorlevel 1 (
107
- call %PYTHON_BIN% -m ensurepip --upgrade >nul 2>nul
108
- )
109
-
110
- if "!FORCE_PIP_REINSTALL!"=="1" (
111
- call %PYTHON_BIN% -m pip install --upgrade --force-reinstall --disable-pip-version-check "%PIP_TARGET%"
112
- if errorlevel 1 (
113
- if "!IS_MANAGED_VENV!"=="1" (
114
- echo PF Gate runtime error: failed to install %PIP_TARGET%. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry. 1>&2
115
- exit /b 1
116
- )
117
- call %PYTHON_BIN% -m pip install --user --upgrade --force-reinstall --disable-pip-version-check "%PIP_TARGET%"
118
- if errorlevel 1 (
119
- echo PF Gate runtime error: failed to install %PIP_TARGET%. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry. 1>&2
120
- exit /b 1
121
- )
122
- )
123
- ) else (
124
- call %PYTHON_BIN% -m pip install --upgrade --disable-pip-version-check "%PIP_TARGET%"
125
- if errorlevel 1 (
126
- if "!IS_MANAGED_VENV!"=="1" (
127
- echo PF Gate runtime error: failed to install %PIP_TARGET%. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry. 1>&2
128
- exit /b 1
129
- )
130
- call %PYTHON_BIN% -m pip install --user --upgrade --disable-pip-version-check "%PIP_TARGET%"
131
- if errorlevel 1 (
132
- echo PF Gate runtime error: failed to install %PIP_TARGET%. Set PF_GATE_PYTHON and PF_GATE_PIP_SPEC, then retry. 1>&2
133
- exit /b 1
134
- )
135
- )
136
- )
137
- )
138
-
139
- if "%PF_GATE_PIP_SPEC%"=="" if defined BUNDLED_HASH if defined WHEEL_STAMP_FILE (
140
- if not exist "%RUNTIME_HOME%" mkdir "%RUNTIME_HOME%"
141
- >"%WHEEL_STAMP_FILE%" echo %BUNDLED_HASH%
142
- )
143
-
144
- call %PYTHON_BIN% -m persons_field.terminal.launcher %*
145
- exit /b %ERRORLEVEL%