diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0467d904d..43305d08b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,7 +23,7 @@ jobs: - name: Check CSP configuration in webClientServer.ts run: | TARGET_FILE="patched-vscode/src/vs/server/node/webClientServer.ts" - REQUIRED_TEXT="'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://openvsxorg.blob.core.windows.net https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;'" + REQUIRED_TEXT="'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;'" if [ ! -f "$TARGET_FILE" ]; then echo "❌ FAIL: Target file $TARGET_FILE does not exist." diff --git a/patched-vscode/build/gulpfile.extensions.js b/patched-vscode/build/gulpfile.extensions.js index 9296656c9..be49d1430 100644 --- a/patched-vscode/build/gulpfile.extensions.js +++ b/patched-vscode/build/gulpfile.extensions.js @@ -48,11 +48,11 @@ const compilations = [ 'extensions/json-language-features/client/tsconfig.json', 'extensions/json-language-features/server/tsconfig.json', 'extensions/markdown-language-features/preview-src/tsconfig.json', - 'extensions/markdown-language-features/server/tsconfig.json', 'extensions/markdown-language-features/tsconfig.json', 'extensions/markdown-math/tsconfig.json', 'extensions/media-preview/tsconfig.json', 'extensions/merge-conflict/tsconfig.json', + 'extensions/terminal-suggest/tsconfig.json', 'extensions/microsoft-authentication/tsconfig.json', 'extensions/notebook-renderers/tsconfig.json', 'extensions/npm/tsconfig.json', @@ -73,12 +73,14 @@ const compilations = [ 'extensions/typescript-language-features/tsconfig.json', 'extensions/vscode-api-tests/tsconfig.json', 'extensions/vscode-colorize-tests/tsconfig.json', + 'extensions/vscode-colorize-perf-tests/tsconfig.json', 'extensions/vscode-test-resolver/tsconfig.json', '.vscode/extensions/vscode-selfhost-test-provider/tsconfig.json', + '.vscode/extensions/vscode-selfhost-import-aid/tsconfig.json', ]; -const getBaseUrl = out => `https://ticino.blob.core.windows.net/sourcemaps/${commit}/${out}`; +const getBaseUrl = out => `https://main.vscode-cdn.net/sourcemaps/${commit}/${out}`; const tasks = compilations.map(function (tsconfigFile) { const absolutePath = path.join(root, tsconfigFile); @@ -108,7 +110,6 @@ const tasks = compilations.map(function (tsconfigFile) { } function createPipeline(build, emitError, transpileOnly) { - const nlsDev = require('vscode-nls-dev'); const tsb = require('./lib/tsb'); const sourcemaps = require('gulp-sourcemaps'); @@ -133,7 +134,6 @@ const tasks = compilations.map(function (tsconfigFile) { .pipe(tsFilter) .pipe(util.loadSourcemaps()) .pipe(compilation()) - .pipe(build ? nlsDev.rewriteLocalizeCalls() : es.through()) .pipe(build ? util.stripSourceMappingURL() : es.through()) .pipe(sourcemaps.write('.', { sourceMappingURL: !build ? null : f => `${baseUrl}/${f.relative}.map`, @@ -143,9 +143,6 @@ const tasks = compilations.map(function (tsconfigFile) { sourceRoot: '../src/', })) .pipe(tsFilter.restore) - .pipe(build ? nlsDev.bundleMetaDataFiles(headerId, headerOut) : es.through()) - // Filter out *.nls.json file. We needed them only to bundle meta data file. - .pipe(filter(['**', '!**/*.nls.json'], { dot: true })) .pipe(reporter.end(emitError)); return es.duplex(input, output); @@ -241,27 +238,61 @@ exports.compileExtensionMediaBuildTask = compileExtensionMediaBuildTask; //#region Azure Pipelines +/** + * Cleans the build directory for extensions + */ const cleanExtensionsBuildTask = task.define('clean-extensions-build', util.rimraf('.build/extensions')); -const compileExtensionsBuildTask = task.define('compile-extensions-build', task.series( +exports.cleanExtensionsBuildTask = cleanExtensionsBuildTask; + +/** + * brings in the marketplace extensions for the build + */ +const bundleMarketplaceExtensionsBuildTask = task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))); + +/** + * Compiles the non-native extensions for the build + * @note this does not clean the directory ahead of it. See {@link cleanExtensionsBuildTask} for that. + */ +const compileNonNativeExtensionsBuildTask = task.define('compile-non-native-extensions-build', task.series( + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-non-native-extensions-build', () => ext.packageNonNativeLocalExtensionsStream().pipe(gulp.dest('.build'))) +)); +gulp.task(compileNonNativeExtensionsBuildTask); +exports.compileNonNativeExtensionsBuildTask = compileNonNativeExtensionsBuildTask; + +/** + * Compiles the native extensions for the build + * @note this does not clean the directory ahead of it. See {@link cleanExtensionsBuildTask} for that. + */ +const compileNativeExtensionsBuildTask = task.define('compile-native-extensions-build', () => ext.packageNativeLocalExtensionsStream().pipe(gulp.dest('.build'))); +gulp.task(compileNativeExtensionsBuildTask); +exports.compileNativeExtensionsBuildTask = compileNativeExtensionsBuildTask; + +/** + * Compiles the extensions for the build. + * This is essentially a helper task that combines {@link cleanExtensionsBuildTask}, {@link compileNonNativeExtensionsBuildTask} and {@link compileNativeExtensionsBuildTask} + */ +const compileAllExtensionsBuildTask = task.define('compile-extensions-build', task.series( cleanExtensionsBuildTask, - task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))), - task.define('bundle-extensions-build', () => ext.packageLocalExtensionsStream(false, false).pipe(gulp.dest('.build'))), + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-extensions-build', () => ext.packageAllLocalExtensionsStream(false, false).pipe(gulp.dest('.build'))), )); +gulp.task(compileAllExtensionsBuildTask); +exports.compileAllExtensionsBuildTask = compileAllExtensionsBuildTask; -gulp.task(compileExtensionsBuildTask); -gulp.task(task.define('extensions-ci', task.series(compileExtensionsBuildTask, compileExtensionMediaBuildTask))); +// This task is run in the compilation stage of the CI pipeline. We only compile the non-native extensions since those can be fully built regardless of platform. +// This defers the native extensions to the platform specific stage of the CI pipeline. +gulp.task(task.define('extensions-ci', task.series(compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask))); const compileExtensionsBuildPullRequestTask = task.define('compile-extensions-build-pr', task.series( cleanExtensionsBuildTask, - task.define('bundle-marketplace-extensions-build', () => ext.packageMarketplaceExtensionsStream(false).pipe(gulp.dest('.build'))), - task.define('bundle-extensions-build-pr', () => ext.packageLocalExtensionsStream(false, true).pipe(gulp.dest('.build'))), + bundleMarketplaceExtensionsBuildTask, + task.define('bundle-extensions-build-pr', () => ext.packageAllLocalExtensionsStream(false, true).pipe(gulp.dest('.build'))), )); - gulp.task(compileExtensionsBuildPullRequestTask); -gulp.task(task.define('extensions-ci-pr', task.series(compileExtensionsBuildPullRequestTask, compileExtensionMediaBuildTask))); - -exports.compileExtensionsBuildTask = compileExtensionsBuildTask; +// This task is run in the compilation stage of the PR pipeline. We compile all extensions in it to verify compilation. +gulp.task(task.define('extensions-ci-pr', task.series(compileExtensionsBuildPullRequestTask, compileExtensionMediaBuildTask))); //#endregion diff --git a/patched-vscode/build/gulpfile.reh.js b/patched-vscode/build/gulpfile.reh.js index c2b81d0cf..0eae23c1a 100644 --- a/patched-vscode/build/gulpfile.reh.js +++ b/patched-vscode/build/gulpfile.reh.js @@ -12,11 +12,13 @@ const util = require('./lib/util'); const { getVersion } = require('./lib/getVersion'); const task = require('./lib/task'); const optimize = require('./lib/optimize'); +const { inlineMeta } = require('./lib/inlineMeta'); const product = require('../product.json'); const rename = require('gulp-rename'); const replace = require('gulp-replace'); const filter = require('gulp-filter'); const { getProductionDependencies } = require('./lib/dependencies'); +const { readISODate } = require('./lib/date'); const vfs = require('vinyl-fs'); const packageJson = require('../package.json'); const flatmap = require('gulp-flatmap'); @@ -24,11 +26,12 @@ const gunzip = require('gulp-gunzip'); const File = require('vinyl'); const fs = require('fs'); const glob = require('glob'); -const { compileBuildTask } = require('./gulpfile.compile'); -const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); -const { vscodeWebEntryPoints, vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web'); +const { compileBuildWithManglingTask } = require('./gulpfile.compile'); +const { cleanExtensionsBuildTask, compileNonNativeExtensionsBuildTask, compileNativeExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions'); +const { vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web'); const cp = require('child_process'); const log = require('fancy-log'); +const buildfile = require('./buildfile'); const REPO_ROOT = path.dirname(__dirname); const commit = getVersion(REPO_ROOT); @@ -39,6 +42,7 @@ const REMOTE_FOLDER = path.join(REPO_ROOT, 'remote'); const BUILD_TARGETS = [ { platform: 'win32', arch: 'x64' }, + { platform: 'win32', arch: 'arm64' }, { platform: 'darwin', arch: 'x64' }, { platform: 'darwin', arch: 'arm64' }, { platform: 'linux', arch: 'x64' }, @@ -50,95 +54,96 @@ const BUILD_TARGETS = [ { platform: 'linux', arch: 'alpine' }, ]; -const serverResources = [ - - // Bootstrap - 'out-build/bootstrap.js', - 'out-build/bootstrap-fork.js', - 'out-build/bootstrap-amd.js', - 'out-build/bootstrap-node.js', +const serverResourceIncludes = [ - // Performance - 'out-build/vs/base/common/performance.js', + // NLS + 'out-build/nls.messages.json', + 'out-build/nls.keys.json', // Required to generate translations. // Process monitor 'out-build/vs/base/node/cpuUsage.sh', 'out-build/vs/base/node/ps.sh', + // External Terminal + 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', + // Terminal shell integration - 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration.ps1', - 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-bash.sh', - 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-env.zsh', - 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-profile.zsh', - 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-rc.zsh', - 'out-build/vs/workbench/contrib/terminal/browser/media/shellIntegration-login.zsh', - 'out-build/vs/workbench/contrib/terminal/browser/media/fish_xdg_data/fish/vendor_conf.d/shellIntegration.fish', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.ps1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/CodeTabExpansion.psm1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/GitTabExpansion.psm1', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-bash.sh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-env.zsh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-profile.zsh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-rc.zsh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration-login.zsh', + 'out-build/vs/workbench/contrib/terminal/common/scripts/shellIntegration.fish', - '!**/test/**' ]; -const serverWithWebResources = [ +const serverResourceExcludes = [ + '!out-build/vs/**/{electron-sandbox,electron-main,electron-utility}/**', + '!out-build/vs/editor/standalone/**', + '!out-build/vs/workbench/**/*-tb.png', + '!**/test/**' +]; - // Include all of server... - ...serverResources, +const serverResources = [ + ...serverResourceIncludes, + ...serverResourceExcludes +]; - // ...and all of web +const serverWithWebResourceIncludes = [ + ...serverResourceIncludes, + 'out-build/vs/code/browser/workbench/*.html', ...vscodeWebResourceIncludes ]; -const serverEntryPoints = [ - { - name: 'vs/server/node/server.main', - exclude: ['vs/css', 'vs/nls'] - }, - { - name: 'vs/server/node/server.cli', - exclude: ['vs/css', 'vs/nls'] - }, - { - name: 'vs/workbench/api/node/extensionHostProcess', - exclude: ['vs/css', 'vs/nls'] - }, - { - name: 'vs/platform/files/node/watcher/watcherMain', - exclude: ['vs/css', 'vs/nls'] - }, - { - name: 'vs/platform/terminal/node/ptyHostMain', - exclude: ['vs/css', 'vs/nls'] - } +const serverWithWebResourceExcludes = [ + ...serverResourceExcludes, + '!out-build/vs/code/**/*-dev.html' +]; + +const serverWithWebResources = [ + ...serverWithWebResourceIncludes, + ...serverWithWebResourceExcludes ]; +const serverEntryPoints = buildfile.codeServer; + +const webEntryPoints = [ + buildfile.workerEditor, + buildfile.workerExtensionHost, + buildfile.workerNotebook, + buildfile.workerLanguageDetection, + buildfile.workerLocalFileSearch, + buildfile.workerOutputLinks, + buildfile.workerBackgroundTokenization, + buildfile.keyboardMaps, + buildfile.codeWeb +].flat(); const serverWithWebEntryPoints = [ // Include all of server ...serverEntryPoints, - // Include workbench web - ...vscodeWebEntryPoints + // Include all of web + ...webEntryPoints, +].flat(); + +const bootstrapEntryPoints = [ + 'out-build/server-main.js', + 'out-build/server-cli.js', + 'out-build/bootstrap-fork.js' ]; function getNodeVersion() { - const yarnrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.yarnrc'), 'utf8'); - const nodeVersion = /^target "(.*)"$/m.exec(yarnrc)[1]; - const internalNodeVersion = /^ms_build_id "(.*)"$/m.exec(yarnrc)[1]; + const npmrc = fs.readFileSync(path.join(REPO_ROOT, 'remote', '.npmrc'), 'utf8'); + const nodeVersion = /^target="(.*)"$/m.exec(npmrc)[1]; + const internalNodeVersion = /^ms_build_id="(.*)"$/m.exec(npmrc)[1]; return { nodeVersion, internalNodeVersion }; } -function getNodeChecksum(nodeVersion, platform, arch, glibcPrefix) { - let expectedName; - switch (platform) { - case 'win32': - expectedName = `win-${arch}/node.exe`; - break; - - case 'darwin': - case 'alpine': - case 'linux': - expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`; - break; - } - +function getNodeChecksum(expectedName) { const nodeJsChecksums = fs.readFileSync(path.join(REPO_ROOT, 'build', 'checksums', 'nodejs.txt'), 'utf8'); for (const line of nodeJsChecksums.split('\n')) { const [checksum, name] = line.split(/\s+/); @@ -182,7 +187,6 @@ if (defaultNodeTask) { function nodejs(platform, arch) { const { fetchUrls, fetchGithub } = require('./lib/fetch'); const untar = require('gulp-untar'); - const crypto = require('crypto'); if (arch === 'armhf') { arch = 'armv7l'; @@ -194,7 +198,24 @@ function nodejs(platform, arch) { log(`Downloading node.js ${nodeVersion} ${platform} ${arch} from ${product.nodejsRepository}...`); const glibcPrefix = process.env['VSCODE_NODE_GLIBC'] ?? ''; - const checksumSha256 = getNodeChecksum(nodeVersion, platform, arch, glibcPrefix); + let expectedName; + switch (platform) { + case 'win32': + expectedName = product.nodejsRepository !== 'https://nodejs.org' ? + `win-${arch}-node.exe` : `win-${arch}/node.exe`; + break; + + case 'darwin': + expectedName = `node-v${nodeVersion}-${platform}-${arch}.tar.gz`; + break; + case 'linux': + expectedName = `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`; + break; + case 'alpine': + expectedName = `node-v${nodeVersion}-linux-${arch}-musl.tar.gz`; + break; + } + const checksumSha256 = getNodeChecksum(expectedName); if (checksumSha256) { log(`Using SHA256 checksum for checking integrity: ${checksumSha256}`); @@ -205,13 +226,13 @@ function nodejs(platform, arch) { switch (platform) { case 'win32': return (product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `win-${arch}-node.exe`, checksumSha256 }) : + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) : fetchUrls(`/dist/v${nodeVersion}/win-${arch}/node.exe`, { base: 'https://nodejs.org', checksumSha256 })) .pipe(rename('node.exe')); case 'darwin': case 'linux': return (product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}${glibcPrefix}-${platform}-${arch}.tar.gz`, checksumSha256 }) : + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) : fetchUrls(`/dist/v${nodeVersion}/node-v${nodeVersion}-${platform}-${arch}.tar.gz`, { base: 'https://nodejs.org', checksumSha256 }) ).pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) .pipe(filter('**/node')) @@ -219,7 +240,7 @@ function nodejs(platform, arch) { .pipe(rename('node')); case 'alpine': return product.nodejsRepository !== 'https://nodejs.org' ? - fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: `node-v${nodeVersion}-${platform}-${arch}.tar.gz`, checksumSha256 }) + fetchGithub(product.nodejsRepository, { version: `${nodeVersion}-${internalNodeVersion}`, name: expectedName, checksumSha256 }) .pipe(flatmap(stream => stream.pipe(gunzip()).pipe(untar()))) .pipe(filter('**/node')) .pipe(util.setExecutableBit('**')) @@ -288,23 +309,32 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa } const name = product.nameShort; - const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' }) - .pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined })); - const date = new Date().toISOString(); + let packageJsonContents; + const packageJsonStream = gulp.src(['remote/package.json'], { base: 'remote' }) + .pipe(json({ name, version, dependencies: undefined, optionalDependencies: undefined, type: 'module' })) + .pipe(es.through(function (file) { + packageJsonContents = file.contents.toString(); + this.emit('data', file); + })); + let productJsonContents; const productJsonStream = gulp.src(['product.json'], { base: '.' }) - .pipe(json({ commit, date, version })); + .pipe(json({ commit, date: readISODate('out-build'), version })) + .pipe(es.through(function (file) { + productJsonContents = file.contents.toString(); + this.emit('data', file); + })); const license = gulp.src(['remote/LICENSE'], { base: 'remote', allowEmpty: true }); const jsFilter = util.filter(data => !data.isDirectory() && /\.js$/.test(data.path)); const productionDependencies = getProductionDependencies(REMOTE_FOLDER); - const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d.path)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); + const dependenciesSrc = productionDependencies.map(d => path.relative(REPO_ROOT, d)).map(d => [`${d}/**`, `!${d}/**/{test,tests}/**`, `!${d}/.bin/**`]).flat(); const deps = gulp.src(dependenciesSrc, { base: 'remote', dot: true }) // filter out unnecessary files, no source maps in server build - .pipe(filter(['**', '!**/package-lock.json', '!**/yarn.lock', '!**/*.js.map'])) + .pipe(filter(['**', '!**/package-lock.json', '!**/*.js.map'])) .pipe(util.cleanNodeModules(path.join(__dirname, '.moduleignore'))) .pipe(util.cleanNodeModules(path.join(__dirname, `.moduleignore.${process.platform}`))) .pipe(jsFilter) @@ -373,13 +403,7 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa ); } - if (platform === 'linux' && process.env['VSCODE_NODE_GLIBC'] === '-glibc-2.17') { - result = es.merge(result, - gulp.src(`resources/server/bin/helpers/check-requirements-linux-legacy.sh`, { base: '.' }) - .pipe(rename(`bin/helpers/check-requirements.sh`)) - .pipe(util.setExecutableBit()) - ); - } else if (platform === 'linux' || platform === 'alpine') { + if (platform === 'linux' || platform === 'alpine') { result = es.merge(result, gulp.src(`resources/server/bin/helpers/check-requirements-linux.sh`, { base: '.' }) .pipe(rename(`bin/helpers/check-requirements.sh`)) @@ -387,6 +411,12 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa ); } + result = inlineMeta(result, { + targetPaths: bootstrapEntryPoints, + packageJsonFn: () => packageJsonContents, + productJsonFn: () => productJsonContents + }); + return result.pipe(vfs.dest(destination)); }; } @@ -401,45 +431,28 @@ function tweakProductForServerWeb(product) { } ['reh', 'reh-web'].forEach(type => { - const optimizeTask = task.define(`optimize-vscode-${type}`, task.series( + const bundleTask = task.define(`bundle-vscode-${type}`, task.series( util.rimraf(`out-vscode-${type}`), - optimize.optimizeTask( + optimize.bundleTask( { out: `out-vscode-${type}`, - amd: { - src: 'out-build', - entryPoints: (type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints).flat(), - otherSources: [], - resources: type === 'reh' ? serverResources : serverWithWebResources, - loaderConfig: optimize.loaderConfig(), - inlineAmdImages: true, - bundleInfo: undefined, - fileContentMapper: createVSCodeWebFileContentMapper('.build/extensions', type === 'reh-web' ? tweakProductForServerWeb(product) : product) - }, - commonJS: { + esm: { src: 'out-build', entryPoints: [ - 'out-build/server-main.js', - 'out-build/server-cli.js' + ...(type === 'reh' ? serverEntryPoints : serverWithWebEntryPoints), + ...bootstrapEntryPoints ], - platform: 'node', - external: [ - 'minimist', - // TODO: we cannot inline `product.json` because - // it is being changed during build time at a later - // point in time (such as `checksums`) - '../product.json', - '../package.json' - ] + resources: type === 'reh' ? serverResources : serverWithWebResources, + fileContentMapper: createVSCodeWebFileContentMapper('.build/extensions', type === 'reh-web' ? tweakProductForServerWeb(product) : product) } } ) )); const minifyTask = task.define(`minify-vscode-${type}`, task.series( - optimizeTask, + bundleTask, util.rimraf(`out-vscode-${type}-min`), - optimize.minifyTask(`out-vscode-${type}`, `https://ticino.blob.core.windows.net/sourcemaps/${commit}/core`) + optimize.minifyTask(`out-vscode-${type}`, `https://main.vscode-cdn.net/sourcemaps/${commit}/core`) )); gulp.task(minifyTask); @@ -453,6 +466,7 @@ function tweakProductForServerWeb(product) { const destinationFolderName = `vscode-${type}${dashed(platform)}${dashed(arch)}`; const serverTaskCI = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}-ci`, task.series( + compileNativeExtensionsBuildTask, gulp.task(`node-${platform}-${arch}`), util.rimraf(path.join(BUILD_ROOT, destinationFolderName)), packageTask(type, platform, arch, sourceFolderName, destinationFolderName) @@ -460,10 +474,11 @@ function tweakProductForServerWeb(product) { gulp.task(serverTaskCI); const serverTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series( - compileBuildTask, - compileExtensionsBuildTask, + compileBuildWithManglingTask, + cleanExtensionsBuildTask, + compileNonNativeExtensionsBuildTask, compileExtensionMediaBuildTask, - minified ? minifyTask : optimizeTask, + minified ? minifyTask : bundleTask, serverTaskCI )); gulp.task(serverTask); diff --git a/patched-vscode/build/npm/dirs.js b/patched-vscode/build/npm/dirs.js index ec8679a59..1d9e1b438 100644 --- a/patched-vscode/build/npm/dirs.js +++ b/patched-vscode/build/npm/dirs.js @@ -5,7 +5,7 @@ const fs = require('fs'); -// Complete list of directories where yarn should be executed to install node modules +// Complete list of directories where npm should be executed to install node modules const dirs = [ '', 'build', @@ -29,7 +29,6 @@ const dirs = [ 'extensions/jake', 'extensions/json-language-features', 'extensions/json-language-features/server', - 'extensions/markdown-language-features/server', 'extensions/markdown-language-features', 'extensions/markdown-math', 'extensions/media-preview', @@ -52,6 +51,7 @@ const dirs = [ 'extensions/typescript-language-features', 'extensions/vscode-api-tests', 'extensions/vscode-colorize-tests', + 'extensions/vscode-colorize-perf-tests', 'extensions/vscode-test-resolver', 'remote', 'remote/web', @@ -59,6 +59,7 @@ const dirs = [ 'test/integration/browser', 'test/monaco', 'test/smoke', + '.vscode/extensions/vscode-selfhost-import-aid', '.vscode/extensions/vscode-selfhost-test-provider', ]; diff --git a/patched-vscode/extensions/sagemaker-idle-extension/.vscodeignore b/patched-vscode/extensions/sagemaker-idle-extension/.vscodeignore index 56b78554c..9b8a9dae4 100644 --- a/patched-vscode/extensions/sagemaker-idle-extension/.vscodeignore +++ b/patched-vscode/extensions/sagemaker-idle-extension/.vscodeignore @@ -8,5 +8,4 @@ tsconfig.json out/test/** out/** cgmanifest.json -yarn.lock preview-src/** diff --git a/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts index 2a11ca447..ccf6fed1d 100644 --- a/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts +++ b/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts @@ -2,22 +2,14 @@ import * as vscode from "vscode"; import * as fs from "fs"; import * as path from "path"; -let idleFilePath: string -let terminalActivityInterval: NodeJS.Timeout | undefined -const LOG_PREFIX = "[sagemaker-idle-extension]" -const CHECK_INTERVAL = 60000; // 60 seconds interval +let idleFilePath: string; export function activate(context: vscode.ExtensionContext) { initializeIdleFilePath(); registerEventListeners(context); - startMonitoringTerminalActivity(); } -export function deactivate() { - if(terminalActivityInterval) { - clearInterval(terminalActivityInterval) - } -} +export function deactivate() {} /** * Initializes the file path where the idle timestamp will be stored. @@ -28,7 +20,7 @@ function initializeIdleFilePath() { idleFilePath = path.join(tmpDirectory, ".sagemaker-last-active-timestamp"); // Set initial lastActivetimestamp - updateLastActivityTimestamp() + updateLastActivityTimestamp(); } /** @@ -56,52 +48,6 @@ function registerEventListeners(context: vscode.ExtensionContext) { ); } -/** - * Starts monitoring terminal activity by setting an interval to check for activity in the /dev/pts directory. - */ -const startMonitoringTerminalActivity = () => { - terminalActivityInterval = setInterval(checkTerminalActivity, CHECK_INTERVAL); -}; - - -/** - * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. - * - * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. - * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. - * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, - * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification - * times of the files in the /dev/pts directory, we can detect terminal activity. - * - * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function - * updates the last activity timestamp. - */ -const checkTerminalActivity = () => { - fs.readdir("/dev/pts", (err, files) => { - if (err) { - console.error(`${LOG_PREFIX} Error reading /dev/pts directory:`, err); - return; - } - - const now = Date.now(); - const activityDetected = files.some((file) => { - const filePath = path.join("/dev/pts", file); - try { - const stats = fs.statSync(filePath); - const mtime = new Date(stats.mtime).getTime(); - return now - mtime < CHECK_INTERVAL; - } catch (error) { - console.error(`${LOG_PREFIX} Error reading file stats:`, error); - return false; - } - }); - - if (activityDetected) { - updateLastActivityTimestamp(); - } - }); -}; - /** * Updates the last activity timestamp by recording the current timestamp in the idle file and * refreshing the status bar. The timestamp should be in ISO 8601 format and set to the UTC timezone. diff --git a/patched-vscode/src/vs/base/common/product.ts b/patched-vscode/src/vs/base/common/product.ts index 2cca4770b..539c115f8 100644 --- a/patched-vscode/src/vs/base/common/product.ts +++ b/patched-vscode/src/vs/base/common/product.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IStringDictionary } from 'vs/base/common/collections'; -import { PlatformName } from 'vs/base/common/platform'; +import { IStringDictionary } from './collections.js'; +import { PlatformName } from './platform.js'; +import { IPolicy } from './policy.js'; export interface IBuiltInExtension { readonly name: string; @@ -55,7 +56,7 @@ export type ExtensionVirtualWorkspaceSupport = { }; export interface IProductConfiguration { - readonly rootEndpoint?: string + readonly rootEndpoint?: string; readonly version: string; readonly date?: string; readonly quality?: string; @@ -83,6 +84,7 @@ export interface IProductConfiguration { readonly webEndpointUrlTemplate?: string; readonly webviewContentExternalBaseUrlTemplate?: string; readonly target?: string; + readonly nlsCoreBaseUrl?: string; readonly settingsSearchBuildId?: number; readonly settingsSearchUrl?: string; @@ -95,15 +97,16 @@ export interface IProductConfiguration { readonly extensionsGallery?: { readonly serviceUrl: string; - readonly servicePPEUrl?: string; - readonly searchUrl?: string; - readonly itemUrl: string; - readonly publisherUrl: string; - readonly resourceUrlTemplate: string; readonly controlUrl: string; + readonly extensionUrlTemplate: string; + readonly resourceUrlTemplate: string; readonly nlsBaseUrl: string; + readonly accessSKUs?: string[]; }; + readonly extensionPublisherOrgs?: readonly string[]; + readonly trustedExtensionPublishers?: readonly string[]; + readonly extensionRecommendations?: IStringDictionary; readonly configBasedExtensionTips?: IStringDictionary; readonly exeBasedExtensionTips?: IStringDictionary; @@ -115,6 +118,8 @@ export interface IProductConfiguration { readonly languageExtensionTips?: readonly string[]; readonly trustedExtensionUrlPublicKeys?: IStringDictionary; readonly trustedExtensionAuthAccess?: string[] | IStringDictionary; + readonly trustedMcpAuthAccess?: string[] | IStringDictionary; + readonly inheritAuthAccountPreference?: IStringDictionary; readonly trustedExtensionProtocolHandlers?: readonly string[]; readonly commandPaletteSuggestedCommandIds?: string[]; @@ -160,7 +165,6 @@ export interface IProductConfiguration { readonly tunnelApplicationConfig?: ITunnelApplicationConfig; readonly npsSurveyUrl?: string; - readonly cesSurveyUrl?: string; readonly surveys?: readonly ISurveyData[]; readonly checksums?: { [path: string]: string }; @@ -174,24 +178,47 @@ export interface IProductConfiguration { readonly extensionPointExtensionKind?: { readonly [extensionPointId: string]: ('ui' | 'workspace' | 'web')[] }; readonly extensionSyncedKeys?: { readonly [extensionId: string]: string[] }; + readonly extensionsEnabledWithApiProposalVersion?: string[]; readonly extensionEnabledApiProposals?: { readonly [extensionId: string]: string[] }; readonly extensionUntrustedWorkspaceSupport?: { readonly [extensionId: string]: ExtensionUntrustedWorkspaceSupport }; readonly extensionVirtualWorkspacesSupport?: { readonly [extensionId: string]: ExtensionVirtualWorkspaceSupport }; + readonly extensionProperties: IStringDictionary<{ + readonly hasPrereleaseVersion?: boolean; + readonly excludeVersionRange?: string; + }>; readonly msftInternalDomains?: string[]; readonly linkProtectionTrustedDomains?: readonly string[]; + readonly defaultAccount?: { + readonly authenticationProvider: { + readonly id: string; + readonly enterpriseProviderId: string; + readonly enterpriseProviderConfig: string; + readonly scopes: string[]; + }; + readonly tokenEntitlementUrl: string; + readonly chatEntitlementUrl: string; + }; + readonly 'configurationSync.store'?: ConfigurationSyncStore; readonly 'editSessions.store'?: Omit; readonly darwinUniversalAssetId?: string; + readonly darwinBundleIdentifier?: string; readonly profileTemplatesUrl?: string; readonly commonlyUsedSettings?: string[]; readonly aiGeneratedWorkspaceTrust?: IAiGeneratedWorkspaceTrust; - readonly gitHubEntitlement?: IGitHubEntitlement; - readonly chatWelcomeView?: IChatWelcomeView; + + readonly defaultChatAgent?: IDefaultChatAgent; readonly chatParticipantRegistry?: string; + + readonly emergencyAlertUrl?: string; + + readonly remoteDefaultExtensionsIfInstalledLocally?: string[]; + + readonly extensionConfigurationPolicy?: IStringDictionary; } export interface ITunnelApplicationConfig { @@ -207,6 +234,7 @@ export interface IExtensionRecommendations { export interface ISettingsEditorOpenCondition { readonly prerelease?: boolean | string; + readonly descriptionOverride?: string; } export interface IExtensionRecommendationCondition { @@ -296,18 +324,41 @@ export interface IAiGeneratedWorkspaceTrust { readonly startupTrustRequestLearnMore: string; } -export interface IGitHubEntitlement { - providerId: string; - command: { title: string; titleWithoutPlaceHolder: string; action: string; when: string }; - entitlementUrl: string; - extensionId: string; - enablementKey: string; - confirmationMessage: string; - confirmationAction: string; -} - -export interface IChatWelcomeView { - welcomeViewId: string; - welcomeViewTitle: string; - welcomeViewContent: string; +export interface IDefaultChatAgent { + readonly extensionId: string; + readonly chatExtensionId: string; + + readonly documentationUrl: string; + readonly skusDocumentationUrl: string; + readonly publicCodeMatchesUrl: string; + readonly manageSettingsUrl: string; + readonly managePlanUrl: string; + readonly manageOverageUrl: string; + readonly upgradePlanUrl: string; + readonly signUpUrl: string; + + readonly providerId: string; + readonly providerName: string; + readonly enterpriseProviderId: string; + readonly enterpriseProviderName: string; + readonly alternativeProviderId: string; + readonly alternativeProviderName: string; + readonly providerUriSetting: string; + readonly providerScopes: string[][]; + + readonly entitlementUrl: string; + readonly entitlementSignupLimitedUrl: string; + + readonly chatQuotaExceededContext: string; + readonly completionsQuotaExceededContext: string; + + readonly walkthroughCommand: string; + readonly completionsMenuCommand: string; + readonly completionsRefreshTokenCommand: string; + readonly chatRefreshTokenCommand: string; + readonly generateCommitMessageCommand: string; + + readonly completionsAdvancedSetting: string; + readonly completionsEnablementSetting: string; + readonly nextEditSuggestionsSetting: string; } diff --git a/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts b/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts index ba6b17125..125bb756b 100644 --- a/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts +++ b/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts @@ -3,51 +3,41 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as fs from 'fs'; -import { FileAccess } from 'vs/base/common/network'; -import * as path from 'vs/base/common/path'; +import * as path from 'path'; +import { promises as fs } from 'fs'; +import { FileAccess } from '../../base/common/network.js'; +import { join } from '../../base/common/path.js'; +import type { INLSConfiguration } from '../../nls.js'; +import { resolveNLSConfiguration } from '../../base/node/nls.js'; -import * as lp from 'vs/base/node/languagePacks'; +const nlsMetadataPath = join(FileAccess.asFileUri('').fsPath); +const nlsConfigurationCache = new Map>(); -const metaData = path.join(FileAccess.asFileUri('').fsPath, 'nls.metadata.json'); -const _cache: Map> = new Map(); - -export function getNLSConfiguration(language: string, userDataPath: string): Promise { - const key = `${language}||${userDataPath}`; - let result = _cache.get(key); +export async function getNLSConfiguration(language: string, userDataPath: string): Promise { + const cacheKey = `${language}||${userDataPath}`; + let result = nlsConfigurationCache.get(cacheKey); if (!result) { - // The OS Locale on the remote side really doesn't matter, so we pass in the same language - result = lp.getNLSConfiguration("dummy_commit", userDataPath, metaData, language, language).then(value => { - if (InternalNLSConfiguration.is(value)) { - value._languagePackSupport = true; - } - // If the configuration has no results keep trying since code-server - // doesn't restart when a language is installed so this result would - // persist (the plugin might not be installed yet for example). - if (value.locale !== 'en' && value.locale !== 'en-us' && Object.keys(value.availableLanguages).length === 0) { - _cache.delete(key); + // passing a dummy commit which is required to resolve language packs + result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: 'dummy_commit', userDataPath, nlsMetadataPath }); + nlsConfigurationCache.set(cacheKey, result); + // If the language pack does not yet exist, it defaults to English, which is + // then cached and you have to restart even if you then install the pack. + result.then((r) => { + if (!language.startsWith('en') && r.resolvedLanguage.startsWith('en')) { + nlsConfigurationCache.delete(cacheKey); } - return value; - }); - _cache.set(key, result); + }) } - return result; -} -export namespace InternalNLSConfiguration { - export function is(value: lp.NLSConfiguration): value is lp.InternalNLSConfiguration { - const candidate: lp.InternalNLSConfiguration = value as lp.InternalNLSConfiguration; - return candidate && typeof candidate._languagePackId === 'string'; - } + return result; } /** - * The code below is copied from from src/main.js. + * Copied from from src/main.js. */ - export const getLocaleFromConfig = async (argvResource: string): Promise => { try { - const content = stripComments(await fs.promises.readFile(argvResource, 'utf8')); + const content = stripComments(await fs.readFile(argvResource, 'utf8')); return JSON.parse(content).locale; } catch (error) { if (error.code !== "ENOENT") { @@ -57,6 +47,9 @@ export const getLocaleFromConfig = async (argvResource: string): Promise } }; +/** + * Copied from from src/main.js. + */ const stripComments = (content: string): string => { const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; @@ -79,4 +72,42 @@ const stripComments = (content: string): string => { return match; } }); -}; \ No newline at end of file +}; + +/** + * Generate translations then return a path to a JavaScript file that sets the + * translations into global variables. This file is loaded by the browser to + * set global variables that the loader uses when looking for translations. + * + * Normally, VS Code pulls these files from a CDN but we want them to be local. + */ +export async function getBrowserNLSConfiguration(locale: string, userDataPath: string): Promise { + if (locale.startsWith('en')) { + return ''; // Use fallback translations. + } + + const nlsConfig = await getNLSConfiguration(locale, userDataPath); + const messagesFile = nlsConfig?.languagePack?.messagesFile; + const resolvedLanguage = nlsConfig?.resolvedLanguage; + if (!messagesFile || !resolvedLanguage) { + return ''; // Use fallback translations. + } + + const nlsFile = path.join(path.dirname(messagesFile), "nls.messages.js"); + try { + await fs.stat(nlsFile); + return nlsFile; // We already generated the file. + } catch (error) { + // ENOENT is fine, that just means we need to generate the file. + if (error.code !== 'ENOENT') { + throw error; + } + } + + const messages = (await fs.readFile(messagesFile)).toString(); + const content = `globalThis._VSCODE_NLS_MESSAGES=${messages}; +globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(resolvedLanguage)};` + await fs.writeFile(nlsFile, content, "utf-8"); + + return nlsFile; +} diff --git a/patched-vscode/src/vs/server/node/webClientServer.ts b/patched-vscode/src/vs/server/node/webClientServer.ts index abb3138f1..d43fa6404 100644 --- a/patched-vscode/src/vs/server/node/webClientServer.ts +++ b/patched-vscode/src/vs/server/node/webClientServer.ts @@ -4,6 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import { createReadStream, promises, existsSync, writeFileSync } from 'fs'; +import { readFile } from 'fs/promises' +import * as path from 'path'; import * as http from 'http'; import { spawn } from 'child_process'; import * as fs from 'fs'; @@ -27,6 +29,7 @@ import { URI } from '../../base/common/uri.js'; import { streamToBuffer } from '../../base/common/buffer.js'; import { IProductConfiguration } from '../../base/common/product.js'; import { isString, Mutable } from '../../base/common/types.js'; +import { getLocaleFromConfig, getBrowserNLSConfiguration } from '../../server/node/remoteLanguagePacks.js'; import { CharCode } from '../../base/common/charCode.js'; import { IExtensionManifest } from '../../platform/extensions/common/extensions.js'; import { ICSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; @@ -96,8 +99,47 @@ export async function serveFile(filePath: string, cacheControl: CacheControl, lo } } +const CHECK_INTERVAL = 60000; // 60 seconds interval const APP_ROOT = dirname(FileAccess.asFileUri('').fsPath); +/** + * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. + * + * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. + * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. + * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, + * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification + * times of the files in the /dev/pts directory, we can detect terminal activity. + * + * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function + * updates the last activity timestamp. + */ +const checkTerminalActivity = (idleFilePath: string) => { + fs.readdir('/dev/pts', (err, files) => { + if (err) { + console.error('Error reading /dev/pts directory:', err); + return; + } + + const now = new Date(); + const activityDetected = files.some((file) => { + const filePath = path.join('/dev/pts', file); + try { + const stats = fs.statSync(filePath); + const mtime = new Date(stats.mtime).getTime(); + return now.getTime() - mtime < CHECK_INTERVAL; + } catch (error) { + console.error('Error reading file stats:', error); + return false; + } + }); + + if (activityDetected) { + fs.writeFileSync(idleFilePath, now.toISOString()); + } + }); +}; + const STATIC_PATH = `/static`; const CALLBACK_PATH = `/callback`; const WEB_EXTENSION_PATH = `/web-extension-resource`; @@ -259,7 +301,10 @@ export class WebClientServer { }; // Prefix routes with basePath for clients - const basePath = getFirstHeader('x-forwarded-prefix') || this._basePath; + const proxyPath = this._environmentService.args["base-path"] || "/"; + const base = relativeRoot(proxyPath); + const vscodeBase = relativePath(proxyPath); + const basePath = vscodeBase || getFirstHeader("x-forwarded-prefix") || this._basePath; const queryConnectionToken = parsedUrl.query[connectionTokenQueryName]; if (typeof queryConnectionToken === 'string') { @@ -301,7 +346,7 @@ export class WebClientServer { let remoteAuthority = ( useTestResolver ? 'test+test' - : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host) + : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host || window.location.host) ); if (!remoteAuthority) { return serveError(req, res, 400, `Bad request.`); @@ -346,6 +391,7 @@ export class WebClientServer { } : undefined; const productConfiguration: Partial> = { + rootEndpoint: base, embedderIdentifier: 'server-distro', extensionsGallery: this._productService.extensionsGallery, }; @@ -365,7 +411,7 @@ export class WebClientServer { const workbenchWebConfiguration = { remoteAuthority, - serverBasePath: basePath, + serverBasePath: this._basePath, webviewEndpoint: staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', userDataPath: this._environmentService.userDataPath, _wrapWebWorkerExtHostInIframe, @@ -379,14 +425,22 @@ export class WebClientServer { }; const cookies = cookie.parse(req.headers.cookie || ''); - const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; + const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath) || cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; let WORKBENCH_NLS_BASE_URL: string | undefined; let WORKBENCH_NLS_URL: string; if (!locale.startsWith('en') && this._productService.nlsCoreBaseUrl) { WORKBENCH_NLS_BASE_URL = this._productService.nlsCoreBaseUrl; WORKBENCH_NLS_URL = `${WORKBENCH_NLS_BASE_URL}${this._productService.commit}/${this._productService.version}/${locale}/nls.messages.js`; } else { - WORKBENCH_NLS_URL = ''; // fallback will apply + try { + const nlsFile = await getBrowserNLSConfiguration(locale, this._environmentService.userDataPath); + WORKBENCH_NLS_URL = nlsFile + ? `${vscodeBase}/vscode-remote-resource?path=${encodeURIComponent(nlsFile)}` + : ''; + } catch (error) { + console.error("Failed to generate translations", error); + WORKBENCH_NLS_URL = ''; + } } const values: { [key: string]: string } = { @@ -394,7 +448,9 @@ export class WebClientServer { WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', WORKBENCH_WEB_BASE_URL: staticRoute, WORKBENCH_NLS_URL, - WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js` + WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js`, + BASE: base, + VS_BASE: vscodeBase }; // DEV --------------------------------------------------------------------------------------- @@ -436,7 +492,7 @@ export class WebClientServer { `frame-src 'self' https://*.vscode-cdn.net data:;`, 'worker-src \'self\' data: blob:;', 'style-src \'self\' \'unsafe-inline\';', - 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://openvsxorg.blob.core.windows.net https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', + 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', 'font-src \'self\' blob:;', 'manifest-src \'self\';' ].join(' '); @@ -540,27 +596,86 @@ export class WebClientServer { } /** - * Handles API requests to retrieve the last activity timestamp. - */ - private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { - try { - const tmpDirectory = '/tmp/' - const idleFilePath = join(tmpDirectory, '.sagemaker-last-active-timestamp'); - - // If idle shutdown file does not exist, this indicates the app UI may never been opened - // Create the initial metadata file - if (!existsSync(idleFilePath)) { - const timestamp = new Date().toISOString(); - writeFileSync(idleFilePath, timestamp); - } - - const data = await promises.readFile(idleFilePath, 'utf8'); - - res.statusCode = 200; - res.setHeader('Content-Type', 'application/json'); - res.end(JSON.stringify({ lastActiveTimestamp: data })); - } catch (error) { - serveError(req, res, 500, error.message) - } - } + * Handles API requests to retrieve the last activity timestamp. + */ + private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { + try { + const tmpDirectory = '/tmp/' + const idleFilePath = path.join(tmpDirectory, '.sagemaker-last-active-timestamp'); + + // If idle shutdown file does not exist, this indicates the app UI may never been opened + // Create the initial metadata file + if (!existsSync(idleFilePath)) { + const timestamp = new Date().toISOString(); + writeFileSync(idleFilePath, timestamp); + } + + checkTerminalActivity(idleFilePath); + + const data = await readFile(idleFilePath, 'utf8'); + + res.statusCode = 200; + res.setHeader('Content-Type', 'application/json'); + res.end(JSON.stringify({ lastActiveTimestamp: data })); + } catch (error) { + serveError(req, res, 500, error.message); + } + } +} + + +/** + * Remove extra slashes in a URL. + * + * This is meant to fill the job of `path.join` so you can concatenate paths and + * then normalize out any extra slashes. + * + * If you are using `path.join` you do not need this but note that `path` is for + * file system paths, not URLs. + */ +export const normalizeUrlPath = (url: string, keepTrailing = false): string => { + return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "") +} + +/** + * Get the relative path that will get us to the root of the page. For each + * slash we need to go up a directory. Will not have a trailing slash. + * + * For example: + * + * / => . + * /foo => . + * /foo/ => ./.. + * /foo/bar => ./.. + * /foo/bar/ => ./../.. + * + * All paths must be relative in order to work behind a reverse proxy since we + * we do not know the base path. Anything that needs to be absolute (for + * example cookies) must get the base path from the frontend. + * + * All relative paths must be prefixed with the relative root to ensure they + * work no matter the depth at which they happen to appear. + * + * For Express `req.originalUrl` should be used as they remove the base from the + * standard `url` property making it impossible to get the true depth. + */ +export const relativeRoot = (originalUrl: string): string => { + const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length + return normalizeUrlPath("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) +} + +/** + * Get the relative path to the current resource. + * + * For example: + * + * / => . + * /foo => ./foo + * /foo/ => . + * /foo/bar => ./bar + * /foo/bar/ => . + */ +export const relativePath = (originalUrl: string): string => { + const parts = originalUrl.split("?", 1)[0].split("/") + return normalizeUrlPath("./" + parts[parts.length - 1]) } diff --git a/patched-vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts b/patched-vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts index d9ad34f1c..627c0c0a2 100644 --- a/patched-vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts @@ -3,15 +3,16 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { localize, localize2 } from 'vs/nls'; -import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput'; -import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Action2, MenuId } from 'vs/platform/actions/common/actions'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { ILanguagePackItem, ILanguagePackService } from 'vs/platform/languagePacks/common/languagePacks'; -import { ILocaleService } from 'vs/workbench/services/localization/common/locale'; -import { IExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/common/extensions'; +import { localize, localize2 } from '../../../../nls.js'; +import { IQuickInputService, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js'; +import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; +import { DisposableStore } from '../../../../base/common/lifecycle.js'; +import { Action2, MenuId } from '../../../../platform/actions/common/actions.js'; +import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; +import { ILanguagePackItem, ILanguagePackService } from '../../../../platform/languagePacks/common/languagePacks.js'; +import { ILocaleService } from '../../../services/localization/common/locale.js'; +import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; +import { language } from '../../../../base/common/platform.js'; export class ConfigureDisplayLanguageAction extends Action2 { public static readonly ID = 'workbench.action.configureLocale'; @@ -37,7 +38,18 @@ export class ConfigureDisplayLanguageAction extends Action2 { const installedLanguages = await languagePackService.getInstalledLanguages(); - const qp = quickInputService.createQuickPick(); + // Clean any existing (Current) text and add it back only to the correct locale + installedLanguages?.forEach(lang => { + if (lang.description) { + lang.description = lang.description.replace(/ \(Current\)$/, ''); + } + if (lang.id?.toLowerCase() === language.toLowerCase()) { + lang.description = (lang.description || '') + localize('currentDisplayLanguage', " (Current)"); + } + }); + + const disposables = new DisposableStore(); + const qp = disposables.add(quickInputService.createQuickPick({ useSeparators: true })); qp.matchOnDescription = true; qp.placeholder = localize('chooseLocale', "Select Display Language"); @@ -46,7 +58,6 @@ export class ConfigureDisplayLanguageAction extends Action2 { qp.items = items.concat(this.withMoreInfoButton(installedLanguages)); } - const disposables = new DisposableStore(); const source = new CancellationTokenSource(); disposables.add(qp.onDispose(() => { source.cancel(); diff --git a/patched-vscode/src/vs/workbench/workbench.web.main.internal.ts b/patched-vscode/src/vs/workbench/workbench.web.main.internal.ts new file mode 100644 index 000000000..a3ca05801 --- /dev/null +++ b/patched-vscode/src/vs/workbench/workbench.web.main.internal.ts @@ -0,0 +1,247 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + + +// ####################################################################### +// ### ### +// ### !!! PLEASE ADD COMMON IMPORTS INTO WORKBENCH.COMMON.MAIN.TS !!! ### +// ### ### +// ####################################################################### + + +//#region --- workbench common + +import './workbench.common.main.js'; + +//#endregion + + +//#region --- workbench parts + +import './browser/parts/dialogs/dialog.web.contribution.js'; + +//#endregion + + +//#region --- workbench (web main) + +import './browser/web.main.js'; + +//#endregion + + +//#region --- workbench services + +import './services/integrity/browser/integrityService.js'; +import './services/search/browser/searchService.js'; +import './services/textfile/browser/browserTextFileService.js'; +import './services/keybinding/browser/keyboardLayoutService.js'; +import './services/extensions/browser/extensionService.js'; +import './services/extensionManagement/browser/extensionsProfileScannerService.js'; +import './services/extensions/browser/extensionsScannerService.js'; +import './services/extensionManagement/browser/webExtensionsScannerService.js'; +import './services/extensionManagement/common/extensionManagementServerService.js'; +import './services/extensionManagement/browser/extensionGalleryManifestService.js'; +import './services/telemetry/browser/telemetryService.js'; +import './services/url/browser/urlService.js'; +import './services/update/browser/updateService.js'; +import './services/workspaces/browser/workspacesService.js'; +import './services/workspaces/browser/workspaceEditingService.js'; +import './services/dialogs/browser/fileDialogService.js'; +import './services/host/browser/browserHostService.js'; +import './services/lifecycle/browser/lifecycleService.js'; +import './services/clipboard/browser/clipboardService.js'; +import './services/localization/electron-sandbox/localeService.js'; +import './services/path/browser/pathService.js'; +import './services/themes/browser/browserHostColorSchemeService.js'; +import './services/encryption/browser/encryptionService.js'; +import './services/secrets/browser/secretStorageService.js'; +import './services/workingCopy/browser/workingCopyBackupService.js'; +import './services/tunnel/browser/tunnelService.js'; +import './services/files/browser/elevatedFileService.js'; +import './services/workingCopy/browser/workingCopyHistoryService.js'; +import './services/userDataSync/browser/webUserDataSyncEnablementService.js'; +import './services/userDataProfile/browser/userDataProfileStorageService.js'; +import './services/configurationResolver/browser/configurationResolverService.js'; +import '../platform/extensionResourceLoader/browser/extensionResourceLoaderService.js'; +import './services/auxiliaryWindow/browser/auxiliaryWindowService.js'; +import './services/browserElements/browser/webBrowserElementsService.js'; + +import { InstantiationType, registerSingleton } from '../platform/instantiation/common/extensions.js'; +import { IAccessibilityService } from '../platform/accessibility/common/accessibility.js'; +import { IContextMenuService } from '../platform/contextview/browser/contextView.js'; +import { ContextMenuService } from '../platform/contextview/browser/contextMenuService.js'; +import { IExtensionTipsService } from '../platform/extensionManagement/common/extensionManagement.js'; +import { ExtensionTipsService } from '../platform/extensionManagement/common/extensionTipsService.js'; +import { IWorkbenchExtensionManagementService } from './services/extensionManagement/common/extensionManagement.js'; +import { ExtensionManagementService } from './services/extensionManagement/common/extensionManagementService.js'; +import { LogLevel } from '../platform/log/common/log.js'; +import { UserDataSyncMachinesService, IUserDataSyncMachinesService } from '../platform/userDataSync/common/userDataSyncMachines.js'; +import { IUserDataSyncStoreService, IUserDataSyncService, IUserDataAutoSyncService, IUserDataSyncLocalStoreService, IUserDataSyncResourceProviderService } from '../platform/userDataSync/common/userDataSync.js'; +import { UserDataSyncStoreService } from '../platform/userDataSync/common/userDataSyncStoreService.js'; +import { UserDataSyncLocalStoreService } from '../platform/userDataSync/common/userDataSyncLocalStoreService.js'; +import { UserDataSyncService } from '../platform/userDataSync/common/userDataSyncService.js'; +import { IUserDataSyncAccountService, UserDataSyncAccountService } from '../platform/userDataSync/common/userDataSyncAccount.js'; +import { UserDataAutoSyncService } from '../platform/userDataSync/common/userDataAutoSyncService.js'; +import { AccessibilityService } from '../platform/accessibility/browser/accessibilityService.js'; +import { ICustomEndpointTelemetryService } from '../platform/telemetry/common/telemetry.js'; +import { NullEndpointTelemetryService } from '../platform/telemetry/common/telemetryUtils.js'; +import { ITitleService } from './services/title/browser/titleService.js'; +import { BrowserTitleService } from './browser/parts/titlebar/titlebarPart.js'; +import { ITimerService, TimerService } from './services/timer/browser/timerService.js'; +import { IDiagnosticsService, NullDiagnosticsService } from '../platform/diagnostics/common/diagnostics.js'; +import { ILanguagePackService } from '../platform/languagePacks/common/languagePacks.js'; +import { WebLanguagePacksService } from '../platform/languagePacks/browser/languagePacks.js'; +import { IWebContentExtractorService, NullWebContentExtractorService, ISharedWebContentExtractorService, NullSharedWebContentExtractorService } from '../platform/webContentExtractor/common/webContentExtractor.js'; +import { IDefaultAccountService, NullDefaultAccountService } from './services/accounts/common/defaultAccount.js'; + +registerSingleton(IWorkbenchExtensionManagementService, ExtensionManagementService, InstantiationType.Delayed); +registerSingleton(IAccessibilityService, AccessibilityService, InstantiationType.Delayed); +registerSingleton(IContextMenuService, ContextMenuService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncStoreService, UserDataSyncStoreService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncMachinesService, UserDataSyncMachinesService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncLocalStoreService, UserDataSyncLocalStoreService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncAccountService, UserDataSyncAccountService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncService, UserDataSyncService, InstantiationType.Delayed); +registerSingleton(IUserDataSyncResourceProviderService, UserDataSyncResourceProviderService, InstantiationType.Delayed); +registerSingleton(IUserDataAutoSyncService, UserDataAutoSyncService, InstantiationType.Eager /* Eager to start auto sync */); +registerSingleton(ITitleService, BrowserTitleService, InstantiationType.Eager); +registerSingleton(IExtensionTipsService, ExtensionTipsService, InstantiationType.Delayed); +registerSingleton(ITimerService, TimerService, InstantiationType.Delayed); +registerSingleton(ICustomEndpointTelemetryService, NullEndpointTelemetryService, InstantiationType.Delayed); +registerSingleton(IDiagnosticsService, NullDiagnosticsService, InstantiationType.Delayed); +registerSingleton(ILanguagePackService, WebLanguagePacksService, InstantiationType.Delayed); +registerSingleton(IWebContentExtractorService, NullWebContentExtractorService, InstantiationType.Delayed); +registerSingleton(ISharedWebContentExtractorService, NullSharedWebContentExtractorService, InstantiationType.Delayed); +registerSingleton(IDefaultAccountService, NullDefaultAccountService, InstantiationType.Delayed); + +//#endregion + + +//#region --- workbench contributions + +// Logs +import './contrib/logs/browser/logs.contribution.js'; + +// Localization +import './contrib/localization/browser/localization.contribution.js'; + +// Performance +import './contrib/performance/browser/performance.web.contribution.js'; + +// Preferences +import './contrib/preferences/browser/keyboardLayoutPicker.js'; + +// Debug +import './contrib/debug/browser/extensionHostDebugService.js'; + +// Welcome Banner +import './contrib/welcomeBanner/browser/welcomeBanner.contribution.js'; + +// Webview +import './contrib/webview/browser/webview.web.contribution.js'; + +// Extensions Management +import './contrib/extensions/browser/extensions.web.contribution.js'; + +// Terminal +import './contrib/terminal/browser/terminal.web.contribution.js'; +import './contrib/externalTerminal/browser/externalTerminal.contribution.js'; +import './contrib/terminal/browser/terminalInstanceService.js'; + +// Tasks +import './contrib/tasks/browser/taskService.js'; + +// Tags +import './contrib/tags/browser/workspaceTagsService.js'; + +// Issues +import './contrib/issue/browser/issue.contribution.js'; + +// Splash +import './contrib/splash/browser/splash.contribution.js'; + +// Remote Start Entry for the Web +import './contrib/remote/browser/remoteStartEntry.contribution.js'; + +// Process Explorer +import './contrib/processExplorer/browser/processExplorer.web.contribution.js'; + +//#endregion + + +//#region --- export workbench factory + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// +// Do NOT change these exports in a way that something is removed unless +// intentional. These exports are used by web embedders and thus require +// an adoption when something changes. +// +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +import { create, commands, env, window, workspace, logger } from './browser/web.factory.js'; +import { Menu } from './browser/web.api.js'; +import { URI } from '../base/common/uri.js'; +import { Event, Emitter } from '../base/common/event.js'; +import { Disposable } from '../base/common/lifecycle.js'; +import { GroupOrientation } from './services/editor/common/editorGroupsService.js'; +import { UserDataSyncResourceProviderService } from '../platform/userDataSync/common/userDataSyncResourceProvider.js'; +import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode } from '../platform/remote/common/remoteAuthorityResolver.js'; + +// TODO@esm remove me once we stop supporting our web-esm-bridge +if ((globalThis as any).__VSCODE_WEB_ESM_PROMISE) { + const exports = { + + // Factory + create: create, + + // Basic Types + URI: URI, + Event: Event, + Emitter: Emitter, + Disposable: Disposable, + // GroupOrientation, + LogLevel: LogLevel, + RemoteAuthorityResolverError: RemoteAuthorityResolverError, + RemoteAuthorityResolverErrorCode: RemoteAuthorityResolverErrorCode, + + // Facade API + env: env, + window: window, + workspace: workspace, + commands: commands, + logger: logger, + Menu: Menu + }; + (globalThis as any).__VSCODE_WEB_ESM_PROMISE(exports); + delete (globalThis as any).__VSCODE_WEB_ESM_PROMISE; +} + +export { + + // Factory + create, + + // Basic Types + URI, + Event, + Emitter, + Disposable, + GroupOrientation, + LogLevel, + RemoteAuthorityResolverError, + RemoteAuthorityResolverErrorCode, + + // Facade API + env, + window, + workspace, + commands, + logger, + Menu +}; + +//#endregion diff --git a/patches/display-language.patch b/patches/display-language.patch index 95c9a2197..2eea9fe80 100644 --- a/patches/display-language.patch +++ b/patches/display-language.patch @@ -1,139 +1,3 @@ -Index: sagemaker-code-editor/vscode/src/vs/base/common/platform.ts -=================================================================== ---- sagemaker-code-editor.orig/vscode/src/vs/base/common/platform.ts -+++ sagemaker-code-editor/vscode/src/vs/base/common/platform.ts -@@ -2,9 +2,6 @@ - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -- --import * as nls from '../../nls.js'; -- - export const LANGUAGE_DEFAULT = 'en'; - - let _isWindows = false; -@@ -23,6 +20,13 @@ - let _translationsConfigFile: string | undefined = undefined; - let _userAgent: string | undefined = undefined; - -+interface NLSConfig { -+ locale: string; -+ osLocale: string; -+ availableLanguages: { [key: string]: string }; -+ _translationsConfigFile: string; -+} -+ - export interface IProcessEnvironment { - [key: string]: string | undefined; - } -@@ -83,15 +80,16 @@ if (typeof nodeProcess === 'obj - const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG']; - if (rawNlsConfig) { - try { -- const nlsConfig: nls.INLSConfiguration = JSON.parse(rawNlsConfig); -+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); -+ const resolved = nlsConfig.availableLanguages['*']; -- _locale = nlsConfig.userLocale; -+ _locale = nlsConfig.locale; - _platformLocale = nlsConfig.osLocale; -- _language = nlsConfig.resolvedLanguage || LANGUAGE_DEFAULT; -+ _language = resolved ? resolved : LANGUAGE_DEFAULT; -- _translationsConfigFile = nlsConfig.languagePack?.translationsConfigFile; -+ _translationsConfigFile = nlsConfig._translationsConfigFile; - } catch (e) { - } - } - _isNative = true; - } - - // Web environment -@@ -104,8 +102,20 @@ else if (typeof navigator === 'object' & - _isMobile = _userAgent?.indexOf('Mobi') >= 0; - _isWeb = true; -- _language = nls.getNLSLanguage() || LANGUAGE_DEFAULT; -- _locale = navigator.language.toLowerCase(); -- _platformLocale = _locale; -+ _locale = LANGUAGE_DEFAULT; -+ _language = _locale; -+ _platformLocale = navigator.language; -+ const el = typeof document !== 'undefined' && document.getElementById('vscode-remote-nls-configuration'); -+ const rawNlsConfig = el && el.getAttribute('data-settings'); -+ if (rawNlsConfig) { -+ try { -+ const nlsConfig: NLSConfig = JSON.parse(rawNlsConfig); -+ const resolved = nlsConfig.availableLanguages['*']; -+ _locale = nlsConfig.locale; -+ _platformLocale = nlsConfig.osLocale; -+ _language = resolved ? resolved : LANGUAGE_DEFAULT; -+ _translationsConfigFile = nlsConfig._translationsConfigFile; -+ } catch (error) { /* Oh well. */ } -+ } - } - - // Unknown environment -Index: sagemaker-code-editor/vscode/src/vs/code/browser/workbench/workbench.html -=================================================================== ---- sagemaker-code-editor.orig/vscode/src/vs/code/browser/workbench/workbench.html -+++ sagemaker-code-editor/vscode/src/vs/code/browser/workbench/workbench.html -@@ -19,6 +19,9 @@ - - - -+ -+ -+ - - - -@@ -37,6 +40,47 @@ - const baseUrl = new URL('{{WORKBENCH_WEB_BASE_URL}}', window.location.origin).toString(); - globalThis._VSCODE_FILE_ROOT = baseUrl + '/out/'; -+ -+ // Set up nls if the user is not using the default language (English) -+ const nlsConfig = {}; -+ // Normalize locale to lowercase because translationServiceUrl is case-sensitive. -+ // ref: https://github.com/microsoft/vscode/issues/187795 -+ const locale = localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase(); -+ try { -+ nlsConfig['vs/nls'] = JSON.parse(document.getElementById("vscode-remote-nls-configuration").getAttribute("data-settings")) -+ if (nlsConfig['vs/nls']._resolvedLanguagePackCoreLocation) { -+ const bundles = Object.create(null) -+ nlsConfig['vs/nls'].loadBundle = (bundle, _language, cb) => { -+ const result = bundles[bundle] -+ if (result) { -+ return cb(undefined, result) -+ } -+ const path = nlsConfig['vs/nls']._resolvedLanguagePackCoreLocation + "/" + bundle.replace(/\//g, "!") + ".nls.json" -+ fetch(`{{WORKBENCH_WEB_BASE_URL}}/../vscode-remote-resource?path=${encodeURIComponent(path)}`) -+ .then((response) => response.json()) -+ .then((json) => { -+ bundles[bundle] = json -+ cb(undefined, json) -+ }) -+ .catch(cb) -+ } -+ } -+ } catch (error) { /* Probably fine. */ } -+ -+ require.config({ -+ baseUrl: `${baseUrl}/out`, -+ recordStats: true, -+ trustedTypesPolicy: window.trustedTypes?.createPolicy('amdLoader', { -+ createScriptURL(value) { -+ if(value.startsWith(window.location.origin)) { -+ return value; -+ } -+ throw new Error(`Invalid script url: ${value}`) -+ } -+ }), -+ paths: self.webPackagePaths, -+ ...nlsConfig -+ }); - - - Index: sagemaker-code-editor/vscode/src/vs/platform/environment/common/environmentService.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/platform/environment/common/environmentService.ts @@ -186,6 +50,139 @@ Index: sagemaker-code-editor/vscode/src/vs/platform/languagePacks/browser/langua + return this.languagePackService.getInstalledLanguages() } } +Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/remoteLanguagePacks.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts +@@ -3,37 +3,111 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + ++import * as path from 'path'; ++import { promises as fs } from 'fs'; + import { FileAccess } from '../../base/common/network.js'; + import { join } from '../../base/common/path.js'; + import type { INLSConfiguration } from '../../nls.js'; + import { resolveNLSConfiguration } from '../../base/node/nls.js'; +-import { Promises } from '../../base/node/pfs.js'; +-import product from '../../platform/product/common/product.js'; + + const nlsMetadataPath = join(FileAccess.asFileUri('').fsPath); +-const defaultMessagesFile = join(nlsMetadataPath, 'nls.messages.json'); + const nlsConfigurationCache = new Map>(); + + export async function getNLSConfiguration(language: string, userDataPath: string): Promise { +- if (!product.commit || !(await Promises.exists(defaultMessagesFile))) { +- return { +- userLocale: 'en', +- osLocale: 'en', +- resolvedLanguage: 'en', +- defaultMessagesFile, +- +- // NLS: below 2 are a relic from old times only used by vscode-nls and deprecated +- locale: 'en', +- availableLanguages: {} +- }; +- } +- + const cacheKey = `${language}||${userDataPath}`; + let result = nlsConfigurationCache.get(cacheKey); + if (!result) { +- result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: product.commit, userDataPath, nlsMetadataPath }); ++ // passing a dummy commit which is required to resolve language packs ++ result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: 'dummy_commit', userDataPath, nlsMetadataPath }); + nlsConfigurationCache.set(cacheKey, result); ++ // If the language pack does not yet exist, it defaults to English, which is ++ // then cached and you have to restart even if you then install the pack. ++ result.then((r) => { ++ if (!language.startsWith('en') && r.resolvedLanguage.startsWith('en')) { ++ nlsConfigurationCache.delete(cacheKey); ++ } ++ }) + } + + return result; + } ++ ++/** ++ * Copied from from src/main.js. ++ */ ++export const getLocaleFromConfig = async (argvResource: string): Promise => { ++ try { ++ const content = stripComments(await fs.readFile(argvResource, 'utf8')); ++ return JSON.parse(content).locale; ++ } catch (error) { ++ if (error.code !== "ENOENT") { ++ console.warn(error) ++ } ++ return 'en'; ++ } ++}; ++ ++/** ++ * Copied from from src/main.js. ++ */ ++const stripComments = (content: string): string => { ++ const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; ++ ++ return content.replace(regexp, (match, _m1, _m2, m3, m4) => { ++ // Only one of m1, m2, m3, m4 matches ++ if (m3) { ++ // A block comment. Replace with nothing ++ return ''; ++ } else if (m4) { ++ // A line comment. If it ends in \r?\n then keep it. ++ const length_1 = m4.length; ++ if (length_1 > 2 && m4[length_1 - 1] === '\n') { ++ return m4[length_1 - 2] === '\r' ? '\r\n' : '\n'; ++ } ++ else { ++ return ''; ++ } ++ } else { ++ // We match a string ++ return match; ++ } ++ }); ++}; ++ ++/** ++ * Generate translations then return a path to a JavaScript file that sets the ++ * translations into global variables. This file is loaded by the browser to ++ * set global variables that the loader uses when looking for translations. ++ * ++ * Normally, VS Code pulls these files from a CDN but we want them to be local. ++ */ ++export async function getBrowserNLSConfiguration(locale: string, userDataPath: string): Promise { ++ if (locale.startsWith('en')) { ++ return ''; // Use fallback translations. ++ } ++ ++ const nlsConfig = await getNLSConfiguration(locale, userDataPath); ++ const messagesFile = nlsConfig?.languagePack?.messagesFile; ++ const resolvedLanguage = nlsConfig?.resolvedLanguage; ++ if (!messagesFile || !resolvedLanguage) { ++ return ''; // Use fallback translations. ++ } ++ ++ const nlsFile = path.join(path.dirname(messagesFile), "nls.messages.js"); ++ try { ++ await fs.stat(nlsFile); ++ return nlsFile; // We already generated the file. ++ } catch (error) { ++ // ENOENT is fine, that just means we need to generate the file. ++ if (error.code !== 'ENOENT') { ++ throw error; ++ } ++ } ++ ++ const messages = (await fs.readFile(messagesFile)).toString(); ++ const content = `globalThis._VSCODE_NLS_MESSAGES=${messages}; ++globalThis._VSCODE_NLS_LANGUAGE=${JSON.stringify(resolvedLanguage)};` ++ await fs.writeFile(nlsFile, content, "utf-8"); ++ ++ return nlsFile; ++} Index: sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts =================================================================== @@ -214,7 +211,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/serverServices.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/serverServices.ts +++ sagemaker-code-editor/vscode/src/vs/server/node/serverServices.ts -@@ -11,7 +11,7 @@ import * as path from '.. +@@ -12,7 +12,7 @@ import * as path from '.. import { IURITransformer } from '../../base/common/uriIpc.js'; import { getMachineId, getSqmMachineId, getdevDeviceId } from '../../base/node/id.js'; import { Promises } from '../../base/node/pfs.js'; @@ -223,7 +220,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/serverServices.ts import { ProtocolConstants } from '../../base/parts/ipc/common/ipc.net.js'; import { IConfigurationService } from '../../platform/configuration/common/configuration.js'; import { ConfigurationService } from '../../platform/configuration/common/configurationService.js'; -@@ -225,6 +225,9 @@ export async function setupServerService +@@ -255,6 +255,9 @@ export async function setupServerService const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); socketServer.registerChannel('extensions', channel); @@ -238,16 +235,149 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/webClientServer.ts +++ sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts -@@ -401,7 +405,7 @@ export class WebClientServer { - `frame-src 'self' https://*.vscode-cdn.net data:;`, - 'worker-src \'self\' data: blob:;', - 'style-src \'self\' \'unsafe-inline\';', -- 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', -+ 'connect-src \'self\' ws: wss: https://main.vscode-cdn.net http://localhost:* https://localhost:* https://login.microsoftonline.com/ https://update.code.visualstudio.com https://*.vscode-unpkg.net/ https://default.exp-tas.com/vscode/ab https://vscode-sync.trafficmanager.net https://vscode-sync-insiders.trafficmanager.net https://*.gallerycdn.vsassets.io https://marketplace.visualstudio.com https://openvsxorg.blob.core.windows.net https://az764295.vo.msecnd.net https://code.visualstudio.com https://*.gallery.vsassets.io https://*.rel.tunnels.api.visualstudio.com wss://*.rel.tunnels.api.visualstudio.com https://*.servicebus.windows.net/ https://vscode.blob.core.windows.net https://vscode.search.windows.net https://vsmarketplacebadges.dev https://vscode.download.prss.microsoft.com https://download.visualstudio.microsoft.com https://*.vscode-unpkg.net https://open-vsx.org;', - 'font-src \'self\' blob:;', - 'manifest-src \'self\';' - ].join(' '); +@@ -25,6 +25,7 @@ import { URI } from '.. + import { streamToBuffer } from '../../base/common/buffer.js'; + import { IProductConfiguration } from '../../base/common/product.js'; + import { isString, Mutable } from '../../base/common/types.js'; ++import { getLocaleFromConfig, getBrowserNLSConfiguration } from '../../server/node/remoteLanguagePacks.js'; + import { CharCode } from '../../base/common/charCode.js'; + import { IExtensionManifest } from '../../platform/extensions/common/extensions.js'; + import { ICSSDevelopmentService } from '../../platform/cssDev/node/cssDevService.js'; +@@ -245,7 +246,10 @@ export class WebClientServer { + }; + + // Prefix routes with basePath for clients +- const basePath = getFirstHeader('x-forwarded-prefix') || this._basePath; ++ const proxyPath = this._environmentService.args["base-path"] || "/"; ++ const base = relativeRoot(proxyPath); ++ const vscodeBase = relativePath(proxyPath); ++ const basePath = vscodeBase || getFirstHeader("x-forwarded-prefix") || this._basePath; + + const queryConnectionToken = parsedUrl.query[connectionTokenQueryName]; + if (typeof queryConnectionToken === 'string') { +@@ -287,7 +291,7 @@ export class WebClientServer { + let remoteAuthority = ( + useTestResolver + ? 'test+test' +- : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host) ++ : (getFirstHeader('x-original-host') || getFirstHeader('x-forwarded-host') || req.headers.host || window.location.host) + ); + if (!remoteAuthority) { + return serveError(req, res, 400, `Bad request.`); +@@ -333,6 +337,7 @@ export class WebClientServer { + } : undefined; + + const productConfiguration: Partial> = { ++ rootEndpoint: base, + embedderIdentifier: 'server-distro', + extensionsGallery: this._webExtensionResourceUrlTemplate && this._productService.extensionsGallery ? { + ...this._productService.extensionsGallery, +@@ -364,7 +369,7 @@ export class WebClientServer { + const workbenchWebConfiguration = { + remoteAuthority, +- serverBasePath: basePath, ++ serverBasePath: this._basePath, + webviewEndpoint: staticRoute + '/out/vs/workbench/contrib/webview/browser/pre', + userDataPath: this._environmentService.userDataPath, + _wrapWebWorkerExtHostInIframe, +@@ -385,22 +390,32 @@ export class WebClientServer { + }; + + const cookies = cookie.parse(req.headers.cookie || ''); +- const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; ++ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath) || cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; + let WORKBENCH_NLS_BASE_URL: string | undefined; + let WORKBENCH_NLS_URL: string; + if (!locale.startsWith('en') && this._productService.nlsCoreBaseUrl) { + WORKBENCH_NLS_BASE_URL = this._productService.nlsCoreBaseUrl; + WORKBENCH_NLS_URL = `${WORKBENCH_NLS_BASE_URL}${this._productService.commit}/${this._productService.version}/${locale}/nls.messages.js`; + } else { +- WORKBENCH_NLS_URL = ''; // fallback will apply ++ try { ++ const nlsFile = await getBrowserNLSConfiguration(locale, this._environmentService.userDataPath); ++ WORKBENCH_NLS_URL = nlsFile ++ ? `${vscodeBase}/vscode-remote-resource?path=${encodeURIComponent(nlsFile)}` ++ : ''; ++ } catch (error) { ++ console.error("Failed to generate translations", error); ++ WORKBENCH_NLS_URL = ''; ++ } + } + + const values: { [key: string]: string } = { + WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), + WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', + WORKBENCH_WEB_BASE_URL: staticRoute, + WORKBENCH_NLS_URL, +- WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js` ++ WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js`, ++ BASE: base, ++ VS_BASE: vscodeBase + }; + + // DEV --------------------------------------------------------------------------------------- +@@ -618,3 +633,60 @@ export class WebClientServer { + serveError(req, res, 500, error.message); + } + } ++ ++ ++/** ++ * Remove extra slashes in a URL. ++ * ++ * This is meant to fill the job of `path.join` so you can concatenate paths and ++ * then normalize out any extra slashes. ++ * ++ * If you are using `path.join` you do not need this but note that `path` is for ++ * file system paths, not URLs. ++ */ ++export const normalizeUrlPath = (url: string, keepTrailing = false): string => { ++ return url.replace(/\/\/+/g, "/").replace(/\/+$/, keepTrailing ? "/" : "") ++} ++ ++/** ++ * Get the relative path that will get us to the root of the page. For each ++ * slash we need to go up a directory. Will not have a trailing slash. ++ * ++ * For example: ++ * ++ * / => . ++ * /foo => . ++ * /foo/ => ./.. ++ * /foo/bar => ./.. ++ * /foo/bar/ => ./../.. ++ * ++ * All paths must be relative in order to work behind a reverse proxy since we ++ * we do not know the base path. Anything that needs to be absolute (for ++ * example cookies) must get the base path from the frontend. ++ * ++ * All relative paths must be prefixed with the relative root to ensure they ++ * work no matter the depth at which they happen to appear. ++ * ++ * For Express `req.originalUrl` should be used as they remove the base from the ++ * standard `url` property making it impossible to get the true depth. ++ */ ++export const relativeRoot = (originalUrl: string): string => { ++ const depth = (originalUrl.split("?", 1)[0].match(/\//g) || []).length ++ return normalizeUrlPath("./" + (depth > 1 ? "../".repeat(depth - 1) : "")) ++} ++ ++/** ++ * Get the relative path to the current resource. ++ * ++ * For example: ++ * ++ * / => . ++ * /foo => ./foo ++ * /foo/ => . ++ * /foo/bar => ./bar ++ * /foo/bar/ => . ++ */ ++export const relativePath = (originalUrl: string): string => { ++ const parts = originalUrl.split("?", 1)[0].split("/") ++ return normalizeUrlPath("./" + parts[parts.length - 1]) ++} Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -340,3 +470,71 @@ Index: sagemaker-code-editor/vscode/src/vs/workbench/services/localization/elect await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true); return true; } + +Index: sagemaker-code-editor/vscode/build/gulpfile.reh.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/gulpfile.reh.js ++++ sagemaker-code-editor/vscode/build/gulpfile.reh.js +@@ -59,6 +59,7 @@ const serverResourceIncludes = [ + + // NLS + 'out-build/nls.messages.json', ++ 'out-build/nls.keys.json', // Required to generate translations. + + // Process monitor + 'out-build/vs/base/node/cpuUsage.sh', +Index: sagemaker-code-editor/vscode/src/vs/workbench/workbench.web.main.internal.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/workbench.web.main.internal.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/workbench.web.main.internal.ts +@@ -53,7 +53,7 @@ import './services/dialogs/browser/fileD + import './services/host/browser/browserHostService.js'; + import './services/lifecycle/browser/lifecycleService.js'; + import './services/clipboard/browser/clipboardService.js'; +-import './services/localization/browser/localeService.js'; ++import './services/localization/electron-sandbox/localeService.js'; + import './services/path/browser/pathService.js'; + import './services/themes/browser/browserHostColorSchemeService.js'; + import './services/encryption/browser/encryptionService.js'; +Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/contrib/localization/common/localizationsActions.ts +@@ -12,6 +12,7 @@ import { ServicesAccessor } from '../../ + import { ILanguagePackItem, ILanguagePackService } from '../../../../platform/languagePacks/common/languagePacks.js'; + import { ILocaleService } from '../../../services/localization/common/locale.js'; + import { IExtensionsWorkbenchService } from '../../extensions/common/extensions.js'; ++import { language } from '../../../../base/common/platform.js'; + + export class ConfigureDisplayLanguageAction extends Action2 { + public static readonly ID = 'workbench.action.configureLocale'; +@@ -37,6 +38,16 @@ export class ConfigureDisplayLanguageAct + + const installedLanguages = await languagePackService.getInstalledLanguages(); + ++ // Clean any existing (Current) text and add it back only to the correct locale ++ installedLanguages?.forEach(lang => { ++ if (lang.description) { ++ lang.description = lang.description.replace(/ \(Current\)$/, ''); ++ } ++ if (lang.id?.toLowerCase() === language.toLowerCase()) { ++ lang.description = (lang.description || '') + localize('currentDisplayLanguage', " (Current)"); ++ } ++ }); ++ + const disposables = new DisposableStore(); + const qp = disposables.add(quickInputService.createQuickPick({ useSeparators: true })); + qp.matchOnDescription = true; + +Index: sagemaker-code-editor/vscode/src/vs/base/common/product.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/base/common/product.ts ++++ sagemaker-code-editor/vscode/src/vs/base/common/product.ts +@@ -56,6 +56,7 @@ export type ExtensionVirtualWorkspaceSup + }; + + export interface IProductConfiguration { ++ readonly rootEndpoint?: string; + readonly version: string; + readonly date?: string; + readonly quality?: string; \ No newline at end of file diff --git a/patches/sagemaker-idle-extension.patch b/patches/sagemaker-idle-extension.patch index f475f6071..3a1966b34 100644 --- a/patches/sagemaker-idle-extension.patch +++ b/patches/sagemaker-idle-extension.patch @@ -137,27 +137,19 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/extension.ts -@@ -0,0 +1,112 @@ +@@ -0,0 +1,58 @@ +import * as vscode from "vscode"; +import * as fs from "fs"; -+import { join } from "path"; ++import * as path from "path"; + -+let idleFilePath: string -+let terminalActivityInterval: NodeJS.Timeout | undefined -+const LOG_PREFIX = "[sagemaker-idle-extension]" -+const CHECK_INTERVAL = 60000; // 60 seconds interval ++let idleFilePath: string; + +export function activate(context: vscode.ExtensionContext) { + initializeIdleFilePath(); + registerEventListeners(context); -+ startMonitoringTerminalActivity(); +} + -+export function deactivate() { -+ if(terminalActivityInterval) { -+ clearInterval(terminalActivityInterval) -+ } -+} ++export function deactivate() {} + +/** + * Initializes the file path where the idle timestamp will be stored. @@ -165,10 +157,10 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte + */ +function initializeIdleFilePath() { + const tmpDirectory = "/tmp/"; -+ idleFilePath = join(tmpDirectory, ".sagemaker-last-active-timestamp"); ++ idleFilePath = path.join(tmpDirectory, ".sagemaker-last-active-timestamp"); + + // Set initial lastActivetimestamp -+ updateLastActivityTimestamp() ++ updateLastActivityTimestamp(); +} + +/** @@ -197,52 +189,6 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte +} + +/** -+ * Starts monitoring terminal activity by setting an interval to check for activity in the /dev/pts directory. -+ */ -+const startMonitoringTerminalActivity = () => { -+ terminalActivityInterval = setInterval(checkTerminalActivity, CHECK_INTERVAL); -+}; -+ -+ -+/** -+ * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. -+ * -+ * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. -+ * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. -+ * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, -+ * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification -+ * times of the files in the /dev/pts directory, we can detect terminal activity. -+ * -+ * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function -+ * updates the last activity timestamp. -+ */ -+const checkTerminalActivity = () => { -+ fs.readdir("/dev/pts", (err, files) => { -+ if (err) { -+ console.error(`${LOG_PREFIX} Error reading /dev/pts directory:`, err); -+ return; -+ } -+ -+ const now = Date.now(); -+ const activityDetected = files.some((file) => { -+ const filePath = join("/dev/pts", file); -+ try { -+ const stats = fs.statSync(filePath); -+ const mtime = new Date(stats.mtime).getTime(); -+ return now - mtime < CHECK_INTERVAL; -+ } catch (error) { -+ console.error(`${LOG_PREFIX} Error reading file stats:`, error); -+ return false; -+ } -+ }); -+ -+ if (activityDetected) { -+ updateLastActivityTimestamp(); -+ } -+ }); -+}; -+ -+/** + * Updates the last activity timestamp by recording the current timestamp in the idle file and + * refreshing the status bar. The timestamp should be in ISO 8601 format and set to the UTC timezone. + */ @@ -279,16 +225,62 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/webClientServer.ts +++ sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts -@@ -3,7 +3,7 @@ +@@ -3,7 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createReadStream, promises } from 'fs'; +import { createReadStream, promises, existsSync, writeFileSync } from 'fs'; ++import { readFile } from 'fs/promises' ++import * as path from 'path'; import * as http from 'http'; import * as url from 'url'; import * as cookie from 'cookie'; -@@ -95,6 +95,7 @@ const APP_ROOT = dirname(FileAccess.asFi +@@ -90,12 +91,52 @@ export async function serveFile(filePath + } + } + ++const CHECK_INTERVAL = 60000; // 60 seconds interval + const APP_ROOT = dirname(FileAccess.asFileUri('').fsPath); + ++/** ++ * Checks for terminal activity by reading the /dev/pts directory and comparing modification times of the files. ++ * ++ * The /dev/pts directory is used in Unix-like operating systems to represent pseudo-terminal (PTY) devices. ++ * Each active terminal session is assigned a PTY device. These devices are represented as files within the /dev/pts directory. ++ * When a terminal session has activity, such as when a user inputs commands or output is written to the terminal, ++ * the modification time (mtime) of the corresponding PTY device file is updated. By monitoring the modification ++ * times of the files in the /dev/pts directory, we can detect terminal activity. ++ * ++ * If activity is detected (i.e., if any PTY device file was modified within the CHECK_INTERVAL), this function ++ * updates the last activity timestamp. ++ */ ++const checkTerminalActivity = (idleFilePath: string) => { ++ fs.readdir('/dev/pts', (err, files) => { ++ if (err) { ++ console.error('Error reading /dev/pts directory:', err); ++ return; ++ } ++ ++ const now = new Date(); ++ const activityDetected = files.some((file) => { ++ const filePath = path.join('/dev/pts', file); ++ try { ++ const stats = fs.statSync(filePath); ++ const mtime = new Date(stats.mtime).getTime(); ++ return now.getTime() - mtime < CHECK_INTERVAL; ++ } catch (error) { ++ console.error('Error reading file stats:', error); ++ return false; ++ } ++ }); ++ ++ if (activityDetected) { ++ fs.writeFileSync(idleFilePath, now.toISOString()); ++ } ++ }); ++}; ++ const STATIC_PATH = `/static`; const CALLBACK_PATH = `/callback`; const WEB_EXTENSION_PATH = `/web-extension-resource`; @@ -296,6 +288,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts export class WebClientServer { + private readonly _webExtensionResourceUrlTemplate: URI | undefined; @@ -131,6 +132,9 @@ export class WebClientServer { // callback support return this._handleCallback(res); @@ -306,33 +299,36 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts if (pathname.startsWith(WEB_EXTENSION_PATH) && pathname.charCodeAt(WEB_EXTENSION_PATH.length) === CharCode.Slash) { // extension resource support return this._handleWebExtensionResource(req, res, pathname.substring(WEB_EXTENSION_PATH.length)); -@@ -496,4 +500,29 @@ export class WebClientServer { +@@ -496,4 +500,31 @@ export class WebClientServer { }); return void res.end(data); } + + /** -+ * Handles API requests to retrieve the last activity timestamp. -+ */ -+ private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { -+ try { -+ const tmpDirectory = '/tmp/' -+ const idleFilePath = join(tmpDirectory, '.sagemaker-last-active-timestamp'); -+ -+ // If idle shutdown file does not exist, this indicates the app UI may never been opened -+ // Create the initial metadata file -+ if (!existsSync(idleFilePath)) { -+ const timestamp = new Date().toISOString(); -+ writeFileSync(idleFilePath, timestamp); -+ } -+ -+ const data = await promises.readFile(idleFilePath, 'utf8'); -+ -+ res.statusCode = 200; -+ res.setHeader('Content-Type', 'application/json'); -+ res.end(JSON.stringify({ lastActiveTimestamp: data })); -+ } catch (error) { -+ serveError(req, res, 500, error.message) -+ } -+ } ++ * Handles API requests to retrieve the last activity timestamp. ++ */ ++ private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { ++ try { ++ const tmpDirectory = '/tmp/' ++ const idleFilePath = path.join(tmpDirectory, '.sagemaker-last-active-timestamp'); ++ ++ // If idle shutdown file does not exist, this indicates the app UI may never been opened ++ // Create the initial metadata file ++ if (!existsSync(idleFilePath)) { ++ const timestamp = new Date().toISOString(); ++ writeFileSync(idleFilePath, timestamp); ++ } ++ ++ checkTerminalActivity(idleFilePath); ++ ++ const data = await readFile(idleFilePath, 'utf8'); ++ ++ res.statusCode = 200; ++ res.setHeader('Content-Type', 'application/json'); ++ res.end(JSON.stringify({ lastActiveTimestamp: data })); ++ } catch (error) { ++ serveError(req, res, 500, error.message); ++ } ++ } } + \ No newline at end of file diff --git a/patches/webview.diff b/patches/webview.diff index 25f77c266..c5e76b871 100644 --- a/patches/webview.diff +++ b/patches/webview.diff @@ -78,6 +78,15 @@ Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/webview/browser/pre { + /** + * @param {MessageEvent} event @@ -344,6 +344,12 @@ const hostname = location.hostname;