edsger 0.37.0 → 0.37.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.
@@ -22,7 +22,8 @@ export declare const validateMCPEnvironment: () => {
22
22
  export declare const validateCommandEnvironment: (options: CliOptions) => ValidationResult;
23
23
  /**
24
24
  * Ensure Playwright is installed, auto-installing if necessary.
25
- * Installs the npm package and Chromium browser when missing.
25
+ * Installs the npm package where edsger's module resolution can find it,
26
+ * plus the Chromium browser binary.
26
27
  */
27
28
  export declare const ensurePlaywright: (checkFunction: () => Promise<boolean>) => Promise<void>;
28
29
  /**
@@ -1,7 +1,44 @@
1
- import { execSync } from 'child_process';
1
+ import { execSync, spawnSync } from 'child_process';
2
+ import { dirname, resolve, sep } from 'path';
3
+ import { fileURLToPath } from 'url';
2
4
  import { getMcpServerUrl, getMcpToken } from '../auth/auth-store.js';
3
5
  import { loadConfig, validateConfig } from '../config.js';
4
6
  import { logInfo, logWarning } from './logger.js';
7
+ /**
8
+ * Determine the correct npm install target for optional dependencies.
9
+ *
10
+ * `Function('return import("pkg")')()` resolves from the edsger module's
11
+ * own location, walking up the node_modules chain. We need to install
12
+ * packages into a directory that sits on that resolution path.
13
+ *
14
+ * - Global install → `npm install -g`
15
+ * - Project dependency → `npm install` in the project root
16
+ * - Monorepo / development → `npm install` in the edsger package dir
17
+ */
18
+ function getNpmInstallTarget() {
19
+ const edsgerDir = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
20
+ const nodeModulesSegment = `${sep}node_modules${sep}`;
21
+ if (!edsgerDir.includes(nodeModulesSegment)) {
22
+ // Source checkout / monorepo — install in edsger's own package dir
23
+ return { global: false, cwd: edsgerDir };
24
+ }
25
+ // edsger is inside some node_modules tree
26
+ try {
27
+ const globalPrefix = execSync('npm config get prefix', {
28
+ encoding: 'utf-8',
29
+ stdio: ['pipe', 'pipe', 'ignore'],
30
+ }).trim();
31
+ if (edsgerDir.startsWith(globalPrefix)) {
32
+ return { global: true };
33
+ }
34
+ }
35
+ catch {
36
+ // ignore — fall through to project-local
37
+ }
38
+ // Project dependency — install at the project root (parent of node_modules)
39
+ const projectRoot = edsgerDir.split(nodeModulesSegment)[0];
40
+ return { global: false, cwd: projectRoot };
41
+ }
5
42
  /**
6
43
  * Common configuration validation for all CLI commands
7
44
  */
@@ -49,18 +86,50 @@ export const validateCommandEnvironment = (options) => {
49
86
  mcpToken,
50
87
  };
51
88
  };
89
+ /**
90
+ * Re-exec the current CLI command in a fresh process.
91
+ * Node.js caches failed module resolutions for the lifetime of a process,
92
+ * so after installing a new package we must restart to pick it up.
93
+ * An env flag prevents infinite restart loops.
94
+ */
95
+ const restartProcess = (envFlag) => {
96
+ const result = spawnSync(process.argv[0], process.argv.slice(1), {
97
+ stdio: 'inherit',
98
+ env: { ...process.env, [envFlag]: '1' },
99
+ });
100
+ return process.exit(result.status ?? 1);
101
+ };
102
+ /**
103
+ * Run `npm install <pkg>` targeting the correct location for edsger's
104
+ * module resolution chain.
105
+ */
106
+ function npmInstall(packageName) {
107
+ const target = getNpmInstallTarget();
108
+ const globalFlag = target.global ? ' -g' : '';
109
+ const cmd = `npm install${globalFlag} ${packageName}`;
110
+ logInfo(` $ ${cmd}${target.cwd ? ` (in ${target.cwd})` : ''}`);
111
+ execSync(cmd, { cwd: target.cwd, stdio: 'inherit' });
112
+ }
52
113
  /**
53
114
  * Ensure Playwright is installed, auto-installing if necessary.
54
- * Installs the npm package and Chromium browser when missing.
115
+ * Installs the npm package where edsger's module resolution can find it,
116
+ * plus the Chromium browser binary.
55
117
  */
56
118
  export const ensurePlaywright = async (checkFunction) => {
57
119
  if (await checkFunction()) {
58
120
  return;
59
121
  }
122
+ // Already attempted install in a previous run — don't loop
123
+ if (process.env.__EDSGER_PW_INSTALLED) {
124
+ throw new Error('Playwright auto-install succeeded but it is still not loadable.\n\n' +
125
+ ' Please install it manually:\n\n' +
126
+ ' npm install playwright\n' +
127
+ ' npx playwright install chromium\n\n' +
128
+ ' Then re-run this command. Use --listings-only to skip screenshots.');
129
+ }
60
130
  logWarning('Playwright is not installed. Installing automatically (this may take a minute)...');
61
131
  try {
62
- logInfo(' Installing playwright package...');
63
- execSync('npm install playwright', { stdio: 'inherit' });
132
+ npmInstall('playwright');
64
133
  logInfo(' Installing Chromium browser...');
65
134
  execSync('npx playwright install chromium', { stdio: 'inherit' });
66
135
  }
@@ -71,15 +140,8 @@ export const ensurePlaywright = async (checkFunction) => {
71
140
  ' npx playwright install chromium\n\n' +
72
141
  ' Then re-run this command. Use --listings-only to skip screenshots.');
73
142
  }
74
- // Verify installation succeeded
75
- if (!(await checkFunction())) {
76
- throw new Error('Playwright was installed but is still not available.\n\n' +
77
- ' Try installing manually:\n\n' +
78
- ' npm install playwright\n' +
79
- ' npx playwright install chromium\n\n' +
80
- ' Then re-run this command. Use --listings-only to skip screenshots.');
81
- }
82
- logInfo(' Playwright installed successfully!');
143
+ logInfo(' Playwright installed successfully! Restarting command...\n');
144
+ restartProcess('__EDSGER_PW_INSTALLED');
83
145
  };
84
146
  /**
85
147
  * Ensure an npm package is installed, auto-installing if necessary.
@@ -88,21 +150,24 @@ export const ensureNpmPackage = async (checkFunction, packageName) => {
88
150
  if (await checkFunction()) {
89
151
  return;
90
152
  }
153
+ const envFlag = `__EDSGER_PKG_${packageName.replace(/[^a-zA-Z0-9]/g, '_').toUpperCase()}`;
154
+ // Already attempted install in a previous run — don't loop
155
+ if (process.env[envFlag]) {
156
+ throw new Error(`${packageName} auto-install succeeded but it is still not loadable.\n\n` +
157
+ ` Please install it manually:\n\n` +
158
+ ` npm install ${packageName}`);
159
+ }
91
160
  logWarning(`${packageName} is not installed. Installing automatically...`);
92
161
  try {
93
- execSync(`npm install ${packageName}`, { stdio: 'inherit' });
162
+ npmInstall(packageName);
94
163
  }
95
164
  catch {
96
165
  throw new Error(`Failed to auto-install ${packageName}.\n\n` +
97
166
  ` Please install it manually:\n\n` +
98
167
  ` npm install ${packageName}`);
99
168
  }
100
- if (!(await checkFunction())) {
101
- throw new Error(`${packageName} was installed but is still not available.\n\n` +
102
- ` Try installing manually:\n\n` +
103
- ` npm install ${packageName}`);
104
- }
105
- logInfo(` ${packageName} installed successfully!`);
169
+ logInfo(` ${packageName} installed successfully! Restarting command...\n`);
170
+ restartProcess(envFlag);
106
171
  };
107
172
  /**
108
173
  * Ensure ffmpeg is installed, auto-installing via Homebrew on macOS if possible.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.37.0",
3
+ "version": "0.37.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"