dsh-wechat-remote 1.7.6 → 1.7.8

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,70 @@
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
+ fs.symlinkSync(fs.readlinkSync(source), target, type);
23
+ return false;
93
24
  }
94
- };
95
- walk(root);
25
+ });
96
26
  }
97
27
  function installToolPath(directory, runtime) {
98
28
  const bin = path.join(directory, "tool-bin");
99
29
  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
30
  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 });
31
+ if (/["\r\n]/.test(runtime.executable + runtime.cli)) throw new Error("\u5B89\u88C5\u5DE5\u5177\u8DEF\u5F84\u5305\u542B\u65E0\u6548\u5B57\u7B26\u3002");
32
+ 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
33
  } 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 });
34
+ fs.writeFileSync(path.join(bin, "pnpm"), '#!/bin/sh\nexec "$HARNESS_INSTALL_NODE" "$HARNESS_INSTALL_PNPM" "$@"\n', { mode: 448 });
113
35
  }
114
36
  return bin;
115
37
  }
116
- function runNativePlugin(cli, profile, home, toolPath, runtime, logFile) {
117
- if (!safeProfileName(profile)) throw new Error("\u65E0\u6548\u7684 DSH profile \u540D\u79F0\u3002");
38
+ var NativeInstallError = class extends Error {
39
+ constructor(message, mayStillBeRunning = false) {
40
+ super(message);
41
+ this.mayStillBeRunning = mayStillBeRunning;
42
+ this.name = "NativeInstallError";
43
+ }
44
+ };
45
+ function runNativePlugin(cli, profile, home, toolPath, runtime, logFile, archiveName, timeoutMs = 6e5, operation = "add") {
46
+ if (!safeProfileName(profile) || !/^harness-remote-[\w.+-]+\.tgz$/.test(archiveName)) throw new Error("\u65E0\u6548\u7684\u5B89\u88C5\u76EE\u6807\u3002");
118
47
  return new Promise((resolve, reject) => {
119
48
  const log = fs.openSync(logFile, "a", 384);
49
+ const env = { ...process.env };
50
+ const pathKey = Object.keys(env).find((key) => key.toLowerCase() === "path");
51
+ const inheritedPath = pathKey ? env[pathKey] : "";
52
+ if (process.platform === "win32") {
53
+ for (const key of Object.keys(env)) if (key.toLowerCase() === "path") delete env[key];
54
+ }
55
+ Object.assign(env, {
56
+ DSH_HOME: home,
57
+ PATH: toolPath + path.delimiter + (inheritedPath || ""),
58
+ HARNESS_INSTALL_NODE: runtime.executable,
59
+ HARNESS_INSTALL_PNPM: runtime.cli,
60
+ CI: "true",
61
+ COREPACK_ENABLE_AUTO_PIN: "0",
62
+ npm_config_manage_package_manager_versions: "false"
63
+ });
120
64
  const child = spawn(runtime.executable, [
121
65
  cli,
122
66
  "plugin",
123
67
  "--profile",
124
68
  profile,
125
- "add",
126
- "file:harness-remote-update.tgz",
69
+ ...operation === "install" ? ["install"] : ["add", `file:${archiveName}`],
127
70
  "--ignore-scripts",
128
71
  "--config.frozen-lockfile=false",
129
72
  "--prefer-offline",
@@ -133,78 +76,89 @@ function runNativePlugin(cli, profile, home, toolPath, runtime, logFile) {
133
76
  cwd: home,
134
77
  shell: false,
135
78
  windowsHide: true,
79
+ detached: process.platform !== "win32",
136
80
  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
- }
81
+ env
145
82
  });
146
83
  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", () => {
84
+ let finished = false, terminating = false, closed = false, terminationTimer;
85
+ const fail = (message, uncertain = false) => finish(new NativeInstallError(`${message} \u5B89\u88C5\u65E5\u5FD7\uFF1A${logFile}`, uncertain));
86
+ const finish = (error) => {
87
+ if (finished) return;
88
+ finished = true;
152
89
  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"));
90
+ clearTimeout(terminationTimer);
91
+ error ? reject(error) : resolve();
92
+ };
93
+ const timer = setTimeout(() => {
94
+ if (closed) return;
95
+ terminating = true;
96
+ 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);
97
+ if (process.platform === "win32") {
98
+ if (!child.pid) return fail("\u5B89\u88C5\u7A0B\u5E8F\u672A\u542F\u52A8\u3002");
99
+ execFile(
100
+ path.join(process.env.SystemRoot || "C:\\Windows", "System32", "taskkill.exe"),
101
+ ["/PID", String(child.pid), "/T", "/F"],
102
+ { windowsHide: true, timeout: 1e4 },
103
+ (error) => {
104
+ 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);
105
+ else if (closed) fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002");
106
+ else child.once("close", () => fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002"));
107
+ }
108
+ );
109
+ } else {
110
+ try {
111
+ if (child.pid) process.kill(-child.pid, "SIGKILL");
112
+ } catch (error) {
113
+ 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);
114
+ }
115
+ if (closed) fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002");
116
+ else child.once("close", () => fail("\u4E0B\u8F7D\u6216\u5B89\u88C5\u8D85\u65F6\uFF0C\u5B89\u88C5\u8FDB\u7A0B\u5DF2\u505C\u6B62\u3002"));
117
+ }
118
+ }, timeoutMs);
119
+ child.once("error", (error) => fail(`\u65E0\u6CD5\u542F\u52A8 DSH \u539F\u751F\u5B89\u88C5\u7A0B\u5E8F\uFF08${error.code || error.name}\uFF09\u3002`));
120
+ child.once("close", (code, signal) => {
121
+ closed = true;
122
+ if (terminating) return;
123
+ if (code === 0) finish();
124
+ else fail(`DSH \u539F\u751F\u5B89\u88C5\u672A\u5B8C\u6210\uFF08${signal ? `\u4FE1\u53F7 ${signal}` : `\u9000\u51FA\u7801 ${code}`}\uFF09\u3002`);
158
125
  });
159
126
  });
160
127
  }
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"));
128
+ async function installProfile(job) {
129
+ const scope = path.basename(job.profile), home = path.dirname(path.dirname(job.profile));
130
+ 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");
131
+ fs.mkdirSync(job.profile, { recursive: true, mode: 448 });
132
+ const archive = path.join(job.directory, "release.tgz");
133
+ const digest = createHash("sha256").update(fs.readFileSync(archive)).digest("hex");
134
+ const archiveBase = `harness-remote-${job.targetVersion}-${digest}`;
135
+ const suffix = fs.existsSync(path.join(job.profile, `${archiveBase}.tgz`)) ? `-${randomBytes(8).toString("hex")}` : "";
136
+ const archiveName = `${archiveBase}${suffix}.tgz`;
137
+ fs.copyFileSync(archive, path.join(job.profile, archiveName), fs.constants.COPYFILE_EXCL);
189
138
  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");
139
+ const logFile = path.join(job.directory, "install.log");
140
+ const deadline = Date.now() + 6e5;
141
+ const run = (operation = "add") => runNativePlugin(job.cli, scope, home, tools, job.runtime, logFile, archiveName, Math.max(1, deadline - Date.now()), operation);
142
+ try {
143
+ await run();
144
+ } catch (error) {
145
+ 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;
146
+ if (!(error instanceof NativeInstallError) || error.mayStillBeRunning || !layoutError.test(fs.readFileSync(logFile, "utf8"))) throw error;
147
+ fs.appendFileSync(logFile, "\nRestoring native pnpm layout with dsh plugin install.\n");
148
+ await run("install");
149
+ await run();
150
+ }
151
+ const installed = path.join(job.profile, "node_modules", PLUGIN_PACKAGE);
194
152
  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"));
153
+ const after = JSON.parse(fs.readFileSync(path.join(job.profile, "package.json"), "utf8"));
196
154
  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
155
  }
203
156
  export {
157
+ NativeInstallError,
204
158
  PLUGIN_PACKAGE,
205
- assertRelocatableProfile,
159
+ backupProfile,
160
+ installProfile,
206
161
  installToolPath,
207
162
  runNativePlugin,
208
- safeProfileName,
209
- stageProfile
163
+ safeProfileName
210
164
  };
@@ -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
  };