fraim 2.0.309 → 2.0.310

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.
@@ -5,7 +5,94 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.installManagedAgent = installManagedAgent;
7
7
  const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
8
9
  const managed_agent_paths_1 = require("./managed-agent-paths");
10
+ function errorMessage(error) {
11
+ return error instanceof Error ? error.message : 'Unknown error';
12
+ }
13
+ function isStaleNpmInstallError(error) {
14
+ const code = typeof error === 'object' && error !== null && 'code' in error
15
+ ? String(error.code || '')
16
+ : '';
17
+ const message = errorMessage(error).toLowerCase();
18
+ return code === 'EEXIST'
19
+ || message.includes('eexist')
20
+ || message.includes('already exists')
21
+ || message.includes('already in use');
22
+ }
23
+ function packagePathSegments(installPackage) {
24
+ let packageName = installPackage.trim();
25
+ if (!packageName)
26
+ return [];
27
+ if (packageName.startsWith('@')) {
28
+ const slashIndex = packageName.indexOf('/');
29
+ if (slashIndex === -1)
30
+ return [];
31
+ const versionAt = packageName.indexOf('@', slashIndex);
32
+ if (versionAt !== -1)
33
+ packageName = packageName.slice(0, versionAt);
34
+ }
35
+ else {
36
+ const versionAt = packageName.lastIndexOf('@');
37
+ if (versionAt > 0)
38
+ packageName = packageName.slice(0, versionAt);
39
+ }
40
+ const segments = packageName.split('/');
41
+ return segments.every((segment) => segment && segment !== '.' && segment !== '..' && !segment.includes('\\'))
42
+ ? segments
43
+ : [];
44
+ }
45
+ function isInsideDirectory(parentDir, candidate) {
46
+ const relative = path_1.default.relative(path_1.default.resolve(parentDir), path_1.default.resolve(candidate));
47
+ return !!relative && !relative.startsWith('..') && !path_1.default.isAbsolute(relative);
48
+ }
49
+ function managedShimNames(command) {
50
+ return process.platform === 'win32'
51
+ ? [command, `${command}.cmd`, `${command}.ps1`]
52
+ : [command];
53
+ }
54
+ function cleanupManagedInstallArtifacts(option, prefix) {
55
+ const resolvedPrefix = path_1.default.resolve(prefix);
56
+ const packageSegments = packagePathSegments(option.installPackage);
57
+ const candidates = new Set();
58
+ if (packageSegments.length > 0) {
59
+ candidates.add(path_1.default.join(resolvedPrefix, 'node_modules', ...packageSegments));
60
+ candidates.add(path_1.default.join(resolvedPrefix, 'lib', 'node_modules', ...packageSegments));
61
+ }
62
+ for (const shimName of managedShimNames(option.launchCommand)) {
63
+ candidates.add(path_1.default.join(resolvedPrefix, shimName));
64
+ candidates.add(path_1.default.join(resolvedPrefix, 'bin', shimName));
65
+ }
66
+ const removed = [];
67
+ for (const candidate of candidates) {
68
+ if (!isInsideDirectory(resolvedPrefix, candidate) || !fs_1.default.existsSync(candidate))
69
+ continue;
70
+ fs_1.default.rmSync(candidate, { recursive: true, force: true });
71
+ removed.push(candidate);
72
+ }
73
+ return removed;
74
+ }
75
+ async function runInstallWithStaleCleanupRetry(option, prefix, runInstall) {
76
+ try {
77
+ await runInstall();
78
+ return;
79
+ }
80
+ catch (error) {
81
+ if (!isStaleNpmInstallError(error))
82
+ throw error;
83
+ const removed = cleanupManagedInstallArtifacts(option, prefix);
84
+ try {
85
+ await runInstall();
86
+ }
87
+ catch (retryError) {
88
+ const cleanupSummary = removed.length > 0
89
+ ? ` Removed stale managed artifacts: ${removed.join(', ')}.`
90
+ : ' No matching stale managed artifacts were found to remove.';
91
+ throw new Error(`${option.label} install hit a stale npm artifact (${errorMessage(error)}).`
92
+ + `${cleanupSummary} Retry failed: ${errorMessage(retryError)}`);
93
+ }
94
+ }
95
+ }
9
96
  // Issue #1284/#1285 (Implementation Strategy §2): the exact "standard npm
10
97
  // global install, then fall back to FRAIM's managed prefix" sequence used to
11
98
  // be duplicated verbatim in `server.ts` (Hub's install-agent route) and
@@ -17,6 +104,7 @@ const managed_agent_paths_1 = require("./managed-agent-paths");
17
104
  // check before invoking this — it only covers the install-then-fallback path.
18
105
  async function installManagedAgent(option, systemPath, deps) {
19
106
  let standardInstallError = null;
107
+ const prefix = (0, managed_agent_paths_1.getPortableNodeBinPath)();
20
108
  try {
21
109
  await deps.runProcess('npm', ['install', '-g', option.installPackage], {
22
110
  PATH: systemPath,
@@ -40,13 +128,14 @@ async function installManagedAgent(option, systemPath, deps) {
40
128
  standardInstallError = `${option.label} standard install completed, but the CLI is not runnable from the user PATH.`;
41
129
  }
42
130
  catch (error) {
43
- standardInstallError = error instanceof Error ? error.message : 'Unknown error';
131
+ if (isStaleNpmInstallError(error))
132
+ cleanupManagedInstallArtifacts(option, prefix);
133
+ standardInstallError = errorMessage(error);
44
134
  }
45
135
  // Defect A fix: co-locate npm-global shims with the node.exe/npm.cmd that
46
136
  // actually runs the install, instead of the flat legacy directory.
47
- const prefix = (0, managed_agent_paths_1.getPortableNodeBinPath)();
48
137
  fs_1.default.mkdirSync(prefix, { recursive: true });
49
- await deps.runProcess('npm', ['install', '-g', option.installPackage], { npm_config_prefix: prefix });
138
+ await runInstallWithStaleCleanupRetry(option, prefix, () => deps.runProcess('npm', ['install', '-g', option.installPackage], { npm_config_prefix: prefix }).then(() => undefined));
50
139
  const ver = deps.commandVersion(option.launchCommand, (0, managed_agent_paths_1.getManagedAgentBinDirs)());
51
140
  if (!ver) {
52
141
  throw new Error(`${option.label} install completed, but the CLI is not runnable from FRAIM's managed PATH. Standard install failure: ${standardInstallError}`);
@@ -647,6 +647,7 @@ class FirstRunSessionService {
647
647
  async runNodeRow() {
648
648
  const row = this.getRow('node');
649
649
  if (this.embeddedDesktop) {
650
+ persistShellPath(this.embeddedDesktop);
650
651
  row.status = 'ok';
651
652
  row.verb = `${process.version} bundled with FRAIM`;
652
653
  this.persist();
@@ -656,6 +657,7 @@ class FirstRunSessionService {
656
657
  // If node is missing here something is severely wrong; fall through to error.
657
658
  const ver = commandVersion('node');
658
659
  if (ver) {
660
+ persistShellPath(this.embeddedDesktop);
659
661
  row.status = 'ok';
660
662
  row.verb = `${ver} installed`;
661
663
  this.persist();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fraim",
3
- "version": "2.0.309",
3
+ "version": "2.0.310",
4
4
  "description": "FRAIM core CLI and MCP package.",
5
5
  "main": "index.js",
6
6
  "bin": {