dsh-wechat-remote 1.7.6 → 1.7.9

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.
@@ -3,127 +3,71 @@
3
3
  // src/install-profile.ts
4
4
  import fs from "node:fs";
5
5
  import path from "node:path";
6
- import { spawn } from "node:child_process";
7
- import { createHash } from "node:crypto";
8
-
9
- // src/secure-file.ts
10
- import { execFileSync } from "node:child_process";
11
- import {
12
- chmodSync,
13
- closeSync,
14
- existsSync,
15
- fsyncSync,
16
- mkdirSync,
17
- openSync,
18
- readFileSync,
19
- renameSync,
20
- rmSync,
21
- writeFileSync
22
- } from "node:fs";
23
- import { dirname } from "node:path";
24
- import { userInfo } from "node:os";
25
- import { randomBytes } from "node:crypto";
26
- function tightenPrivateFile(file) {
27
- try {
28
- chmodSync(file, 384);
29
- } catch {
30
- }
31
- if (process.platform !== "win32") return;
32
- try {
33
- execFileSync("icacls", [file, "/inheritance:r", "/grant:r", `${userInfo().username}:F`], {
34
- timeout: 5e3,
35
- windowsHide: true,
36
- stdio: "ignore"
37
- });
38
- } catch {
39
- }
40
- }
41
- function writePrivateJsonAtomic(file, value) {
42
- const parent = dirname(file);
43
- mkdirSync(parent, { recursive: true, mode: 448 });
44
- const temporary = `${file}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
45
- try {
46
- writeFileSync(temporary, `${JSON.stringify(value, null, 2)}
47
- `, {
48
- encoding: "utf8",
49
- mode: 384,
50
- flag: "wx"
51
- });
52
- const fd = openSync(temporary, "r+");
53
- try {
54
- try {
55
- fsyncSync(fd);
56
- } catch {
57
- }
58
- } finally {
59
- closeSync(fd);
60
- }
61
- tightenPrivateFile(temporary);
62
- renameSync(temporary, file);
63
- tightenPrivateFile(file);
64
- } finally {
65
- if (existsSync(temporary)) {
66
- try {
67
- rmSync(temporary, { force: true });
68
- } catch {
69
- }
70
- }
71
- }
72
- }
73
-
74
- // src/install-runtime.ts
75
- var INSTALL_PNPM_VERSION = "11.22.0";
76
-
77
- // src/install-profile.ts
6
+ import { createHash, randomBytes } from "node:crypto";
7
+ import { spawn, execFile } from "node:child_process";
78
8
  var PLUGIN_PACKAGE = "@harness-remote/dsh-wechat-remote";
79
9
  function safeProfileName(value) {
80
10
  return /^[A-Za-z0-9_-]{1,80}$/.test(value);
81
11
  }
82
- var hash = (file) => createHash("sha256").update(fs.readFileSync(file)).digest("hex");
83
- function assertRelocatableProfile(root) {
84
- const walk = (directory) => {
85
- for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
86
- const file = path.join(directory, entry.name);
87
- if (entry.isSymbolicLink()) {
88
- const relative = path.relative(root, fs.realpathSync(file));
89
- if (path.isAbsolute(fs.readlinkSync(file)) || !relative || relative.startsWith("..") || path.isAbsolute(relative)) {
90
- throw new Error("\u63D2\u4EF6\u4F9D\u8D56\u5305\u542B\u4E0D\u53EF\u8FC1\u79FB\u7684\u94FE\u63A5\uFF0C\u672A\u4FEE\u6539\u5F53\u524D\u5B89\u88C5\u3002");
91
- }
92
- } else if (entry.isDirectory()) walk(file);
12
+ function backupProfile(profile, backup) {
13
+ if (fs.existsSync(backup)) throw new Error("\u672C\u6B21\u5B89\u88C5\u5907\u4EFD\u5DF2\u5B58\u5728\uFF0C\u672A\u8986\u76D6\u3002");
14
+ fs.cpSync(profile, backup, {
15
+ recursive: true,
16
+ dereference: false,
17
+ verbatimSymlinks: true,
18
+ mode: fs.constants.COPYFILE_FICLONE,
19
+ filter(source, target) {
20
+ if (!fs.lstatSync(source).isSymbolicLink()) return true;
21
+ const type = process.platform === "win32" ? fs.statSync(source, { throwIfNoEntry: false })?.isDirectory() ? "junction" : "file" : void 0;
22
+ const link = fs.readlinkSync(source);
23
+ fs.symlinkSync(type === "junction" ? path.resolve(path.dirname(source), link) : link, target, type);
24
+ return false;
93
25
  }
94
- };
95
- walk(root);
26
+ });
96
27
  }
97
28
  function installToolPath(directory, runtime) {
98
29
  const bin = path.join(directory, "tool-bin");
99
30
  fs.mkdirSync(bin, { mode: 448 });
100
- const runner = path.join(bin, "pnpm-run.cjs");
101
- fs.writeFileSync(runner, `require('node:child_process').spawnSync(${JSON.stringify(runtime.executable)},[${JSON.stringify(runtime.cli)},...process.argv.slice(2)],{stdio:'inherit',shell:false,windowsHide:true}).status===0?process.exit(0):process.exit(1)
102
- `, { mode: 384 });
103
31
  if (process.platform === "win32") {
104
- if (/["\r\n]/.test(runtime.executable)) throw new Error("Node \u5B89\u88C5\u8DEF\u5F84\u4E0D\u53D7\u652F\u6301\u3002");
105
- fs.writeFileSync(path.join(bin, "pnpm.cmd"), `@echo off\r
106
- "${runtime.executable.replace(/%/g, "%%")}" "%~dp0pnpm-run.cjs" %*\r
107
- `, { mode: 448 });
32
+ if (/["\r\n]/.test(runtime.executable + runtime.cli)) throw new Error("\u5B89\u88C5\u5DE5\u5177\u8DEF\u5F84\u5305\u542B\u65E0\u6548\u5B57\u7B26\u3002");
33
+ fs.writeFileSync(path.join(bin, "pnpm.cmd"), '@echo off\r\nsetlocal DisableDelayedExpansion\r\n"%HARNESS_INSTALL_NODE%" "%HARNESS_INSTALL_PNPM%" %*\r\n', { mode: 448 });
108
34
  } else {
109
- const quote = (s) => "'" + s.replace(/'/g, `'"'"'`) + "'";
110
- fs.writeFileSync(path.join(bin, "pnpm"), `#!/bin/sh
111
- exec ${quote(runtime.executable)} ${quote(runner)} "$@"
112
- `, { mode: 448 });
35
+ fs.writeFileSync(path.join(bin, "pnpm"), '#!/bin/sh\nexec "$HARNESS_INSTALL_NODE" "$HARNESS_INSTALL_PNPM" "$@"\n', { mode: 448 });
113
36
  }
114
37
  return bin;
115
38
  }
116
- function runNativePlugin(cli, profile, home, toolPath, runtime, logFile) {
117
- if (!safeProfileName(profile)) throw new Error("\u65E0\u6548\u7684 DSH profile \u540D\u79F0\u3002");
39
+ var NativeInstallError = class extends Error {
40
+ constructor(message, mayStillBeRunning = false) {
41
+ super(message);
42
+ this.mayStillBeRunning = mayStillBeRunning;
43
+ this.name = "NativeInstallError";
44
+ }
45
+ };
46
+ function runNativePlugin(cli, profile, home, toolPath, runtime, logFile, archiveName, timeoutMs = 6e5, operation = "add") {
47
+ if (!safeProfileName(profile) || !/^harness-remote-[\w.+-]+\.tgz$/.test(archiveName)) throw new Error("\u65E0\u6548\u7684\u5B89\u88C5\u76EE\u6807\u3002");
118
48
  return new Promise((resolve, reject) => {
119
49
  const log = fs.openSync(logFile, "a", 384);
50
+ const env = { ...process.env };
51
+ const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path");
52
+ const inheritedPath = pathKey ? env[pathKey] : "";
53
+ if (process.platform === "win32") {
54
+ for (const key of Object.keys(env)) if (key.toLowerCase() === "path") delete env[key];
55
+ }
56
+ Object.assign(env, {
57
+ DSH_HOME: home,
58
+ PATH: toolPath + path.delimiter + (inheritedPath || ""),
59
+ HARNESS_INSTALL_NODE: runtime.executable,
60
+ HARNESS_INSTALL_PNPM: runtime.cli,
61
+ CI: "true",
62
+ COREPACK_ENABLE_AUTO_PIN: "0",
63
+ npm_config_manage_package_manager_versions: "false"
64
+ });
120
65
  const child = spawn(runtime.executable, [
121
66
  cli,
122
67
  "plugin",
123
68
  "--profile",
124
69
  profile,
125
- "add",
126
- "file:harness-remote-update.tgz",
70
+ ...operation === "install" ? ["install"] : ["add", `file:${archiveName}`],
127
71
  "--ignore-scripts",
128
72
  "--config.frozen-lockfile=false",
129
73
  "--prefer-offline",
@@ -133,78 +77,89 @@ function runNativePlugin(cli, profile, home, toolPath, runtime, logFile) {
133
77
  cwd: home,
134
78
  shell: false,
135
79
  windowsHide: true,
80
+ detached: process.platform !== "win32",
136
81
  stdio: ["ignore", log, log],
137
- env: {
138
- ...process.env,
139
- DSH_HOME: home,
140
- PATH: toolPath + path.delimiter + (process.env.PATH || ""),
141
- CI: "true",
142
- COREPACK_ENABLE_AUTO_PIN: "0",
143
- npm_config_manage_package_manager_versions: "false"
144
- }
82
+ env
145
83
  });
146
84
  fs.closeSync(log);
147
- const timer = setTimeout(() => {
148
- child.kill();
149
- reject(new Error("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u539F\u63D2\u4EF6\u672A\u66FF\u6362\u3002"));
150
- }, 24e4);
151
- child.once("error", () => {
85
+ let finished = false, terminating = false, closed = false, terminationTimer;
86
+ const fail = (message, uncertain = false) => finish(new NativeInstallError(`${message} \u5B89\u88C5\u65E5\u5FD7\uFF1A${logFile}`, uncertain));
87
+ const finish = (error) => {
88
+ if (finished) return;
89
+ finished = true;
152
90
  clearTimeout(timer);
153
- reject(new Error("\u65E0\u6CD5\u542F\u52A8 DSH \u539F\u751F\u5B89\u88C5\u7A0B\u5E8F\u3002"));
154
- });
155
- child.once("close", (code) => {
156
- clearTimeout(timer);
157
- code === 0 ? resolve() : reject(new Error("\u5B89\u88C5\u672A\u5B8C\u6210\uFF0C\u539F\u63D2\u4EF6\u672A\u66FF\u6362\uFF1B\u8BE6\u60C5\u5DF2\u4FDD\u7559\u5728\u672C\u673A\u5B89\u88C5\u65E5\u5FD7\u3002"));
91
+ clearTimeout(terminationTimer);
92
+ error ? reject(error) : resolve();
93
+ };
94
+ const timer = setTimeout(() => {
95
+ if (closed) return;
96
+ terminating = true;
97
+ terminationTimer = setTimeout(() => fail("\u5B89\u88C5\u8D85\u65F6\uFF0C\u5C1A\u4E0D\u80FD\u786E\u8BA4\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u9000\u51FA\uFF1B\u672A\u81EA\u52A8\u56DE\u9000\uFF0C\u8BF7\u52FF\u91CD\u590D\u5B89\u88C5\u3002", true), 15e3);
98
+ if (process.platform === "win32") {
99
+ if (!child.pid) return fail("\u5B89\u88C5\u7A0B\u5E8F\u672A\u542F\u52A8\u3002");
100
+ execFile(
101
+ path.join(process.env.SystemRoot || "C:\\Windows", "System32", "taskkill.exe"),
102
+ ["/PID", String(child.pid), "/T", "/F"],
103
+ { windowsHide: true, timeout: 1e4 },
104
+ (error) => {
105
+ if (error) fail("\u5B89\u88C5\u8D85\u65F6\uFF0C\u65E0\u6CD5\u786E\u8BA4\u5B89\u88C5\u8FDB\u7A0B\u6811\u5DF2\u9000\u51FA\uFF1B\u672A\u81EA\u52A8\u56DE\u9000\uFF0C\u8BF7\u52FF\u91CD\u590D\u5B89\u88C5\u3002", true);
106
+ else if (closed) fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002");
107
+ else child.once("close", () => fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002"));
108
+ }
109
+ );
110
+ } else {
111
+ try {
112
+ if (child.pid) process.kill(-child.pid, "SIGKILL");
113
+ } catch (error) {
114
+ if (error.code !== "ESRCH") return fail("\u65E0\u6CD5\u505C\u6B62\u8D85\u65F6\u7684\u5B89\u88C5\u8FDB\u7A0B\uFF1B\u672A\u81EA\u52A8\u56DE\u9000\u3002", true);
115
+ }
116
+ if (closed) fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002");
117
+ else child.once("close", () => fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002"));
118
+ }
119
+ }, timeoutMs);
120
+ child.once("error", (error) => fail(`\u65E0\u6CD5\u542F\u52A8 DSH \u539F\u751F\u5B89\u88C5\u7A0B\u5E8F\uFF08${error.code || error.name}\uFF09\u3002`));
121
+ child.once("close", (code, signal) => {
122
+ closed = true;
123
+ if (terminating) return;
124
+ if (code === 0) finish();
125
+ else fail(`DSH \u539F\u751F\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${signal ? `\u4FE1\u53F7 ${signal}` : `\u9000\u51FA\u7801 ${code}`}\uFF09\u3002`);
158
126
  });
159
127
  });
160
128
  }
161
- async function stageProfile(job) {
162
- const scope = path.basename(job.profile);
163
- if (!safeProfileName(scope) || !fs.statSync(job.directory).isDirectory()) throw new Error("\u5B89\u88C5\u76EE\u6807\u4E0D\u660E\u786E\u3002");
164
- const stagingHome = path.join(job.directory, "staging-home"), staged = path.join(stagingHome, "profiles", scope);
165
- fs.mkdirSync(staged, { recursive: true, mode: 448 });
166
- if (fs.existsSync(job.profile)) {
167
- if (fs.lstatSync(job.profile).isSymbolicLink() || fs.realpathSync(job.profile) !== path.resolve(job.profile)) throw new Error("\u4E0D\u652F\u6301\u81EA\u52A8\u66FF\u6362\u94FE\u63A5\u5F62\u5F0F\u7684 profile\u3002");
168
- fs.cpSync(job.profile, staged, {
169
- recursive: true,
170
- dereference: false,
171
- filter: (p) => !["node_modules", ".harness-remote-update.lock"].includes(path.basename(p))
172
- });
173
- }
174
- let before = null;
175
- const filename = path.join(staged, "package.json");
176
- if (fs.existsSync(filename)) {
177
- before = JSON.parse(fs.readFileSync(filename, "utf8"));
178
- for (const [name, spec] of Object.entries(before.dependencies || {})) {
179
- if (name !== PLUGIN_PACKAGE && !/^[~^]?\d+\.\d+\.\d+(?:-[\w.-]+)?$/.test(String(spec))) throw new Error("\u5176\u4ED6\u63D2\u4EF6\u4F7F\u7528\u4E86\u672C\u5730\u94FE\u63A5\u6216\u7279\u6B8A\u6765\u6E90\uFF0C\u672A\u4FEE\u6539\u5F53\u524D\u5B89\u88C5\u3002");
180
- }
181
- for (const e of fs.readdirSync(staged, { withFileTypes: true })) if (e.isSymbolicLink()) throw new Error("profile \u914D\u7F6E\u5305\u542B\u94FE\u63A5\uFF0C\u672A\u4FEE\u6539\u5F53\u524D\u5B89\u88C5\u3002");
182
- if (before.packageManager && !/^pnpm@\d+\.\d+\.\d+(?:\+.*)?$/.test(before.packageManager)) throw new Error("\u5F53\u524D profile \u4F7F\u7528\u4E86\u5176\u4ED6\u5305\u7BA1\u7406\u5668\uFF0C\u672A\u4FEE\u6539\u5B89\u88C5\u3002");
183
- writePrivateJsonAtomic(filename, { ...before, dependencies: {
184
- ...before.dependencies,
185
- [PLUGIN_PACKAGE]: "file:harness-remote-update.tgz"
186
- }, packageManager: `pnpm@${INSTALL_PNPM_VERSION}` });
187
- }
188
- fs.copyFileSync(path.join(job.directory, "release.tgz"), path.join(staged, "harness-remote-update.tgz"));
129
+ async function installProfile(job) {
130
+ const scope = path.basename(job.profile), home = path.dirname(path.dirname(job.profile));
131
+ if (!safeProfileName(scope) || path.dirname(job.profile) !== path.join(home, "profiles") || !/^[\w.+-]{1,80}$/.test(job.targetVersion)) throw new Error("\u5B89\u88C5\u76EE\u6807\u4E0D\u660E\u786E\u3002");
132
+ fs.mkdirSync(job.profile, { recursive: true, mode: 448 });
133
+ const archive = path.join(job.directory, "release.tgz");
134
+ const digest = createHash("sha256").update(fs.readFileSync(archive)).digest("hex");
135
+ const archiveBase = `harness-remote-${job.targetVersion}-${digest}`;
136
+ const suffix = fs.existsSync(path.join(job.profile, `${archiveBase}.tgz`)) ? `-${randomBytes(8).toString("hex")}` : "";
137
+ const archiveName = `${archiveBase}${suffix}.tgz`;
138
+ fs.copyFileSync(archive, path.join(job.profile, archiveName), fs.constants.COPYFILE_EXCL);
189
139
  const tools = installToolPath(job.directory, job.runtime);
190
- await runNativePlugin(job.cli, scope, stagingHome, tools, job.runtime, path.join(job.directory, "install.log"));
191
- const installed = fs.realpathSync(path.join(staged, "node_modules", PLUGIN_PACKAGE));
192
- const relative = path.relative(staged, installed);
193
- if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) throw new Error("\u6682\u5B58\u63D2\u4EF6\u4E0D\u5728\u5B89\u88C5\u76EE\u5F55\u5185\u3002");
140
+ const logFile = path.join(job.directory, "install.log");
141
+ const deadline = Date.now() + 6e5;
142
+ const run = (operation = "add") => runNativePlugin(job.cli, scope, home, tools, job.runtime, logFile, archiveName, Math.max(1, deadline - Date.now()), operation);
143
+ try {
144
+ await run();
145
+ } catch (error) {
146
+ const layoutError = /^\s*(?:\[ERR_PNPM_|ERR_PNPM_)(?:UNEXPECTED_VIRTUAL_STORE|UNEXPECTED_STORE|MODULES_BREAKING_CHANGE|VIRTUAL_STORE_DIR_MAX_LENGTH_DIFF|PUBLIC_HOIST_PATTERN_DIFF|HOIST_PATTERN_DIFF)(?:\]|\s)/m;
147
+ if (!(error instanceof NativeInstallError) || error.mayStillBeRunning || !layoutError.test(fs.readFileSync(logFile, "utf8"))) throw error;
148
+ fs.appendFileSync(logFile, "\nRestoring native pnpm layout with dsh plugin install.\n");
149
+ await run("install");
150
+ await run();
151
+ }
152
+ const installed = path.join(job.profile, "node_modules", PLUGIN_PACKAGE);
194
153
  if (JSON.parse(fs.readFileSync(path.join(installed, "package.json"), "utf8")).version !== job.targetVersion) throw new Error("\u5B89\u88C5\u540E\u63D2\u4EF6\u7248\u672C\u4E0D\u5339\u914D\u3002");
195
- const after = JSON.parse(fs.readFileSync(filename, "utf8"));
154
+ const after = JSON.parse(fs.readFileSync(path.join(job.profile, "package.json"), "utf8"));
196
155
  if (!after.dsh?.profile?.bundles?.includes(PLUGIN_PACKAGE)) throw new Error("DSH \u5C1A\u672A\u5C06\u63D2\u4EF6\u6CE8\u518C\u4E3A\u539F\u751F profile \u5C42\u3002");
197
- for (const name of Object.keys(before?.dependencies || {})) if (name !== PLUGIN_PACKAGE) {
198
- if (hash(path.join(job.profile, "node_modules", name, "package.json")) !== hash(path.join(staged, "node_modules", name, "package.json"))) throw new Error("\u5B89\u88C5\u8BD5\u56FE\u6539\u53D8\u5176\u4ED6\u63D2\u4EF6\uFF0C\u539F\u5B89\u88C5\u4FDD\u6301\u4E0D\u53D8\u3002");
199
- }
200
- assertRelocatableProfile(staged);
201
- return staged;
202
156
  }
203
157
  export {
158
+ NativeInstallError,
204
159
  PLUGIN_PACKAGE,
205
- assertRelocatableProfile,
160
+ backupProfile,
161
+ installProfile,
206
162
  installToolPath,
207
163
  runNativePlugin,
208
- safeProfileName,
209
- stageProfile
164
+ safeProfileName
210
165
  };
@@ -6,6 +6,22 @@ import path from "node:path";
6
6
  import { createRequire } from "node:module";
7
7
  import { spawn } from "node:child_process";
8
8
  var INSTALL_PNPM_VERSION = "11.22.0";
9
+ async function pinInstallRuntime(runtime, directory) {
10
+ let root = path.dirname(runtime.cli);
11
+ while (!fs.existsSync(path.join(root, "package.json"))) {
12
+ const parent = path.dirname(root);
13
+ if (parent === root) throw new Error("\u65E0\u6CD5\u5B9A\u4F4D\u5B89\u88C5\u5DE5\u5177\u5305\u3002");
14
+ root = parent;
15
+ }
16
+ const manifest = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8"));
17
+ if (manifest.name !== "pnpm" || manifest.version !== runtime.version) throw new Error("\u5B89\u88C5\u5DE5\u5177\u7248\u672C\u4E0D\u4E00\u81F4\u3002");
18
+ const target = path.join(directory, "install-runtime");
19
+ if (fs.existsSync(target)) throw new Error("\u672C\u6B21\u5B89\u88C5\u5DE5\u5177\u76EE\u5F55\u5DF2\u5B58\u5728\u3002");
20
+ fs.cpSync(root, target, { recursive: true, dereference: false, verbatimSymlinks: true, mode: fs.constants.COPYFILE_FICLONE });
21
+ const pinned = { ...runtime, cli: path.join(target, path.relative(root, runtime.cli)) };
22
+ await verifyInstallRuntime(pinned);
23
+ return pinned;
24
+ }
9
25
  function resolveInstallRuntime(owner, executable = process.execPath, nodeVersion = process.versions.node) {
10
26
  const [major, minor] = nodeVersion.split(".").map(Number);
11
27
  if (!(major > 22 || major === 22 && minor >= 13)) throw new Error("\u5F53\u524D Node.js \u7248\u672C\u4E0D\u6EE1\u8DB3\u5B89\u88C5\u8981\u6C42\uFF0822.13 \u6216\u66F4\u9AD8\uFF09\uFF0C\u672A\u4FEE\u6539\u63D2\u4EF6\u3002");
@@ -59,6 +75,7 @@ function verifyInstallRuntime(runtime) {
59
75
  }
60
76
  export {
61
77
  INSTALL_PNPM_VERSION,
78
+ pinInstallRuntime,
62
79
  resolveInstallRuntime,
63
80
  verifyInstallRuntime
64
81
  };