cwbridge 1.8.0 → 2.0.1

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/README.md CHANGED
@@ -10,6 +10,18 @@ minimal launcher; it does not include the Python source code.
10
10
  npx --yes cwbridge
11
11
  ```
12
12
 
13
+ If you want the `cwbridge` command to come from the npm prefix instead of a
14
+ temporary npx cache, install the package globally:
15
+
16
+ ```powershell
17
+ npm install -g cwbridge
18
+ cwbridge --version
19
+ ```
20
+
21
+ The npm global prefix must be on your PATH (`npm prefix -g` prints it), then
22
+ plain `cwbridge` works in every new terminal. Update it with
23
+ `npm install -g cwbridge@latest` and remove it with `npm uninstall -g cwbridge`.
24
+
13
25
  Pass normal CWBridge arguments after the command, for example:
14
26
 
15
27
  ```powershell
@@ -25,6 +37,17 @@ the newest launcher with:
25
37
  npx --yes cwbridge@latest
26
38
  ```
27
39
 
40
+ An installed CWBridge can also update itself explicitly:
41
+
42
+ ```powershell
43
+ cwbridge update --check
44
+ cwbridge update
45
+ ```
46
+
47
+ These commands download the published package, verify the registry's integrity
48
+ digest and replace the installed executable. Nothing is checked or installed
49
+ automatically, and `cwbridge version` reports the installed build and paths.
50
+
28
51
  Set `CWBRIDGE_NO_UPDATE_CHECK=1` to disable the launcher update check.
29
52
 
30
53
  This package is currently Windows x64 only.
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: 51372379 bytes
4
+ sha256: d550827cf17c6fc4f0a13a955808f42f4e0e610d015a2775f23767fe22a259a1
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.1",
4
4
  "description": "Windows launcher for the compiled CWBridge executable",
5
5
  "license": "UNLICENSED",
6
6
  "private": false,