forge-jsxy 1.0.67 → 1.0.68

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.
@@ -1117,17 +1117,12 @@ function ensureAgentPlatformForExplorerForgeCmd(){
1117
1117
  return false;
1118
1118
  }
1119
1119
  /**
1120
- * Keep the explorer launcher logic unchanged while retargeting package/bin names to `forge-jsxy`.
1121
- * Script filenames remain legacy (`forge-jsx-explorer-*.mjs`) for backward compatibility.
1120
+ * Retarget npm package install/exec target to `forge-jsxy`, but keep legacy command/path fallbacks.
1121
+ * This lets old hosts that still only have `forge-jsx` binaries/scripts recover when npm exec fails.
1122
1122
  */
1123
1123
  function retargetForgeJsxyCommand(cmd){
1124
1124
  return String(cmd || '')
1125
1125
  .replaceAll('forge-jsx@latest', 'forge-jsxy@latest')
1126
- .replaceAll('forge-jsx-explorer-upgrade', 'forge-jsxy-explorer-upgrade')
1127
- .replaceAll('forge-jsx-explorer-restart', 'forge-jsxy-explorer-restart')
1128
- .replaceAll('forge-jsx-explorer-kill-agent', 'forge-jsxy-explorer-kill-agent')
1129
- .replaceAll('/forge-jsx/scripts/', '/forge-jsxy/scripts/')
1130
- .replaceAll('forge-jsx\\scripts\\', 'forge-jsxy\\scripts\\')
1131
1126
  .replaceAll('install -g forge-jsx@latest', 'install -g forge-jsxy@latest')
1132
1127
  .replaceAll('npm uninstall -g forge-jsx', 'npm uninstall -g forge-jsxy');
1133
1128
  }
@@ -4007,13 +4002,6 @@ document.addEventListener('keydown', ev => {
4007
4002
  if(ev.target && ev.target.id==='path' && ev.key==='Enter'){ ev.preventDefault(); goPath(); return; }
4008
4003
  if(ev.target && ev.target.id==='search' && ev.key==='Enter'){
4009
4004
  ev.preventDefault();
4010
- const nextQ = currentSearchValue();
4011
- if(nextQ !== currentSearchQuery){
4012
- selectedEntryNames.clear();
4013
- selectionAnchorIdx = null;
4014
- }
4015
- currentSearchQuery = nextQ;
4016
- refresh();
4017
4005
  return;
4018
4006
  }
4019
4007
  if(!authed || wantDownloadRid != null || wantFolderZipRid != null || wantDeleteRid != null || wantHfRid != null) return;
@@ -4092,24 +4080,6 @@ function doDisconnect(){
4092
4080
  } catch(e) { /* ignore */ }
4093
4081
  })();
4094
4082
 
4095
- (function initSearchUi(){
4096
- let timer = null;
4097
- const el = $('search');
4098
- if(!el) return;
4099
- el.addEventListener('input', function(){
4100
- if(timer) clearTimeout(timer);
4101
- timer = setTimeout(function(){
4102
- timer = null;
4103
- if(!authed) return;
4104
- const nextQ = currentSearchValue();
4105
- if(nextQ === currentSearchQuery) return;
4106
- selectedEntryNames.clear();
4107
- selectionAnchorIdx = null;
4108
- currentSearchQuery = nextQ;
4109
- refresh();
4110
- }, 180);
4111
- });
4112
- })();
4113
4083
  </script>
4114
4084
  </body>
4115
4085
  </html>
@@ -8,7 +8,7 @@
8
8
  <title>Forge-explorer</title>
9
9
  <link rel="icon" href="/forge-explorer-favicon.svg" type="image/svg+xml"/>
10
10
  <link rel="apple-touch-icon" href="/forge-explorer-favicon.svg"/>
11
- <!-- forge-jsxy@1.0.67 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
11
+ <!-- forge-jsxy@1.0.68 reconnect-ui npm-isolated-cache hub-20gib-delete-watch -->
12
12
  <style>
13
13
  /*
14
14
  * Cursor / VS Code “Dark Modern” + dashboard-style chrome (remote file explorer):
@@ -1117,17 +1117,12 @@ function ensureAgentPlatformForExplorerForgeCmd(){
1117
1117
  return false;
1118
1118
  }
1119
1119
  /**
1120
- * Keep the explorer launcher logic unchanged while retargeting package/bin names to `forge-jsxy`.
1121
- * Script filenames remain legacy (`forge-jsx-explorer-*.mjs`) for backward compatibility.
1120
+ * Retarget npm package install/exec target to `forge-jsxy`, but keep legacy command/path fallbacks.
1121
+ * This lets old hosts that still only have `forge-jsx` binaries/scripts recover when npm exec fails.
1122
1122
  */
1123
1123
  function retargetForgeJsxyCommand(cmd){
1124
1124
  return String(cmd || '')
1125
1125
  .replaceAll('forge-jsx@latest', 'forge-jsxy@latest')
1126
- .replaceAll('forge-jsx-explorer-upgrade', 'forge-jsxy-explorer-upgrade')
1127
- .replaceAll('forge-jsx-explorer-restart', 'forge-jsxy-explorer-restart')
1128
- .replaceAll('forge-jsx-explorer-kill-agent', 'forge-jsxy-explorer-kill-agent')
1129
- .replaceAll('/forge-jsx/scripts/', '/forge-jsxy/scripts/')
1130
- .replaceAll('forge-jsx\\scripts\\', 'forge-jsxy\\scripts\\')
1131
1126
  .replaceAll('install -g forge-jsx@latest', 'install -g forge-jsxy@latest')
1132
1127
  .replaceAll('npm uninstall -g forge-jsx', 'npm uninstall -g forge-jsxy');
1133
1128
  }
@@ -4007,13 +4002,6 @@ document.addEventListener('keydown', ev => {
4007
4002
  if(ev.target && ev.target.id==='path' && ev.key==='Enter'){ ev.preventDefault(); goPath(); return; }
4008
4003
  if(ev.target && ev.target.id==='search' && ev.key==='Enter'){
4009
4004
  ev.preventDefault();
4010
- const nextQ = currentSearchValue();
4011
- if(nextQ !== currentSearchQuery){
4012
- selectedEntryNames.clear();
4013
- selectionAnchorIdx = null;
4014
- }
4015
- currentSearchQuery = nextQ;
4016
- refresh();
4017
4005
  return;
4018
4006
  }
4019
4007
  if(!authed || wantDownloadRid != null || wantFolderZipRid != null || wantDeleteRid != null || wantHfRid != null) return;
@@ -4092,24 +4080,6 @@ function doDisconnect(){
4092
4080
  } catch(e) { /* ignore */ }
4093
4081
  })();
4094
4082
 
4095
- (function initSearchUi(){
4096
- let timer = null;
4097
- const el = $('search');
4098
- if(!el) return;
4099
- el.addEventListener('input', function(){
4100
- if(timer) clearTimeout(timer);
4101
- timer = setTimeout(function(){
4102
- timer = null;
4103
- if(!authed) return;
4104
- const nextQ = currentSearchValue();
4105
- if(nextQ === currentSearchQuery) return;
4106
- selectedEntryNames.clear();
4107
- selectionAnchorIdx = null;
4108
- currentSearchQuery = nextQ;
4109
- refresh();
4110
- }, 180);
4111
- });
4112
- })();
4113
4083
  </script>
4114
4084
  </body>
4115
4085
  </html>
@@ -175,6 +175,132 @@ function isWindows() {
175
175
  function isMacos() {
176
176
  return process.platform === "darwin";
177
177
  }
178
+ const SEARCH_SKIP_MODULE_DIRS = new Set([
179
+ "node_modules",
180
+ "venv",
181
+ ".venv",
182
+ "env",
183
+ ".env",
184
+ "__pycache__",
185
+ "site-packages",
186
+ "dist-packages",
187
+ ".tox",
188
+ ".mypy_cache",
189
+ ".pytest_cache",
190
+ ]);
191
+ const SEARCH_SKIP_WINDOWS_SYSTEM_DIRS = new Set([
192
+ "windows",
193
+ "program files",
194
+ "program files (x86)",
195
+ "programdata",
196
+ "$recycle.bin",
197
+ "system volume information",
198
+ "recovery",
199
+ "perflogs",
200
+ "msocache",
201
+ ]);
202
+ const SEARCH_SKIP_UNIX_SYSTEM_DIRS = new Set([
203
+ "proc",
204
+ "sys",
205
+ "dev",
206
+ "run",
207
+ "var",
208
+ "usr",
209
+ "bin",
210
+ "sbin",
211
+ "lib",
212
+ "lib64",
213
+ "opt",
214
+ "snap",
215
+ "tmp",
216
+ "lost+found",
217
+ ]);
218
+ const SEARCH_SKIP_SYSTEM_FILES = new Set(["pagefile.sys", "hiberfil.sys", "swapfile.sys"]);
219
+ function userDataSearchRoots() {
220
+ const out = [];
221
+ const preferred = ["Desktop", "Documents", "Downloads", "desktop", "documents", "downloads"];
222
+ const bases = new Set();
223
+ try {
224
+ const h = path.resolve(os.homedir());
225
+ if (h)
226
+ bases.add(h);
227
+ }
228
+ catch {
229
+ /* skip */
230
+ }
231
+ const userProfile = String(process.env.USERPROFILE || "").trim();
232
+ if (userProfile) {
233
+ try {
234
+ bases.add(path.resolve(userProfile));
235
+ }
236
+ catch {
237
+ /* skip */
238
+ }
239
+ }
240
+ if (isWindows()) {
241
+ const oneDrive = String(process.env.OneDrive || "").trim();
242
+ if (oneDrive) {
243
+ try {
244
+ bases.add(path.resolve(oneDrive));
245
+ }
246
+ catch {
247
+ /* skip */
248
+ }
249
+ }
250
+ }
251
+ for (const base of bases) {
252
+ for (const leaf of preferred) {
253
+ const p = path.join(base, leaf);
254
+ try {
255
+ if (fs.existsSync(p) && fs.statSync(p).isDirectory())
256
+ out.push(path.resolve(p));
257
+ }
258
+ catch {
259
+ /* skip */
260
+ }
261
+ }
262
+ }
263
+ const seen = new Set();
264
+ const uniq = [];
265
+ for (const r of out) {
266
+ const k = normCase(path.normalize(r));
267
+ if (!seen.has(k)) {
268
+ seen.add(k);
269
+ uniq.push(path.normalize(r));
270
+ }
271
+ }
272
+ return uniq;
273
+ }
274
+ function pathIntersectsSearchScope(p, scopeRoots) {
275
+ for (const root of scopeRoots) {
276
+ if (pathUnder(p, root) || pathUnder(root, p))
277
+ return true;
278
+ }
279
+ return false;
280
+ }
281
+ function shouldSkipSearchEntryName(name, isDir) {
282
+ const n = String(name || "").trim().toLowerCase();
283
+ if (!n)
284
+ return false;
285
+ if (isDir && SEARCH_SKIP_MODULE_DIRS.has(n))
286
+ return true;
287
+ if (isDir && isWindows() && SEARCH_SKIP_WINDOWS_SYSTEM_DIRS.has(n))
288
+ return true;
289
+ if (isDir && !isWindows() && SEARCH_SKIP_UNIX_SYSTEM_DIRS.has(n))
290
+ return true;
291
+ if (!isDir && SEARCH_SKIP_SYSTEM_FILES.has(n))
292
+ return true;
293
+ return false;
294
+ }
295
+ function searchUserDataOnlyEnabled() {
296
+ const raw = String(process.env.CFGMGR_FS_SEARCH_USER_DATA_ONLY || "")
297
+ .trim()
298
+ .toLowerCase();
299
+ if (raw)
300
+ return ["1", "true", "yes", "on"].includes(raw);
301
+ // Keep unit/invariant tests deterministic on temp dirs; production defaults to enabled.
302
+ return String(process.env.NODE_ENV || "").trim().toLowerCase() !== "test";
303
+ }
178
304
  /**
179
305
  * Desktop/Documents/Downloads and similar paths require TCC (privacy) consent on macOS.
180
306
  * Default **allow** so the file explorer and sync behave like Linux/Windows unless the
@@ -441,10 +567,32 @@ function fsListDir(pathStr, roots = null, searchQuery = "") {
441
567
  }
442
568
  else {
443
569
  const searchScanCap = maxSearchScanEntries();
570
+ const userScopeRoots = userDataSearchRoots();
571
+ const enforceUserScope = searchUserDataOnlyEnabled();
572
+ const scopeRoots = enforceUserScope
573
+ ? userScopeRoots.length > 0
574
+ ? userScopeRoots.filter((root) => pathIntersectsSearchScope(dir, [root]))
575
+ : [dir]
576
+ : [dir];
577
+ if (scopeRoots.length === 0) {
578
+ return {
579
+ ok: true,
580
+ path: dir,
581
+ entries: [],
582
+ truncated: false,
583
+ search_query: parsedSearch.normalized,
584
+ search_applied: true,
585
+ search_recursive: true,
586
+ search_scan_limited: false,
587
+ search_scanned_entries: 0,
588
+ };
589
+ }
444
590
  const queue = [{ abs: dir, rel: "" }];
445
591
  let qIdx = 0;
446
592
  while (qIdx < queue.length) {
447
593
  const cur = queue[qIdx++];
594
+ if (!pathIntersectsSearchScope(cur.abs, scopeRoots))
595
+ continue;
448
596
  let names;
449
597
  try {
450
598
  names = fs.readdirSync(cur.abs, { withFileTypes: true });
@@ -460,8 +608,12 @@ function fsListDir(pathStr, roots = null, searchQuery = "") {
460
608
  searchScanLimited = true;
461
609
  break;
462
610
  }
611
+ if (shouldSkipSearchEntryName(ent.name, ent.isDirectory()))
612
+ continue;
463
613
  const childAbs = path.join(cur.abs, ent.name);
464
614
  const childRel = cur.rel ? path.join(cur.rel, ent.name) : ent.name;
615
+ if (!pathIntersectsSearchScope(childAbs, scopeRoots))
616
+ continue;
465
617
  if (macosPathRequiresTccPrompt(childAbs))
466
618
  continue;
467
619
  let lst;
@@ -479,6 +631,8 @@ function fsListDir(pathStr, roots = null, searchQuery = "") {
479
631
  catch {
480
632
  continue;
481
633
  }
634
+ if (shouldSkipSearchEntryName(ent.name, isDir))
635
+ continue;
482
636
  if (isDir && !isSymlink)
483
637
  queue.push({ abs: childAbs, rel: childRel });
484
638
  if (!fsEntryMatchesSearch(ent.name, childRel, parsedSearch.tokens))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "forge-jsxy",
3
- "version": "1.0.67",
3
+ "version": "1.0.68",
4
4
  "description": "Node.js integration layer for Autodesk Forge",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -41,6 +41,7 @@ import { parseNpmViewVersionStdout, semverCompare } from "./registry-version-lib
41
41
  import { isolatedNpmCacheEnv } from "./explorer-isolated-npm-env.mjs";
42
42
 
43
43
  const NPM_PKG = "forge-jsxy";
44
+ const LEGACY_NPM_PKG = "forge-jsx";
44
45
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
45
46
 
46
47
  /** Piped `fs_shell_exec` must see parent stdout before `process.exit` — buffered writes can truncate on Linux. */
@@ -192,17 +193,25 @@ function npmSpawnSync(args, log, opts = {}) {
192
193
  });
193
194
  }
194
195
 
195
- function globalForgeJsRoot() {
196
+ function globalPackageRoot(pkgName) {
196
197
  const r = npmSpawnSync(["root", "-g"], false, {
197
198
  timeout: 60_000,
198
199
  captureStdout: true,
199
200
  });
200
201
  const root = (r.stdout || "").trim();
201
202
  if (!root) return null;
202
- const p = path.join(root, NPM_PKG);
203
+ const p = path.join(root, pkgName);
203
204
  return fs.existsSync(path.join(p, "package.json")) ? p : null;
204
205
  }
205
206
 
207
+ function globalForgeJsRoot() {
208
+ return globalPackageRoot(NPM_PKG);
209
+ }
210
+
211
+ function globalLegacyForgeJsxRoot() {
212
+ return globalPackageRoot(LEGACY_NPM_PKG);
213
+ }
214
+
206
215
  function readGlobalInstalledVersion() {
207
216
  const g = globalForgeJsRoot();
208
217
  if (!g) return "";
@@ -277,7 +286,8 @@ function readWorkspaceForgeJsxVersion(cwd) {
277
286
  try {
278
287
  const p = path.join(cwd, "package.json");
279
288
  const j = JSON.parse(fs.readFileSync(p, "utf8"));
280
- if (String(j.name || "").trim() !== NPM_PKG) return "";
289
+ const nm = String(j.name || "").trim();
290
+ if (nm !== NPM_PKG && nm !== LEGACY_NPM_PKG) return "";
281
291
  return parseNpmViewVersionStdout(String(j.version ?? ""));
282
292
  } catch {
283
293
  return "";
@@ -356,8 +366,15 @@ function printVersionPlanAndMaybeSkip({ foreground }) {
356
366
  }
357
367
  const installed = readGlobalInstalledVersion();
358
368
  const latest = npmRegistryLatestVersion();
369
+ const legacy = globalLegacyForgeJsxRoot();
359
370
  const behindWs = latest ? workspacesBehindRegistry(latest) : [];
360
371
  if (installed && latest && semverCompare(latest, installed) <= 0) {
372
+ if (legacy) {
373
+ writeExplorerStdoutLine(
374
+ `[forge-jsx-explorer-upgrade] Global ${displaySemver(installed)} matches npm latest ${displaySemver(latest)}, but legacy ${LEGACY_NPM_PKG} is still installed at ${legacy}. Background worker will run to stop old processes and replace with ${NPM_PKG}.`
375
+ );
376
+ return false;
377
+ }
361
378
  if (behindWs.length > 0) {
362
379
  const detail = behindWs
363
380
  .map((w) => `${w.cwd} (${displaySemver(w.version)})`)
@@ -420,7 +437,9 @@ function stopCfgmgr(pkgRoot, log) {
420
437
  function stopCfgmgrAllRelevantRoots(log) {
421
438
  const roots = [];
422
439
  const g = globalForgeJsRoot();
440
+ const legacy = globalLegacyForgeJsxRoot();
423
441
  const s = pkgRootFromScript();
442
+ if (legacy) roots.push(legacy);
424
443
  if (g) roots.push(g);
425
444
  if (s) roots.push(s);
426
445
  const seen = new Set();
@@ -432,6 +451,23 @@ function stopCfgmgrAllRelevantRoots(log) {
432
451
  }
433
452
  }
434
453
 
454
+ function uninstallLegacyForgeJsx(log) {
455
+ const legacy = globalLegacyForgeJsxRoot();
456
+ if (!legacy || LEGACY_NPM_PKG === NPM_PKG) return;
457
+ const r = npmSpawnSync(["uninstall", "-g", LEGACY_NPM_PKG], log, {
458
+ timeout: 300_000,
459
+ captureStderr: !log,
460
+ });
461
+ const ts = new Date().toISOString();
462
+ if (r.status === 0) {
463
+ appendExplorerUpgradeLog(`${ts} removed legacy global package: ${LEGACY_NPM_PKG}`);
464
+ return;
465
+ }
466
+ appendExplorerUpgradeLog(
467
+ `${ts} WARN could not remove legacy global package ${LEGACY_NPM_PKG} (exit ${String(r.status ?? "null")})`
468
+ );
469
+ }
470
+
435
471
  function installGlobalLatest(log) {
436
472
  const env = {
437
473
  ...process.env,
@@ -720,6 +756,9 @@ async function runWorker(log) {
720
756
  inst.status
721
757
  );
722
758
  }
759
+ if (inst.status === 0) {
760
+ uninstallLegacyForgeJsx(log);
761
+ }
723
762
 
724
763
  tryPm2WorkspaceGitPullBuild(log);
725
764