@lumi-ai-lab/harness-data 0.0.13 → 0.0.14

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumi-ai-lab/harness-data",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "Installer and updater for Harness Data",
5
5
  "type": "module",
6
6
  "bin": {
@@ -3,7 +3,7 @@ import path from "node:path";
3
3
  import { commandExists, run } from "../lib/exec.js";
4
4
  import { writeLocalConfig, linkAgents } from "../lib/config.js";
5
5
  import { ask, askSecret, chooseAgent } from "../lib/prompt.js";
6
- import { resolveWorkspaceDir, writeState } from "../lib/paths.js";
6
+ import { readUserState, resolveWorkspaceDir, writeState } from "../lib/paths.js";
7
7
  import { installToolsFromManifest, manifestDigest, readManifest } from "../lib/manifest.js";
8
8
  import { binaryName, platformKey } from "../lib/platform.js";
9
9
  import { resolveLatestManifest } from "../lib/tool-release.js";
@@ -16,6 +16,14 @@ import { gitUrls, runGitWithProtocol } from "../lib/git-auth.js";
16
16
  const runtimeRepo = "lumi-ai-lab/harness-data";
17
17
  const wikisRepo = "lumi-ai-lab/harness-data-wikis";
18
18
 
19
+ function readInstallState(runtimeDir) {
20
+ try {
21
+ return JSON.parse(fs.readFileSync(path.join(runtimeDir, ".harness", "installer-state.json"), "utf8"));
22
+ } catch {
23
+ return readUserState();
24
+ }
25
+ }
26
+
19
27
  async function requireCommands(commands) {
20
28
  for (const command of commands) {
21
29
  if (!(await commandExists(command))) throw new Error(`missing required command: ${command}`);
@@ -233,17 +241,18 @@ export async function installCommand(options = {}) {
233
241
  step(3, 8, "安装 CLI 工具");
234
242
  const manifestPath = path.resolve(options.manifest || path.join(runtimeDir, "bootstrap", "cli-manifest.json"));
235
243
  const tokenMode = await hasGithubAuth(options);
244
+ const installState = readInstallState(runtimeDir);
236
245
 
237
246
  let manifest;
238
247
  let localTools = {};
239
248
  if (tokenMode) {
240
249
  manifest = readManifest(manifestPath);
241
250
  const latestManifest = await resolveLatestManifest(manifest, key, options);
242
- manifest = await installToolsFromManifest(runtimeDir, manifestPath, { ...options, manifestOverride: latestManifest });
251
+ manifest = await installToolsFromManifest(runtimeDir, manifestPath, { ...options, state: installState, manifestOverride: latestManifest });
243
252
  } else {
244
253
  manifest = readManifest(manifestPath);
245
254
  const latestManifest = await resolveLatestManifest(manifest, key, { ...options, tools: ["data-harness-cli"] });
246
- await installToolsFromManifest(runtimeDir, manifestPath, { ...options, manifestOverride: latestManifest });
255
+ manifest = await installToolsFromManifest(runtimeDir, manifestPath, { ...options, state: installState, manifestOverride: latestManifest });
247
256
  localTools = await installLocalTools(runtimeDir, options);
248
257
  }
249
258
  ok(`${Object.keys(manifest.installedTools || {}).length + Object.keys(localTools).length} 个 CLI 已安装到 bin/`);
@@ -4,7 +4,7 @@ import https from "node:https";
4
4
  import path from "node:path";
5
5
  import { commandExists, run } from "./exec.js";
6
6
  import { platformKey } from "./platform.js";
7
- import { action, warn } from "./log.js";
7
+ import { action, skip, warn } from "./log.js";
8
8
 
9
9
  export function readManifest(file) {
10
10
  return JSON.parse(fs.readFileSync(file, "utf8"));
@@ -136,6 +136,35 @@ function fileSha256(file) {
136
136
  return crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
137
137
  }
138
138
 
139
+ function executable(file) {
140
+ try {
141
+ fs.accessSync(file, fs.constants.X_OK);
142
+ return true;
143
+ } catch {
144
+ return false;
145
+ }
146
+ }
147
+
148
+ function reusableInstalledTool(workspace, tool, asset, options = {}) {
149
+ const current = tool.version ? optionsStateTool(options, tool.name) : null;
150
+ if (!current?.version || current.version !== tool.version) return null;
151
+ if (!current.sha256) return null;
152
+ const binary = path.join(workspace, "bin", tool.binary);
153
+ if (!executable(binary)) return null;
154
+ const actualSha = fileSha256(binary);
155
+ if (actualSha !== current.sha256) return null;
156
+ return {
157
+ version: current.version,
158
+ asset: current.asset || assetName(asset),
159
+ sha256: current.sha256,
160
+ ...(current.assetSha256 ? { assetSha256: current.assetSha256 } : {})
161
+ };
162
+ }
163
+
164
+ function optionsStateTool(options, name) {
165
+ return options?.state?.tools?.[name] || null;
166
+ }
167
+
139
168
  export async function installToolsFromManifest(workspace, manifestPath, options = {}) {
140
169
  const manifest = options.manifestOverride || readManifest(manifestPath);
141
170
  const only = options.tools ? new Set(options.tools) : null;
@@ -150,6 +179,12 @@ export async function installToolsFromManifest(workspace, manifestPath, options
150
179
  if (only && !only.has(tool.name)) continue;
151
180
  const asset = tool.platforms?.[key];
152
181
  if (!asset?.url) throw new Error(`manifest missing ${tool.name} asset for ${key}`);
182
+ const reusable = !options.force ? reusableInstalledTool(workspace, tool, asset, options) : null;
183
+ if (reusable) {
184
+ if (options.log !== false) skip(`${tool.name} 已是最新 ${tool.version}`);
185
+ installedTools[tool.name] = reusable;
186
+ continue;
187
+ }
153
188
  const archive = path.join(cacheDir, assetName(asset));
154
189
  if (options.log !== false) action(`下载 ${tool.name} ${tool.version} (${key})`);
155
190
  await downloadAsset(tool, asset, archive, options);
@@ -165,10 +200,12 @@ export async function installToolsFromManifest(workspace, manifestPath, options
165
200
  const binary = path.join(binDir, tool.binary);
166
201
  if (!fs.existsSync(binary)) throw new Error(`${tool.binary} was not extracted to bin/`);
167
202
  fs.chmodSync(binary, 0o755);
203
+ const binarySha = fileSha256(binary);
168
204
  installedTools[tool.name] = {
169
205
  version: tool.version || "",
170
206
  asset: assetName(asset),
171
- sha256: sha || actualSha
207
+ sha256: binarySha,
208
+ assetSha256: sha || actualSha
172
209
  };
173
210
  }
174
211
  Object.defineProperty(manifest, "installedTools", { value: installedTools, enumerable: false });