@spzhongwin/skill-logger-plugin 1.0.12 → 1.0.13

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/dist/index.js CHANGED
@@ -106,7 +106,6 @@ import fsSync from "node:fs";
106
106
  import path3 from "node:path";
107
107
  import os2 from "node:os";
108
108
  import { execFile } from "node:child_process";
109
- import { promisify } from "node:util";
110
109
  import { randomUUID, createHash } from "node:crypto";
111
110
 
112
111
  // src/skill-version.ts
@@ -139,14 +138,37 @@ function parseSkillVersion(content) {
139
138
  }
140
139
 
141
140
  // src/updater.ts
142
- var execFileAsync = promisify(execFile);
141
+ var EXTRACT_TIMEOUT_MS = 3e4;
143
142
  var ATTEMPT_COOLDOWN_MS = 30 * 60 * 1e3;
144
143
  function skillIdentityHash(code, version) {
145
144
  return createHash("sha256").update(`${code} ${code} ${version}`).digest("hex");
146
145
  }
146
+ function extractorCommandForPlatform(platform, zipPath, destDir) {
147
+ return platform === "darwin" ? { command: "ditto", args: ["-x", "-k", zipPath, destDir] } : { command: "unzip", args: ["-o", "-q", zipPath, "-d", destDir] };
148
+ }
149
+ function runExtractor(command, args) {
150
+ return new Promise((resolve, reject) => {
151
+ const child = execFile(command, args, {
152
+ encoding: "utf8",
153
+ timeout: EXTRACT_TIMEOUT_MS,
154
+ killSignal: "SIGKILL",
155
+ maxBuffer: 1024 * 1024
156
+ }, (error, _stdout, stderr) => {
157
+ if (!error) {
158
+ resolve();
159
+ return;
160
+ }
161
+ const detail = String(stderr || "").trim();
162
+ const timeout = error.killed ? `\uFF0C\u8D85\u8FC7 ${EXTRACT_TIMEOUT_MS}ms \u5DF2\u7EC8\u6B62` : "";
163
+ reject(new Error(`${command} \u89E3\u538B\u5931\u8D25${timeout}: ${detail || error.message}`));
164
+ });
165
+ child.stdin?.end();
166
+ });
167
+ }
147
168
  async function systemUnzip(zipPath, destDir) {
148
169
  await fs3.mkdir(destDir, { recursive: true });
149
- await execFileAsync("unzip", ["-o", "-q", zipPath, "-d", destDir]);
170
+ const { command, args } = extractorCommandForPlatform(process.platform, zipPath, destDir);
171
+ await runExtractor(command, args);
150
172
  }
151
173
  var SkillUpdater = class {
152
174
  getConfig;
@@ -464,7 +486,7 @@ var SkillUpdater = class {
464
486
  emit("download.file.written", { bytes: buf.byteLength });
465
487
  const staging = path3.join(work, "staging");
466
488
  const unzipStartedAt = Date.now();
467
- emit("unzip.start");
489
+ emit("unzip.start", { extractor: process.platform === "darwin" ? "ditto" : "unzip" });
468
490
  await this.unzip(zipPath, staging);
469
491
  emit("unzip.completed", { elapsedMs: Date.now() - unzipStartedAt });
470
492
  const srcRoot = await this.locateSkillRoot(staging, 0);
@@ -1354,11 +1376,11 @@ import path6 from "node:path";
1354
1376
  // src/identity.ts
1355
1377
  import os3 from "node:os";
1356
1378
  import { execFile as execFile2 } from "node:child_process";
1357
- import { promisify as promisify2 } from "node:util";
1358
- var execFileAsync2 = promisify2(execFile2);
1379
+ import { promisify } from "node:util";
1380
+ var execFileAsync = promisify(execFile2);
1359
1381
  async function getGitConfigValue(key) {
1360
1382
  try {
1361
- const { stdout } = await execFileAsync2("git", ["config", "--global", key]);
1383
+ const { stdout } = await execFileAsync("git", ["config", "--global", key]);
1362
1384
  return stdout.trim();
1363
1385
  } catch {
1364
1386
  console.warn(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@spzhongwin/skill-logger-plugin",
3
- "version": "1.0.12",
3
+ "version": "1.0.13",
4
4
  "type": "module",
5
5
  "exports": "./dist/index.js",
6
6
  "scripts": {
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
4
4
  import path from "node:path";
5
5
  import os from "node:os";
6
6
  import { createHash } from "node:crypto";
7
- import { SkillUpdater, type OutdatedCopy } from "./updater.ts";
7
+ import { extractorCommandForPlatform, SkillUpdater, type OutdatedCopy } from "./updater.ts";
8
8
  import type { PluginConfig } from "./types.ts";
9
9
 
10
10
  /** 与 updater/服务端一致的身份哈希:code code version(空格分隔)。 */
@@ -72,6 +72,17 @@ const outdatedFor = (target: string): OutdatedCopy[] => [
72
72
  ];
73
73
 
74
74
  describe("SkillUpdater", () => {
75
+ it("macOS 使用 ditto 解压,其他平台保持 unzip", () => {
76
+ assert.deepEqual(extractorCommandForPlatform("darwin", "/tmp/a.zip", "/tmp/out"), {
77
+ command: "ditto",
78
+ args: ["-x", "-k", "/tmp/a.zip", "/tmp/out"],
79
+ });
80
+ assert.deepEqual(extractorCommandForPlatform("linux", "/tmp/a.zip", "/tmp/out"), {
81
+ command: "unzip",
82
+ args: ["-o", "-q", "/tmp/a.zip", "-d", "/tmp/out"],
83
+ });
84
+ });
85
+
75
86
  it("手动更新可复用同一次下载,同时覆盖用户与内置模板目录并记录关键阶段", async () => {
76
87
  const calls: string[] = [];
77
88
  const traces: Array<{ stage: string; data?: Record<string, unknown> }> = [];
package/src/updater.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  * 流程(每个 skill@version 仅下载一次,覆盖其全部 workspace 副本):
6
6
  * 1. POST {platformBaseUrl}/skill_package/pull {skillName, version} → { url }
7
7
  * 2. GET url → zip 字节 → 写入临时文件
8
- * 3. 解压到临时 staging 目录(默认调用系统 unzip)
8
+ * 3. 解压到临时 staging 目录(macOS 使用 ditto,其他系统使用 unzip)
9
9
  * 4. 在 staging 内定位含 SKILL.md 的目录作为源根(兼容包内带或不带顶层目录)
10
10
  * 5. 逐个目标目录「直接覆盖、不备份」:先复制到同级临时目录,再删旧、原子 rename 换入,
11
11
  * 避免中途失败留下损坏的半成品;不保留 .bak。
@@ -20,12 +20,11 @@ import fsSync from "node:fs";
20
20
  import path from "node:path";
21
21
  import os from "node:os";
22
22
  import { execFile } from "node:child_process";
23
- import { promisify } from "node:util";
24
23
  import { randomUUID, createHash } from "node:crypto";
25
24
  import type { PluginConfig } from "./types.ts";
26
25
  import { parseSkillVersion, readSkillVersion } from "./skill-version.ts";
27
26
 
28
- const execFileAsync = promisify(execFile);
27
+ const EXTRACT_TIMEOUT_MS = 30_000;
29
28
 
30
29
  /**
31
30
  * 同一 skill@version 的更新尝试冷却期。
@@ -77,10 +76,42 @@ export type UpdaterOptions = {
77
76
  cooldownStatePath?: string;
78
77
  };
79
78
 
80
- /** 默认解压:系统 unzip -o -q(覆盖、静默)。 */
79
+ export function extractorCommandForPlatform(
80
+ platform: NodeJS.Platform,
81
+ zipPath: string,
82
+ destDir: string,
83
+ ): { command: string; args: string[] } {
84
+ return platform === "darwin"
85
+ ? { command: "ditto", args: ["-x", "-k", zipPath, destDir] }
86
+ : { command: "unzip", args: ["-o", "-q", zipPath, "-d", destDir] };
87
+ }
88
+
89
+ function runExtractor(command: string, args: string[]): Promise<void> {
90
+ return new Promise((resolve, reject) => {
91
+ const child = execFile(command, args, {
92
+ encoding: "utf8",
93
+ timeout: EXTRACT_TIMEOUT_MS,
94
+ killSignal: "SIGKILL",
95
+ maxBuffer: 1024 * 1024,
96
+ }, (error, _stdout, stderr) => {
97
+ if (!error) {
98
+ resolve();
99
+ return;
100
+ }
101
+ const detail = String(stderr || "").trim();
102
+ const timeout = error.killed ? `,超过 ${EXTRACT_TIMEOUT_MS}ms 已终止` : "";
103
+ reject(new Error(`${command} 解压失败${timeout}: ${detail || error.message}`));
104
+ });
105
+ // 防止 unzip 遇到异常包时弹出 Continue? 并永久等待输入。
106
+ child.stdin?.end();
107
+ });
108
+ }
109
+
110
+ /** 默认解压:macOS 的 ditto 正确支持 ZIP 中文文件名;其余平台保持原 unzip 行为。 */
81
111
  async function systemUnzip(zipPath: string, destDir: string): Promise<void> {
82
112
  await fs.mkdir(destDir, { recursive: true });
83
- await execFileAsync("unzip", ["-o", "-q", zipPath, "-d", destDir]);
113
+ const { command, args } = extractorCommandForPlatform(process.platform, zipPath, destDir);
114
+ await runExtractor(command, args);
84
115
  }
85
116
 
86
117
  export class SkillUpdater {
@@ -448,7 +479,7 @@ export class SkillUpdater {
448
479
  // 2. 调用系统解压工具 unzip
449
480
  const staging = path.join(work, "staging");
450
481
  const unzipStartedAt = Date.now();
451
- emit("unzip.start");
482
+ emit("unzip.start", { extractor: process.platform === "darwin" ? "ditto" : "unzip" });
452
483
  await this.unzip(zipPath, staging);
453
484
  emit("unzip.completed", { elapsedMs: Date.now() - unzipStartedAt });
454
485