Files
vscode/build/gulpfile.vscode.ts
2026-03-12 19:31:36 -07:00

899 lines
34 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 gulp from 'gulp';
import * as fs from 'fs';
import * as path from 'path';
import es from 'event-stream';
import vfs from 'vinyl-fs';
import rename from 'gulp-rename';
import replace from 'gulp-replace';
import filter from 'gulp-filter';
import electron from '@vscode/gulp-electron';
import jsonEditor from 'gulp-json-editor';
import * as util from './lib/util.ts';
import { getVersion } from './lib/getVersion.ts';
import { readISODate, writeISODate } from './lib/date.ts';
import * as task from './lib/task.ts';
import buildfile from './buildfile.ts';
import * as optimize from './lib/optimize.ts';
import { inlineMeta } from './lib/inlineMeta.ts';
import packageJson from '../package.json' with { type: 'json' };
import product from '../product.json' with { type: 'json' };
import * as crypto from 'crypto';
import * as i18n from './lib/i18n.ts';
import { getProductionDependencies } from './lib/dependencies.ts';
import { config } from './lib/electron.ts';
import { createAsar } from './lib/asar.ts';
import minimist from 'minimist';
import { compileBuildWithoutManglingTask, compileBuildWithManglingTask } from './gulpfile.compile.ts';
import { compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileAllExtensionsBuildTask, compileExtensionMediaBuildTask, cleanExtensionsBuildTask } from './gulpfile.extensions.ts';
import { copyCodiconsTask } from './lib/compilation.ts';
import type { EmbeddedProductInfo } from './lib/embeddedType.ts';
import { useEsbuildTranspile } from './buildConfig.ts';
import { promisify } from 'util';
import globCallback from 'glob';
import rceditCallback from 'rcedit';
import * as cp from 'child_process';
const glob = promisify(globCallback);
const rcedit = promisify(rceditCallback);
const root = path.dirname(import.meta.dirname);
const commit = getVersion(root);
// Build
const vscodeEntryPoints = [
buildfile.workerEditor,
buildfile.workerExtensionHost,
buildfile.workerNotebook,
buildfile.workerLanguageDetection,
buildfile.workerLocalFileSearch,
buildfile.workerProfileAnalysis,
buildfile.workerOutputLinks,
buildfile.workerBackgroundTokenization,
buildfile.workbenchDesktop,
buildfile.code
].flat();
const vscodeResourceIncludes = [
// NLS
'out-build/nls.messages.json',
'out-build/nls.keys.json',
// Workbench
'out-build/vs/code/electron-browser/workbench/workbench.html',
'out-build/vs/sessions/electron-browser/sessions.html',
// Electron Preload
'out-build/vs/base/parts/sandbox/electron-browser/preload.js',
'out-build/vs/base/parts/sandbox/electron-browser/preload-aux.js',
'out-build/vs/platform/browserView/electron-browser/preload-browserView.js',
// Node Scripts
'out-build/vs/base/node/{terminateProcess.sh,cpuUsage.sh,ps.sh}',
// Touchbar
'out-build/vs/workbench/browser/parts/editor/media/*.png',
'out-build/vs/workbench/contrib/debug/browser/media/*.png',
// External Terminal
'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt',
// Terminal shell integration
'out-build/vs/workbench/contrib/terminal/common/scripts/*.fish',
'out-build/vs/workbench/contrib/terminal/common/scripts/*.ps1',
'out-build/vs/workbench/contrib/terminal/common/scripts/*.psm1',
'out-build/vs/workbench/contrib/terminal/common/scripts/*.sh',
'out-build/vs/workbench/contrib/terminal/common/scripts/*.zsh',
'out-build/vs/workbench/contrib/terminal/common/scripts/psreadline/**',
// Accessibility Signals
'out-build/vs/platform/accessibilitySignal/browser/media/*.mp3',
// Welcome
'out-build/vs/workbench/contrib/welcomeGettingStarted/common/media/**/*.{svg,png}',
// Sessions
'out-build/vs/sessions/contrib/chat/browser/media/*.svg',
'out-build/vs/sessions/prompts/*.prompt.md',
// Extensions
'out-build/vs/workbench/contrib/extensions/browser/media/{theme-icon.png,language-icon.svg}',
'out-build/vs/workbench/services/extensionManagement/common/media/*.{svg,png}',
// Webview
'out-build/vs/workbench/contrib/webview/browser/pre/*.{js,html}',
// Extension Host Worker
'out-build/vs/workbench/services/extensions/worker/webWorkerExtensionHostIframe.html',
// Tree Sitter highlights
'out-build/vs/editor/common/languages/highlights/*.scm',
// Tree Sitter injection queries
'out-build/vs/editor/common/languages/injections/*.scm'
];
const vscodeResources = [
// Includes
...vscodeResourceIncludes,
// Excludes
'!out-build/vs/code/browser/**',
'!out-build/vs/editor/standalone/**',
'!out-build/vs/code/**/*-dev.html',
'!out-build/vs/workbench/contrib/issue/**/*-dev.html',
'!**/test/**'
];
const bootstrapEntryPoints = [
'out-build/main.js',
'out-build/cli.js',
'out-build/bootstrap-fork.js'
];
const bundleVSCodeTask = task.define('bundle-vscode', task.series(
util.rimraf('out-vscode'),
// Optimize: bundles source files automatically based on
// import statements based on the passed in entry points.
// In addition, concat window related bootstrap files into
// a single file.
optimize.bundleTask(
{
out: 'out-vscode',
esm: {
src: 'out-build',
entryPoints: [
...vscodeEntryPoints,
...bootstrapEntryPoints
],
resources: vscodeResources,
skipTSBoilerplateRemoval: entryPoint => entryPoint === 'vs/code/electron-browser/workbench/workbench' || entryPoint === 'vs/sessions/electron-browser/sessions'
}
}
)
));
gulp.task(bundleVSCodeTask);
// esbuild-based bundle tasks (drop-in replacement for bundle-vscode / minify-vscode)
function runEsbuildTranspile(outDir: string, excludeTests: boolean): Promise<void> {
return new Promise((resolve, reject) => {
const scriptPath = path.join(root, 'build/next/index.ts');
const args = [scriptPath, 'transpile', '--out', outDir];
if (excludeTests) {
args.push('--exclude-tests');
}
const proc = cp.spawn(process.execPath, args, {
cwd: root,
stdio: 'inherit'
});
proc.on('error', reject);
proc.on('close', code => {
if (code === 0) {
resolve();
} else {
reject(new Error(`esbuild transpile failed with exit code ${code} (outDir: ${outDir})`));
}
});
});
}
function runEsbuildBundle(outDir: string, minify: boolean, nls: boolean, target: 'desktop' | 'server' | 'server-web' = 'desktop', sourceMapBaseUrl?: string): Promise<void> {
return new Promise((resolve, reject) => {
// const tsxPath = path.join(root, 'build/node_modules/tsx/dist/cli.mjs');
const scriptPath = path.join(root, 'build/next/index.ts');
const args = [scriptPath, 'bundle', '--out', outDir, '--target', target];
if (minify) {
args.push('--minify');
args.push('--mangle-privates');
}
if (nls) {
args.push('--nls');
}
if (sourceMapBaseUrl) {
args.push('--source-map-base-url', sourceMapBaseUrl);
}
const proc = cp.spawn(process.execPath, args, {
cwd: root,
stdio: 'inherit'
});
proc.on('error', reject);
proc.on('close', code => {
if (code === 0) {
resolve();
} else {
reject(new Error(`esbuild bundle failed with exit code ${code} (outDir: ${outDir}, minify: ${minify}, nls: ${nls}, target: ${target})`));
}
});
});
}
function runTsGoTypeCheck(): Promise<void> {
return new Promise((resolve, reject) => {
const proc = cp.spawn('tsgo', ['--project', 'src/tsconfig.json', '--noEmit', '--skipLibCheck'], {
cwd: root,
stdio: 'inherit',
shell: true
});
proc.on('error', reject);
proc.on('close', code => {
if (code === 0) {
resolve();
} else {
reject(new Error(`tsgo typecheck failed with exit code ${code}`));
}
});
});
}
const sourceMappingURLBase = `https://main.vscode-cdn.net/sourcemaps/${commit}`;
const isCI = !!process.env['CI'] || !!process.env['BUILD_ARTIFACTSTAGINGDIRECTORY'] || !!process.env['GITHUB_WORKSPACE'];
const useCdnSourceMapsForPackagingTasks = isCI;
const stripSourceMapsInPackagingTasks = isCI;
const minifyVSCodeTask = task.define('minify-vscode', task.series(
bundleVSCodeTask,
util.rimraf('out-vscode-min'),
optimize.minifyTask('out-vscode', `${sourceMappingURLBase}/core`)
));
gulp.task(minifyVSCodeTask);
gulp.task(task.define('core-ci-old', task.series(
gulp.task('compile-build-with-mangling') as task.Task,
task.parallel(
gulp.task('minify-vscode') as task.Task,
gulp.task('minify-vscode-reh') as task.Task,
gulp.task('minify-vscode-reh-web') as task.Task,
)
)));
gulp.task(task.define('core-ci', task.series(
copyCodiconsTask,
compileNonNativeExtensionsBuildTask,
compileExtensionMediaBuildTask,
writeISODate('out-build'),
// Type-check with tsgo (no emit)
task.define('tsgo-typecheck', () => runTsGoTypeCheck()),
// Transpile individual files to out-build first (for unit tests)
task.define('esbuild-out-build', () => runEsbuildTranspile('out-build', false)),
// Then bundle for shipping (bundles also write NLS files to out-build)
task.parallel(
task.define('esbuild-vscode-min', () => runEsbuildBundle('out-vscode-min', true, true, 'desktop', `${sourceMappingURLBase}/core`)),
task.define('esbuild-vscode-reh-min', () => runEsbuildBundle('out-vscode-reh-min', true, true, 'server', `${sourceMappingURLBase}/core`)),
task.define('esbuild-vscode-reh-web-min', () => runEsbuildBundle('out-vscode-reh-web-min', true, true, 'server-web', `${sourceMappingURLBase}/core`)),
)
)));
const coreCIPR = task.define('core-ci-pr', task.series(
gulp.task('compile-build-without-mangling') as task.Task,
task.parallel(
gulp.task('minify-vscode') as task.Task,
gulp.task('minify-vscode-reh') as task.Task,
gulp.task('minify-vscode-reh-web') as task.Task,
)
));
gulp.task(coreCIPR);
/**
* Compute checksums for some files.
*
* @param out The out folder to read the file from.
* @param filenames The paths to compute a checksum for.
* @return A map of paths to checksums.
*/
function computeChecksums(out: string, filenames: string[]): Record<string, string> {
const result: Record<string, string> = {};
filenames.forEach(function (filename) {
const fullPath = path.join(process.cwd(), out, filename);
result[filename] = computeChecksum(fullPath);
});
return result;
}
/**
* Compute checksums for a file.
*
* @param filename The absolute path to a filename.
* @return The checksum for `filename`.
*/
function computeChecksum(filename: string): string {
const contents = fs.readFileSync(filename);
const hash = crypto
.createHash('sha256')
.update(contents)
.digest('base64')
.replace(/=+$/, '');
return hash;
}
const copilotPlatforms = [
'darwin-arm64', 'darwin-x64',
'linux-arm64', 'linux-x64',
'win32-arm64', 'win32-x64',
];
/**
* Returns a glob filter that strips @github/copilot platform packages and
* prebuilt native modules for architectures other than the build target.
* On stable builds, all copilot SDK dependencies are stripped entirely.
*/
function getCopilotExcludeFilter(platform: string, arch: string, quality: string | undefined): string[] {
const targetPlatformArch = `${platform}-${arch}`;
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}/**`);
// Strip agent host SDK dependencies entirely from stable builds
if (quality === 'stable') {
excludes.push(
'!**/node_modules/@github/copilot/**',
'!**/node_modules/@github/copilot-sdk/**',
'!**/node_modules/@github/copilot-*/**',
);
}
return ['**', ...excludes];
}
function packageTask(platform: string, arch: string, sourceFolderName: string, destinationFolderName: string, _opts?: { stats?: boolean }) {
const destination = path.join(path.dirname(root), destinationFolderName);
platform = platform || process.platform;
const task = () => {
const out = sourceFolderName;
const versionedResourcesFolder = util.getVersionedResourcesFolder(platform, commit!);
const checksums = computeChecksums(out, [
'vs/base/parts/sandbox/electron-browser/preload.js',
'vs/workbench/workbench.desktop.main.js',
'vs/workbench/workbench.desktop.main.css',
'vs/workbench/api/node/extensionHostProcess.js',
'vs/code/electron-browser/workbench/workbench.html',
'vs/code/electron-browser/workbench/workbench.js',
'vs/sessions/sessions.desktop.main.js',
'vs/sessions/sessions.desktop.main.css',
'vs/sessions/electron-browser/sessions.html',
'vs/sessions/electron-browser/sessions.js'
]);
const src = gulp.src(out + '/**', { base: '.' })
.pipe(rename(function (path) { path.dirname = path.dirname!.replace(new RegExp('^' + out), 'out'); }))
.pipe(util.setExecutableBit(['**/*.sh']));
const platformSpecificBuiltInExtensionsExclusions = product.builtInExtensions.filter(ext => {
if (!(ext as { platforms?: string[] }).platforms) {
return false;
}
const set = new Set((ext as { platforms?: string[] }).platforms);
return !set.has(platform);
}).map(ext => `!.build/extensions/${ext.name}/**`);
const extensions = gulp.src(['.build/extensions/**', ...platformSpecificBuiltInExtensionsExclusions], { base: '.build', dot: true });
const sourceFilterPattern = stripSourceMapsInPackagingTasks
? ['**', '!**/*.{js,css}.map']
: ['**'];
const sources = es.merge(src, extensions)
.pipe(filter(sourceFilterPattern, { dot: true }));
let version = packageJson.version;
const quality = (product as { quality?: string }).quality;
if (quality && quality !== 'stable') {
version += '-' + quality;
}
const name = product.nameShort;
const packageJsonUpdates: Record<string, unknown> = { name, version };
if (platform === 'linux') {
packageJsonUpdates.desktopName = `${product.applicationName}.desktop`;
}
let packageJsonContents: string;
const packageJsonStream = gulp.src(['package.json'], { base: '.' })
.pipe(jsonEditor(packageJsonUpdates))
.pipe(es.through(function (file) {
packageJsonContents = file.contents.toString();
this.emit('data', file);
}));
let productJsonContents: string;
const productJsonStream = gulp.src(['product.json'], { base: '.' })
.pipe(jsonEditor({ commit, date: readISODate(out), checksums, version }))
.pipe(es.through(function (file) {
productJsonContents = file.contents.toString();
this.emit('data', file);
}));
const isInsiderOrExploration = quality === 'insider' || quality === 'exploration';
const embedded = isInsiderOrExploration
? (product as typeof product & { embedded?: EmbeddedProductInfo }).embedded
: undefined;
const packageSubJsonStream = isInsiderOrExploration
? gulp.src(['package.json'], { base: '.' })
.pipe(jsonEditor((json: Record<string, unknown>) => {
json.name = `sessions-${quality || 'oss-dev'}`;
return json;
}))
.pipe(rename('package.sub.json'))
: undefined;
const productSubJsonStream = embedded
? gulp.src(['product.json'], { base: '.' })
.pipe(jsonEditor((json: Record<string, unknown>) => {
Object.keys(embedded).forEach(key => {
json[key] = embedded[key as keyof EmbeddedProductInfo];
});
return json;
}))
.pipe(rename('product.sub.json'))
: undefined;
const license = gulp.src([product.licenseFileName, 'ThirdPartyNotices.txt', 'licenses/**'], { base: '.', allowEmpty: true });
// TODO the API should be copied to `out` during compile, not here
const api = gulp.src('src/vscode-dts/vscode.d.ts').pipe(rename('out/vscode-dts/vscode.d.ts'));
const telemetry = gulp.src('.build/telemetry/**', { base: '.build/telemetry', dot: true });
const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path));
const root = path.resolve(path.join(import.meta.dirname, '..'));
const productionDependencies = getProductionDependencies(root);
const dependenciesSrc = productionDependencies.map(d => path.relative(root, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`]).flat().concat('!**/*.mk');
const depFilterPattern = ['**', `!**/${config.version}/**`, '!**/bin/darwin-arm64-87/**', '!**/package-lock.json', '!**/yarn.lock'];
if (stripSourceMapsInPackagingTasks) {
depFilterPattern.push('!**/*.{js,css}.map');
}
const deps = gulp.src(dependenciesSrc, { base: '.', dot: true })
.pipe(filter(depFilterPattern))
.pipe(util.cleanNodeModules(path.join(import.meta.dirname, '.moduleignore')))
.pipe(util.cleanNodeModules(path.join(import.meta.dirname, `.moduleignore.${process.platform}`)))
.pipe(filter(getCopilotExcludeFilter(platform, arch, quality)))
.pipe(jsFilter)
.pipe(util.rewriteSourceMappingURL(sourceMappingURLBase))
.pipe(jsFilter.restore)
.pipe(createAsar(path.join(process.cwd(), 'node_modules'), [
'**/*.node',
'**/@vscode/ripgrep/bin/*',
'**/@github/copilot-*/**',
'**/node-pty/build/Release/*',
'**/node-pty/build/Release/conpty/*',
'**/node-pty/lib/worker/conoutSocketWorker.js',
'**/node-pty/lib/shared/conout.js',
'**/*.wasm',
'**/@vscode/vsce-sign/bin/*',
], [
'**/*.mk',
'!node_modules/vsda/**' // stay compatible with extensions that depend on us shipping `vsda` into ASAR
], [
'node_modules/vsda/**' // retain copy of `vsda` in node_modules for internal use
], 'node_modules.asar'));
const mergeStreams = [
packageJsonStream,
productJsonStream,
license,
api,
telemetry,
sources,
deps
];
if (packageSubJsonStream) {
mergeStreams.push(packageSubJsonStream);
}
if (productSubJsonStream) {
mergeStreams.push(productSubJsonStream);
}
let all = es.merge(...mergeStreams);
if (platform === 'win32') {
all = es.merge(all, gulp.src([
'resources/win32/bower.ico',
'resources/win32/c.ico',
'resources/win32/code.ico',
'resources/win32/config.ico',
'resources/win32/cpp.ico',
'resources/win32/csharp.ico',
'resources/win32/css.ico',
'resources/win32/default.ico',
'resources/win32/go.ico',
'resources/win32/html.ico',
'resources/win32/jade.ico',
'resources/win32/java.ico',
'resources/win32/javascript.ico',
'resources/win32/json.ico',
'resources/win32/less.ico',
'resources/win32/markdown.ico',
'resources/win32/php.ico',
'resources/win32/powershell.ico',
'resources/win32/python.ico',
'resources/win32/react.ico',
'resources/win32/ruby.ico',
'resources/win32/sass.ico',
'resources/win32/shell.ico',
'resources/win32/sql.ico',
'resources/win32/typescript.ico',
'resources/win32/vue.ico',
'resources/win32/xml.ico',
'resources/win32/yaml.ico',
'resources/win32/code_70x70.png',
'resources/win32/code_150x150.png'
], { base: '.' }));
if (embedded) {
all = es.merge(all, gulp.src('resources/win32/sessions.ico', { base: '.' }));
}
} else if (platform === 'linux') {
const policyDest = gulp.src('.build/policies/linux/**', { base: '.build/policies/linux' })
.pipe(rename(f => f.dirname = `policies/${f.dirname}`));
all = es.merge(all, gulp.src('resources/linux/code.png', { base: '.' }), policyDest);
} else if (platform === 'darwin') {
const shortcut = gulp.src('resources/darwin/bin/code.sh')
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(rename('bin/code'));
const policyDest = gulp.src('.build/policies/darwin/**', { base: '.build/policies/darwin' })
.pipe(rename(f => f.dirname = `policies/${f.dirname}`));
all = es.merge(all, shortcut, policyDest);
}
const electronConfig = {
...config,
platform,
arch: arch === 'armhf' ? 'arm' : arch,
ffmpegChromium: false,
darwinAssetsCar: 'resources/darwin/code.car',
...(embedded ? {
darwinMiniAppName: embedded.nameShort,
darwinMiniAppBundleIdentifier: embedded.darwinBundleIdentifier,
darwinMiniAppIcon: 'resources/darwin/sessions.icns',
darwinMiniAppAssetsCar: 'resources/darwin/sessions.car',
darwinMiniAppBundleURLTypes: [{
role: 'Viewer',
name: embedded.nameLong,
urlSchemes: [embedded.urlProtocol]
}],
win32ProxyAppName: embedded.nameShort,
win32ProxyIcon: 'resources/win32/sessions.ico',
} : {})
};
let result: NodeJS.ReadWriteStream = all
.pipe(util.skipDirectories())
.pipe(util.fixWin32DirectoryPermissions())
.pipe(filter(['**', '!**/.github/**'], { dot: true })) // https://github.com/microsoft/vscode/issues/116523
.pipe(electron(electronConfig))
.pipe(filter([
'**',
'!LICENSE',
'!version',
...(platform === 'darwin' && !isInsiderOrExploration ? ['!**/Contents/Applications', '!**/Contents/Applications/**'] : []),
...(platform === 'win32' && !isInsiderOrExploration ? ['!**/electron_proxy.exe'] : []),
], { dot: true }));
if (platform === 'linux') {
result = es.merge(result, gulp.src('resources/completions/bash/code', { base: '.' })
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(rename(function (f) { f.basename = product.applicationName; })));
result = es.merge(result, gulp.src('resources/completions/zsh/_code', { base: '.' })
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(rename(function (f) { f.basename = '_' + product.applicationName; })));
}
if (platform === 'win32') {
result = es.merge(result, gulp.src('resources/win32/bin/code.js', { base: 'resources/win32', allowEmpty: true }));
if (versionedResourcesFolder) {
result = es.merge(result, gulp.src('resources/win32/versioned/bin/code.cmd', { base: 'resources/win32/versioned' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(replace('@@VERSIONFOLDER@@', versionedResourcesFolder))
.pipe(rename(function (f) { f.basename = product.applicationName; })));
result = es.merge(result, gulp.src('resources/win32/versioned/bin/code.sh', { base: 'resources/win32/versioned' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(replace('@@PRODNAME@@', product.nameLong))
.pipe(replace('@@VERSION@@', version))
.pipe(replace('@@COMMIT@@', String(commit)))
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(replace('@@VERSIONFOLDER@@', versionedResourcesFolder))
.pipe(replace('@@SERVERDATAFOLDER@@', product.serverDataFolderName || '.vscode-remote'))
.pipe(replace('@@QUALITY@@', quality!))
.pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; })));
} else {
result = es.merge(result, gulp.src('resources/win32/bin/code.cmd', { base: 'resources/win32' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(rename(function (f) { f.basename = product.applicationName; })));
result = es.merge(result, gulp.src('resources/win32/bin/code.sh', { base: 'resources/win32' })
.pipe(replace('@@NAME@@', product.nameShort))
.pipe(replace('@@PRODNAME@@', product.nameLong))
.pipe(replace('@@VERSION@@', version))
.pipe(replace('@@COMMIT@@', String(commit)))
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(replace('@@SERVERDATAFOLDER@@', product.serverDataFolderName || '.vscode-remote'))
.pipe(replace('@@QUALITY@@', String(quality)))
.pipe(rename(function (f) { f.basename = product.applicationName; f.extname = ''; })));
}
result = es.merge(result, gulp.src('resources/win32/VisualElementsManifest.xml', { base: 'resources/win32' })
.pipe(replace('@@VERSIONFOLDER@@', versionedResourcesFolder ? `${versionedResourcesFolder}\\` : ''))
.pipe(rename(product.nameShort + '.VisualElementsManifest.xml')));
result = es.merge(result, gulp.src('.build/policies/win32/**', { base: '.build/policies/win32' })
.pipe(rename(f => f.dirname = `policies/${f.dirname}`)));
if (quality === 'stable' || quality === 'insider') {
result = es.merge(result, gulp.src('.build/win32/appx/**', { base: '.build/win32' }));
const rawVersion = version.replace(/-\w+$/, '').split('.');
const appxVersion = `${rawVersion[0]}.0.${rawVersion[1]}.${rawVersion[2]}`;
result = es.merge(result, gulp.src('resources/win32/appx/AppxManifest.xml', { base: '.' })
.pipe(replace('@@AppxPackageName@@', product.win32AppUserModelId))
.pipe(replace('@@AppxPackageVersion@@', appxVersion))
.pipe(replace('@@AppxPackageDisplayName@@', product.nameLong))
.pipe(replace('@@AppxPackageDescription@@', product.win32NameVersion))
.pipe(replace('@@ApplicationIdShort@@', product.win32RegValueName))
.pipe(replace('@@ApplicationExe@@', product.nameShort + '.exe'))
.pipe(replace('@@FileExplorerContextMenuID@@', quality === 'stable' ? 'OpenWithCode' : 'OpenWithCodeInsiders'))
.pipe(replace('@@FileExplorerContextMenuCLSID@@', (product as { win32ContextMenu?: Record<string, { clsid: string }> }).win32ContextMenu![arch].clsid))
.pipe(replace('@@FileExplorerContextMenuDLL@@', `${quality === 'stable' ? 'code' : 'code_insider'}_explorer_command_${arch}.dll`))
.pipe(rename(f => f.dirname = `appx/manifest`)));
}
} else if (platform === 'linux') {
result = es.merge(result, gulp.src('resources/linux/bin/code.sh', { base: '.' })
.pipe(replace('@@PRODNAME@@', product.nameLong))
.pipe(replace('@@APPNAME@@', product.applicationName))
.pipe(rename('bin/' + product.applicationName)));
}
result = inlineMeta(result, {
targetPaths: bootstrapEntryPoints,
packageJsonFn: () => packageJsonContents,
productJsonFn: () => productJsonContents
});
return result.pipe(vfs.dest(destination));
};
task.taskName = `package-${platform}-${arch}`;
return task;
}
function patchWin32DependenciesTask(destinationFolderName: string) {
const cwd = path.join(path.dirname(root), destinationFolderName);
return async () => {
const versionedResourcesFolder = util.getVersionedResourcesFolder('win32', commit!);
const deps = (await Promise.all([
glob('**/*.node', { cwd, ignore: 'extensions/node_modules/@parcel/watcher/**' }),
glob('**/rg.exe', { cwd }),
glob('**/*explorer_command*.dll', { cwd }),
])).flatMap(o => o);
const packageJson = JSON.parse(await fs.promises.readFile(path.join(cwd, versionedResourcesFolder, 'resources', 'app', 'package.json'), 'utf8'));
const product = JSON.parse(await fs.promises.readFile(path.join(cwd, versionedResourcesFolder, 'resources', 'app', 'product.json'), 'utf8'));
const baseVersion = packageJson.version.replace(/-.*$/, '');
const patchPromises = deps.map<Promise<unknown>>(async dep => {
const basename = path.basename(dep);
await rcedit(path.join(cwd, dep), {
'file-version': baseVersion,
'version-string': {
'CompanyName': 'Microsoft Corporation',
'FileDescription': product.nameLong,
'FileVersion': packageJson.version,
'InternalName': basename,
'LegalCopyright': 'Copyright (C) 2026 Microsoft. All rights reserved',
'OriginalFilename': basename,
'ProductName': product.nameLong,
'ProductVersion': packageJson.version,
}
});
});
await Promise.all(patchPromises);
};
}
/**
* Copies VS Code's own node-pty and ripgrep binaries into the copilot SDK's
* expected locations so the copilot CLI subprocess can find them at runtime.
* The copilot-bundled prebuilds and ripgrep are stripped by .moduleignore;
* this replaces them with the same binaries VS Code already ships, avoiding
* new system dependency requirements.
*
* node-pty: `prebuilds/{platform}-{arch}/` (pty.node + spawn-helper)
* ripgrep: `ripgrep/bin/{platform}-{arch}/` (rg binary)
*/
function copyCopilotNativeDepsTask(platform: string, arch: string, destinationFolderName: string) {
const outputDir = path.join(path.dirname(root), destinationFolderName);
return async () => {
const appBase = platform === 'darwin'
? path.join(outputDir, `${product.nameLong}.app`, 'Contents', 'Resources', 'app')
: path.join(outputDir, 'resources', 'app');
// On stable builds the copilot SDK is stripped entirely -- nothing to copy into.
const copilotDir = path.join(appBase, 'node_modules', '@github', 'copilot');
if (!fs.existsSync(copilotDir)) {
const quality = (product as { quality?: string }).quality;
if (quality && quality !== 'stable') {
throw new Error(`[copyCopilotNativeDeps] Copilot SDK directory not found at ${copilotDir} -- unexpected for ${quality} build`);
}
console.log(`[copyCopilotNativeDeps] Skipping -- copilot SDK not present (stable build)`);
return;
}
const platformArch = `${platform === 'win32' ? 'win32' : platform}-${arch}`;
// Copy node-pty (pty.node + spawn-helper) into copilot prebuilds
const nodePtySource = path.join(appBase, 'node_modules', 'node-pty', 'build', 'Release');
const copilotPrebuildsDir = path.join(appBase, 'node_modules', '@github', 'copilot', 'prebuilds', platformArch);
if (fs.existsSync(nodePtySource)) {
fs.mkdirSync(copilotPrebuildsDir, { recursive: true });
fs.cpSync(nodePtySource, copilotPrebuildsDir, { recursive: true });
console.log(`[copyCopilotNativeDeps] Copied node-pty to ${copilotPrebuildsDir}`);
}
// Copy ripgrep (rg binary) into copilot ripgrep
const rgBinary = platform === 'win32' ? 'rg.exe' : 'rg';
const ripgrepSource = path.join(appBase, 'node_modules', '@vscode', 'ripgrep', 'bin', rgBinary);
const copilotRipgrepDir = path.join(appBase, 'node_modules', '@github', 'copilot', 'ripgrep', 'bin', platformArch);
if (fs.existsSync(ripgrepSource)) {
fs.mkdirSync(copilotRipgrepDir, { recursive: true });
fs.copyFileSync(ripgrepSource, path.join(copilotRipgrepDir, rgBinary));
console.log(`[copyCopilotNativeDeps] Copied ripgrep to ${copilotRipgrepDir}`);
}
};
}
const buildRoot = path.dirname(root);
const BUILD_TARGETS = [
{ platform: 'win32', arch: 'x64' },
{ platform: 'win32', arch: 'arm64' },
{ platform: 'darwin', arch: 'x64', opts: { stats: true } },
{ platform: 'darwin', arch: 'arm64', opts: { stats: true } },
{ platform: 'linux', arch: 'x64' },
{ platform: 'linux', arch: 'armhf' },
{ platform: 'linux', arch: 'arm64' },
];
BUILD_TARGETS.forEach(buildTarget => {
const dashed = (str: string) => (str ? `-${str}` : ``);
const platform = buildTarget.platform;
const arch = buildTarget.arch;
const opts = buildTarget.opts;
const [vscode, vscodeMin] = ['', 'min'].map(minified => {
const sourceFolderName = `out-vscode${dashed(minified)}`;
const destinationFolderName = `VSCode${dashed(platform)}${dashed(arch)}`;
const packageTasks: task.Task[] = [
compileNativeExtensionsBuildTask,
util.rimraf(path.join(buildRoot, destinationFolderName)),
packageTask(platform, arch, sourceFolderName, destinationFolderName, opts),
copyCopilotNativeDepsTask(platform, arch, destinationFolderName)
];
if (platform === 'win32') {
packageTasks.push(patchWin32DependenciesTask(destinationFolderName));
}
const vscodeTaskCI = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series(...packageTasks));
gulp.task(vscodeTaskCI);
let vscodeTask: task.Task;
if (useEsbuildTranspile) {
const esbuildBundleTask = task.define(
`esbuild-bundle${dashed(platform)}${dashed(arch)}${dashed(minified)}`,
() => runEsbuildBundle(
sourceFolderName,
!!minified,
true,
'desktop',
minified && useCdnSourceMapsForPackagingTasks ? `${sourceMappingURLBase}/core` : undefined
)
);
vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series(
copyCodiconsTask,
cleanExtensionsBuildTask,
compileNonNativeExtensionsBuildTask,
compileExtensionMediaBuildTask,
writeISODate('out-build'),
esbuildBundleTask,
vscodeTaskCI
));
} else {
vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series(
minified ? compileBuildWithManglingTask : compileBuildWithoutManglingTask,
cleanExtensionsBuildTask,
compileNonNativeExtensionsBuildTask,
compileExtensionMediaBuildTask,
minified ? minifyVSCodeTask : bundleVSCodeTask,
vscodeTaskCI
));
}
gulp.task(vscodeTask);
return vscodeTask;
});
if (process.platform === platform && process.arch === arch) {
gulp.task(task.define('vscode', task.series(vscode)));
gulp.task(task.define('vscode-min', task.series(vscodeMin)));
}
});
// #region nls
const innoSetupConfig: Record<string, { codePage: string; defaultInfo?: { name: string; id: string } }> = {
'zh-cn': { codePage: 'CP936', defaultInfo: { name: 'Simplified Chinese', id: '$0804', } },
'zh-tw': { codePage: 'CP950', defaultInfo: { name: 'Traditional Chinese', id: '$0404' } },
'ko': { codePage: 'CP949', defaultInfo: { name: 'Korean', id: '$0412' } },
'ja': { codePage: 'CP932' },
'de': { codePage: 'CP1252' },
'fr': { codePage: 'CP1252' },
'es': { codePage: 'CP1252' },
'ru': { codePage: 'CP1251' },
'it': { codePage: 'CP1252' },
'pt-br': { codePage: 'CP1252' },
'hu': { codePage: 'CP1250' },
'tr': { codePage: 'CP1254' }
};
gulp.task(task.define(
'vscode-translations-export',
task.series(
gulp.task('core-ci') as task.Task,
compileAllExtensionsBuildTask,
function () {
const pathToMetadata = './out-build/nls.metadata.json';
const pathToExtensions = '.build/extensions/*';
const pathToSetup = 'build/win32/i18n/messages.en.isl';
return es.merge(
gulp.src(pathToMetadata).pipe(i18n.createXlfFilesForCoreBundle()),
gulp.src(pathToSetup).pipe(i18n.createXlfFilesForIsl()),
gulp.src(pathToExtensions).pipe(i18n.createXlfFilesForExtensions())
).pipe(vfs.dest('../vscode-translations-export'));
}
)
));
gulp.task('vscode-translations-import', function () {
const options = minimist(process.argv.slice(2), {
string: 'location',
default: {
location: '../vscode-translations-import'
}
});
return es.merge([...i18n.defaultLanguages, ...i18n.extraLanguages].map(language => {
const id = language.id;
return gulp.src(`${options.location}/${id}/vscode-setup/messages.xlf`)
.pipe(i18n.prepareIslFiles(language, innoSetupConfig[language.id]))
.pipe(vfs.dest(`./build/win32/i18n`));
}));
});
// #endregion