iterate-harness 1.12.4 → 1.12.6
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 +90 -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,19 @@ 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
|
+
return path.join(homeDir, TARBALL_CACHE_DIR_NAME, `iterate-harness-${version}${ext}`);
|
|
130
165
|
}
|
|
131
166
|
|
|
132
167
|
function pipInstallArgs(target) {
|
|
@@ -225,16 +260,29 @@ function runStep(command, args, options) {
|
|
|
225
260
|
}
|
|
226
261
|
}
|
|
227
262
|
|
|
263
|
+
// Preferred install URL: the pre-built wheel pinned to this npm version.
|
|
264
|
+
// An explicit env override always wins (e.g. a local dev wheel or git ref).
|
|
228
265
|
function installUrl(version, env) {
|
|
229
266
|
const override = env ? env[INSTALL_URL_ENV_VAR] : undefined;
|
|
230
267
|
if (override) {
|
|
231
268
|
return override;
|
|
232
269
|
}
|
|
270
|
+
return wheelAssetUrl(version);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Last-resort fallback URL: the pinned source archive. Only relevant when the
|
|
274
|
+
// user did not override the install URL (an explicit override means they own
|
|
275
|
+
// the source and no archive fallback should kick in).
|
|
276
|
+
function installFallbackUrl(version, env) {
|
|
277
|
+
const override = env ? env[INSTALL_URL_ENV_VAR] : undefined;
|
|
278
|
+
if (override) {
|
|
279
|
+
return null;
|
|
280
|
+
}
|
|
233
281
|
return releaseTarballUrl(version);
|
|
234
282
|
}
|
|
235
283
|
|
|
236
284
|
// ---------------------------------------------------------------------------
|
|
237
|
-
//
|
|
285
|
+
// Artifact download fallback (Node-side TLS — survives broken pip trust stores)
|
|
238
286
|
// ---------------------------------------------------------------------------
|
|
239
287
|
|
|
240
288
|
function downloadFile(url, dest, redirectBudget) {
|
|
@@ -353,14 +401,19 @@ async function downloadTarballTo(url, dest, deps) {
|
|
|
353
401
|
}
|
|
354
402
|
|
|
355
403
|
// ---------------------------------------------------------------------------
|
|
356
|
-
// Install chain: pip from URL first, Node-download + local
|
|
404
|
+
// Install chain: pip from URL (pre-built wheel) first, Node-download + local
|
|
405
|
+
// pip as TLS fallback; missing wheel falls back to the source archive.
|
|
357
406
|
// ---------------------------------------------------------------------------
|
|
358
407
|
|
|
359
408
|
async function installHarness(options) {
|
|
360
409
|
const python = options.python;
|
|
361
410
|
const url = options.url;
|
|
411
|
+
const homeDir = options.homeDir;
|
|
412
|
+
const version = options.version;
|
|
362
413
|
const runStepFn = options.runStepFn || runStep;
|
|
363
414
|
const downloader = options.downloader || downloadTarballTo;
|
|
415
|
+
// Optional terminal fallback (e.g. the source archive if the wheel is missing).
|
|
416
|
+
const fallback = options.fallback;
|
|
364
417
|
|
|
365
418
|
try {
|
|
366
419
|
runStepFn(python, pipInstallArgs(url));
|
|
@@ -372,10 +425,17 @@ async function installHarness(options) {
|
|
|
372
425
|
process.stderr.write(
|
|
373
426
|
`[iterate-harness] pip install from ${url} failed; retrying via direct download ...\n`
|
|
374
427
|
);
|
|
375
|
-
const cachePath = downloadCachePath(
|
|
428
|
+
const cachePath = downloadCachePath(homeDir, version, artifactExtensionFor(url));
|
|
376
429
|
try {
|
|
377
430
|
await downloader(url, cachePath);
|
|
378
431
|
} catch (downloadError) {
|
|
432
|
+
if (fallback) {
|
|
433
|
+
process.stderr.write(
|
|
434
|
+
`[iterate-harness] primary artifact unavailable (${downloadError.message}); ` +
|
|
435
|
+
`falling back to source archive ${fallback.url} ...\n`
|
|
436
|
+
);
|
|
437
|
+
return installHarness(fallback);
|
|
438
|
+
}
|
|
379
439
|
throw new BootstrapError(
|
|
380
440
|
`${primaryError.message}\n\n(direct-download fallback also failed: ${downloadError.message})`
|
|
381
441
|
);
|
|
@@ -404,8 +464,18 @@ async function bootstrap(homeDir, version, env) {
|
|
|
404
464
|
}
|
|
405
465
|
|
|
406
466
|
const url = installUrl(version, env);
|
|
407
|
-
|
|
408
|
-
|
|
467
|
+
const fallbackUrl = installFallbackUrl(version, env);
|
|
468
|
+
process.stderr.write(`[iterate-harness] installing iterate-harness ${version} ...\n`);
|
|
469
|
+
await installHarness({
|
|
470
|
+
python: executable.python,
|
|
471
|
+
url,
|
|
472
|
+
homeDir,
|
|
473
|
+
version,
|
|
474
|
+
fallback:
|
|
475
|
+
fallbackUrl != null
|
|
476
|
+
? { python: executable.python, url: fallbackUrl, homeDir, version }
|
|
477
|
+
: undefined,
|
|
478
|
+
});
|
|
409
479
|
|
|
410
480
|
const stampPath = path.join(homeDir, STAMP_FILE_NAME);
|
|
411
481
|
fs.writeFileSync(stampPath, `${version}\n`, "utf8");
|
|
@@ -489,6 +559,7 @@ async function runHarness(args, env) {
|
|
|
489
559
|
|
|
490
560
|
module.exports = {
|
|
491
561
|
BootstrapError,
|
|
562
|
+
DEFAULT_ARTIFACT_EXT,
|
|
492
563
|
MAX_DOWNLOAD_REDIRECTS,
|
|
493
564
|
MIN_PYTHON_MAJOR,
|
|
494
565
|
MIN_PYTHON_MINOR,
|
|
@@ -496,12 +567,15 @@ module.exports = {
|
|
|
496
567
|
HOME_ENV_VAR,
|
|
497
568
|
INSTALL_URL_ENV_VAR,
|
|
498
569
|
SKIP_INSTALL_ENV_VAR,
|
|
570
|
+
WHEEL_ASSET_SUFFIX,
|
|
571
|
+
artifactExtensionFor,
|
|
499
572
|
detectPython,
|
|
500
573
|
curlDownload,
|
|
501
574
|
downloadCachePath,
|
|
502
575
|
downloadFile,
|
|
503
576
|
downloadTarballTo,
|
|
504
577
|
ensureRuntime,
|
|
578
|
+
installFallbackUrl,
|
|
505
579
|
installHarness,
|
|
506
580
|
installUrl,
|
|
507
581
|
isRemoteHttpUrl,
|
|
@@ -515,4 +589,6 @@ module.exports = {
|
|
|
515
589
|
reportBootstrapFailure,
|
|
516
590
|
runHarness,
|
|
517
591
|
venvExecutablePaths,
|
|
592
|
+
wheelAssetName,
|
|
593
|
+
wheelAssetUrl,
|
|
518
594
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "iterate-harness",
|
|
3
|
-
"version": "1.12.
|
|
3
|
+
"version": "1.12.6",
|
|
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",
|