cwbridge 1.8.0 → 2.0.0

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/bin/cwbridge.exe CHANGED
@@ -1,4 +1,4 @@
1
1
  [diffend] Oversized file quarantined before diffing.
2
2
  name: package/bin/cwbridge.exe
3
- size: 50913558 bytes
4
- sha256: 104a2481145e3d88594ce40c46fe0bcbc343eaa0a435744715976c9c8ffc75b3
3
+ size: 51337789 bytes
4
+ sha256: 013119a9f4092606338f36c6a865845aa61ab08f906cd5831dd7578609be53db
package/bin/cwbridge.js CHANGED
@@ -4,12 +4,91 @@
4
4
  const fs = require("node:fs");
5
5
  const https = require("node:https");
6
6
  const path = require("node:path");
7
- const { spawnSync } = require("node:child_process");
7
+ const { spawnSync, execFileSync } = require("node:child_process");
8
8
 
9
9
  const packageInfo = require("../package.json");
10
10
  const UPDATE_CHECK_MS = 24 * 60 * 60 * 1000;
11
11
  const UPDATE_TIMEOUT_MS = 900;
12
12
 
13
+ const PROGRAM_DIR =
14
+ (process.env.LOCALAPPDATA && path.join(process.env.LOCALAPPDATA, "Programs", "CWBridge")) ||
15
+ (process.env.APPDATA && path.join(process.env.APPDATA, "CWBridge", "program"));
16
+ const BIN_DIR = process.env.APPDATA && path.join(process.env.APPDATA, "CWBridge", "bin");
17
+ const ALIAS_MARKER = "Generated by CWBridge - safe to replace";
18
+ const ALIAS_PATH = BIN_DIR && path.join(BIN_DIR, "cwbridge.cmd");
19
+ const PROGRAM_PATH = PROGRAM_DIR && path.join(PROGRAM_DIR, "cwbridge.exe");
20
+
21
+ function isOwnedAliasPresent(aliasPath = ALIAS_PATH) {
22
+ if (!aliasPath || !fs.existsSync(aliasPath)) {
23
+ return false;
24
+ }
25
+ try {
26
+ return fs.readFileSync(aliasPath, "utf8").includes(ALIAS_MARKER);
27
+ } catch {
28
+ return false;
29
+ }
30
+ }
31
+
32
+ function copyExecutable(executable, destination) {
33
+ // The destination can be locked while CWBridge is running (EBUSY/EPERM).
34
+ // Retry briefly, then continue with the existing installation.
35
+ const attempts = 5;
36
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
37
+ try {
38
+ if (path.resolve(executable) === path.resolve(destination)) {
39
+ return true;
40
+ }
41
+ fs.copyFileSync(executable, destination);
42
+ return true;
43
+ } catch (error) {
44
+ if (attempt === attempts || !["EBUSY", "EPERM", "EACCES"].includes(error.code)) {
45
+ if (fs.existsSync(destination)) {
46
+ return true; // Keep the existing executable; it may be up to date.
47
+ }
48
+ throw error;
49
+ }
50
+ const wait = attempt * 200;
51
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, wait);
52
+ }
53
+ }
54
+ return false;
55
+ }
56
+
57
+ function installPermanently(executable, hooks = {}) {
58
+ // Copy into the per-user program folder and create a PATH-backed alias so
59
+ // the plain "cwbridge" command works without npx afterwards. All target
60
+ // paths are injectable so tests never touch a real installation.
61
+ const paths = hooks.paths || {
62
+ programDir: PROGRAM_DIR,
63
+ binDir: BIN_DIR,
64
+ aliasPath: ALIAS_PATH,
65
+ programPath: PROGRAM_PATH,
66
+ };
67
+ const addUser = hooks.addUserPath;
68
+ if (!paths.programDir || !paths.binDir || !paths.aliasPath || !paths.programPath) {
69
+ return false;
70
+ }
71
+ try {
72
+ fs.mkdirSync(paths.programDir, { recursive: true });
73
+ fs.mkdirSync(paths.binDir, { recursive: true });
74
+ copyExecutable(executable, paths.programPath);
75
+ const alias =
76
+ "@echo off\r\n" +
77
+ ":: " + ALIAS_MARKER + "\r\n" +
78
+ `\"${paths.programPath}\" %*\r\n`;
79
+ fs.writeFileSync(paths.aliasPath, alias, "utf8");
80
+ if (addUser) {
81
+ addUser(paths.binDir);
82
+ }
83
+ return true;
84
+ } catch (error) {
85
+ if (process.env.CWBRIDGE_DEBUG_INSTALL === "1") {
86
+ console.error(`CWBridge auto-install failed: ${error.message}`);
87
+ }
88
+ return false;
89
+ }
90
+ }
91
+
13
92
  function parseVersion(value) {
14
93
  const match = /^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/.exec(String(value));
15
94
  return match ? match.slice(1, 4).map(Number) : null;
@@ -101,6 +180,37 @@ function fetchLatestVersion() {
101
180
  });
102
181
  }
103
182
 
183
+ function defaultRegRun(args) {
184
+ return execFileSync("reg.exe", args, { encoding: "utf8", timeout: 3000 });
185
+ }
186
+
187
+ function addUserPath(directory, run = defaultRegRun) {
188
+ // Best-effort HKCU\Environment\Path update; no admin rights required.
189
+ try {
190
+ const query = String(run(["query", "HKCU\\Environment", "/v", "Path"]));
191
+ const valueLine = query
192
+ .split(/\r?\n/)
193
+ .find((line) => /^\s*Path\s+REG_(?:EXPAND_)?SZ\s+(.+)$/i.test(line));
194
+ if (!valueLine) {
195
+ return false;
196
+ }
197
+ // reg.exe output: " Path REG_EXPAND_SZ C:\a;C:\b"
198
+ const currentValue = valueLine.trim().match(/^\s*Path\s+REG_(?:EXPAND_)?SZ\s+(.+)$/i)[1].trim();
199
+ const alreadyPresent = currentValue
200
+ .split(";")
201
+ .some((entry) => entry.trim().replace(/\\+$/, "").toLowerCase() === String(directory).toLowerCase());
202
+ if (alreadyPresent) {
203
+ return false;
204
+ }
205
+ const updated = currentValue ? `${currentValue};${directory}` : directory;
206
+ run(["add", "HKCU\\Environment", "/v", "Path", "/t", "REG_EXPAND_SZ", "/d", updated, "/f"]);
207
+ return true;
208
+ } catch {
209
+ // PATH update is best effort; alias and program paths still print.
210
+ return false;
211
+ }
212
+ }
213
+
104
214
  async function reportAvailableUpdate() {
105
215
  if (process.env.CWBRIDGE_NO_UPDATE_CHECK === "1") {
106
216
  return;
@@ -135,6 +245,14 @@ async function main() {
135
245
  return;
136
246
  }
137
247
 
248
+ if (!isOwnedAliasPresent()) {
249
+ if (installPermanently(executable)) {
250
+ console.log(`CWBridge installed for the current user: ${PROGRAM_PATH}`);
251
+ console.log(`Command: ${ALIAS_PATH}`);
252
+ console.log("Open a new terminal, then run: cwbridge (setup runs on first use)");
253
+ }
254
+ }
255
+
138
256
  await reportAvailableUpdate();
139
257
  const result = spawnSync(executable, process.argv.slice(2), {
140
258
  stdio: "inherit",
@@ -155,4 +273,13 @@ if (require.main === module) {
155
273
  });
156
274
  }
157
275
 
158
- module.exports = { isNewerVersion, parseVersion };
276
+ module.exports = {
277
+ isNewerVersion,
278
+ parseVersion,
279
+ addUserPath,
280
+ installPermanently,
281
+ isOwnedAliasPresent,
282
+ ALIAS_PATH,
283
+ BIN_DIR,
284
+ PROGRAM_PATH,
285
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cwbridge",
3
- "version": "1.8.0",
3
+ "version": "2.0.0",
4
4
  "description": "Windows launcher for the compiled CWBridge executable",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,