Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
_getCommitHash,
listPlugins,
_readLockFile,
_resolveEsbuildBin,
uninstallPlugin,
updatePlugin,
_parseSource,
Expand Down Expand Up @@ -180,6 +181,16 @@ describe('getCommitHash', () => {
});
});

describe('resolveEsbuildBin', () => {
it('resolves a usable esbuild executable path', () => {
const binPath = _resolveEsbuildBin();
expect(binPath).not.toBeNull();
expect(typeof binPath).toBe('string');
expect(fs.existsSync(binPath!)).toBe(true);
expect(binPath?.endsWith('esbuild')).toBe(true);
});
});

describe('listPlugins', () => {
const testDir = path.join(PLUGINS_DIR, '__test-list-plugin__');

Expand Down
53 changes: 46 additions & 7 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { execFileSync } from 'node:child_process';
import { execSync, execFileSync } from 'node:child_process';
import { PLUGINS_DIR } from './discovery.js';
import { getErrorMessage } from './errors.js';
import { log } from './logger.js';
Expand Down Expand Up @@ -393,19 +393,57 @@ function linkHostOpencli(pluginDir: string): void {
}
}

/**
* Resolve the path to the esbuild CLI executable with fallback strategies.
*/
export function resolveEsbuildBin(): string | null {
try {
const pkgUrl = import.meta.resolve('esbuild/package.json');
if (pkgUrl.startsWith('file://')) {
const pkgPath = new URL(pkgUrl).pathname;
const pkgRaw = fs.readFileSync(pkgPath, 'utf8');
const pkg = JSON.parse(pkgRaw);
if (pkg.bin && typeof pkg.bin === 'object' && pkg.bin.esbuild) {
const binPath = path.resolve(path.dirname(pkgPath), pkg.bin.esbuild);
if (fs.existsSync(binPath)) return binPath;
} else if (typeof pkg.bin === 'string') {
const binPath = path.resolve(path.dirname(pkgPath), pkg.bin);
if (fs.existsSync(binPath)) return binPath;
}
}
} catch {
// ignore package resolution failures
}

const thisFile = new URL(import.meta.url).pathname;
const hostRoot = path.resolve(path.dirname(thisFile), '..');
const binFallback = path.join(hostRoot, 'node_modules', '.bin', 'esbuild');
if (fs.existsSync(binFallback)) {
return binFallback;
}

try {
const globalBin = execSync('which esbuild', { encoding: 'utf-8', stdio: 'pipe' }).trim();
if (globalBin && fs.existsSync(globalBin)) {
return globalBin;
}
} catch {
// ignore PATH lookup failures
}

return null;
}

/**
* Transpile TS plugin files to JS so they work in production mode.
* Uses esbuild from the host opencli's node_modules for fast single-file transpilation.
*/
function transpilePluginTs(pluginDir: string): void {
try {
// Resolve esbuild binary from the host opencli's node_modules
const thisFile = new URL(import.meta.url).pathname;
const hostRoot = path.resolve(path.dirname(thisFile), '..');
const esbuildBin = path.join(hostRoot, 'node_modules', '.bin', 'esbuild');
const esbuildBin = resolveEsbuildBin();

if (!fs.existsSync(esbuildBin)) {
log.debug('esbuild not found in host node_modules, skipping TS transpilation');
if (!esbuildBin) {
log.debug('esbuild not found in host node_modules, via resolve, or in PATH, skipping TS transpilation');
return;
}

Expand Down Expand Up @@ -438,6 +476,7 @@ function transpilePluginTs(pluginDir: string): void {
}

export {
resolveEsbuildBin as _resolveEsbuildBin,
getCommitHash as _getCommitHash,
parseSource as _parseSource,
readLockFile as _readLockFile,
Expand Down
Loading