iterate-harness 1.12.5 → 1.12.7
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 +9 -5
- package/README.zh-CN.md +5 -2
- package/lib/bootstrap.js +98 -14
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -26,10 +26,14 @@ On first run the wrapper:
|
|
|
26
26
|
overridable via `ITERATE_HARNESS_PYTHON`).
|
|
27
27
|
2. Creates a managed virtualenv at `~/.iterate-harness-npm/venv`
|
|
28
28
|
(overridable via `ITERATE_HARNESS_NPM_HOME`).
|
|
29
|
-
3. `pip install`s the harness **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
3. `pip install`s the harness **pinned to this npm package's version** — npm
|
|
30
|
+
`1.6.0` always installs harness `v1.6.0`. Upgrading the npm package
|
|
31
|
+
automatically re-installs the matching harness version on the next run.
|
|
32
|
+
The preferred artifact is the **pre-built wheel** uploaded to the GitHub
|
|
33
|
+
release (`iterate_harness-<version>-py3-none-any.whl`, which already bundles
|
|
34
|
+
the compiled frontend assets — exactly like iterate-skill-installer ships
|
|
35
|
+
pre-wrapped assets). If the wheel is missing, the wrapper falls back to the
|
|
36
|
+
pinned **source archive** as a last resort.
|
|
33
37
|
4. Delegates to the venv's real `ih` executable with argv, stdio, signals and
|
|
34
38
|
exit codes forwarded.
|
|
35
39
|
|
|
@@ -50,7 +54,7 @@ have Node, so the TUI always works.
|
|
|
50
54
|
| --- | --- |
|
|
51
55
|
| `ITERATE_HARNESS_PYTHON` | Use this interpreter instead of auto-detection |
|
|
52
56
|
| `ITERATE_HARNESS_NPM_HOME` | Runtime dir (default `~/.iterate-harness-npm`) |
|
|
53
|
-
| `ITERATE_HARNESS_INSTALL_URL` | Install from this pip URL instead of the pinned release
|
|
57
|
+
| `ITERATE_HARNESS_INSTALL_URL` | Install from this pip URL instead of the pinned release wheel (e.g. a git branch for testing) |
|
|
54
58
|
| `ITERATE_HARNESS_SKIP_INSTALL=1` | Skip bootstrap entirely; just run the already-installed `ih` |
|
|
55
59
|
|
|
56
60
|
## npx (no global install)
|
package/README.zh-CN.md
CHANGED
|
@@ -25,8 +25,11 @@ harness 本身是**一个 Python 包**。这个 npm 包是一个薄分发包装
|
|
|
25
25
|
可通过 `ITERATE_HARNESS_PYTHON` 覆盖)。
|
|
26
26
|
2. 在 `~/.iterate-harness-npm/venv` 创建托管虚拟环境
|
|
27
27
|
(可通过 `ITERATE_HARNESS_NPM_HOME` 覆盖)。
|
|
28
|
-
3. `pip install` **与本 npm 包版本锁定的** harness
|
|
28
|
+
3. `pip install` **与本 npm 包版本锁定的** harness 发布版本 —— npm 的 `1.6.0`
|
|
29
29
|
总会安装 harness `v1.6.0`。升级 npm 包会在下次运行时自动重装对应的 harness 版本。
|
|
30
|
+
优先下载 GitHub 发布里的**预构建 wheel**(`iterate_harness-<version>-py3-none-any.whl`,
|
|
31
|
+
已内置编译好的前端资源——和 iterate-skill-installer 分发预打包资源一致)。
|
|
32
|
+
若 wheel 缺失,再兑底使用锁定的**源码归档**。
|
|
30
33
|
4. 代理调用 venv 里真正的 `ih` 可执行文件,转发 argv、stdio、信号与退出码。
|
|
31
34
|
|
|
32
35
|
React TUI 的前端依赖(`node_modules`)会在首次启动 TUI 时由 harness 自动安装——
|
|
@@ -44,7 +47,7 @@ npm 用户必然有 Node,所以 TUI 始终可用。
|
|
|
44
47
|
| --- | --- |
|
|
45
48
|
| `ITERATE_HARNESS_PYTHON` | 使用该解释器,而非自动检测 |
|
|
46
49
|
| `ITERATE_HARNESS_NPM_HOME` | 运行时目录(默认 `~/.iterate-harness-npm`) |
|
|
47
|
-
| `ITERATE_HARNESS_INSTALL_URL` | 从这个 pip URL 安装,而非锁定的发布
|
|
50
|
+
| `ITERATE_HARNESS_INSTALL_URL` | 从这个 pip URL 安装,而非锁定的发布 wheel(例如测试用的 git 分支) |
|
|
48
51
|
| `ITERATE_HARNESS_SKIP_INSTALL=1` | 完全跳过引导安装,直接运行已装好的 `ih` |
|
|
49
52
|
|
|
50
53
|
## npx(免全局安装)
|
package/lib/bootstrap.js
CHANGED
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 1. Resolve a Python interpreter >= 3.10 (env override first).
|
|
9
9
|
* 2. Create/reuse the venv at ~/.iterate-harness-npm/venv.
|
|
10
|
-
* 3. pip-install the harness release
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* 3. pip-install the harness release pinned to THIS npm package's version
|
|
11
|
+
* (lockstep: npm 1.12.6 installs harness v1.12.6). The preferred artifact is
|
|
12
|
+
* the pre-built wheel uploaded to the GitHub release (it already contains
|
|
13
|
+
* the compiled frontend assets, exactly like iterate-skill-installer ships
|
|
14
|
+
* pre-wrapped assets). pip-installing the wheel avoids building from source,
|
|
15
|
+
* which previously failed because the source archive has no frontend/web/
|
|
16
|
+
* dist for pyproject.toml's force-include. When pip cannot fetch the wheel
|
|
17
|
+
* (typically a broken Python TLS trust store on macOS python.org builds),
|
|
18
|
+
* the wrapper retries by downloading the wheel with Node's own https stack
|
|
19
|
+
* — and, failing that, with curl (system trust store) — then pip-installs
|
|
20
|
+
* the local file. If the wheel is missing from the release entirely, the
|
|
21
|
+
* wrapper falls back to the pinned source archive as a last resort.
|
|
16
22
|
* 4. Stamp the installed version; a mismatch (npm upgrade) triggers a
|
|
17
23
|
* re-install on the next run.
|
|
18
24
|
* 5. Spawn the venv's `ih` with argv/stdio/signals forwarded and the exit
|
|
@@ -48,7 +54,15 @@ const HOME_ENV_VAR = "ITERATE_HARNESS_NPM_HOME";
|
|
|
48
54
|
const INSTALL_URL_ENV_VAR = "ITERATE_HARNESS_INSTALL_URL";
|
|
49
55
|
const SKIP_INSTALL_ENV_VAR = "ITERATE_HARNESS_SKIP_INSTALL";
|
|
50
56
|
|
|
51
|
-
const
|
|
57
|
+
const HARNESS_GITHUB_REPO = "jingzhao-l/iterate-harness";
|
|
58
|
+
const HARNESS_RELEASE_DOWNLOAD_URL = `https://github.com/${HARNESS_GITHUB_REPO}/releases/download`;
|
|
59
|
+
const HARNESS_REPO_ARCHIVE_URL = `https://github.com/${HARNESS_GITHUB_REPO}/archive/refs/tags`;
|
|
60
|
+
|
|
61
|
+
// Preferred installable artifact: the pre-built wheel (frontend dist baked in).
|
|
62
|
+
const WHEEL_ASSET_SUFFIX = "-py3-none-any.whl";
|
|
63
|
+
// Last-resort artifact: the pinned source archive (pip builds from source).
|
|
64
|
+
const DEFAULT_ARTIFACT_EXT = ".tar.gz";
|
|
65
|
+
const WHEEL_ARTIFACT_EXT = ".whl";
|
|
52
66
|
|
|
53
67
|
const MIN_PYTHON_MAJOR = 3;
|
|
54
68
|
const MIN_PYTHON_MINOR = 10;
|
|
@@ -62,6 +76,16 @@ class BootstrapError extends Error {}
|
|
|
62
76
|
// Pure helpers (unit-tested in test/bootstrap.test.js)
|
|
63
77
|
// ---------------------------------------------------------------------------
|
|
64
78
|
|
|
79
|
+
function wheelAssetName(version) {
|
|
80
|
+
return `iterate_harness-${version}${WHEEL_ASSET_SUFFIX}`;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Preferred installable source: the pre-built wheel (frontend dist baked in).
|
|
84
|
+
function wheelAssetUrl(version) {
|
|
85
|
+
return `${HARNESS_RELEASE_DOWNLOAD_URL}/v${version}/${wheelAssetName(version)}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Last-resort installable source: the pinned source archive (builds on pip).
|
|
65
89
|
function releaseTarballUrl(version) {
|
|
66
90
|
return `${HARNESS_REPO_ARCHIVE_URL}/v${version}.tar.gz`;
|
|
67
91
|
}
|
|
@@ -125,8 +149,27 @@ function isRemoteHttpUrl(target) {
|
|
|
125
149
|
return /^https?:\/\//i.test(String(target || ""));
|
|
126
150
|
}
|
|
127
151
|
|
|
128
|
-
|
|
129
|
-
|
|
152
|
+
// Infer the on-disk extension (for the cache file) from a download URL so
|
|
153
|
+
// pip-installing the local copy keeps a correct .whl / .tar.gz suffix.
|
|
154
|
+
function artifactExtensionFor(url) {
|
|
155
|
+
const match = /\.(tar\.gz|whl)$/i.exec(String(url || ""));
|
|
156
|
+
if (!match) {
|
|
157
|
+
return DEFAULT_ARTIFACT_EXT;
|
|
158
|
+
}
|
|
159
|
+
return match[1].toLowerCase() === "whl" ? WHEEL_ARTIFACT_EXT : DEFAULT_ARTIFACT_EXT;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function downloadCachePath(homeDir, version, extension) {
|
|
163
|
+
const ext = extension || DEFAULT_ARTIFACT_EXT;
|
|
164
|
+
if (ext === WHEEL_ARTIFACT_EXT) {
|
|
165
|
+
// A locally-cached wheel must keep a valid PEP 427 filename (e.g.
|
|
166
|
+
// `iterate_harness-1.12.6-py3-none-any.whl`). pip refuses any `*.whl`
|
|
167
|
+
// whose name lacks the `{python}-{abi}-{platform}` tags, so the fallback
|
|
168
|
+
// cache file reuses the real release asset name rather than a bare
|
|
169
|
+
// `iterate-harness-{version}.whl`.
|
|
170
|
+
return path.join(homeDir, TARBALL_CACHE_DIR_NAME, wheelAssetName(version));
|
|
171
|
+
}
|
|
172
|
+
return path.join(homeDir, TARBALL_CACHE_DIR_NAME, `iterate-harness-${version}${ext}`);
|
|
130
173
|
}
|
|
131
174
|
|
|
132
175
|
function pipInstallArgs(target) {
|
|
@@ -225,16 +268,29 @@ function runStep(command, args, options) {
|
|
|
225
268
|
}
|
|
226
269
|
}
|
|
227
270
|
|
|
271
|
+
// Preferred install URL: the pre-built wheel pinned to this npm version.
|
|
272
|
+
// An explicit env override always wins (e.g. a local dev wheel or git ref).
|
|
228
273
|
function installUrl(version, env) {
|
|
229
274
|
const override = env ? env[INSTALL_URL_ENV_VAR] : undefined;
|
|
230
275
|
if (override) {
|
|
231
276
|
return override;
|
|
232
277
|
}
|
|
278
|
+
return wheelAssetUrl(version);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Last-resort fallback URL: the pinned source archive. Only relevant when the
|
|
282
|
+
// user did not override the install URL (an explicit override means they own
|
|
283
|
+
// the source and no archive fallback should kick in).
|
|
284
|
+
function installFallbackUrl(version, env) {
|
|
285
|
+
const override = env ? env[INSTALL_URL_ENV_VAR] : undefined;
|
|
286
|
+
if (override) {
|
|
287
|
+
return null;
|
|
288
|
+
}
|
|
233
289
|
return releaseTarballUrl(version);
|
|
234
290
|
}
|
|
235
291
|
|
|
236
292
|
// ---------------------------------------------------------------------------
|
|
237
|
-
//
|
|
293
|
+
// Artifact download fallback (Node-side TLS — survives broken pip trust stores)
|
|
238
294
|
// ---------------------------------------------------------------------------
|
|
239
295
|
|
|
240
296
|
function downloadFile(url, dest, redirectBudget) {
|
|
@@ -353,14 +409,19 @@ async function downloadTarballTo(url, dest, deps) {
|
|
|
353
409
|
}
|
|
354
410
|
|
|
355
411
|
// ---------------------------------------------------------------------------
|
|
356
|
-
// Install chain: pip from URL first, Node-download + local
|
|
412
|
+
// Install chain: pip from URL (pre-built wheel) first, Node-download + local
|
|
413
|
+
// pip as TLS fallback; missing wheel falls back to the source archive.
|
|
357
414
|
// ---------------------------------------------------------------------------
|
|
358
415
|
|
|
359
416
|
async function installHarness(options) {
|
|
360
417
|
const python = options.python;
|
|
361
418
|
const url = options.url;
|
|
419
|
+
const homeDir = options.homeDir;
|
|
420
|
+
const version = options.version;
|
|
362
421
|
const runStepFn = options.runStepFn || runStep;
|
|
363
422
|
const downloader = options.downloader || downloadTarballTo;
|
|
423
|
+
// Optional terminal fallback (e.g. the source archive if the wheel is missing).
|
|
424
|
+
const fallback = options.fallback;
|
|
364
425
|
|
|
365
426
|
try {
|
|
366
427
|
runStepFn(python, pipInstallArgs(url));
|
|
@@ -372,10 +433,17 @@ async function installHarness(options) {
|
|
|
372
433
|
process.stderr.write(
|
|
373
434
|
`[iterate-harness] pip install from ${url} failed; retrying via direct download ...\n`
|
|
374
435
|
);
|
|
375
|
-
const cachePath = downloadCachePath(
|
|
436
|
+
const cachePath = downloadCachePath(homeDir, version, artifactExtensionFor(url));
|
|
376
437
|
try {
|
|
377
438
|
await downloader(url, cachePath);
|
|
378
439
|
} catch (downloadError) {
|
|
440
|
+
if (fallback) {
|
|
441
|
+
process.stderr.write(
|
|
442
|
+
`[iterate-harness] primary artifact unavailable (${downloadError.message}); ` +
|
|
443
|
+
`falling back to source archive ${fallback.url} ...\n`
|
|
444
|
+
);
|
|
445
|
+
return installHarness(fallback);
|
|
446
|
+
}
|
|
379
447
|
throw new BootstrapError(
|
|
380
448
|
`${primaryError.message}\n\n(direct-download fallback also failed: ${downloadError.message})`
|
|
381
449
|
);
|
|
@@ -404,8 +472,18 @@ async function bootstrap(homeDir, version, env) {
|
|
|
404
472
|
}
|
|
405
473
|
|
|
406
474
|
const url = installUrl(version, env);
|
|
407
|
-
|
|
408
|
-
|
|
475
|
+
const fallbackUrl = installFallbackUrl(version, env);
|
|
476
|
+
process.stderr.write(`[iterate-harness] installing iterate-harness ${version} ...\n`);
|
|
477
|
+
await installHarness({
|
|
478
|
+
python: executable.python,
|
|
479
|
+
url,
|
|
480
|
+
homeDir,
|
|
481
|
+
version,
|
|
482
|
+
fallback:
|
|
483
|
+
fallbackUrl != null
|
|
484
|
+
? { python: executable.python, url: fallbackUrl, homeDir, version }
|
|
485
|
+
: undefined,
|
|
486
|
+
});
|
|
409
487
|
|
|
410
488
|
const stampPath = path.join(homeDir, STAMP_FILE_NAME);
|
|
411
489
|
fs.writeFileSync(stampPath, `${version}\n`, "utf8");
|
|
@@ -489,6 +567,7 @@ async function runHarness(args, env) {
|
|
|
489
567
|
|
|
490
568
|
module.exports = {
|
|
491
569
|
BootstrapError,
|
|
570
|
+
DEFAULT_ARTIFACT_EXT,
|
|
492
571
|
MAX_DOWNLOAD_REDIRECTS,
|
|
493
572
|
MIN_PYTHON_MAJOR,
|
|
494
573
|
MIN_PYTHON_MINOR,
|
|
@@ -496,12 +575,15 @@ module.exports = {
|
|
|
496
575
|
HOME_ENV_VAR,
|
|
497
576
|
INSTALL_URL_ENV_VAR,
|
|
498
577
|
SKIP_INSTALL_ENV_VAR,
|
|
578
|
+
WHEEL_ASSET_SUFFIX,
|
|
579
|
+
artifactExtensionFor,
|
|
499
580
|
detectPython,
|
|
500
581
|
curlDownload,
|
|
501
582
|
downloadCachePath,
|
|
502
583
|
downloadFile,
|
|
503
584
|
downloadTarballTo,
|
|
504
585
|
ensureRuntime,
|
|
586
|
+
installFallbackUrl,
|
|
505
587
|
installHarness,
|
|
506
588
|
installUrl,
|
|
507
589
|
isRemoteHttpUrl,
|
|
@@ -515,4 +597,6 @@ module.exports = {
|
|
|
515
597
|
reportBootstrapFailure,
|
|
516
598
|
runHarness,
|
|
517
599
|
venvExecutablePaths,
|
|
600
|
+
wheelAssetName,
|
|
601
|
+
wheelAssetUrl,
|
|
518
602
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-harness",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.7",
|
|
4
4
|
"description": "npm distribution wrapper for iterate-harness — installs the Python harness (ih CLI) into a managed venv on first run and exposes it as a global command",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "commonjs",
|