diff --git a/ci/build/build-vscode.sh b/ci/build/build-vscode.sh index 7f4a14f0730d..871a801aa30f 100755 --- a/ci/build/build-vscode.sh +++ b/ci/build/build-vscode.sh @@ -108,7 +108,7 @@ EOF ) > product.json - VSCODE_QUALITY=stable npm run gulp compile-copilot-extension-build + VSCODE_QUALITY=stable npm run gulp compile-copilot-extension-full-build npm run gulp core-ci npm run gulp "vscode-reh-web-$VSCODE_TARGET${MINIFY:+-min}-ci" diff --git a/patches/copilot.diff b/patches/copilot.diff new file mode 100644 index 000000000000..feb61f91713b --- /dev/null +++ b/patches/copilot.diff @@ -0,0 +1,163 @@ +Index: code-server/lib/vscode/build/gulpfile.extensions.ts +=================================================================== +--- code-server.orig/lib/vscode/build/gulpfile.extensions.ts ++++ code-server/lib/vscode/build/gulpfile.extensions.ts +@@ -287,6 +287,29 @@ export const compileCopilotExtensionBuil + gulp.task(compileCopilotExtensionBuildTask); + + /** ++ * Compiles the built-in copilot extension with proper `.vscodeignore` filtering ++ * and materializes native dependency shims (`node-pty`, `ripgrep`). ++ * Produces output equivalent to what CI ships from the pre-built VSIX. ++ * ++ * The result is placed in `.build/extensions/copilot/` and can be copied ++ * directly into a VS Code Insiders installation at: ++ * `/resources/app/extensions/copilot/` ++ */ ++export const compileCopilotExtensionFullBuildTask = task.define('compile-copilot-extension-full-build', task.series( ++ // Step 1: Clean previous copilot build output ++ task.define('clean-copilot-build', util.rimraf('.build/extensions/copilot')), ++ // Step 2: Build and package with proper `.vscodeignore` filtering ++ task.define('package-copilot-extension-full', () => ext.packageCopilotExtensionFullStream().pipe(gulp.dest('.build'))), ++ // Step 3: Materialize native dependency shims (`node-pty`, `ripgrep`) ++ task.define('copilot-extension-native-shims', () => { ++ const copilotExtDir = path.join(root, '.build', 'extensions', 'copilot'); ++ ext.prepareCopilotExtensionNativeShims(copilotExtDir); ++ return Promise.resolve(); ++ }) ++)); ++gulp.task(compileCopilotExtensionFullBuildTask); ++ ++/** + * Compiles the extensions for the build. + * This is essentially a helper task that combines {@link cleanExtensionsBuildTask}, {@link compileNonNativeExtensionsBuildTask} and {@link compileNativeExtensionsBuildTask} + */ +Index: code-server/lib/vscode/build/lib/extensions.ts +=================================================================== +--- code-server.orig/lib/vscode/build/lib/extensions.ts ++++ code-server/lib/vscode/build/lib/extensions.ts +@@ -24,6 +24,7 @@ import { getProductionDependencies } fro + import { type IExtensionDefinition, getExtensionStream } from './builtInExtensions.ts'; + import { fetchUrls, fetchGithub } from './fetch.ts'; + import { createTsgoStream, spawnTsgo } from './tsgo.ts'; ++import { prepareBuiltInCopilotExtensionShims } from './copilot.ts'; + import vzip from 'gulp-vinyl-zip'; + + import { createRequire } from 'module'; +@@ -482,6 +483,116 @@ export function packageCopilotExtensionS + ).pipe(util2.setExecutableBit(['**/*.sh'])); + } + ++/** ++ * Package the built-in copilot extension as a properly filtered VSIX-equivalent. ++ * Unlike {@link packageCopilotExtensionStream}, this uses vsce.listFiles with ++ * PackageManager.Npm so that .vscodeignore is respected for dependency filtering, ++ * producing output equivalent to what CI ships from the pre-built VSIX. ++ */ ++export function packageCopilotExtensionFullStream(): Stream { ++ const vsce = require('@vscode/vsce') as typeof import('@vscode/vsce'); ++ const extensionPath = path.join(root, 'extensions', 'copilot'); ++ if (!fs.existsSync(extensionPath)) { ++ return es.readArray([]); ++ } ++ ++ const esbuildConfigFileName = '.esbuild.ts'; ++ const esbuildScript = path.join(extensionPath, esbuildConfigFileName); ++ if (!fs.existsSync(esbuildScript)) { ++ throw new Error(`Copilot esbuild script not found at ${esbuildScript}`); ++ } ++ ++ const result = es.through(); ++ ++ // Step 1: Run esbuild to compile the extension ++ new Promise((resolve, reject) => { ++ const proc = cp.execFile(process.argv[0], [esbuildScript], { cwd: extensionPath }, (error, _stdout, stderr) => { ++ if (error) { ++ return reject(error); ++ } ++ const matches = (stderr || '').match(/\> (.+): error: (.+)?/g); ++ fancyLog(`Bundled extension: ${ansiColors.yellow(path.join('copilot', esbuildConfigFileName))} with ${matches ? matches.length : 0} errors.`); ++ for (const match of matches || []) { ++ fancyLog.error(match); ++ } ++ return resolve(); ++ }); ++ proc.stdout!.on('data', (data) => { ++ fancyLog(`${ansiColors.green('esbuilding copilot')}: ${data.toString('utf8')}`); ++ }); ++ }).then(() => { ++ // Step 2: Use `vsce.listFiles` with Npm package manager so `.vscodeignore` ++ // is applied to both source files AND `node_modules` dependencies. ++ // This is the key difference from `packageCopilotExtensionStream` which ++ // uses `PackageManager.None` and then blindly merges all production deps. ++ return vsce.listFiles({ cwd: extensionPath, packageManager: vsce.PackageManager.Npm }); ++ }).then(fileNames => { ++ const files = fileNames ++ .map(fileName => path.join(extensionPath, fileName)) ++ .map(filePath => new File({ ++ path: filePath, ++ stat: fs.statSync(filePath), ++ base: extensionPath, ++ contents: fs.createReadStream(filePath) ++ })); ++ ++ es.readArray(files).pipe(result); ++ }).catch(err => { ++ console.error('Failed to package copilot extension:', err); ++ result.emit('error', err); ++ }); ++ ++ // Apply the same package.json cleanup as bundled extensions get ++ const cleaned = updateExtensionPackageJSON( ++ result.pipe(rename(p => p.dirname = `extensions/copilot/${p.dirname}`)), ++ (data: any) => { ++ delete data.scripts; ++ delete data.dependencies; ++ delete data.devDependencies; ++ if (data.main) { ++ data.main = data.main.replace('/out/', '/dist/'); ++ } ++ return data; ++ } ++ ); ++ ++ return minifyExtensionResources(cleaned) ++ .pipe(util2.setExecutableBit(['**/*.sh'])); ++} ++ ++/** ++ * Materializes native dependency shims (`node-pty`, `ripgrep`) into the copilot ++ * extension output at {@link outputDir}. Uses the root `node_modules` as the ++ * source for native binaries, targeting the current platform/arch. ++ * ++ * This is the equivalent of what {@link copyCopilotNativeDepsTask} in ++ * `gulpfile.vscode.ts` does during a full product build, but scoped to ++ * just the standalone copilot extension output. ++ * ++ * Failures are logged as warnings rather than throwing, since the copilot ++ * extension can still create shims at runtime if they are missing. ++ */ ++export function prepareCopilotExtensionNativeShims(outputDir: string): void { ++ const platform = process.platform; ++ const arch = process.arch; ++ const appNodeModulesDir = path.join(root, 'node_modules'); ++ ++ if (!fs.existsSync(outputDir)) { ++ fancyLog.warn('[prepareCopilotExtensionNativeShims] Copilot extension not found at', outputDir, '- skipping shims'); ++ return; ++ } ++ ++ try { ++ prepareBuiltInCopilotExtensionShims(platform, arch, outputDir, appNodeModulesDir); ++ fancyLog(`[prepareCopilotExtensionNativeShims] Materialized native shims for ${platform}-${arch}`); ++ } catch (err) { ++ // Downgrade to a warning for local builds since the extension ++ // can still function without shims (it creates them at runtime). ++ fancyLog.warn(`[prepareCopilotExtensionNativeShims] Failed to materialize shims: ${err}`); ++ fancyLog.warn('[prepareCopilotExtensionNativeShims] The extension will still work but will create shims at runtime.'); ++ } ++} ++ + export function packageMarketplaceExtensionsStream(forWeb: boolean): Stream { + const marketplaceExtensionsDescriptions = [ + ...builtInExtensions.filter(({ name }) => (forWeb ? !marketplaceWebExtensionsExclude.has(name) : true)), diff --git a/patches/series b/patches/series index ffc15fdd9d75..3935786a69b6 100644 --- a/patches/series +++ b/patches/series @@ -22,3 +22,4 @@ clipboard.diff display-language.diff trusted-domains.diff signature-verification.diff +copilot.diff