Files
vscode/build/lib/copilot.ts

163 lines
7.1 KiB
TypeScript

/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as fs from 'fs';
import * as path from 'path';
/**
* The platforms that @github/copilot ships platform-specific packages for.
* These are the `@github/copilot-{platform}` optional dependency packages.
*/
export const copilotPlatforms = [
'darwin-arm64', 'darwin-x64',
'linux-arm64', 'linux-x64',
'win32-arm64', 'win32-x64',
];
/**
* Converts VS Code build platform/arch to the values that Node.js reports
* at runtime via `process.platform` and `process.arch`.
*
* The copilot SDK's `loadNativeModule` looks up native binaries under
* `prebuilds/${process.platform}-${process.arch}/`, so the directory names
* must match these runtime values exactly.
*/
function toNodePlatformArch(platform: string, arch: string): { nodePlatform: string; nodeArch: string } {
// alpine is musl-linux; Node still reports process.platform === 'linux'
let nodePlatform = platform === 'alpine' ? 'linux' : platform;
let nodeArch = arch;
if (arch === 'armhf') {
// VS Code build uses 'armhf'; Node reports process.arch === 'arm'
nodeArch = 'arm';
} else if (arch === 'alpine') {
// Legacy: { platform: 'linux', arch: 'alpine' } means alpine-x64
nodePlatform = 'linux';
nodeArch = 'x64';
}
return { nodePlatform, nodeArch };
}
/**
* Returns a glob filter that strips @github/copilot platform packages
* for architectures other than the build target.
*
* For platforms the copilot SDK doesn't natively support (e.g. alpine, armhf),
* ALL platform packages are stripped - that's fine because the SDK doesn't ship
* binaries for those platforms anyway, and we replace them with VS Code's own.
*/
export function getCopilotExcludeFilter(platform: string, arch: string): string[] {
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
const targetPlatformArch = `${nodePlatform}-${nodeArch}`;
const nonTargetPlatforms = copilotPlatforms.filter(p => p !== targetPlatformArch);
// Strip wrong-architecture @github/copilot-{platform} packages.
// All copilot prebuilds are stripped by .moduleignore; VS Code's own
// node-pty is copied into the prebuilds location by a post-packaging task.
const excludes = nonTargetPlatforms.map(p => `!**/node_modules/@github/copilot-${p}/**`);
return ['**', ...excludes];
}
/**
* Copies VS Code's own node-pty binaries into the copilot SDK's
* expected locations so the copilot CLI subprocess can find them at runtime.
* The copilot-bundled prebuilds are stripped by .moduleignore;
* this replaces them with the same binaries VS Code already ships, avoiding
* new system dependency requirements.
*
* This works even for platforms the copilot SDK doesn't natively support
* (e.g. alpine, armhf) because the SDK's native module loader simply
* looks for `prebuilds/{process.platform}-{process.arch}/pty.node` - it
* doesn't validate the platform against a supported list.
*
* Failures are logged but do not throw, to avoid breaking the build on
* platforms where something unexpected happens.
*
* @param nodeModulesDir Absolute path to the node_modules directory that
* contains both the source binaries (node-pty) and the copilot SDK
* target directories.
*/
export function copyCopilotNativeDeps(platform: string, arch: string, nodeModulesDir: string): void {
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
const platformArch = `${nodePlatform}-${nodeArch}`;
const copilotBase = path.join(nodeModulesDir, '@github', 'copilot');
if (!fs.existsSync(copilotBase)) {
console.warn(`[copyCopilotNativeDeps] @github/copilot not found at ${copilotBase}, skipping`);
return;
}
const nodePtySource = path.join(nodeModulesDir, 'node-pty', 'build', 'Release');
if (!fs.existsSync(nodePtySource)) {
console.warn(`[copyCopilotNativeDeps] node-pty source not found at ${nodePtySource}, skipping`);
return;
}
try {
// Copy node-pty (pty.node + spawn-helper on Unix, conpty.node + conpty/ on Windows)
// into copilot prebuilds so the SDK finds them via loadNativeModule.
const copilotPrebuildsDir = path.join(copilotBase, 'prebuilds', platformArch);
fs.mkdirSync(copilotPrebuildsDir, { recursive: true });
fs.cpSync(nodePtySource, copilotPrebuildsDir, { recursive: true });
console.log(`[copyCopilotNativeDeps] Copied node-pty from ${nodePtySource} to ${copilotPrebuildsDir}`);
} catch (err) {
console.warn(`[copyCopilotNativeDeps] Failed to copy node-pty for ${platformArch}: ${err}`);
}
}
/**
* Materializes copilot CLI shims directly inside the built-in copilot extension.
*
* This is used when copilot is shipped as a built-in extension so startup does
* not need to create shims at runtime. The destination layout matches the
* runtime shim logic in the copilot extension:
* - node-pty: node_modules/@github/copilot/sdk/prebuilds/{platform-arch}
* - ripgrep: node_modules/@github/copilot/sdk/ripgrep/bin/{platform-arch}
* - marker: node_modules/@github/copilot/shims.txt
*
* Failures throw to fail the build because built-in packaging must guarantee
* these artifacts are present.
*/
export function prepareBuiltInCopilotExtensionShims(platform: string, arch: string, builtInCopilotExtensionDir: string, appNodeModulesDir: string): void {
const { nodePlatform, nodeArch } = toNodePlatformArch(platform, arch);
const platformArch = `${nodePlatform}-${nodeArch}`;
const extensionNodeModules = path.join(builtInCopilotExtensionDir, 'node_modules');
const copilotBase = path.join(extensionNodeModules, '@github', 'copilot');
const copilotSdkBase = path.join(copilotBase, 'sdk');
if (!fs.existsSync(copilotSdkBase)) {
throw new Error(`[prepareBuiltInCopilotExtensionShims] Copilot SDK directory not found at ${copilotSdkBase}`);
}
const nodePtySource = path.join(appNodeModulesDir, 'node-pty', 'build', 'Release');
if (!fs.existsSync(nodePtySource)) {
throw new Error(`[prepareBuiltInCopilotExtensionShims] node-pty source not found at ${nodePtySource}`);
}
const ripgrepSource = path.join(appNodeModulesDir, '@vscode', 'ripgrep', 'bin');
if (!fs.existsSync(ripgrepSource)) {
throw new Error(`[prepareBuiltInCopilotExtensionShims] ripgrep source not found at ${ripgrepSource}`);
}
const nodePtyDest = path.join(copilotSdkBase, 'prebuilds', platformArch);
const ripgrepDest = path.join(copilotSdkBase, 'ripgrep', 'bin', platformArch);
const shimMarkerPath = path.join(copilotBase, 'shims.txt');
try {
fs.mkdirSync(nodePtyDest, { recursive: true });
fs.cpSync(nodePtySource, nodePtyDest, { recursive: true });
fs.mkdirSync(ripgrepDest, { recursive: true });
fs.cpSync(ripgrepSource, ripgrepDest, { recursive: true });
fs.writeFileSync(shimMarkerPath, 'Shims created successfully');
console.log(`[prepareBuiltInCopilotExtensionShims] Materialized shims for ${platformArch} in ${builtInCopilotExtensionDir}`);
} catch (err) {
throw new Error(`[prepareBuiltInCopilotExtensionShims] Failed to materialize shims for ${platformArch}: ${err}`);
}
}