forge-jsxy 1.0.82 → 1.0.84

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.
@@ -10,7 +10,7 @@
10
10
  <link rel="apple-touch-icon" href="/forge-explorer-favicon.svg"/>
11
11
  <link rel="stylesheet" href="/forge-explorer-codicons/codicon.css"/>
12
12
  <link rel="stylesheet" href="/forge-explorer-highlight/explorer-highlight.css"/>
13
- <!-- forge-jsxy@1.0.82 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
13
+ <!-- forge-jsxy@1.0.84 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
14
14
  <script>
15
15
  (function () {
16
16
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-jsxy",
3
- "version": "1.0.82",
3
+ "version": "1.0.84",
4
4
  "description": "Node.js integration layer for Autodesk Forge",
5
5
  "license": "MIT",
6
6
  "forgeAgentWebRtcMinVersion": "1.0.71",
@@ -16,6 +16,11 @@
16
16
  *
17
17
  * CLI: `node scripts/forge-isolated-runtime.mjs` | `[--print-dist-dir]` | `[-h]`
18
18
  * `npm run bootstrap:isolated-runtime`
19
+ *
20
+ * **Shells / OS:** During **`npm install`**, npm sets **`npm_execpath`** and **`npm_node_execpath`**. We always
21
+ * spawn **`node npm-cli.js …`** with **`shell: false`** and argv arrays — same behavior from **Windows**
22
+ * (Git Bash, cmd.exe, PowerShell) and **Unix** (bash, zsh, Terminal.app, etc.): paths with spaces stay intact.
23
+ * Fallbacks: Windows **`npm.cmd`** next to Node / `where`; Linux/macOS **`npm`** next to Node / **`PATH`**.
19
24
  */
20
25
  import { spawnSync } from "node:child_process";
21
26
  import { existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
@@ -46,10 +51,6 @@ export function resolveInstallWorkingDir() {
46
51
  return path.join(os.homedir(), ".local", "share", "cfgmgr");
47
52
  }
48
53
 
49
- function npmCmd() {
50
- return process.platform === "win32" ? "npm.cmd" : "npm";
51
- }
52
-
53
54
  function readPackageVersion() {
54
55
  const pkgJsonPath = path.join(pkgRoot, "package.json");
55
56
  if (!existsSync(pkgJsonPath)) return "0.0.0";
@@ -79,6 +80,111 @@ function npmEnv() {
79
80
  };
80
81
  }
81
82
 
83
+ function stripEnvQuotes(v) {
84
+ const t = String(v || "").trim();
85
+ if (t.length >= 2 && ((t.startsWith('"') && t.endsWith('"')) || (t.startsWith("'") && t.endsWith("'")))) {
86
+ return t.slice(1, -1).trim();
87
+ }
88
+ return t;
89
+ }
90
+
91
+ /**
92
+ * During `npm install`, npm sets **`npm_execpath`** → **`npm-cli.js`** and **`npm_node_execpath`** → the
93
+ * node binary that npm uses. Spawning **`node <npm-cli.js> …`** matches npm’s own invocation on **every OS**
94
+ * (Windows Git Bash / cmd / PowerShell; Linux/macOS bash / zsh / fish — npm normalizes the env).
95
+ */
96
+ function resolveNpmCliJsFromLifecycleEnv() {
97
+ const raw = stripEnvQuotes(process.env.npm_execpath || process.env.NPM_CLI_JS || "");
98
+ if (!raw) return "";
99
+ const p = path.normalize(raw);
100
+ if (!existsSync(p)) return "";
101
+ return /\.m?js$/i.test(p) ? p : "";
102
+ }
103
+
104
+ function resolveNodeBinaryForNpmCli() {
105
+ const raw = stripEnvQuotes(
106
+ process.env.npm_node_execpath || process.env.NPM_NODE_EXECPATH || ""
107
+ );
108
+ if (raw) {
109
+ const p = path.normalize(raw);
110
+ if (existsSync(p)) return p;
111
+ }
112
+ return process.execPath;
113
+ }
114
+
115
+ /**
116
+ * Absolute `npm.cmd` for Windows — **never** use `shell: true` with argv here: cmd splits on spaces and
117
+ * breaks `npm pack C:\\...\\New folder\\...` → `ENOENT ...\\New\\package.json`.
118
+ * Same-directory-as-node covers stock Node installs; `where` covers nvm-windows / odd layouts.
119
+ */
120
+ export function resolveNpmExecutableWin32() {
121
+ const beside = path.join(path.dirname(process.execPath), "npm.cmd");
122
+ if (existsSync(beside)) return beside;
123
+ const wh = spawnSync(process.env.ComSpec || "cmd.exe", ["/d", "/s", "/c", "where npm.cmd"], {
124
+ encoding: "utf8",
125
+ windowsHide: true,
126
+ env: npmEnv(),
127
+ maxBuffer: 1024 * 1024,
128
+ });
129
+ if (wh.status === 0) {
130
+ const line = (wh.stdout || "")
131
+ .split(/\r?\n/)
132
+ .map((s) => s.trim())
133
+ .filter(Boolean)[0];
134
+ if (line && existsSync(line)) return line;
135
+ }
136
+ return beside;
137
+ }
138
+
139
+ /** Linux/macOS: prefer `npm` next to this `node` (Homebrew, apt, Volta layout); else `PATH`. */
140
+ export function resolveNpmExecutablePosix() {
141
+ const beside = path.join(path.dirname(process.execPath), "npm");
142
+ if (existsSync(beside)) return beside;
143
+ return "npm";
144
+ }
145
+
146
+ /**
147
+ * Run npm subcommands with argv-style args (paths with spaces stay single arguments).
148
+ * Prefer **`node npm-cli.js`** from npm’s lifecycle env — **Windows + Linux + macOS** parity across shells.
149
+ */
150
+ function npmSpawnSync(npmArgv, extraOptions) {
151
+ const merged = {
152
+ encoding: "utf8",
153
+ windowsHide: true,
154
+ env: npmEnv(),
155
+ ...extraOptions,
156
+ };
157
+
158
+ const npmCli = resolveNpmCliJsFromLifecycleEnv();
159
+ if (npmCli) {
160
+ const nodeBin = resolveNodeBinaryForNpmCli();
161
+ return spawnSync(nodeBin, [npmCli, ...npmArgv], { ...merged, shell: false });
162
+ }
163
+
164
+ if (process.platform === "win32") {
165
+ const exe = resolveNpmExecutableWin32();
166
+ return spawnSync(exe, npmArgv, { ...merged, shell: false });
167
+ }
168
+
169
+ const posixNpm = resolveNpmExecutablePosix();
170
+ return spawnSync(posixNpm, npmArgv, { ...merged, shell: false });
171
+ }
172
+
173
+ /** When stderr/stdout are empty but status is null or non-zero — explain spawn/PATH/signal. */
174
+ function formatSpawnFailure(pr, label) {
175
+ const io = (pr.stderr || pr.stdout || "").trim();
176
+ if (io) return io.slice(0, 2000);
177
+ const parts = [`${label}: subprocess failed`];
178
+ if (pr.status !== null && pr.status !== undefined) parts.push(`exit=${pr.status}`);
179
+ else parts.push("exit=null (npm not started — PATH/npm.cmd, antivirus, or shell issue)");
180
+ if (pr.signal) parts.push(`signal=${pr.signal}`);
181
+ if (pr.error) parts.push(pr.error.message || String(pr.error));
182
+ parts.push(
183
+ "npm spawn uses npm_execpath when present (any shell/OS), else npm next to Node — argv-only, no shell; paths with spaces OK. EPERM on Windows: stop processes locking node_modules, retry."
184
+ );
185
+ return parts.join(" — ");
186
+ }
187
+
82
188
  /**
83
189
  * @returns {{ ok: boolean, tgzPath: string, error?: string }}
84
190
  */
@@ -88,18 +194,15 @@ function npmPackToDir(packDir) {
88
194
  } catch (e) {
89
195
  return { ok: false, tgzPath: "", error: e instanceof Error ? e.message : String(e) };
90
196
  }
91
- const pr = spawnSync(npmCmd(), ["pack", pkgRoot, "--pack-destination", packDir], {
197
+ const pr = npmSpawnSync(["pack", pkgRoot, "--pack-destination", packDir], {
92
198
  cwd: packDir,
93
- encoding: "utf8",
94
- windowsHide: true,
95
- env: npmEnv(),
96
199
  maxBuffer: 10 * 1024 * 1024,
97
200
  });
98
201
  if (pr.status !== 0) {
99
202
  return {
100
203
  ok: false,
101
204
  tgzPath: "",
102
- error: (pr.stderr || pr.stdout || "").trim() || `npm pack exit ${pr.status}`,
205
+ error: formatSpawnFailure(pr, "npm pack"),
103
206
  };
104
207
  }
105
208
  const lines = (pr.stdout || "")
@@ -129,11 +232,8 @@ function npmInstallTgzLocalPrefix(tgzPath, prefixRoot) {
129
232
  "--no-fund",
130
233
  ];
131
234
  const cwd = existsSync(prefixRoot) ? prefixRoot : os.tmpdir();
132
- return spawnSync(npmCmd(), args, {
235
+ return npmSpawnSync(args, {
133
236
  cwd,
134
- encoding: "utf8",
135
- windowsHide: true,
136
- env: npmEnv(),
137
237
  maxBuffer: 40 * 1024 * 1024,
138
238
  });
139
239
  }
@@ -187,8 +287,13 @@ export function bootstrapDurableForgeJsxy() {
187
287
 
188
288
  const r = npmInstallTgzLocalPrefix(tgzPath, versionDir);
189
289
  if (r.status !== 0) {
190
- const err = (r.stderr || r.stdout || "").trim() || `npm install exit ${r.status}`;
191
- return { ok: false, distDir: "", versionDir, version, error: err };
290
+ return {
291
+ ok: false,
292
+ distDir: "",
293
+ versionDir,
294
+ version,
295
+ error: formatSpawnFailure(r, "npm install (durable prefix)"),
296
+ };
192
297
  }
193
298
 
194
299
  const distDir = resolveDistDirUnderLocalPrefix(versionDir);