From f83019b505ecdbef147fd3bdb43071c20eab25bc Mon Sep 17 00:00:00 2001 From: Austin Jang Date: Wed, 1 Oct 2025 13:36:04 -0700 Subject: [PATCH 1/4] Remove module type from service worker and update sha --- patches/webview.diff | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/patches/webview.diff b/patches/webview.diff index 25f77c266..6d96dc0de 100644 --- a/patches/webview.diff +++ b/patches/webview.diff @@ -74,10 +74,19 @@ Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/webview/browser/pre -+ content="default-src 'none'; script-src 'sha256-1qYtPnTQa4VwKNJO61EOhs2agF9TvuQSYIJ27OgzZqI=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> ++ content="default-src 'none'; script-src 'sha256-Oi71Tq4Buohx0KDH3yEbVJUzABnqYv9iVLo420HZXqI=' 'self'; frame-src 'self'; style-src 'unsafe-inline';"> { + /** + * @param {MessageEvent} event @@ -344,6 +344,12 @@ const hostname = location.hostname; From 54c75c3f23444b61aa29eaa881d72f3b4cc83986 Mon Sep 17 00:00:00 2001 From: Austin Jang Date: Tue, 7 Oct 2025 11:28:06 -0700 Subject: [PATCH 2/4] Update display language patch --- patches/display-language.patch | 182 ++++++++++++++++++++++++++++++++- 1 file changed, 180 insertions(+), 2 deletions(-) diff --git a/patches/display-language.patch b/patches/display-language.patch index 95c9a2197..0f8575f9e 100644 --- a/patches/display-language.patch +++ b/patches/display-language.patch @@ -85,9 +85,16 @@ Index: sagemaker-code-editor/vscode/src/vs/code/browser/workbench/workbench.html -@@ -37,6 +40,47 @@ +@@ -33,8 +36,53 @@ + ++ ++ + + @@ -140,6 +152,15 @@ Index: sagemaker-code-editor/vscode/src/vs/code/browser/workbench/workbench.html +- +- +- +- +- ++ ++ ++ + Index: sagemaker-code-editor/vscode/src/vs/platform/environment/common/environmentService.ts =================================================================== @@ -197,8 +218,7 @@ 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 -@@ -0,38 +0,80 @@ import { FileAccess } from 'vs/base/comm - /*--------------------------------------------------------------------------------------------- +@@ -1,38 +1,80 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ @@ -209,16 +229,12 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts -import { resolveNLSConfiguration } from '../../base/node/nls.js'; -import { Promises } from '../../base/node/pfs.js'; -import product from '../../platform/product/common/product.js'; -+import * as path from '../../base/common/path.js'; -+import * as lp from '../../base/node/languagePacks.js'; - +- -const nlsMetadataPath = join(FileAccess.asFileUri('').fsPath); -const defaultMessagesFile = join(nlsMetadataPath, 'nls.messages.json'); -const nlsConfigurationCache = new Map>(); -+const metaData = path.join(FileAccess.asFileUri('').fsPath, 'nls.metadata.json'); -+const _cache: Map> = new Map(); - --export async function getNLSConfiguration(language: string, userDataPath: string): Promise { +- +-export async function getNLSConfiguration(language: string, userDataPath: string): Promise { - if (!product.commit || !(await Promises.exists(defaultMessagesFile))) { - return { - userLocale: 'en', @@ -237,6 +253,17 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts - if (!result) { - result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: product.commit, userDataPath, nlsMetadataPath }); - nlsConfigurationCache.set(cacheKey, result); +- } +- +- return result; +-} ++import * as fs from 'fs'; ++import * as path from '../../base/common/path.js'; ++import * as lp from '../../base/node/languagePacks.js'; ++ ++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); @@ -255,10 +282,9 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts + return value; + }); + _cache.set(key, result); - } -- - return result; - } ++ } ++ return result; ++} + +export namespace InternalNLSConfiguration { + export function is(value: lp.NLSConfiguration): value is lp.InternalNLSConfiguration { @@ -306,7 +332,6 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts + } + }); +}; -\ No newline at end of file Index: sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/serverEnvironmentService.ts @@ -358,23 +383,18 @@ 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 -@@ -0,8 +0,10 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - --import { createReadStream, promises } from 'fs'; -+import { createReadStream } from 'fs'; -+import { Promises } from '../../base/node/pfs.js'; -+import * as path from 'path'; - import * as http from 'http'; +@@ -8,6 +8,7 @@ import * as url from 'url'; import * as cookie from 'cookie'; -@@ -30,9 +32,9 @@ import { URI } from '../../base/common/uri.js' + import * as crypto from 'crypto'; ++import * as path from 'path'; + import { isEqualOrParent } from '../../base/common/extpath.js'; + import { getMediaMime } from '../../base/common/mime.js'; + import { isLinux } from '../../base/common/platform.js'; +@@ -25,9 +26,9 @@ import { streamToBuffer } from '../../base/common/buffer.js'; import { IProductConfiguration } from '../../base/common/product.js'; - import { isString } from '../../base/common/types.js'; + import { isString, Mutable } from '../../base/common/types.js'; +import { getLocaleFromConfig, getNLSConfiguration } from '../../server/node/remoteLanguagePacks.js'; import { CharCode } from '../../base/common/charCode.js'; import { IExtensionManifest } from '../../platform/extensions/common/extensions.js'; @@ -382,13 +402,13 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts const textMimeType: { [ext: string]: string | undefined } = { '.html': 'text/html', -@@ -393,25 +394,19 @@ export class WebClientServer { + '.js': 'text/javascript', +@@ -393,26 +394,20 @@ export class WebClientServer { workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']), productConfiguration, - callbackRoute: callbackRoute -+ callbackRoute: this._callbackRoute - }; - +- }; +- - const cookies = cookie.parse(req.headers.cookie || ''); - const locale = cookies['vscode.nls.locale'] || req.headers['accept-language']?.split(',')[0]?.toLowerCase() || 'en'; - let WORKBENCH_NLS_BASE_URL: string | undefined; @@ -399,23 +419,32 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts - } else { - WORKBENCH_NLS_URL = ''; // fallback will apply - } -+ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath); -+ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath) - const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl; - const values: { [key: string]: string } = { - WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), - WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', +- +- const values: { [key: string]: string } = { +- WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), +- WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', - WORKBENCH_WEB_BASE_URL: staticRoute, - WORKBENCH_NLS_URL, -- ORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js` +- WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js` +- }; ++ callbackRoute: this._callbackRoute ++ }; ++ ++ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath); ++ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath) ++ const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl; ++ const values: { [key: string]: string } = { ++ WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), ++ WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', + WORKBENCH_WEB_BASE_URL: this._staticRoute, + WORKBENCH_NLS_BASE_URL: nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : '', + BASE: base, + VS_BASE: vscodeBase, + NLS_CONFIGURATION: asJSON(nlsConfiguration), - }; ++ }; - if (useTestResolver) { + // DEV --------------------------------------------------------------------------------------- + // DEV: This is for development and enables loading CSS via import-statements via import-maps. @@ -401,7 +405,7 @@ export class WebClientServer { `frame-src 'self' https://*.vscode-cdn.net data:;`, 'worker-src \'self\' data: blob:;', From 129e9a79297308d5d4c35faffdd98740ab61fc46 Mon Sep 17 00:00:00 2001 From: Austin Jang Date: Tue, 25 Nov 2025 15:58:50 -0800 Subject: [PATCH 4/4] Fix display language and update idle extension --- patches/display-language.patch | 553 ++++++++++++------------- patches/sagemaker-idle-extension.patch | 168 ++++---- 2 files changed, 354 insertions(+), 367 deletions(-) diff --git a/patches/display-language.patch b/patches/display-language.patch index 7fcfb4621..2eea9fe80 100644 --- a/patches/display-language.patch +++ b/patches/display-language.patch @@ -1,167 +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 @@ - - - -+ -+ -+ - - - -@@ -25,6 +28,6 @@ - - - -- -+ - - - -@@ -32,18 +35,61 @@ - - - - -+ -+ - - -- -- -- -- -- -+ -+ -+ - - Index: sagemaker-code-editor/vscode/src/vs/platform/environment/common/environmentService.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/platform/environment/common/environmentService.ts @@ -218,23 +54,24 @@ 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 -@@ -1,38 +1,80 @@ - * Copyright (c) Microsoft Corporation. All rights reserved. +@@ -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 { 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 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 { + const nlsConfigurationCache = new Map>(); + + export async function getNLSConfiguration(language: string, userDataPath: string): Promise { - if (!product.commit || !(await Promises.exists(defaultMessagesFile))) { - return { - userLocale: 'en', @@ -248,58 +85,31 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts - }; - } - -- const cacheKey = `${language}||${userDataPath}`; -- let result = nlsConfigurationCache.get(cacheKey); -- if (!result) { + const cacheKey = `${language}||${userDataPath}`; + let result = nlsConfigurationCache.get(cacheKey); + if (!result) { - result = resolveNLSConfiguration({ userLocale: language, osLocale: language, commit: product.commit, userDataPath, nlsMetadataPath }); -- nlsConfigurationCache.set(cacheKey, result); -- } -- -- return result; --} -+import * as fs from 'fs'; -+import * as path from '../../base/common/path.js'; -+import * as lp from '../../base/node/languagePacks.js'; -+ -+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); -+ 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; ++ // 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); + } -+ // 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); -+ } -+ 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") { @@ -309,6 +119,9 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts + } +}; + ++/** ++ * Copied from from src/main.js. ++ */ +const stripComments = (content: string): string => { + const regexp = /('(?:[^\\']*(?:\\.)?)*')|('(?:[^\\']*(?:\\.)?)*')|(\/\*(?:\r?\n|.)*?\*\/)|(\/{2,}.*?(?:(?:\r?\n)|$))/g; + @@ -332,6 +145,45 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts + } + }); +}; ++ ++/** ++ * 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 =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/server/node/serverEnvironmentService.ts @@ -359,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'; @@ -368,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); @@ -383,78 +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 -@@ -8,6 +8,7 @@ - import * as url from 'url'; - import * as cookie from 'cookie'; - import * as crypto from 'crypto'; -+import * as path from 'path'; - import { isEqualOrParent } from '../../base/common/extpath.js'; - import { getMediaMime } from '../../base/common/mime.js'; - import { isLinux } from '../../base/common/platform.js'; -@@ -25,9 +26,9 @@ +@@ -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, getNLSConfiguration } from '../../server/node/remoteLanguagePacks.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'; + 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 textMimeType: { [ext: string]: string | undefined } = { - '.html': 'text/html', - '.js': 'text/javascript', -@@ -393,26 +394,20 @@ export class WebClientServer { - workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']), - productConfiguration, -- callbackRoute: callbackRoute -- }; -- -- const cookies = cookie.parse(req.headers.cookie || ''); + 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'; -- 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 { ++ 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 -- } -- -- const values: { [key: string]: string } = { -- WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), -- WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', -- WORKBENCH_WEB_BASE_URL: staticRoute, -- WORKBENCH_NLS_URL, ++ 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` -- }; -+ callbackRoute: this._callbackRoute -+ }; -+ -+ const locale = this._environmentService.args.locale || await getLocaleFromConfig(this._environmentService.argvResource.fsPath); -+ const nlsConfiguration = await getNLSConfiguration(locale, this._environmentService.userDataPath) -+ const nlsBaseUrl = this._productService.extensionsGallery?.nlsBaseUrl; -+ const values: { [key: string]: string } = { -+ WORKBENCH_WEB_CONFIGURATION: asJSON(workbenchWebConfiguration), -+ WORKBENCH_AUTH_SESSION: authSessionInfo ? asJSON(authSessionInfo) : '', -+ WORKBENCH_WEB_BASE_URL: this._staticRoute, -+ WORKBENCH_NLS_BASE_URL: nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : '', ++ WORKBENCH_NLS_FALLBACK_URL: `${staticRoute}/out/nls.messages.js`, + BASE: base, -+ VS_BASE: vscodeBase, -+ NLS_CONFIGURATION: asJSON(nlsConfiguration), -+ }; ++ VS_BASE: vscodeBase + }; // DEV --------------------------------------------------------------------------------------- - // DEV: This is for development and enables loading CSS via import-statements via import-maps. -@@ -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(' '); - +@@ -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 @@ -547,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