probe-research 0.10.5 → 0.11.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/bin/probe-research.js +98 -16
- package/package.json +1 -1
package/bin/probe-research.js
CHANGED
|
@@ -27,6 +27,20 @@ const os = require("node:os");
|
|
|
27
27
|
|
|
28
28
|
const DIST = "probe-research";
|
|
29
29
|
|
|
30
|
+
/**
|
|
31
|
+
* Where the published-version manifest lives. The SessionStart hook already
|
|
32
|
+
* reads this exact endpoint to decide whether to nudge, so the launcher and the
|
|
33
|
+
* nudge cannot disagree about what "latest" means.
|
|
34
|
+
*
|
|
35
|
+
* `PROBE_BASE_URL` is honoured for the same reason the hook honours it: a
|
|
36
|
+
* self-hosted tenant's latest is not this one's, and asking the wrong host
|
|
37
|
+
* would tell them they are behind forever.
|
|
38
|
+
*/
|
|
39
|
+
const MANIFEST_PATH = "/v1/client-version";
|
|
40
|
+
const DEFAULT_BASE = "https://api.research.prbe.ai";
|
|
41
|
+
/** How long a currency check may delay a command that could already run. */
|
|
42
|
+
const MANIFEST_TIMEOUT_MS = 1500;
|
|
43
|
+
|
|
30
44
|
/**
|
|
31
45
|
* The minimum CLI version this launcher needs — NOT this package's own version.
|
|
32
46
|
*
|
|
@@ -41,15 +55,22 @@ const DIST = "probe-research";
|
|
|
41
55
|
* install can never pair old-CLI with new-prose (0066 tags review);
|
|
42
56
|
* 3. a shipped CLI version is KNOWN BROKEN and this is the repair path.
|
|
43
57
|
*
|
|
44
|
-
* (3) is why the floor
|
|
58
|
+
* (3) is why the floor was 0.27.1. 0.26.0 through 0.27.0 crash on every fresh
|
|
45
59
|
* `probe wizard` — a KeyError the moment the plan names auto-update (#120) —
|
|
46
60
|
* and they all cleared the previous 0.24.0 floor, so this launcher happily
|
|
47
61
|
* handed users straight to a CLI that could not complete a setup. `npx
|
|
48
62
|
* probe-research` IS the advertised from-zero entry point; it has to be able
|
|
49
63
|
* to repair the thing it installed. A healthy 0.26.x install pays one refetch,
|
|
50
64
|
* which is the right trade against a broken one staying broken forever.
|
|
65
|
+
*
|
|
66
|
+
* (1) is why it is now 0.36.0. The dashboard's last onboarding step hands out
|
|
67
|
+
* `npx probe-research backfill`, and `probe backfill` does not exist before
|
|
68
|
+
* 0.36.0. Arguments are forwarded to whatever `probe` this launcher resolves,
|
|
69
|
+
* so under the old floor a user with 0.35.0 on PATH gets `No such command
|
|
70
|
+
* 'backfill'` from a command the product just told them to run. A floor is the
|
|
71
|
+
* only thing that can catch it: the copied command is identical either way.
|
|
51
72
|
*/
|
|
52
|
-
const MIN_CLI = "0.
|
|
73
|
+
const MIN_CLI = "0.36.0";
|
|
53
74
|
const UV_INSTALL = "curl -LsSf https://astral.sh/uv/install.sh | sh";
|
|
54
75
|
|
|
55
76
|
/** Compare dotted numeric versions. Returns true when `a` >= `b`. */
|
|
@@ -79,10 +100,43 @@ function installedVersion() {
|
|
|
79
100
|
return m ? m[1] : null;
|
|
80
101
|
}
|
|
81
102
|
|
|
103
|
+
function baseUrl() {
|
|
104
|
+
const env = process.env.PROBE_BASE_URL;
|
|
105
|
+
if (env && /^https?:\/\//.test(env)) return env.replace(/\/+$/, "");
|
|
106
|
+
return DEFAULT_BASE;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* The newest published CLI, or null if that cannot be established quickly.
|
|
111
|
+
*
|
|
112
|
+
* NULL IS THE IMPORTANT RETURN. Offline, behind a proxy, self-hosted with the
|
|
113
|
+
* route absent, or simply slow -- every one of those must leave a working local
|
|
114
|
+
* install runnable. A currency check that can strand a user offline is worse
|
|
115
|
+
* than the staleness it fixes, so every failure here falls open.
|
|
116
|
+
*/
|
|
117
|
+
async function latestVersion() {
|
|
118
|
+
try {
|
|
119
|
+
const res = await fetch(`${baseUrl()}${MANIFEST_PATH}`, {
|
|
120
|
+
signal: AbortSignal.timeout(MANIFEST_TIMEOUT_MS),
|
|
121
|
+
headers: { accept: "application/json" },
|
|
122
|
+
});
|
|
123
|
+
if (!res.ok) return null;
|
|
124
|
+
const body = await res.json();
|
|
125
|
+
const found = body && body.cli && body.cli.latest;
|
|
126
|
+
return typeof found === "string" && /^\d+(\.\d+)+$/.test(found) ? found : null;
|
|
127
|
+
} catch {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
82
132
|
function has(cmd) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
133
|
+
// ONE shell string, never (command, args[], shell:true): Node deprecated that
|
|
134
|
+
// pairing in DEP0190 because the args are concatenated rather than escaped,
|
|
135
|
+
// and it printed a security warning on every single launcher run. `cmd` is a
|
|
136
|
+
// literal at all three call sites, so there is nothing to escape here -- but
|
|
137
|
+
// a warning users see on the from-zero entry point is its own problem.
|
|
138
|
+
const line = process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`;
|
|
139
|
+
const r = spawnSync(line, { stdio: "ignore", shell: true });
|
|
86
140
|
return r.status === 0;
|
|
87
141
|
}
|
|
88
142
|
|
|
@@ -97,27 +151,50 @@ function run(cmd, args) {
|
|
|
97
151
|
process.exit(r.status === null ? 1 : r.status);
|
|
98
152
|
}
|
|
99
153
|
|
|
100
|
-
function main() {
|
|
154
|
+
async function main() {
|
|
101
155
|
const args = process.argv.slice(2);
|
|
102
156
|
// `npx probe-research` with no arguments runs the wizard — that is the entire
|
|
103
157
|
// reason this package exists, so it should not require remembering a verb.
|
|
104
158
|
const forwarded = args.length ? args : ["wizard"];
|
|
105
159
|
const wanted = MIN_CLI;
|
|
106
160
|
|
|
107
|
-
//
|
|
108
|
-
//
|
|
161
|
+
// Hand off to an existing install only when it is BOTH above the floor and
|
|
162
|
+
// current. The floor alone is not enough, and that was the bug: `npx <tool>`
|
|
163
|
+
// means "run the latest", but a floor is satisfied forever, so anyone who had
|
|
164
|
+
// ever installed the CLI was frozen on it and npx silently never delivered
|
|
165
|
+
// another update.
|
|
166
|
+
//
|
|
167
|
+
// That is the same failure the `--refresh` flag below exists to fix -- uv
|
|
168
|
+
// serving whatever it resolved on day one -- in the other branch. It was
|
|
169
|
+
// fixed there and left standing here.
|
|
170
|
+
let latest = null;
|
|
109
171
|
if (has("probe")) {
|
|
110
172
|
const found = installedVersion();
|
|
111
|
-
if (found && atLeast(found, wanted))
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
173
|
+
if (found && atLeast(found, wanted)) {
|
|
174
|
+
latest = await latestVersion();
|
|
175
|
+
// Unknown latest means run what we have: see latestVersion's contract.
|
|
176
|
+
if (!latest || atLeast(found, latest)) return run("probe", forwarded);
|
|
177
|
+
console.error(
|
|
178
|
+
`probe-research: probe ${found} is behind ${latest}; fetching the latest…`,
|
|
179
|
+
);
|
|
180
|
+
} else {
|
|
181
|
+
console.error(
|
|
182
|
+
`probe-research: installed probe ${found || "(unknown version)"} is older than ` +
|
|
183
|
+
`${wanted}; fetching the latest…`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
116
186
|
}
|
|
117
187
|
// A FLOOR, not an exact pin. Unpinned, `uv tool run --from probe-research`
|
|
118
188
|
// reuses an already-installed 0.8.2 tool; pinned exactly, it demands a PyPI
|
|
119
189
|
// version that may not exist. `>=` fixes both.
|
|
120
|
-
|
|
190
|
+
// Resolve to the newest version we actually know about, not merely the floor.
|
|
191
|
+
// `uv tool run` reuses an installed tool that satisfies the constraint, so a
|
|
192
|
+
// stale 0.46.0 satisfies `>=0.36.0` and uv hands back the very version we
|
|
193
|
+
// just decided was out of date -- the currency check above would print
|
|
194
|
+
// "fetching the latest" and then change nothing. Measured exactly that before
|
|
195
|
+
// this line existed.
|
|
196
|
+
const target = latest && atLeast(latest, wanted) ? latest : wanted;
|
|
197
|
+
const spec = `${DIST}>=${target}`;
|
|
121
198
|
// REFRESH. uv caches the ENVIRONMENT it built for a requirement, so a range
|
|
122
199
|
// like `>=0.10.0` keeps serving whatever it resolved the FIRST time — every
|
|
123
200
|
// user frozen on the version they happened to run on day one, with the
|
|
@@ -155,8 +232,13 @@ function main() {
|
|
|
155
232
|
// because it was resolved before the install ran.
|
|
156
233
|
const uv = `${os.homedir()}/.local/bin/uv`;
|
|
157
234
|
return run(uv, [
|
|
158
|
-
"tool", "run", "--refresh", "--from",
|
|
235
|
+
"tool", "run", "--refresh", "--from", spec, "probe", ...forwarded,
|
|
159
236
|
]);
|
|
160
237
|
}
|
|
161
238
|
|
|
162
|
-
main()
|
|
239
|
+
main().catch((err) => {
|
|
240
|
+
// A throw here would exit 0 under an unhandled rejection on some Node builds,
|
|
241
|
+
// which reads as success to whatever invoked the launcher.
|
|
242
|
+
console.error(`probe-research: ${err && err.message ? err.message : err}`);
|
|
243
|
+
process.exit(1);
|
|
244
|
+
});
|