@clovapi/cli 0.1.23 → 0.1.26

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": "@clovapi/cli",
3
- "version": "0.1.23",
3
+ "version": "0.1.26",
4
4
  "description": "Install clovapi CLI from GitHub Releases",
5
5
  "repository": {
6
6
  "type": "git",
@@ -19,6 +19,7 @@
19
19
  "bin",
20
20
  "scripts/install.js",
21
21
  "scripts/paths.js",
22
+ "scripts/win-replace.js",
22
23
  "vendor",
23
24
  "README.md"
24
25
  ],
@@ -15,6 +15,7 @@ const {
15
15
  exeName,
16
16
  vendorBinPath,
17
17
  } = require("./paths");
18
+ const { installBinaryWindows } = require("./win-replace");
18
19
 
19
20
  const PLATFORM_MAP = {
20
21
  darwin: "darwin",
@@ -155,12 +156,14 @@ async function withInstallLock(fn) {
155
156
  }
156
157
 
157
158
  function installBinary(sourcePath, targetPath) {
159
+ if (process.platform === "win32") {
160
+ installBinaryWindows(sourcePath, targetPath);
161
+ return;
162
+ }
158
163
  fs.mkdirSync(path.dirname(targetPath), { recursive: true });
159
164
  const tmpPath = path.join(path.dirname(targetPath), `.clovapi-install-${process.pid}-${Date.now()}`);
160
165
  fs.copyFileSync(sourcePath, tmpPath);
161
- if (process.platform !== "win32") {
162
- fs.chmodSync(tmpPath, 0o755);
163
- }
166
+ fs.chmodSync(tmpPath, 0o755);
164
167
  try {
165
168
  fs.renameSync(tmpPath, targetPath);
166
169
  } catch {
@@ -0,0 +1,128 @@
1
+ const { spawnSync } = require("node:child_process");
2
+ const fs = require("node:fs");
3
+ const path = require("node:path");
4
+
5
+ function sleepSync(ms) {
6
+ const deadline = Date.now() + ms;
7
+ while (Date.now() < deadline) {
8
+ /* spin */
9
+ }
10
+ }
11
+
12
+ function stopProxy(cliPath) {
13
+ const target = String(cliPath || "").trim();
14
+ if (!target || !fs.existsSync(target)) {
15
+ return;
16
+ }
17
+ try {
18
+ spawnSync(target, ["proxy", "stop"], {
19
+ stdio: "ignore",
20
+ windowsHide: true,
21
+ timeout: 15000,
22
+ });
23
+ } catch {
24
+ /* best effort */
25
+ }
26
+ sleepSync(400);
27
+ }
28
+
29
+ function removeIfExists(filePath) {
30
+ try {
31
+ fs.rmSync(filePath, { force: true });
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ }
37
+
38
+ function runDeferredWindowsReplace(targetPath, pendingPath, cliPath) {
39
+ const script = [
40
+ "$ErrorActionPreference = 'Continue'",
41
+ `$target = ${JSON.stringify(targetPath)}`,
42
+ `$pending = ${JSON.stringify(pendingPath)}`,
43
+ `$cli = ${JSON.stringify(cliPath || targetPath)}`,
44
+ "for ($i = 0; $i -lt 40; $i++) {",
45
+ " try {",
46
+ " if (Test-Path $cli) { & $cli proxy stop 2>$null | Out-Null }",
47
+ " Start-Sleep -Milliseconds 300",
48
+ " if (Test-Path $target) { Remove-Item -LiteralPath $target -Force -ErrorAction Stop }",
49
+ " Move-Item -LiteralPath $pending -Destination $target -Force -ErrorAction Stop",
50
+ " Remove-Item -LiteralPath ($target + '.old') -Force -ErrorAction SilentlyContinue",
51
+ " exit 0",
52
+ " } catch {",
53
+ " Start-Sleep -Milliseconds 500",
54
+ " }",
55
+ "}",
56
+ "exit 1",
57
+ ].join("\n");
58
+
59
+ const result = spawnSync(
60
+ "powershell.exe",
61
+ ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", script],
62
+ {
63
+ windowsHide: true,
64
+ timeout: 60000,
65
+ }
66
+ );
67
+ return (
68
+ result.status === 0 &&
69
+ fs.existsSync(targetPath) &&
70
+ !fs.existsSync(pendingPath)
71
+ );
72
+ }
73
+
74
+ function installBinaryWindows(sourcePath, targetPath) {
75
+ const target = path.resolve(String(targetPath || "").trim());
76
+ const source = path.resolve(String(sourcePath || "").trim());
77
+ if (!target || !source || !fs.existsSync(source)) {
78
+ throw new Error("install paths are invalid");
79
+ }
80
+
81
+ stopProxy(target);
82
+ fs.mkdirSync(path.dirname(target), { recursive: true });
83
+
84
+ const tmpPath = path.join(
85
+ path.dirname(target),
86
+ `.clovapi-install-${process.pid}-${Date.now()}`
87
+ );
88
+ fs.copyFileSync(source, tmpPath);
89
+
90
+ for (let attempt = 0; attempt < 8; attempt += 1) {
91
+ try {
92
+ if (fs.existsSync(target)) {
93
+ removeIfExists(target);
94
+ if (fs.existsSync(target)) {
95
+ try {
96
+ fs.renameSync(target, `${target}.old`);
97
+ } catch {
98
+ /* still locked */
99
+ }
100
+ }
101
+ }
102
+ fs.renameSync(tmpPath, target);
103
+ removeIfExists(`${target}.old`);
104
+ return;
105
+ } catch {
106
+ stopProxy(target);
107
+ sleepSync(300 * (attempt + 1));
108
+ }
109
+ }
110
+
111
+ removeIfExists(tmpPath);
112
+ const pendingPath = `${target}.new`;
113
+ fs.copyFileSync(source, pendingPath);
114
+ if (runDeferredWindowsReplace(target, pendingPath, target)) {
115
+ return;
116
+ }
117
+
118
+ removeIfExists(pendingPath);
119
+ throw new Error(
120
+ `EPERM: operation not permitted, replace '${target}'. Stop the clovapi proxy and retry.`
121
+ );
122
+ }
123
+
124
+ module.exports = {
125
+ installBinaryWindows,
126
+ runDeferredWindowsReplace,
127
+ stopProxy,
128
+ };