From aec1a0967f7f5b29ca143955c6130ee9477629a0 Mon Sep 17 00:00:00 2001 From: Cindy Zhao Date: Mon, 14 Oct 2024 10:39:30 -0700 Subject: [PATCH 01/20] Add sagemaker-open-notebook-extension --- patched-vscode/build/gulpfile.extensions.js | 1 + patched-vscode/build/npm/dirs.js | 1 + .../.vscodeignore | 12 + .../README.md | 18 + .../extension-browser.webpack.config.js | 17 + .../extension.webpack.config.js | 20 ++ .../package.json | 44 +++ .../src/extension.ts | 80 +++++ .../tsconfig.json | 10 + .../yarn.lock | 4 + .../browser/extensions.contribution.ts | 7 +- .../browser/extensionsWorkbenchService.ts | 11 + .../sagemaker-open-notebook-extension.patch | 309 ++++++++++++++++++ patches/series | 3 +- 14 files changed, 535 insertions(+), 2 deletions(-) create mode 100644 patched-vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore create mode 100644 patched-vscode/extensions/sagemaker-open-notebook-extension/README.md create mode 100644 patched-vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js create mode 100644 patched-vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js create mode 100644 patched-vscode/extensions/sagemaker-open-notebook-extension/package.json create mode 100644 patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts create mode 100644 patched-vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json create mode 100644 patched-vscode/extensions/sagemaker-open-notebook-extension/yarn.lock create mode 100644 patches/sagemaker-open-notebook-extension.patch diff --git a/patched-vscode/build/gulpfile.extensions.js b/patched-vscode/build/gulpfile.extensions.js index 1b8bd9b3..d5d57f6d 100644 --- a/patched-vscode/build/gulpfile.extensions.js +++ b/patched-vscode/build/gulpfile.extensions.js @@ -63,6 +63,7 @@ const compilations = [ 'extensions/sagemaker-extension/tsconfig.json', 'extensions/sagemaker-idle-extension/tsconfig.json', 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', + 'extensions/sagemaker-open-notebook-extension/tsconfig.json', 'extensions/tunnel-forwarding/tsconfig.json', 'extensions/typescript-language-features/test-workspace/tsconfig.json', 'extensions/typescript-language-features/web/tsconfig.json', diff --git a/patched-vscode/build/npm/dirs.js b/patched-vscode/build/npm/dirs.js index 2aa1c43f..ae459ee0 100644 --- a/patched-vscode/build/npm/dirs.js +++ b/patched-vscode/build/npm/dirs.js @@ -42,6 +42,7 @@ const dirs = [ 'extensions/sagemaker-extension', 'extensions/sagemaker-idle-extension', 'extensions/sagemaker-terminal-crash-mitigation', + 'extensions/sagemaker-open-notebook-extension', 'extensions/search-result', 'extensions/simple-browser', 'extensions/tunnel-forwarding', diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore b/patched-vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore new file mode 100644 index 00000000..56b78554 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore @@ -0,0 +1,12 @@ +.vscode/** +.vscode-test/** +out/test/** +out/** +test/** +src/** +tsconfig.json +out/test/** +out/** +cgmanifest.json +yarn.lock +preview-src/** diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/README.md b/patched-vscode/extensions/sagemaker-open-notebook-extension/README.md new file mode 100644 index 00000000..0efc94ac --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/README.md @@ -0,0 +1,18 @@ +# Code Editor Open Notebook Extension + +The Open Notebook extension enables users to download, transform, and display sample notebooks from a public Amazon S3 bucket owned by the SageMaker team. This extension streamlines the process of accessing and working with SageMaker sample notebooks directly within Code Editor. + +## Features + +- Download sample notebooks from a specified S3 bucket +- Transform notebooks for compatibility with VSCode +- Display notebooks within the Code Editor environment +- Utilize URL parameters to open specific notebooks + +## Usage + +The extension uses parameters from the URL to open the desired notebook. The required parameters are: +- Notebook key: The identifier for the specific notebook in the S3 bucket +- Cluster ID: The ID of the SageMaker cluster +- Region: The AWS region where the S3 bucket is located + diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js b/patched-vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js new file mode 100644 index 00000000..68271e0e --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../shared.webpack.config').browser; + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts' + }, +}); diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js b/patched-vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js new file mode 100644 index 00000000..59852626 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/package.json b/patched-vscode/extensions/sagemaker-open-notebook-extension/package.json new file mode 100644 index 00000000..6a62df9c --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/package.json @@ -0,0 +1,44 @@ +{ + "name": "sagemaker-open-notebook-extension", + "displayName": "Sagemaker open notebook Extension", + "description": "To download and open sample notebook when open code editor", + "extensionKind": [ + "workspace" + ], + "version": "1.0.0", + "publisher": "sagemaker", + "license": "MIT", + "engines": { + "vscode": "^1.70.0" + }, + "main": "./out/extension", + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, + "contributes": { + "configuration": { + "type": "object", + "title": "Sagemaker Open Notebook Extension", + "properties": {} + }, + "commands": [ + ] + }, + "scripts": { + "compile": "gulp compile-extension:sagemaker-open-notebook-extension", + "watch": "npm run build-preview && gulp watch-extension:sagemaker-open-notebook-extension", + "vscode:prepublish": "npm run build-ext", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-open-notebook-extension ./tsconfig.json" + }, + "dependencies": {}, + "repository": {} +} diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts new file mode 100644 index 00000000..8e9133ad --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts @@ -0,0 +1,80 @@ + +import * as vscode from 'vscode'; +import * as https from 'https'; +import * as fs from 'fs'; +import * as path from 'path'; +import * as os from 'os'; +import * as console from 'console'; + +export function activate() { + const config = vscode.workspace.getConfiguration('extensions.openNotebookData'); + const notebookKey = config.get('notebookKey') as string; + const clusterId = config.get('clusterId') as string; + const region = config.get('region') as string; + if(notebookKey){ + loadAndDisplayNotebook(notebookKey, clusterId, region); + } + +} + +async function loadAndDisplayNotebook(fileKey: string, clusterId: string, region: string) { + const bucketName = `jumpstart-cache-prod-${region}`; + const url = `https://${bucketName}.s3.${region}.amazonaws.com/${fileKey}`; + try { + let content = await downloadFile(url); + content = processNotebookContent(content, clusterId, region); + const tempDir = os.tmpdir(); + const tempFilePath = path.join(tempDir, 'downloaded-notebook.ipynb'); + fs.writeFileSync(tempFilePath, content); + const uri = vscode.Uri.file(tempFilePath); + await openNotebookDocument(uri); + } catch (error) { + vscode.window.showErrorMessage('Error downloading or opening notebook: ' + error.message); + } +} + +function processNotebookContent(content: string, clusterId: string, region: string): string { + const notebook = JSON.parse(content); + notebook.cells = notebook.cells.map((cell: any) => { + if (cell.metadata && + cell.metadata.jumpStartAlterations && + cell.metadata.jumpStartAlterations.includes('clusterId')) { + cell.source = [ + "%%bash\n", + `aws ssm start-session --target sagemaker-cluster:${clusterId} --region ${region}` + ]; + cell.cell_type = "code"; + } + return cell; + }); + return JSON.stringify(notebook, null, 2); +} + +async function openNotebookDocument(uri: vscode.Uri) { + try { + // Open the notebook document + const document = await vscode.workspace.openNotebookDocument(uri); + // Show the notebook document in a notebook editor + await vscode.window.showNotebookDocument(document); + } catch (error) { + console.error('Failed to open notebook:', error); + vscode.window.showErrorMessage('Failed to open notebook: ' + error.message); + } +} + +function downloadFile(url: string): Promise { + return new Promise((resolve, reject) => { + https.get(url, (response) => { + let data = ''; + response.on('data', (chunk) => { + data += chunk; + }); + response.on('end', () => { + resolve(data); + }); + }).on('error', (error) => { + reject(error); + }); + }); +} +export function deactivate() {} diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json b/patched-vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json new file mode 100644 index 00000000..3e1b4164 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out" + }, + "include": [ + "../sagemaker-open-notebook-extension/src/**/*", + "../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/yarn.lock b/patched-vscode/extensions/sagemaker-open-notebook-extension/yarn.lock new file mode 100644 index 00000000..fb57ccd1 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 0ff4dca2..43dc8077 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -258,7 +258,12 @@ Registry.as(ConfigurationExtensions.Configuration) type: 'boolean', description: localize('extensionsInQuickAccess', "When enabled, extensions can be searched for via Quick Access and report issues from there."), default: true - } + }, + 'extensions.openNotebookData': { + type: 'object', + scope: ConfigurationScope.APPLICATION, + default: {}, + }, } }); diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 3eb8c7ee..83cb13bb 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -950,6 +950,17 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension urlService.registerHandler(this); this.whenInitialized = this.initialize(); + + //Update workspace to open notebook + const urlParams = new URLSearchParams(window.location.search); + const notebookKey = urlParams.get('openNotebook'); + const clusterId = urlParams.get('clusterId'); + const region = urlParams.get('region'); + configurationService.updateValue('extensions.openNotebookData', { + notebookKey: notebookKey, + clusterId: clusterId, + region: region, + }); } private async initialize(): Promise { diff --git a/patches/sagemaker-open-notebook-extension.patch b/patches/sagemaker-open-notebook-extension.patch new file mode 100644 index 00000000..ec48a6f5 --- /dev/null +++ b/patches/sagemaker-open-notebook-extension.patch @@ -0,0 +1,309 @@ +Index: sagemaker-code-editor/vscode/build/gulpfile.extensions.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/gulpfile.extensions.js ++++ sagemaker-code-editor/vscode/build/gulpfile.extensions.js +@@ -63,6 +63,7 @@ const compilations = [ + 'extensions/sagemaker-extension/tsconfig.json', + 'extensions/sagemaker-idle-extension/tsconfig.json', + 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', ++ 'extensions/sagemaker-open-notebook-extension/tsconfig.json', + 'extensions/tunnel-forwarding/tsconfig.json', + 'extensions/typescript-language-features/test-workspace/tsconfig.json', + 'extensions/typescript-language-features/web/tsconfig.json', +Index: sagemaker-code-editor/vscode/build/npm/dirs.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/npm/dirs.js ++++ sagemaker-code-editor/vscode/build/npm/dirs.js +@@ -42,6 +42,7 @@ const dirs = [ + 'extensions/sagemaker-extension', + 'extensions/sagemaker-idle-extension', + 'extensions/sagemaker-terminal-crash-mitigation', ++ 'extensions/sagemaker-open-notebook-extension', + 'extensions/search-result', + 'extensions/simple-browser', + 'extensions/tunnel-forwarding', +Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +@@ -258,7 +258,12 @@ Registry.as(Conf + type: 'boolean', + description: localize('extensionsInQuickAccess', "When enabled, extensions can be searched for via Quick Access and report issues from there."), + default: true +- } ++ }, ++ 'extensions.openNotebookData': { ++ type: 'object', ++ scope: ConfigurationScope.APPLICATION, ++ default: {}, ++ }, + } + }); + +Index: sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +@@ -950,6 +950,17 @@ export class ExtensionsWorkbenchService + urlService.registerHandler(this); + + this.whenInitialized = this.initialize(); ++ ++ //Update workspace to open notebook ++ const urlParams = new URLSearchParams(window.location.search); ++ const notebookKey = urlParams.get('openNotebook'); ++ const clusterId = urlParams.get('clusterId'); ++ const region = urlParams.get('region'); ++ configurationService.updateValue('extensions.openNotebookData', { ++ notebookKey: notebookKey, ++ clusterId: clusterId, ++ region: region, ++ }); + } + + private async initialize(): Promise { +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/extension-browser.webpack.config.js +@@ -0,0 +1,17 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withBrowserDefaults = require('../shared.webpack.config').browser; ++ ++module.exports = withBrowserDefaults({ ++ context: __dirname, ++ entry: { ++ extension: './src/extension.ts' ++ }, ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/extension.webpack.config.js +@@ -0,0 +1,20 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withDefaults = require('../shared.webpack.config'); ++ ++module.exports = withDefaults({ ++ context: __dirname, ++ resolve: { ++ mainFields: ['module', 'main'] ++ }, ++ entry: { ++ extension: './src/extension.ts', ++ } ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/package.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/package.json +@@ -0,0 +1,44 @@ ++{ ++ "name": "sagemaker-open-notebook-extension", ++ "displayName": "Sagemaker open notebook Extension", ++ "description": "To download and open sample notebook when open code editor", ++ "extensionKind": [ ++ "workspace" ++ ], ++ "version": "1.0.0", ++ "publisher": "sagemaker", ++ "license": "MIT", ++ "engines": { ++ "vscode": "^1.70.0" ++ }, ++ "main": "./out/extension", ++ "categories": [ ++ "Other" ++ ], ++ "activationEvents": [ ++ "*" ++ ], ++ "capabilities": { ++ "virtualWorkspaces": true, ++ "untrustedWorkspaces": { ++ "supported": true ++ } ++ }, ++ "contributes": { ++ "configuration": { ++ "type": "object", ++ "title": "Sagemaker Open Notebook Extension", ++ "properties": {} ++ }, ++ "commands": [ ++ ] ++ }, ++ "scripts": { ++ "compile": "gulp compile-extension:sagemaker-open-notebook-extension", ++ "watch": "npm run build-preview && gulp watch-extension:sagemaker-open-notebook-extension", ++ "vscode:prepublish": "npm run build-ext", ++ "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-open-notebook-extension ./tsconfig.json" ++ }, ++ "dependencies": {}, ++ "repository": {} ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts +@@ -0,0 +1,80 @@ ++ ++import * as vscode from 'vscode'; ++import * as https from 'https'; ++import * as fs from 'fs'; ++import * as path from 'path'; ++import * as os from 'os'; ++import * as console from 'console'; ++ ++export function activate() { ++ const config = vscode.workspace.getConfiguration('extensions.openNotebookData'); ++ const notebookKey = config.get('notebookKey') as string; ++ const clusterId = config.get('clusterId') as string; ++ const region = config.get('region') as string; ++ if(notebookKey){ ++ loadAndDisplayNotebook(notebookKey, clusterId, region); ++ } ++ ++} ++ ++async function loadAndDisplayNotebook(fileKey: string, clusterId: string, region: string) { ++ const bucketName = `jumpstart-cache-prod-${region}`; ++ const url = `https://${bucketName}.s3.${region}.amazonaws.com/${fileKey}`; ++ try { ++ let content = await downloadFile(url); ++ content = processNotebookContent(content, clusterId, region); ++ const tempDir = os.tmpdir(); ++ const tempFilePath = path.join(tempDir, 'downloaded-notebook.ipynb'); ++ fs.writeFileSync(tempFilePath, content); ++ const uri = vscode.Uri.file(tempFilePath); ++ await openNotebookDocument(uri); ++ } catch (error) { ++ vscode.window.showErrorMessage('Error downloading or opening notebook: ' + error.message); ++ } ++} ++ ++function processNotebookContent(content: string, clusterId: string, region: string): string { ++ const notebook = JSON.parse(content); ++ notebook.cells = notebook.cells.map((cell: any) => { ++ if (cell.metadata && ++ cell.metadata.jumpStartAlterations && ++ cell.metadata.jumpStartAlterations.includes('clusterId')) { ++ cell.source = [ ++ "%%bash\n", ++ `aws ssm start-session --target sagemaker-cluster:${clusterId} --region ${region}` ++ ]; ++ cell.cell_type = "code"; ++ } ++ return cell; ++ }); ++ return JSON.stringify(notebook, null, 2); ++} ++ ++async function openNotebookDocument(uri: vscode.Uri) { ++ try { ++ // Open the notebook document ++ const document = await vscode.workspace.openNotebookDocument(uri); ++ // Show the notebook document in a notebook editor ++ await vscode.window.showNotebookDocument(document); ++ } catch (error) { ++ console.error('Failed to open notebook:', error); ++ vscode.window.showErrorMessage('Failed to open notebook: ' + error.message); ++ } ++} ++ ++function downloadFile(url: string): Promise { ++ return new Promise((resolve, reject) => { ++ https.get(url, (response) => { ++ let data = ''; ++ response.on('data', (chunk) => { ++ data += chunk; ++ }); ++ response.on('end', () => { ++ resolve(data); ++ }); ++ }).on('error', (error) => { ++ reject(error); ++ }); ++ }); ++} ++export function deactivate() {} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/tsconfig.json +@@ -0,0 +1,10 @@ ++{ ++ "extends": "../tsconfig.base.json", ++ "compilerOptions": { ++ "outDir": "./out" ++ }, ++ "include": [ ++ "../sagemaker-open-notebook-extension/src/**/*", ++ "../../src/vscode-dts/vscode.d.ts" ++ ] ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/yarn.lock +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/yarn.lock +@@ -0,0 +1,4 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ ++ +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/.vscodeignore +@@ -0,0 +1,12 @@ ++.vscode/** ++.vscode-test/** ++out/test/** ++out/** ++test/** ++src/** ++tsconfig.json ++out/test/** ++out/** ++cgmanifest.json ++yarn.lock ++preview-src/** +Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/README.md +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/README.md +@@ -0,0 +1,18 @@ ++# Code Editor Open Notebook Extension ++ ++The Open Notebook extension enables users to download, transform, and display sample notebooks from a public Amazon S3 bucket owned by the SageMaker team. This extension streamlines the process of accessing and working with SageMaker sample notebooks directly within Code Editor. ++ ++## Features ++ ++- Download sample notebooks from a specified S3 bucket ++- Transform notebooks for compatibility with VSCode ++- Display notebooks within the Code Editor environment ++- Utilize URL parameters to open specific notebooks ++ ++## Usage ++ ++The extension uses parameters from the URL to open the desired notebook. The required parameters are: ++- Notebook key: The identifier for the specific notebook in the S3 bucket ++- Cluster ID: The ID of the SageMaker cluster ++- Region: The AWS region where the S3 bucket is located ++ diff --git a/patches/series b/patches/series index 9091c133..280c2e07 100644 --- a/patches/series +++ b/patches/series @@ -7,4 +7,5 @@ local-storage.diff sagemaker-integration.diff license.diff sagemaker-idle-extension.patch -terminal-crash-mitigation.patch \ No newline at end of file +terminal-crash-mitigation.patch +sagemaker-open-notebook-extension.patch From ec903b19747253f4ea168d29004aa7c0f1107750 Mon Sep 17 00:00:00 2001 From: Alvin Solidum Date: Tue, 15 Oct 2024 00:32:07 -0700 Subject: [PATCH 02/20] Move .sagemaker-last-active-timestamp to /tmp/ --- .../sagemaker-idle-extension/src/extension.ts | 11 ++++------ .../src/vs/server/node/webClientServer.ts | 8 ++----- patches/sagemaker-idle-extension.patch | 22 ++++++------------- 3 files changed, 13 insertions(+), 28 deletions(-) diff --git a/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts index 0fbb35e6..b6c1f46a 100644 --- a/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts +++ b/patched-vscode/extensions/sagemaker-idle-extension/src/extension.ts @@ -21,20 +21,17 @@ export function deactivate() { /** * Initializes the file path where the idle timestamp will be stored. - * It sets the path to a hidden file in the user's home directory. + * It sets the path to a hidden file in the /tmp/ directory. */ function initializeIdleFilePath() { - const homeDirectory = process.env.HOME || process.env.USERPROFILE; - if (!homeDirectory) { - console.log(`${LOG_PREFIX} Unable to determine the home directory.`); - return; - } - idleFilePath = path.join(homeDirectory, ".sagemaker-last-active-timestamp"); + const tmpDirectory = "/tmp/"; + idleFilePath = path.join(tmpDirectory, ".sagemaker-last-active-timestamp"); // Set initial lastActivetimestamp updateLastActivityTimestamp() } + /** * Registers event listeners to monitor user activity within the VSCode editor. * It listens to document changes, editor focus changes, text selection changes, and terminal events. diff --git a/patched-vscode/src/vs/server/node/webClientServer.ts b/patched-vscode/src/vs/server/node/webClientServer.ts index 670d3bf0..330d587d 100644 --- a/patched-vscode/src/vs/server/node/webClientServer.ts +++ b/patched-vscode/src/vs/server/node/webClientServer.ts @@ -463,12 +463,8 @@ export class WebClientServer { */ private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { try { - const homeDirectory = process.env.HOME || process.env.USERPROFILE; - if (!homeDirectory) { - throw new Error('Home directory not found'); - } - - const idleFilePath = path.join(homeDirectory, '.code-editor-last-active-timestamp'); + const tmpDirectory = '/tmp/' + const idleFilePath = path.join(tmpDirectory, '.sagemaker-last-active-timestamp'); const data = await readFile(idleFilePath, 'utf8'); res.statusCode = 200; diff --git a/patches/sagemaker-idle-extension.patch b/patches/sagemaker-idle-extension.patch index f42b600d..32c2d6e4 100644 --- a/patches/sagemaker-idle-extension.patch +++ b/patches/sagemaker-idle-extension.patch @@ -147,7 +147,7 @@ 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,116 @@ +@@ -0,0 +1,112 @@ +import * as vscode from "vscode"; +import * as fs from "fs"; +import * as path from "path"; @@ -171,15 +171,11 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-idle-extension/src/exte + +/** + * Initializes the file path where the idle timestamp will be stored. -+ * It sets the path to a hidden file in the user's home directory. ++ * It sets the path to a hidden file in the /tmp/ directory. + */ +function initializeIdleFilePath() { -+ const homeDirectory = process.env.HOME || process.env.USERPROFILE; -+ if (!homeDirectory) { -+ console.log(`${LOG_PREFIX} Unable to determine the home directory.`); -+ return; -+ } -+ idleFilePath = path.join(homeDirectory, ".sagemaker-last-active-timestamp"); ++ const tmpDirectory = "/tmp/"; ++ idleFilePath = path.join(tmpDirectory, ".sagemaker-last-active-timestamp"); + + // Set initial lastActivetimestamp + updateLastActivityTimestamp() @@ -327,7 +323,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts if (pathname === this._callbackRoute) { // callback support return this._handleCallback(res); -@@ -451,6 +457,27 @@ export class WebClientServer { +@@ -451,6 +457,23 @@ export class WebClientServer { }); return void res.end(data); } @@ -337,12 +333,8 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts + */ + private async _handleIdle(req: http.IncomingMessage, res: http.ServerResponse): Promise { + try { -+ const homeDirectory = process.env.HOME || process.env.USERPROFILE; -+ if (!homeDirectory) { -+ throw new Error('Home directory not found'); -+ } -+ -+ const idleFilePath = path.join(homeDirectory, '.sagemaker-last-active-timestamp'); ++ const tmpDirectory = '/tmp/' ++ const idleFilePath = path.join(tmpDirectory, '.sagemaker-last-active-timestamp'); + const data = await readFile(idleFilePath, 'utf8'); + + res.statusCode = 200; From 63d851f91dc0dda4c3c0ca17e43a63db9531f506 Mon Sep 17 00:00:00 2001 From: Cindy Zhao Date: Tue, 3 Dec 2024 08:23:33 -0800 Subject: [PATCH 03/20] fix:validate region from url params to prevent xss attack --- .../src/extension.ts | 11 +++++++++++ patches/sagemaker-open-notebook-extension.patch | 13 ++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts b/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts index 8e9133ad..4e81577a 100644 --- a/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts +++ b/patched-vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts @@ -17,7 +17,18 @@ export function activate() { } +function isValidRegion(region: string): boolean { + // This regex allows for characters, numbers, and hyphens + const regionRegex = /^[a-zA-Z0-9-]+$/; + return regionRegex.test(region); +} + async function loadAndDisplayNotebook(fileKey: string, clusterId: string, region: string) { + if (!isValidRegion(region)) { + vscode.window.showErrorMessage('Invalid region format. Region should only contain characters, numbers, and hyphens.'); + return; + } + const bucketName = `jumpstart-cache-prod-${region}`; const url = `https://${bucketName}.s3.${region}.amazonaws.com/${fileKey}`; try { diff --git a/patches/sagemaker-open-notebook-extension.patch b/patches/sagemaker-open-notebook-extension.patch index ec48a6f5..5e44c585 100644 --- a/patches/sagemaker-open-notebook-extension.patch +++ b/patches/sagemaker-open-notebook-extension.patch @@ -162,7 +162,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension/src/extension.ts -@@ -0,0 +1,80 @@ +@@ -0,0 +1,91 @@ + +import * as vscode from 'vscode'; +import * as https from 'https'; @@ -182,7 +182,18 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-open-notebook-extension + +} + ++function isValidRegion(region: string): boolean { ++ // This regex allows for characters, numbers, and hyphens ++ const regionRegex = /^[a-zA-Z0-9-]+$/; ++ return regionRegex.test(region); ++} ++ +async function loadAndDisplayNotebook(fileKey: string, clusterId: string, region: string) { ++ if (!isValidRegion(region)) { ++ vscode.window.showErrorMessage('Invalid region format. Region should only contain characters, numbers, and hyphens.'); ++ return; ++ } ++ + const bucketName = `jumpstart-cache-prod-${region}`; + const url = `https://${bucketName}.s3.${region}.amazonaws.com/${fileKey}`; + try { From c2a781208fb6e7e78d7a908f170b37c6286ae056 Mon Sep 17 00:00:00 2001 From: Zuoyuan Huang Date: Sat, 7 Dec 2024 07:44:48 +0000 Subject: [PATCH 04/20] Idle endpoint creates metadata file if not exist **Description** * Idle endpoint creates metadata file if not exist **Motivation** * Fix the issue of space that has never been opened will not auto shutdown **Testing Done** * Build CE package **Backwards Compatibility Criteria (if any)** * N/A --- patches/sagemaker-idle-extension.patch | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/patches/sagemaker-idle-extension.patch b/patches/sagemaker-idle-extension.patch index 32c2d6e4..4dd98f63 100644 --- a/patches/sagemaker-idle-extension.patch +++ b/patches/sagemaker-idle-extension.patch @@ -289,10 +289,12 @@ 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 -@@ -4,6 +4,7 @@ +@@ -3,7 +3,8 @@ + * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import { createReadStream } from 'fs'; +-import { createReadStream } from 'fs'; ++import { createReadStream, existsSync, writeFileSync } from 'fs'; +import {readFile } from 'fs/promises'; import { Promises } from 'vs/base/node/pfs'; import * as path from 'path'; @@ -323,7 +325,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts if (pathname === this._callbackRoute) { // callback support return this._handleCallback(res); -@@ -451,6 +457,23 @@ export class WebClientServer { +@@ -451,6 +457,31 @@ export class WebClientServer { }); return void res.end(data); } @@ -335,6 +337,14 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/webClientServer.ts + 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); ++ } ++ + const data = await readFile(idleFilePath, 'utf8'); + + res.statusCode = 200; From b6ea7ce307556488d53f5123db07a3a6fb24dcce Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Wed, 12 Mar 2025 14:52:56 -0700 Subject: [PATCH 05/20] initial patch --- patches/display-language.diff | 395 ++++++++++++++++++++++++++++++++++ patches/series | 1 + 2 files changed, 396 insertions(+) create mode 100644 patches/display-language.diff diff --git a/patches/display-language.diff b/patches/display-language.diff new file mode 100644 index 00000000..30af1fde --- /dev/null +++ b/patches/display-language.diff @@ -0,0 +1,395 @@ +Index: sagemaker-code-editor/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts ++++ sagemaker-code-editor/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts +@@ -5,18 +5,24 @@ + + import { CancellationTokenSource } from 'vs/base/common/cancellation'; + import { URI } from 'vs/base/common/uri'; ++import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; + import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; + import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; +-import { ILanguagePackItem, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks'; ++import { ILanguagePackItem, ILanguagePackService, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks'; + import { ILogService } from 'vs/platform/log/common/log'; ++import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; + + export class WebLanguagePacksService extends LanguagePackBaseService { ++ private readonly languagePackService: ILanguagePackService; ++ + constructor( ++ @IRemoteAgentService remoteAgentService: IRemoteAgentService, + @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, + @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, + @ILogService private readonly logService: ILogService + ) { + super(extensionGalleryService); ++ this.languagePackService = ProxyChannel.toService(remoteAgentService.getConnection()!.getChannel('languagePacks')) + } + + async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise { +@@ -72,6 +78,6 @@ export class WebLanguagePacksService ext + + // Web doesn't have a concept of language packs, so we just return an empty array + getInstalledLanguages(): Promise { +- return Promise.resolve([]); ++ return this.languagePackService.getInstalledLanguages() + } + } +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 'vs/base/common/pa + import { IURITransformer } from 'vs/base/common/uriIpc'; + import { getMachineId, getSqmMachineId, getdevDeviceId } from 'vs/base/node/id'; + import { Promises } from 'vs/base/node/pfs'; +-import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; ++import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; + import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; + import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; + import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; +@@ -225,6 +225,9 @@ export async function setupServerService + const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); + socketServer.registerChannel('extensions', channel); + ++ const languagePackChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService), disposables); ++ socketServer.registerChannel('languagePacks', languagePackChannel); ++ + // clean up extensions folder + remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp()); + +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,8 +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 'vs/nls'; +- + export const LANGUAGE_DEFAULT = 'en'; + + let _isWindows = false; +@@ -112,17 +110,21 @@ else if (typeof navigator === 'object' & + _isMobile = _userAgent?.indexOf('Mobi') >= 0; + _isWeb = true; + +- const configuredLocale = nls.getConfiguredDefaultLocale( +- // This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale` +- // to ensure that the NLS AMD Loader plugin has been loaded and configured. +- // This is because the loader plugin decides what the default locale is based on +- // how it's able to resolve the strings. +- nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_') +- ); +- +- _locale = configuredLocale || LANGUAGE_DEFAULT; ++ _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 @@ + + + ++ ++ ++ + + + +@@ -46,14 +49,26 @@ + // 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(); +- if (!locale.startsWith('en')) { +- nlsConfig['vs/nls'] = { +- availableLanguages: { +- '*': locale +- }, +- translationServiceUrl: '{{WORKBENCH_NLS_BASE_URL}}' +- }; +- } ++ 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`, +Index: sagemaker-code-editor/vscode/src/vs/platform/environment/common/environmentService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/platform/environment/common/environmentService.ts ++++ sagemaker-code-editor/vscode/src/vs/platform/environment/common/environmentService.ts +@@ -101,7 +101,7 @@ export abstract class AbstractNativeEnvi + return URI.file(join(vscodePortable, 'argv.json')); + } + +- return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json'); ++ return joinPath(this.appSettingsHome, 'argv.json'); + } + + @memoize +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 +@@ -32,6 +32,12 @@ export function getNLSConfiguration(lang + 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); ++ } + return value; + }); + _cache.set(key, result); +@@ -46,3 +52,43 @@ export namespace InternalNLSConfiguratio + return candidate && typeof candidate._languagePackId === 'string'; + } + } ++ ++/** ++ * The code below is copied from from src/main.js. ++ */ ++ ++export const getLocaleFromConfig = async (argvResource: string): Promise => { ++ try { ++ const content = stripComments(await fs.promises.readFile(argvResource, 'utf8')); ++ return JSON.parse(content).locale; ++ } catch (error) { ++ if (error.code !== "ENOENT") { ++ console.warn(error) ++ } ++ return 'en'; ++ } ++}; ++ ++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; ++ } ++ }); ++}; +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 +@@ -28,6 +28,7 @@ import { URI } from 'vs/base/common/uri' + import { streamToBuffer } from 'vs/base/common/buffer'; + import { IProductConfiguration } from 'vs/base/common/product'; + import { isString } from 'vs/base/common/types'; ++import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks'; + import { CharCode } from 'vs/base/common/charCode'; + import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; + +@@ -351,6 +352,8 @@ export class WebClientServer { + 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), +@@ -359,6 +362,7 @@ export class WebClientServer { + WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''), + BASE: base, + VS_BASE: vscodeBase, ++ NLS_CONFIGURATION: asJSON(nlsConfiguration), + }; + + if (useTestResolver) { +Index: sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/server/node/serverEnvironmentService.ts ++++ sagemaker-code-editor/vscode/src/vs/server/node/serverEnvironmentService.ts +@@ -14,6 +14,8 @@ import { URI } from 'vs/base/common/uri' + + export const serverOptions: OptionDescriptions> = { + ++ 'locale': { type: 'string' }, ++ + /* ----- server setup ----- */ + + 'host': { type: 'string', cat: 'o', args: 'ip-address', description: nls.localize('host', "The host name or IP address the server should listen to. If not set, defaults to 'localhost'.") }, +@@ -97,6 +99,8 @@ export const serverOptions: OptionDescri + + export interface ServerParsedArgs { + ++ 'locale'?: string; ++ + /* ----- server setup ----- */ + + host?: string; +Index: sagemaker-code-editor/vscode/src/vs/workbench/workbench.web.main.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/workbench.web.main.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/workbench.web.main.ts +@@ -52,7 +52,7 @@ import 'vs/workbench/services/dialogs/br + import 'vs/workbench/services/host/browser/browserHostService'; + import 'vs/workbench/services/lifecycle/browser/lifecycleService'; + import 'vs/workbench/services/clipboard/browser/clipboardService'; +-import 'vs/workbench/services/localization/browser/localeService'; ++import 'vs/workbench/services/localization/electron-sandbox/localeService'; + import 'vs/workbench/services/path/browser/pathService'; + import 'vs/workbench/services/themes/browser/browserHostColorSchemeService'; + import 'vs/workbench/services/encryption/browser/encryptionService'; +@@ -118,8 +118,9 @@ registerSingleton(ILanguagePackService, + // Logs + import 'vs/workbench/contrib/logs/browser/logs.contribution'; + +-// Localization +-import 'vs/workbench/contrib/localization/browser/localization.contribution'; ++// Localization. This does not actually import anything specific to Electron so ++// it should be safe. ++import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution'; + + // Performance + import 'vs/workbench/contrib/performance/browser/performance.web.contribution'; +Index: sagemaker-code-editor/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts +=================================================================== +--- sagemaker-code-editor.orig/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts ++++ sagemaker-code-editor/vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts +@@ -51,7 +51,8 @@ class NativeLocaleService implements ILo + @IProductService private readonly productService: IProductService + ) { } + +- private async validateLocaleFile(): Promise { ++ // Make public just so we do not have to patch all the unused code out. ++ public async validateLocaleFile(): Promise { + try { + const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' }); + +@@ -78,9 +79,6 @@ class NativeLocaleService implements ILo + } + + private async writeLocaleValue(locale: string | undefined): Promise { +- if (!(await this.validateLocaleFile())) { +- return false; +- } + await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true); + return true; + } +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 ++++ sagemaker-code-editor/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +@@ -341,9 +341,6 @@ export class InstallAction extends Exten + if (this.extension.isBuiltin) { + return; + } +- if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) { +- return; +- } + if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) { + this.enabled = this.options.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion; + this.updateLabel(); +@@ -614,7 +611,7 @@ export abstract class InstallInOtherServ + } + + if (isLanguagePackExtension(this.extension.local.manifest)) { +- return true; ++ return false; + } + + // Prefers to run on UI +@@ -1848,17 +1845,6 @@ export class SetLanguageAction extends E + update(): void { + this.enabled = false; + this.class = SetLanguageAction.DisabledClass; +- if (!this.extension) { +- return; +- } +- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { +- return; +- } +- if (this.extension.gallery && language === getLocale(this.extension.gallery)) { +- return; +- } +- this.enabled = true; +- this.class = SetLanguageAction.EnabledClass; + } + + override async run(): Promise { +@@ -1875,7 +1861,6 @@ export class ClearLanguageAction extends + private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`; + + constructor( +- @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, + @ILocaleService private readonly localeService: ILocaleService, + ) { + super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false); +@@ -1885,17 +1870,6 @@ export class ClearLanguageAction extends + update(): void { + this.enabled = false; + this.class = ClearLanguageAction.DisabledClass; +- if (!this.extension) { +- return; +- } +- if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { +- return; +- } +- if (this.extension.gallery && language !== getLocale(this.extension.gallery)) { +- return; +- } +- this.enabled = true; +- this.class = ClearLanguageAction.EnabledClass; + } + + override async run(): Promise { diff --git a/patches/series b/patches/series index 280c2e07..240a749b 100644 --- a/patches/series +++ b/patches/series @@ -9,3 +9,4 @@ license.diff sagemaker-idle-extension.patch terminal-crash-mitigation.patch sagemaker-open-notebook-extension.patch +display-language.diff From 334ab3c5e00274c09e8c6b45d3a7e63243fbac7e Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Wed, 19 Mar 2025 12:00:31 -0700 Subject: [PATCH 06/20] Remove check for metadata file and commit --- patches/display-language.diff | 64 +++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 14 deletions(-) diff --git a/patches/display-language.diff b/patches/display-language.diff index 30af1fde..f3569816 100644 --- a/patches/display-language.diff +++ b/patches/display-language.diff @@ -169,20 +169,56 @@ 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 -@@ -32,6 +32,12 @@ export function getNLSConfiguration(lang - 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); -+ } - return value; - }); - _cache.set(key, result); -@@ -46,3 +52,43 @@ export namespace InternalNLSConfiguratio +@@ -18,26 +18,28 @@ function exists(file: string) { + } + + export function getNLSConfiguration(language: string, userDataPath: string): Promise { +- return exists(metaData).then((fileExists) => { +- if (!fileExists || !product.commit) { +- // console.log(`==> MetaData or commit unknown. Using default language.`); +- // The OS Locale on the remote side really doesn't matter, so we return the default locale +- return Promise.resolve({ locale: 'en', osLocale: 'en', availableLanguages: {} }); +- } +- 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(product.commit, userDataPath, metaData, language, language).then(value => { +- if (InternalNLSConfiguration.is(value)) { +- value._languagePackSupport = true; +- } +- return value; +- }); +- _cache.set(key, result); +- } +- return result; +- }); ++ console.log("metadata file: ", metaData); ++ ++ 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(product.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); ++ } ++ return value; ++ }); ++ _cache.set(key, result); ++ } ++ console.log("result: ", result); ++ return result; + } + + export namespace InternalNLSConfiguration { +@@ -46,3 +48,43 @@ export namespace InternalNLSConfiguratio return candidate && typeof candidate._languagePackId === 'string'; } } From 5b11c2f67c80d79080956db9200d3c1fc000c7dd Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Wed, 19 Mar 2025 14:53:01 -0700 Subject: [PATCH 07/20] remove exists --- patches/display-language.diff | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/patches/display-language.diff b/patches/display-language.diff index f3569816..3cc1180c 100644 --- a/patches/display-language.diff +++ b/patches/display-language.diff @@ -169,9 +169,14 @@ 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 -@@ -18,26 +18,28 @@ function exists(file: string) { - } +@@ -13,31 +13,29 @@ import product from 'vs/platform/product + const metaData = path.join(FileAccess.asFileUri('').fsPath, 'nls.metadata.json'); + const _cache: Map> = new Map(); +-function exists(file: string) { +- return new Promise(c => fs.exists(file, c)); +-} +- export function getNLSConfiguration(language: string, userDataPath: string): Promise { - return exists(metaData).then((fileExists) => { - if (!fileExists || !product.commit) { @@ -218,7 +223,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts } export namespace InternalNLSConfiguration { -@@ -46,3 +48,43 @@ export namespace InternalNLSConfiguratio +@@ -46,3 +44,43 @@ export namespace InternalNLSConfiguratio return candidate && typeof candidate._languagePackId === 'string'; } } From 666ac07a451a083639e2ae6540205ae4ba17db43 Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Wed, 19 Mar 2025 21:11:00 -0700 Subject: [PATCH 08/20] pass dummy commit --- patches/display-language.diff | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/display-language.diff b/patches/display-language.diff index 3cc1180c..aac81928 100644 --- a/patches/display-language.diff +++ b/patches/display-language.diff @@ -204,7 +204,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts + 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(product.commit, userDataPath, metaData, language, language).then(value => { ++ result = lp.getNLSConfiguration("dummy_commmit", userDataPath, metaData, language, language).then(value => { + if (InternalNLSConfiguration.is(value)) { + value._languagePackSupport = true; + } From f3ad502ee6288b982b5019eff463d82a6c8823b4 Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Wed, 19 Mar 2025 22:19:24 -0700 Subject: [PATCH 09/20] Remove product --- patches/display-language.diff | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/patches/display-language.diff b/patches/display-language.diff index aac81928..36fd29f8 100644 --- a/patches/display-language.diff +++ b/patches/display-language.diff @@ -169,7 +169,12 @@ 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 -@@ -13,31 +13,29 @@ import product from 'vs/platform/product +@@ -8,36 +8,33 @@ import { FileAccess } from 'vs/base/comm + import * as path from 'vs/base/common/path'; + + import * as lp from 'vs/base/node/languagePacks'; +-import product from 'vs/platform/product/common/product'; + const metaData = path.join(FileAccess.asFileUri('').fsPath, 'nls.metadata.json'); const _cache: Map> = new Map(); @@ -223,7 +228,7 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts } export namespace InternalNLSConfiguration { -@@ -46,3 +44,43 @@ export namespace InternalNLSConfiguratio +@@ -46,3 +43,43 @@ export namespace InternalNLSConfiguratio return candidate && typeof candidate._languagePackId === 'string'; } } From 5fcdb548b4f666bb130b9158c305bafdea1c29a3 Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 20 Mar 2025 16:58:00 -0700 Subject: [PATCH 10/20] remove extra logging --- patches/display-language.diff | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/patches/display-language.diff b/patches/display-language.diff index 36fd29f8..b1844731 100644 --- a/patches/display-language.diff +++ b/patches/display-language.diff @@ -169,7 +169,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 -@@ -8,36 +8,33 @@ import { FileAccess } from 'vs/base/comm +@@ -8,36 +8,30 @@ import { FileAccess } from 'vs/base/comm import * as path from 'vs/base/common/path'; import * as lp from 'vs/base/node/languagePacks'; @@ -203,8 +203,6 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts - } - return result; - }); -+ console.log("metadata file: ", metaData); -+ + const key = `${language}||${userDataPath}`; + let result = _cache.get(key); + if (!result) { @@ -223,12 +221,11 @@ Index: sagemaker-code-editor/vscode/src/vs/server/node/remoteLanguagePacks.ts + }); + _cache.set(key, result); + } -+ console.log("result: ", result); + return result; } export namespace InternalNLSConfiguration { -@@ -46,3 +43,43 @@ export namespace InternalNLSConfiguratio +@@ -46,3 +40,43 @@ export namespace InternalNLSConfiguratio return candidate && typeof candidate._languagePackId === 'string'; } } From 34c670ba3e875520d7771c4d9448fef65a86c77b Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 20 Mar 2025 17:21:30 -0700 Subject: [PATCH 11/20] Update patched-vscode --- patched-vscode/src/vs/base/common/platform.ts | 24 +++--- .../vs/code/browser/workbench/workbench.html | 31 +++++-- .../environment/common/environmentService.ts | 2 +- .../languagePacks/browser/languagePacks.ts | 10 ++- .../src/vs/server/node/remoteLanguagePacks.ts | 84 +++++++++++++------ .../server/node/serverEnvironmentService.ts | 4 + .../src/vs/server/node/serverServices.ts | 5 +- .../src/vs/server/node/webClientServer.ts | 16 +++- .../extensions/browser/extensionsActions.ts | 28 +------ .../electron-sandbox/localeService.ts | 6 +- .../src/vs/workbench/workbench.web.main.ts | 7 +- 11 files changed, 133 insertions(+), 84 deletions(-) diff --git a/patched-vscode/src/vs/base/common/platform.ts b/patched-vscode/src/vs/base/common/platform.ts index 2251c7db..aac70df5 100644 --- a/patched-vscode/src/vs/base/common/platform.ts +++ b/patched-vscode/src/vs/base/common/platform.ts @@ -2,8 +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 'vs/nls'; - export const LANGUAGE_DEFAULT = 'en'; let _isWindows = false; @@ -112,17 +110,21 @@ else if (typeof navigator === 'object' && !isElectronRenderer) { _isMobile = _userAgent?.indexOf('Mobi') >= 0; _isWeb = true; - const configuredLocale = nls.getConfiguredDefaultLocale( - // This call _must_ be done in the file that calls `nls.getConfiguredDefaultLocale` - // to ensure that the NLS AMD Loader plugin has been loaded and configured. - // This is because the loader plugin decides what the default locale is based on - // how it's able to resolve the strings. - nls.localize({ key: 'ensureLoaderPluginIsLoaded', comment: ['{Locked}'] }, '_') - ); - - _locale = configuredLocale || LANGUAGE_DEFAULT; + _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 diff --git a/patched-vscode/src/vs/code/browser/workbench/workbench.html b/patched-vscode/src/vs/code/browser/workbench/workbench.html index cfee57cc..96fe4a0b 100644 --- a/patched-vscode/src/vs/code/browser/workbench/workbench.html +++ b/patched-vscode/src/vs/code/browser/workbench/workbench.html @@ -19,6 +19,9 @@ + + + @@ -46,14 +49,26 @@ // 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(); - if (!locale.startsWith('en')) { - nlsConfig['vs/nls'] = { - availableLanguages: { - '*': locale - }, - translationServiceUrl: '{{WORKBENCH_NLS_BASE_URL}}' - }; - } + 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`, diff --git a/patched-vscode/src/vs/platform/environment/common/environmentService.ts b/patched-vscode/src/vs/platform/environment/common/environmentService.ts index cd55aa9b..a09dde3f 100644 --- a/patched-vscode/src/vs/platform/environment/common/environmentService.ts +++ b/patched-vscode/src/vs/platform/environment/common/environmentService.ts @@ -101,7 +101,7 @@ export abstract class AbstractNativeEnvironmentService implements INativeEnviron return URI.file(join(vscodePortable, 'argv.json')); } - return joinPath(this.userHome, this.productService.dataFolderName, 'argv.json'); + return joinPath(this.appSettingsHome, 'argv.json'); } @memoize diff --git a/patched-vscode/src/vs/platform/languagePacks/browser/languagePacks.ts b/patched-vscode/src/vs/platform/languagePacks/browser/languagePacks.ts index 62dedb22..c83c335b 100644 --- a/patched-vscode/src/vs/platform/languagePacks/browser/languagePacks.ts +++ b/patched-vscode/src/vs/platform/languagePacks/browser/languagePacks.ts @@ -5,18 +5,24 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { URI } from 'vs/base/common/uri'; +import { ProxyChannel } from 'vs/base/parts/ipc/common/ipc'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; -import { ILanguagePackItem, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks'; +import { ILanguagePackItem, ILanguagePackService, LanguagePackBaseService } from 'vs/platform/languagePacks/common/languagePacks'; import { ILogService } from 'vs/platform/log/common/log'; +import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export class WebLanguagePacksService extends LanguagePackBaseService { + private readonly languagePackService: ILanguagePackService; + constructor( + @IRemoteAgentService remoteAgentService: IRemoteAgentService, @IExtensionResourceLoaderService private readonly extensionResourceLoaderService: IExtensionResourceLoaderService, @IExtensionGalleryService extensionGalleryService: IExtensionGalleryService, @ILogService private readonly logService: ILogService ) { super(extensionGalleryService); + this.languagePackService = ProxyChannel.toService(remoteAgentService.getConnection()!.getChannel('languagePacks')) } async getBuiltInExtensionTranslationsUri(id: string, language: string): Promise { @@ -72,6 +78,6 @@ export class WebLanguagePacksService extends LanguagePackBaseService { // Web doesn't have a concept of language packs, so we just return an empty array getInstalledLanguages(): Promise { - return Promise.resolve([]); + return this.languagePackService.getInstalledLanguages() } } diff --git a/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts b/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts index 682b6f2b..2b39c3f8 100644 --- a/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts +++ b/patched-vscode/src/vs/server/node/remoteLanguagePacks.ts @@ -8,36 +8,30 @@ import { FileAccess } from 'vs/base/common/network'; import * as path from 'vs/base/common/path'; import * as lp from 'vs/base/node/languagePacks'; -import product from 'vs/platform/product/common/product'; const metaData = path.join(FileAccess.asFileUri('').fsPath, 'nls.metadata.json'); const _cache: Map> = new Map(); -function exists(file: string) { - return new Promise(c => fs.exists(file, c)); -} - export function getNLSConfiguration(language: string, userDataPath: string): Promise { - return exists(metaData).then((fileExists) => { - if (!fileExists || !product.commit) { - // console.log(`==> MetaData or commit unknown. Using default language.`); - // The OS Locale on the remote side really doesn't matter, so we return the default locale - return Promise.resolve({ locale: 'en', osLocale: 'en', availableLanguages: {} }); - } - 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(product.commit, userDataPath, metaData, language, language).then(value => { - if (InternalNLSConfiguration.is(value)) { - value._languagePackSupport = true; - } - return value; - }); - _cache.set(key, result); - } - return result; - }); + 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_commmit", 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); + } + return value; + }); + _cache.set(key, result); + } + return result; } export namespace InternalNLSConfiguration { @@ -46,3 +40,43 @@ export namespace InternalNLSConfiguration { return candidate && typeof candidate._languagePackId === 'string'; } } + +/** + * The code below is copied from from src/main.js. + */ + +export const getLocaleFromConfig = async (argvResource: string): Promise => { + try { + const content = stripComments(await fs.promises.readFile(argvResource, 'utf8')); + return JSON.parse(content).locale; + } catch (error) { + if (error.code !== "ENOENT") { + console.warn(error) + } + return 'en'; + } +}; + +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; + } + }); +}; diff --git a/patched-vscode/src/vs/server/node/serverEnvironmentService.ts b/patched-vscode/src/vs/server/node/serverEnvironmentService.ts index 83d4aac7..0bbddc94 100644 --- a/patched-vscode/src/vs/server/node/serverEnvironmentService.ts +++ b/patched-vscode/src/vs/server/node/serverEnvironmentService.ts @@ -14,6 +14,8 @@ import { URI } from 'vs/base/common/uri'; export const serverOptions: OptionDescriptions> = { + 'locale': { type: 'string' }, + /* ----- server setup ----- */ 'host': { type: 'string', cat: 'o', args: 'ip-address', description: nls.localize('host', "The host name or IP address the server should listen to. If not set, defaults to 'localhost'.") }, @@ -97,6 +99,8 @@ export const serverOptions: OptionDescriptions> = { export interface ServerParsedArgs { + 'locale'?: string; + /* ----- server setup ----- */ host?: string; diff --git a/patched-vscode/src/vs/server/node/serverServices.ts b/patched-vscode/src/vs/server/node/serverServices.ts index 46f2f004..e3609574 100644 --- a/patched-vscode/src/vs/server/node/serverServices.ts +++ b/patched-vscode/src/vs/server/node/serverServices.ts @@ -11,7 +11,7 @@ import * as path from 'vs/base/common/path'; import { IURITransformer } from 'vs/base/common/uriIpc'; import { getMachineId, getSqmMachineId, getdevDeviceId } from 'vs/base/node/id'; import { Promises } from 'vs/base/node/pfs'; -import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; +import { ClientConnectionEvent, IMessagePassingProtocol, IPCServer, ProxyChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; import { ProtocolConstants } from 'vs/base/parts/ipc/common/ipc.net'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { ConfigurationService } from 'vs/platform/configuration/common/configurationService'; @@ -225,6 +225,9 @@ export async function setupServerServices(connectionToken: ServerConnectionToken const channel = new ExtensionManagementChannel(extensionManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)); socketServer.registerChannel('extensions', channel); + const languagePackChannel = ProxyChannel.fromService(accessor.get(ILanguagePackService), disposables); + socketServer.registerChannel('languagePacks', languagePackChannel); + // clean up extensions folder remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp()); diff --git a/patched-vscode/src/vs/server/node/webClientServer.ts b/patched-vscode/src/vs/server/node/webClientServer.ts index 330d587d..5646dab9 100644 --- a/patched-vscode/src/vs/server/node/webClientServer.ts +++ b/patched-vscode/src/vs/server/node/webClientServer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { createReadStream } from 'fs'; +import { createReadStream, existsSync, writeFileSync } from 'fs'; import {readFile } from 'fs/promises'; import { Promises } from 'vs/base/node/pfs'; import * as path from 'path'; @@ -28,6 +28,7 @@ import { URI } from 'vs/base/common/uri'; import { streamToBuffer } from 'vs/base/common/buffer'; import { IProductConfiguration } from 'vs/base/common/product'; import { isString } from 'vs/base/common/types'; +import { getLocaleFromConfig, getNLSConfiguration } from 'vs/server/node/remoteLanguagePacks'; import { CharCode } from 'vs/base/common/charCode'; import { IExtensionManifest } from 'vs/platform/extensions/common/extensions'; @@ -351,6 +352,8 @@ export class WebClientServer { 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), @@ -359,6 +362,7 @@ export class WebClientServer { WORKBENCH_NLS_BASE_URL: vscodeBase + (nlsBaseUrl ? `${nlsBaseUrl}${!nlsBaseUrl.endsWith('/') ? '/' : ''}${this._productService.commit}/${this._productService.version}/` : ''), BASE: base, VS_BASE: vscodeBase, + NLS_CONFIGURATION: asJSON(nlsConfiguration), }; if (useTestResolver) { @@ -390,7 +394,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:;', + '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://*.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(' '); @@ -465,6 +469,14 @@ export class WebClientServer { 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); + } + const data = await readFile(idleFilePath, 'utf8'); res.statusCode = 200; diff --git a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index fc174208..e5e2bfd0 100644 --- a/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/patched-vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -341,9 +341,6 @@ export class InstallAction extends ExtensionAction { if (this.extension.isBuiltin) { return; } - if (this.extensionsWorkbenchService.canSetLanguage(this.extension)) { - return; - } if (this.extension.state === ExtensionState.Uninstalled && await this.extensionsWorkbenchService.canInstall(this.extension)) { this.enabled = this.options.installPreReleaseVersion ? this.extension.hasPreReleaseVersion : this.extension.hasReleaseVersion; this.updateLabel(); @@ -614,7 +611,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction { } if (isLanguagePackExtension(this.extension.local.manifest)) { - return true; + return false; } // Prefers to run on UI @@ -1848,17 +1845,6 @@ export class SetLanguageAction extends ExtensionAction { update(): void { this.enabled = false; this.class = SetLanguageAction.DisabledClass; - if (!this.extension) { - return; - } - if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { - return; - } - if (this.extension.gallery && language === getLocale(this.extension.gallery)) { - return; - } - this.enabled = true; - this.class = SetLanguageAction.EnabledClass; } override async run(): Promise { @@ -1875,7 +1861,6 @@ export class ClearLanguageAction extends ExtensionAction { private static readonly DisabledClass = `${ClearLanguageAction.EnabledClass} disabled`; constructor( - @IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService, @ILocaleService private readonly localeService: ILocaleService, ) { super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false); @@ -1885,17 +1870,6 @@ export class ClearLanguageAction extends ExtensionAction { update(): void { this.enabled = false; this.class = ClearLanguageAction.DisabledClass; - if (!this.extension) { - return; - } - if (!this.extensionsWorkbenchService.canSetLanguage(this.extension)) { - return; - } - if (this.extension.gallery && language !== getLocale(this.extension.gallery)) { - return; - } - this.enabled = true; - this.class = ClearLanguageAction.EnabledClass; } override async run(): Promise { diff --git a/patched-vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts b/patched-vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts index d7124758..4a7686df 100644 --- a/patched-vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts +++ b/patched-vscode/src/vs/workbench/services/localization/electron-sandbox/localeService.ts @@ -51,7 +51,8 @@ class NativeLocaleService implements ILocaleService { @IProductService private readonly productService: IProductService ) { } - private async validateLocaleFile(): Promise { + // Make public just so we do not have to patch all the unused code out. + public async validateLocaleFile(): Promise { try { const content = await this.textFileService.read(this.environmentService.argvResource, { encoding: 'utf8' }); @@ -78,9 +79,6 @@ class NativeLocaleService implements ILocaleService { } private async writeLocaleValue(locale: string | undefined): Promise { - if (!(await this.validateLocaleFile())) { - return false; - } await this.jsonEditingService.write(this.environmentService.argvResource, [{ path: ['locale'], value: locale }], true); return true; } diff --git a/patched-vscode/src/vs/workbench/workbench.web.main.ts b/patched-vscode/src/vs/workbench/workbench.web.main.ts index e4a0a7e3..bdbc65d6 100644 --- a/patched-vscode/src/vs/workbench/workbench.web.main.ts +++ b/patched-vscode/src/vs/workbench/workbench.web.main.ts @@ -52,7 +52,7 @@ import 'vs/workbench/services/dialogs/browser/fileDialogService'; import 'vs/workbench/services/host/browser/browserHostService'; import 'vs/workbench/services/lifecycle/browser/lifecycleService'; import 'vs/workbench/services/clipboard/browser/clipboardService'; -import 'vs/workbench/services/localization/browser/localeService'; +import 'vs/workbench/services/localization/electron-sandbox/localeService'; import 'vs/workbench/services/path/browser/pathService'; import 'vs/workbench/services/themes/browser/browserHostColorSchemeService'; import 'vs/workbench/services/encryption/browser/encryptionService'; @@ -118,8 +118,9 @@ registerSingleton(ILanguagePackService, WebLanguagePacksService, InstantiationTy // Logs import 'vs/workbench/contrib/logs/browser/logs.contribution'; -// Localization -import 'vs/workbench/contrib/localization/browser/localization.contribution'; +// Localization. This does not actually import anything specific to Electron so +// it should be safe. +import 'vs/workbench/contrib/localization/electron-sandbox/localization.contribution'; // Performance import 'vs/workbench/contrib/performance/browser/performance.web.contribution'; From 08be9632f6c883449f8318bd14c344360796346a Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 20 Mar 2025 17:27:59 -0700 Subject: [PATCH 12/20] Add patch header --- patches/display-language.diff | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/patches/display-language.diff b/patches/display-language.diff index b1844731..d8d279db 100644 --- a/patches/display-language.diff +++ b/patches/display-language.diff @@ -1,3 +1,22 @@ +Add display language support + +We can remove this once upstream supports all language packs. + +1. Proxies language packs to the service on the backend. +2. NLS configuration is embedded into the HTML for the browser to pick up. This + code to generate this configuration is copied from the native portion. +3. Remove configuredLocale since we have our own thing. +4. Move the argv.json file to the server instead of in-browser storage. This is + where the current locale is stored and currently the server needs to be able + to read it. +5. Add the locale flag. +6. Remove the redundant locale verification. It does the same as the existing + one but is worse because it does not handle non-existent or empty files. +7. Replace some caching and Node requires because code-server does not restart + when changing the language unlike native Code. +8. Make language extensions installable like normal rather than using the + special set/clear language actions. + Index: sagemaker-code-editor/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts =================================================================== --- sagemaker-code-editor.orig/vscode/src/vs/platform/languagePacks/browser/languagePacks.ts From 013a9e97ddbbc18ebcc98afb26abc22bbd93c0b3 Mon Sep 17 00:00:00 2001 From: Samuel <161381369+aws-pangestu@users.noreply.github.com> Date: Thu, 27 Mar 2025 17:39:25 -0700 Subject: [PATCH 13/20] Create CODEOWNERS Signed-off-by: Samuel <161381369+aws-pangestu@users.noreply.github.com> --- CODEOWNERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..f621d85c --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1 @@ +* @aws/sagemaker-code-editor From a36ff93002fb8163c34b810fa5f169990fe67e09 Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 10 Apr 2025 00:21:46 -0700 Subject: [PATCH 14/20] init patch and impl initial patch and implementation up to getting the unsynced extensions and showing them in a dropdown menu --- patches/sagemaker-extensions-sync.patch | 490 ++++++++++++++++++++++++ patches/series | 1 + 2 files changed, 491 insertions(+) create mode 100644 patches/sagemaker-extensions-sync.patch diff --git a/patches/sagemaker-extensions-sync.patch b/patches/sagemaker-extensions-sync.patch new file mode 100644 index 00000000..e4bcc4ee --- /dev/null +++ b/patches/sagemaker-extensions-sync.patch @@ -0,0 +1,490 @@ +Index: sagemaker-code-editor/vscode/build/gulpfile.extensions.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/gulpfile.extensions.js ++++ sagemaker-code-editor/vscode/build/gulpfile.extensions.js +@@ -62,6 +62,7 @@ const compilations = [ + 'extensions/simple-browser/tsconfig.json', + 'extensions/sagemaker-extension/tsconfig.json', + 'extensions/sagemaker-idle-extension/tsconfig.json', ++ 'extensions/sagemaker-extensions-sync/tsconfig.json', + 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', + 'extensions/sagemaker-open-notebook-extension/tsconfig.json', + 'extensions/tunnel-forwarding/tsconfig.json', +Index: sagemaker-code-editor/vscode/build/npm/dirs.js +=================================================================== +--- sagemaker-code-editor.orig/vscode/build/npm/dirs.js ++++ sagemaker-code-editor/vscode/build/npm/dirs.js +@@ -40,6 +40,7 @@ const dirs = [ + 'extensions/php-language-features', + 'extensions/references-view', + 'extensions/sagemaker-extension', ++ 'extensions/sagemaker-extensions-sync', + 'extensions/sagemaker-idle-extension', + 'extensions/sagemaker-terminal-crash-mitigation', + 'extensions/sagemaker-open-notebook-extension', +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/.vscodeignore +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/.vscodeignore +@@ -0,0 +1,12 @@ ++.vscode/** ++.vscode-test/** ++out/test/** ++out/** ++test/** ++src/** ++tsconfig.json ++out/test/** ++out/** ++cgmanifest.json ++yarn.lock ++preview-src/** +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/README.md +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/README.md +@@ -0,0 +1,3 @@ ++# SageMaker Code Editor Extensions Sync ++ ++Notifies users if the extensions directory is missing pre-packaged extensions from SageMaker Distribution and give them the option to sync them. +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js +@@ -0,0 +1,17 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withBrowserDefaults = require('../shared.webpack.config').browser; ++ ++module.exports = withBrowserDefaults({ ++ context: __dirname, ++ entry: { ++ extension: './src/extension.ts' ++ }, ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js +@@ -0,0 +1,20 @@ ++/*--------------------------------------------------------------------------------------------- ++ * Copyright Amazon.com Inc. or its affiliates. All rights reserved. ++ * Licensed under the MIT License. See License.txt in the project root for license information. ++ *--------------------------------------------------------------------------------------------*/ ++ ++//@ts-check ++ ++'use strict'; ++ ++const withDefaults = require('../shared.webpack.config'); ++ ++module.exports = withDefaults({ ++ context: __dirname, ++ resolve: { ++ mainFields: ['module', 'main'] ++ }, ++ entry: { ++ extension: './src/extension.ts', ++ } ++}); +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/package.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/package.json +@@ -0,0 +1,44 @@ ++{ ++ "name": "sagemaker-extensions-sync", ++ "displayName": "SageMaker Extension Sync", ++ "description": "Sync pre-packaged extensions from SageMaker Distribution", ++ "extensionKind": [ ++ "workspace" ++ ], ++ "version": "1.0.0", ++ "publisher": "sagemaker", ++ "license": "MIT", ++ "engines": { ++ "vscode": "^1.70.0" ++ }, ++ "main": "./out/extension", ++ "categories": [ ++ "Other" ++ ], ++ "activationEvents": [ ++ "*" ++ ], ++ "capabilities": { ++ "virtualWorkspaces": true, ++ "untrustedWorkspaces": { ++ "supported": true ++ } ++ }, ++ "contributes": { ++ "commands": [ ++ { ++ "command": "extensions-sync.syncExtensions", ++ "title": "Sync Extensions from SageMaker Distribution", ++ "category": "Extensions Sync" ++ } ++ ] ++ }, ++ "scripts": { ++ "compile": "gulp compile-extension:sagemaker-extensions-sync", ++ "watch": "npm run build-preview && gulp watch-extension:sagemaker-extensions-sync", ++ "vscode:prepublish": "npm run build-ext", ++ "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-idle-extension ./tsconfig.json" ++ }, ++ "dependencies": {}, ++ "repository": {} ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/constants.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/constants.ts +@@ -0,0 +1,21 @@ ++// constants ++export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; ++export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker-code-editor-server-data/extensions"; ++export const LOG_PREFIX = "[sagemaker-extensions-sync]" ++ ++export class ExtensionInfo { ++ constructor( ++ public name: string, ++ public publisher: string, ++ public version: string, ++ public path: string | null ++ ) {} ++ ++ get identifier(): string { ++ return `${this.publisher}.${this.name}@${this.version}`; ++ } ++ ++ toString(): string { ++ return `ExtensionInfo: ${this.identifier} (${this.path})`; ++ } ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/extension.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/extension.ts +@@ -0,0 +1,288 @@ ++import * as fs from "fs/promises"; ++import * as path from "path"; ++import * as process from "process"; ++import * as vscode from 'vscode'; ++import { execFile } from "child_process"; ++import { promisify } from "util"; ++ ++import { ++ ExtensionInfo, ++ IMAGE_EXTENSIONS_DIR, ++ LOG_PREFIX, ++ PERSISTENT_VOLUME_EXTENSIONS_DIR, ++} from "./constants" ++ ++async function getExtensionsFromDirectory(directoryPath: string): Promise { ++ const results: ExtensionInfo[] = []; ++ try { ++ const items = await fs.readdir(directoryPath); ++ ++ for (const item of items) { ++ const itemPath = path.join(directoryPath, item); ++ const stats = await fs.stat(itemPath); ++ ++ if (stats.isDirectory()) { ++ const packageJsonPath = path.join(itemPath, "package.json"); ++ ++ try { ++ const packageData = JSON.parse(await fs.readFile(packageJsonPath, "utf8")); ++ ++ if (packageData.name && packageData.publisher && packageData.version) { ++ results.push(new ExtensionInfo( ++ packageData.name, ++ packageData.publisher, ++ packageData.version, ++ itemPath, ++ )); ++ } ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error reading package.json in ${itemPath}:`, error); ++ } ++ } ++ } ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error reading directory ${directoryPath}:`, error); ++ } ++ return results; ++} ++ ++async function getInstalledExtensions(): Promise { ++ const command = "./scripts/code-server.sh"; ++ // todo: uncomment correct code ++ //const command = "sagemaker-code-editor"; ++ const args = ["--list-extensions", "--show-versions", ]; ++ // "--extensions-dir", PERSISTENT_VOLUME_EXTENSIONS_DIR]; ++ ++ const execFileAsync = promisify(execFile); ++ try { ++ const { stdout, stderr } = await execFileAsync(command, args); ++ if (stderr) { ++ throw new Error("stderr"); ++ } ++ return stdout.split("\n").filter(line => line.trim() !== ""); ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error getting list of installed extensions:`, error); ++ throw error; ++ } ++} ++ ++async function installExtension(extensionId: string, ++ prePackagedExtensionInfo: ExtensionInfo, ++ installedExtensionInfo?: ExtensionInfo | undefined ++): Promise { ++ console.log(`${LOG_PREFIX} copying ${prePackagedExtensionInfo.path} and removing ${installedExtensionInfo?.path}`); ++} ++ ++export async function activate(context: vscode.ExtensionContext) { ++ ++ // this extension will only activate within a sagemaker app ++ const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; ++ if (!isSageMakerApp) { ++ return; ++ } else { ++ vscode.window.showInformationMessage(`App type: ${process.env.SAGEMAKER_APP_TYPE_LOWERCASE}`); ++ } ++ ++ const prePackagedExtensions = await getExtensionsFromDirectory("/Users/pangestu/.vscode-server-oss-dev/extensions"); ++ // todo: uncomment correct code ++ // const prePackagedExtensions: ExtensionInfo[] = await getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR); ++ const prePackagedExtensionsById: Record = {}; ++ prePackagedExtensions.forEach(extension => { ++ prePackagedExtensionsById[extension.identifier] = extension; ++ }); ++ ++ console.log(`${LOG_PREFIX} Found pre-packaged extensions: `, prePackagedExtensions); ++ ++ const pvExtensions = await getExtensionsFromDirectory(PERSISTENT_VOLUME_EXTENSIONS_DIR); ++ const pvExtensionsByName: Record = {}; ++ const pvExtensionsById: Record = {}; ++ pvExtensions.forEach(extension => { ++ pvExtensionsByName[extension.name] = extension; ++ pvExtensionsById[extension.identifier] = extension; ++ }); ++ console.log(`${LOG_PREFIX} Found extensions in persistent volume: `, JSON.stringify(pvExtensionsByName, null, 2)); ++ ++ // get installed extensions. this could be different from pvExtensions b/c vscode doesn't delete the assets ++ // for an old extension when uninstalling or changing versions ++ const installedExtensions = new Set(await getInstalledExtensions()); ++ console.log(`${LOG_PREFIX} Found installed extensions: `, Array.from(installedExtensions)); ++ ++ // check each pre-packaged extension, record if it is not in installed extensions or version mismatch ++ // store unsynced extensions as {identifier pre-packaged ext: currently installed version} ++ // todo: remove the clear below ++ installedExtensions.clear(); ++ const unsyncedExtensions: Record = {} ++ prePackagedExtensions.forEach(extension => { ++ const id = extension.identifier; ++ if (!(installedExtensions.has(id))){ ++ unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version ?? null; ++ } ++ }); ++ console.log(`${LOG_PREFIX} Unsynced extensions: `, JSON.stringify(unsyncedExtensions, null, 2)); ++ ++ if (unsyncedExtensions) { ++ const selection = await vscode.window.showWarningMessage( ++ 'Warning: You have unsynchronized extensions from SageMaker Distribution, which could result in incompatibilities with Code Editor. Do you want to install them?', ++ "Synchronize Extensions", "Dismiss"); ++ ++ if (selection === "Synchronize Extensions") { ++ const quickPick = vscode.window.createQuickPick(); ++ quickPick.items = Object.keys(unsyncedExtensions).map(extensionId => ({ ++ label: extensionId, ++ description: `Currently installed version: ${unsyncedExtensions[extensionId]}` ++ })); ++ quickPick.placeholder = 'Select extensions to install'; ++ quickPick.canSelectMany = true; ++ quickPick.ignoreFocusOut = true; ++ ++ quickPick.onDidAccept(() => { ++ const selectedExtensions = quickPick.selectedItems.map(item => item.label); ++ selectedExtensions.forEach(async extensionId => { ++ await installExtension(extensionId, prePackagedExtensionsById[extensionId], pvExtensionsById[extensionId]); ++ }); ++ quickPick.hide(); ++ vscode.window.showInformationMessage(`You selected: ${selectedExtensions.join(', ')}`, { modal: true }); ++ }); ++ ++ quickPick.show(); ++ } ++ } ++} ++ ++// export function activate(context: vscode.ExtensionContext) { ++ ++// // this extension will only activate within a sagemaker app ++// const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; ++// if (!isSageMakerApp) { ++// return; ++// } else { ++// vscode.window.showInformationMessage(`App type: ${process.env.SAGEMAKER_APP_TYPE_LOWERCASE}`); ++// } ++ ++// let prePackagedExtensions: ExtensionInfo[] = []; ++// getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR).then( ++// extensions => { ++// prePackagedExtensions = extensions; ++// console.log("Found pre-packaged extensions: ", extensions); ++// } ++// ); ++ ++// // index by extension name only, e.g. jupyter ++// const pvExtensionsByName: Record = {} ++ ++// //index by full extension identifier, e.g. ms-toolsai.jupyter@2024.5.0 ++// const pvExtensionsByNameVersion: Record = {}; ++ ++// // getExtensionsFromDirectory(PERSISTENT_VOLUME_EXTENSIONS_DIR).then( ++// getExtensionsFromDirectory("/Users/pangestu/.vscode-server-oss-dev/extensions").then( ++// extensions => { ++// extensions.forEach(extension => { ++// pvExtensionsByName[extension.name] = extension; ++// pvExtensionsByNameVersion[extension.identifier] = extension; ++// }); ++// console.log("Found extensions in persistent volume: ", pvExtensionsByNameVersion); ++// } ++// ); ++ ++// let installedExtensions: Set; ++// getInstalledExtensions().then( ++// extensions => { ++// installedExtensions = new Set(extensions); ++// console.log("Found installed extensions: ", extensions); ++// } ++// ) ++ ++// // check each pre-packaged extension, record if it is not in installed extensions or version mismatch ++// // store unsynced extensions as {identifier pre-packaged ext: currently installed version} ++// installedExtensions = new Set(); ++// const unsyncedExtensions: Record = {} ++// prePackagedExtensions.forEach(extension => { ++// const id = extension.identifier; ++// if (!(id in installedExtensions)){ ++// unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version; ++// } ++// }); ++// console.log("Unsynced extensions: ", unsyncedExtensions); ++ ++// const showErrorNotification = vscode.commands.registerCommand('notifications-sample.showError', () => { ++// showExtensionsQuickPick(); ++// }); ++ ++// vscode.window.showErrorMessage('Error Notification'); ++ ++// // Notification with actions ++// const showWarningNotificationWithActions = vscode.commands.registerCommand('notifications-sample.showWarningWithActions', async () => { ++// const selection = await vscode.window.showWarningMessage( ++// 'Warning: You have unsynchronized extensions from SageMaker Distribution, which could result in incompatibilities with Code Editor. Do you want to install them?', ++// "Synchronize Extensions", "Dismiss"); ++ ++// if (selection === "Synchronize Extensions") { ++// const extensions = ['ms-toolsai.jupyter@2023.9.100 (current version: 2022.12.100)', ++// 'amazonwebservices.aws-toolkit-vscode@3.30.0 (current version: n/a)', ++// 'ms-toolsai.jupyter-renderers@1.0.19 (current version: 1.0.14)']; ++// const quickPick = vscode.window.createQuickPick(); ++// quickPick.items = extensions.map(label => ({ label })); ++// quickPick.placeholder = 'Select extensions to install'; ++// quickPick.canSelectMany = true; ++// quickPick.ignoreFocusOut = true; ++ ++// quickPick.onDidAccept(() => { ++// const selectedExtensions = quickPick.selectedItems.map(item => item.label); ++// vscode.window.showInformationMessage(`You selected: ${selectedExtensions.join(', ')}`, { modal: true }); ++// quickPick.hide(); ++// }); ++ ++// quickPick.show(); ++// } else if (selection === "Dismiss") { ++// vscode.window.showInformationMessage('You dismissed the synchronization.', { modal: true }); ++// } ++ ++// }); ++ ++// // Progress notification with option to cancel ++// const showProgressNotification = vscode.commands.registerCommand('notifications-sample.showProgress', () => { ++// vscode.window.withProgress({ ++// location: vscode.ProgressLocation.Notification, ++// title: "Progress Notification", ++// cancellable: true ++// }, (progress, token) => { ++// token.onCancellationRequested(() => { ++// console.log("User canceled the long running operation"); ++// }); ++ ++// progress.report({ increment: 0 }); ++ ++// setTimeout(() => { ++// progress.report({ increment: 10, message: "Still going..." }); ++// }, 1000); ++ ++// setTimeout(() => { ++// progress.report({ increment: 40, message: "Still going even more..." }); ++// }, 2000); ++ ++// setTimeout(() => { ++// progress.report({ increment: 50, message: "I am long running! - almost there..." }); ++// }, 3000); ++ ++// const p = new Promise(resolve => { ++// setTimeout(() => { ++// resolve(); ++// }, 5000); ++// }); ++ ++// return p; ++// }); ++// }); ++ ++// // Show all notifications to show do not disturb behavior ++// const showAllNotifications = vscode.commands.registerCommand('notifications-sample.showAll', () => { ++// vscode.commands.executeCommand('notifications-sample.showInfo'); ++// vscode.commands.executeCommand('notifications-sample.showWarning'); ++// vscode.commands.executeCommand('notifications-sample.showWarningWithActions'); ++// vscode.commands.executeCommand('notifications-sample.showError'); ++// vscode.commands.executeCommand('notifications-sample.showProgress'); ++// vscode.commands.executeCommand('notifications-sample.showInfoAsModal'); ++// }); ++ ++// context.subscriptions.push(showErrorNotification, showProgressNotification, showWarningNotificationWithActions, showAllNotifications); ++// } +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json +@@ -0,0 +1,10 @@ ++{ ++ "extends": "../tsconfig.base.json", ++ "compilerOptions": { ++ "outDir": "./out" ++ }, ++ "include": [ ++ "../sagemaker-extensions-sync/src/**/*", ++ "../../src/vscode-dts/vscode.d.ts" ++ ] ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/yarn.lock +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/yarn.lock +@@ -0,0 +1,4 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ ++ diff --git a/patches/series b/patches/series index 240a749b..b6f476ca 100644 --- a/patches/series +++ b/patches/series @@ -10,3 +10,4 @@ sagemaker-idle-extension.patch terminal-crash-mitigation.patch sagemaker-open-notebook-extension.patch display-language.diff +sagemaker-extensions-sync.patch From a3aa37ef05fd9f7b872fae3b59ed576f235ee44d Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 10 Apr 2025 19:13:16 -0700 Subject: [PATCH 15/20] Add installation logic Add installation logic. Everything should work now --- patches/sagemaker-extensions-sync.patch | 324 +++++++++++------------- patches/series | 1 + 2 files changed, 142 insertions(+), 183 deletions(-) diff --git a/patches/sagemaker-extensions-sync.patch b/patches/sagemaker-extensions-sync.patch index e4bcc4ee..213b2e1e 100644 --- a/patches/sagemaker-extensions-sync.patch +++ b/patches/sagemaker-extensions-sync.patch @@ -46,7 +46,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/README. @@ -0,0 +1,3 @@ +# SageMaker Code Editor Extensions Sync + -+Notifies users if the extensions directory is missing pre-packaged extensions from SageMaker Distribution and give them the option to sync them. ++Notifies users if the extensions directory is missing pre-packaged extensions from SageMaker Distribution and give them the option to sync them. \ No newline at end of file Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js =================================================================== @@ -102,7 +102,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/package @@ -0,0 +1,44 @@ +{ + "name": "sagemaker-extensions-sync", -+ "displayName": "SageMaker Extension Sync", ++ "displayName": "SageMaker Extensions Sync", + "description": "Sync pre-packaged extensions from SageMaker Distribution", + "extensionKind": [ + "workspace" @@ -148,10 +148,13 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/con =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/constants.ts -@@ -0,0 +1,21 @@ +@@ -0,0 +1,24 @@ +// constants -+export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; -+export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker-code-editor-server-data/extensions"; ++//todo: uncomment correct code ++// export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; ++export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/Users/pangestu/.vscode-server-oss-dev/extensions"; ++// export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker-code-editor-server-data/extensions"; ++export const IMAGE_EXTENSIONS_DIR = "/tmp/extensions"; +export const LOG_PREFIX = "[sagemaker-extensions-sync]" + +export class ExtensionInfo { @@ -174,13 +177,13 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/extension.ts -@@ -0,0 +1,288 @@ +@@ -0,0 +1,243 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import * as process from "process"; +import * as vscode from 'vscode'; +import { execFile } from "child_process"; -+import { promisify } from "util"; ++import { promisify } from "util"; + +import { + ExtensionInfo, @@ -196,12 +199,12 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + + for (const item of items) { + const itemPath = path.join(directoryPath, item); -+ const stats = await fs.stat(itemPath); ++ try { ++ const stats = await fs.stat(itemPath); + -+ if (stats.isDirectory()) { -+ const packageJsonPath = path.join(itemPath, "package.json"); ++ if (stats.isDirectory()) { ++ const packageJsonPath = path.join(itemPath, "package.json"); + -+ try { + const packageData = JSON.parse(await fs.readFile(packageJsonPath, "utf8")); + + if (packageData.name && packageData.publisher && packageData.version) { @@ -212,9 +215,10 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + itemPath, + )); + } -+ } catch (error) { -+ console.error(`${LOG_PREFIX} Error reading package.json in ${itemPath}:`, error); + } ++ } catch (error) { ++ // fs.stat will break on dangling simlinks. Just skip to the next file ++ console.error(`${LOG_PREFIX} Error reading package.json in ${itemPath}:`, error); + } + } + } catch (error) { @@ -224,12 +228,12 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext +} + +async function getInstalledExtensions(): Promise { -+ const command = "./scripts/code-server.sh"; ++ const command = "./scripts/code-server.sh"; + // todo: uncomment correct code + //const command = "sagemaker-code-editor"; + const args = ["--list-extensions", "--show-versions", ]; + // "--extensions-dir", PERSISTENT_VOLUME_EXTENSIONS_DIR]; -+ ++ + const execFileAsync = promisify(execFile); + try { + const { stdout, stderr } = await execFileAsync(command, args); @@ -243,11 +247,93 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + } +} + -+async function installExtension(extensionId: string, -+ prePackagedExtensionInfo: ExtensionInfo, -+ installedExtensionInfo?: ExtensionInfo | undefined ++async function refreshExtensionsMetadata(): Promise { ++ const metaDataFile = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, "extensions.json"); ++ try { ++ await fs.unlink(metaDataFile); ++ } catch (error) { ++ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { ++ console.error(`${LOG_PREFIX} Error removing metadata file:`, error); ++ } ++ } ++} ++ ++async function installExtension( ++ prePackagedExtensionInfo: ExtensionInfo, installedExtensionInfo?: ExtensionInfo | undefined +): Promise { -+ console.log(`${LOG_PREFIX} copying ${prePackagedExtensionInfo.path} and removing ${installedExtensionInfo?.path}`); ++ if (installedExtensionInfo) { ++ console.log(`${LOG_PREFIX} Upgrading extension from ${installedExtensionInfo.identifier} to ${prePackagedExtensionInfo.identifier}`); ++ } else { ++ console.log(`${LOG_PREFIX} Installing extension ${prePackagedExtensionInfo.identifier}`); ++ } ++ try { ++ if (!prePackagedExtensionInfo.path) { ++ throw new Error(`Extension path missing for ${prePackagedExtensionInfo.identifier}`); ++ } ++ ++ const targetPath = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, path.basename(prePackagedExtensionInfo.path)); ++ ++ // Remove existing symlink or directory if it exists ++ try { ++ console.log(`${LOG_PREFIX} Removing existing folder ${targetPath}`); ++ await fs.unlink(targetPath); ++ } catch (error) { ++ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { ++ console.error(`${LOG_PREFIX} Error removing existing extension:`, error); ++ throw error; ++ } ++ // if file already doesn't exist then keep going ++ } ++ ++ // Create new symlink ++ try { ++ console.log(`${LOG_PREFIX} Adding extension to persistent volume directory`); ++ await fs.symlink(prePackagedExtensionInfo.path, targetPath, 'dir'); ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error adding extension to persistent volume directory:`, error); ++ throw error; ++ } ++ ++ // Handle .obsolete file ++ const OBSOLETE_FILE = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, '.obsolete'); ++ let obsoleteData: Record = {}; ++ ++ try { ++ const obsoleteContent = await fs.readFile(OBSOLETE_FILE, 'utf-8'); ++ console.log(`${LOG_PREFIX} .obsolete file found`); ++ obsoleteData = JSON.parse(obsoleteContent); ++ } catch (error) { ++ if ((error as NodeJS.ErrnoException).code === 'ENOENT') { ++ console.log(`${LOG_PREFIX} .obsolete file not found. Creating a new one.`); ++ } else { ++ console.warn(`${LOG_PREFIX} Error reading .obsolete file:`, error); ++ // Backup malformed file ++ const backupPath = `${OBSOLETE_FILE}.bak`; ++ await fs.rename(OBSOLETE_FILE, backupPath); ++ console.log(`${LOG_PREFIX} Backed up malformed .obsolete file to ${backupPath}`); ++ } ++ } ++ ++ if (installedExtensionInfo?.path) { ++ const obsoleteBasename = path.basename(installedExtensionInfo.path); ++ obsoleteData[obsoleteBasename] = true; ++ } ++ const obsoleteBasenamePrepackaged = path.basename(prePackagedExtensionInfo.path); ++ obsoleteData[obsoleteBasenamePrepackaged] = false; ++ ++ try { ++ console.log(`${LOG_PREFIX} Writing to .obsolete file.`); ++ await fs.writeFile(OBSOLETE_FILE, JSON.stringify(obsoleteData, null, 2)); ++ } catch (error) { ++ console.error(`${LOG_PREFIX} Error writing .obsolete file:`, error); ++ throw error; ++ } ++ ++ console.log(`${LOG_PREFIX} Installed ${prePackagedExtensionInfo.identifier}`); ++ } catch (error) { ++ vscode.window.showErrorMessage(`Could not install extension ${prePackagedExtensionInfo.identifier}`); ++ console.error(`${LOG_PREFIX} ${error}`); ++ } +} + +export async function activate(context: vscode.ExtensionContext) { @@ -256,13 +342,14 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; + if (!isSageMakerApp) { + return; -+ } else { -+ vscode.window.showInformationMessage(`App type: ${process.env.SAGEMAKER_APP_TYPE_LOWERCASE}`); + } + -+ const prePackagedExtensions = await getExtensionsFromDirectory("/Users/pangestu/.vscode-server-oss-dev/extensions"); -+ // todo: uncomment correct code -+ // const prePackagedExtensions: ExtensionInfo[] = await getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR); ++ // get installed extensions. this could be different from pvExtensions b/c vscode doesn't delete the assets ++ // for an old extension when uninstalling or changing versions ++ const installedExtensions = new Set(await getInstalledExtensions()); ++ console.log(`${LOG_PREFIX} Found installed extensions: `, Array.from(installedExtensions)); ++ ++ const prePackagedExtensions: ExtensionInfo[] = await getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR); + const prePackagedExtensionsById: Record = {}; + prePackagedExtensions.forEach(extension => { + prePackagedExtensionsById[extension.identifier] = extension; @@ -274,20 +361,15 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + const pvExtensionsByName: Record = {}; + const pvExtensionsById: Record = {}; + pvExtensions.forEach(extension => { -+ pvExtensionsByName[extension.name] = extension; -+ pvExtensionsById[extension.identifier] = extension; ++ if (installedExtensions.has(extension.identifier)) { // only index extensions that are installed ++ pvExtensionsByName[extension.name] = extension; ++ pvExtensionsById[extension.identifier] = extension; ++ } + }); -+ console.log(`${LOG_PREFIX} Found extensions in persistent volume: `, JSON.stringify(pvExtensionsByName, null, 2)); -+ -+ // get installed extensions. this could be different from pvExtensions b/c vscode doesn't delete the assets -+ // for an old extension when uninstalling or changing versions -+ const installedExtensions = new Set(await getInstalledExtensions()); -+ console.log(`${LOG_PREFIX} Found installed extensions: `, Array.from(installedExtensions)); ++ console.log(`${LOG_PREFIX} Found extensions in persistent volume: `, JSON.stringify(pvExtensionsById, null, 2)); + + // check each pre-packaged extension, record if it is not in installed extensions or version mismatch + // store unsynced extensions as {identifier pre-packaged ext: currently installed version} -+ // todo: remove the clear below -+ installedExtensions.clear(); + const unsyncedExtensions: Record = {} + prePackagedExtensions.forEach(extension => { + const id = extension.identifier; @@ -297,172 +379,48 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + }); + console.log(`${LOG_PREFIX} Unsynced extensions: `, JSON.stringify(unsyncedExtensions, null, 2)); + -+ if (unsyncedExtensions) { ++ if (Object.keys(unsyncedExtensions).length !== 0) { + const selection = await vscode.window.showWarningMessage( -+ 'Warning: You have unsynchronized extensions from SageMaker Distribution, which could result in incompatibilities with Code Editor. Do you want to install them?', ++ 'Warning: You have unsynchronized extensions from SageMaker Distribution \ ++ which could result in incompatibilities with Code Editor. Do you want to install them?', + "Synchronize Extensions", "Dismiss"); + + if (selection === "Synchronize Extensions") { + const quickPick = vscode.window.createQuickPick(); -+ quickPick.items = Object.keys(unsyncedExtensions).map(extensionId => ({ -+ label: extensionId, -+ description: `Currently installed version: ${unsyncedExtensions[extensionId]}` ++ quickPick.items = Object.keys(unsyncedExtensions).map(extensionId => ({ ++ label: extensionId, ++ description: unsyncedExtensions[extensionId] ? `Currently installed version: ${unsyncedExtensions[extensionId]}` : undefined, + })); + quickPick.placeholder = 'Select extensions to install'; + quickPick.canSelectMany = true; + quickPick.ignoreFocusOut = true; + -+ quickPick.onDidAccept(() => { ++ quickPick.onDidAccept(async () => { + const selectedExtensions = quickPick.selectedItems.map(item => item.label); -+ selectedExtensions.forEach(async extensionId => { -+ await installExtension(extensionId, prePackagedExtensionsById[extensionId], pvExtensionsById[extensionId]); -+ }); ++ await Promise.all( ++ selectedExtensions.map(extensionId => { ++ const extensionName = prePackagedExtensionsById[extensionId].name; ++ installExtension(prePackagedExtensionsById[extensionId], pvExtensionsByName[extensionName]); ++ }) ++ ); ++ await refreshExtensionsMetadata(); ++ + quickPick.hide(); -+ vscode.window.showInformationMessage(`You selected: ${selectedExtensions.join(', ')}`, { modal: true }); ++ await vscode.window.showInformationMessage( ++ 'Extensions have been installed. Would you like to reload the window?', ++ { modal: true }, ++ 'Reload' ++ ).then(selection => { ++ if (selection === 'Reload') { ++ vscode.commands.executeCommand('workbench.action.reloadWindow'); ++ } ++ }); + }); + + quickPick.show(); -+ } ++ } + } +} -+ -+// export function activate(context: vscode.ExtensionContext) { -+ -+// // this extension will only activate within a sagemaker app -+// const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; -+// if (!isSageMakerApp) { -+// return; -+// } else { -+// vscode.window.showInformationMessage(`App type: ${process.env.SAGEMAKER_APP_TYPE_LOWERCASE}`); -+// } -+ -+// let prePackagedExtensions: ExtensionInfo[] = []; -+// getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR).then( -+// extensions => { -+// prePackagedExtensions = extensions; -+// console.log("Found pre-packaged extensions: ", extensions); -+// } -+// ); -+ -+// // index by extension name only, e.g. jupyter -+// const pvExtensionsByName: Record = {} -+ -+// //index by full extension identifier, e.g. ms-toolsai.jupyter@2024.5.0 -+// const pvExtensionsByNameVersion: Record = {}; -+ -+// // getExtensionsFromDirectory(PERSISTENT_VOLUME_EXTENSIONS_DIR).then( -+// getExtensionsFromDirectory("/Users/pangestu/.vscode-server-oss-dev/extensions").then( -+// extensions => { -+// extensions.forEach(extension => { -+// pvExtensionsByName[extension.name] = extension; -+// pvExtensionsByNameVersion[extension.identifier] = extension; -+// }); -+// console.log("Found extensions in persistent volume: ", pvExtensionsByNameVersion); -+// } -+// ); -+ -+// let installedExtensions: Set; -+// getInstalledExtensions().then( -+// extensions => { -+// installedExtensions = new Set(extensions); -+// console.log("Found installed extensions: ", extensions); -+// } -+// ) -+ -+// // check each pre-packaged extension, record if it is not in installed extensions or version mismatch -+// // store unsynced extensions as {identifier pre-packaged ext: currently installed version} -+// installedExtensions = new Set(); -+// const unsyncedExtensions: Record = {} -+// prePackagedExtensions.forEach(extension => { -+// const id = extension.identifier; -+// if (!(id in installedExtensions)){ -+// unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version; -+// } -+// }); -+// console.log("Unsynced extensions: ", unsyncedExtensions); -+ -+// const showErrorNotification = vscode.commands.registerCommand('notifications-sample.showError', () => { -+// showExtensionsQuickPick(); -+// }); -+ -+// vscode.window.showErrorMessage('Error Notification'); -+ -+// // Notification with actions -+// const showWarningNotificationWithActions = vscode.commands.registerCommand('notifications-sample.showWarningWithActions', async () => { -+// const selection = await vscode.window.showWarningMessage( -+// 'Warning: You have unsynchronized extensions from SageMaker Distribution, which could result in incompatibilities with Code Editor. Do you want to install them?', -+// "Synchronize Extensions", "Dismiss"); -+ -+// if (selection === "Synchronize Extensions") { -+// const extensions = ['ms-toolsai.jupyter@2023.9.100 (current version: 2022.12.100)', -+// 'amazonwebservices.aws-toolkit-vscode@3.30.0 (current version: n/a)', -+// 'ms-toolsai.jupyter-renderers@1.0.19 (current version: 1.0.14)']; -+// const quickPick = vscode.window.createQuickPick(); -+// quickPick.items = extensions.map(label => ({ label })); -+// quickPick.placeholder = 'Select extensions to install'; -+// quickPick.canSelectMany = true; -+// quickPick.ignoreFocusOut = true; -+ -+// quickPick.onDidAccept(() => { -+// const selectedExtensions = quickPick.selectedItems.map(item => item.label); -+// vscode.window.showInformationMessage(`You selected: ${selectedExtensions.join(', ')}`, { modal: true }); -+// quickPick.hide(); -+// }); -+ -+// quickPick.show(); -+// } else if (selection === "Dismiss") { -+// vscode.window.showInformationMessage('You dismissed the synchronization.', { modal: true }); -+// } -+ -+// }); -+ -+// // Progress notification with option to cancel -+// const showProgressNotification = vscode.commands.registerCommand('notifications-sample.showProgress', () => { -+// vscode.window.withProgress({ -+// location: vscode.ProgressLocation.Notification, -+// title: "Progress Notification", -+// cancellable: true -+// }, (progress, token) => { -+// token.onCancellationRequested(() => { -+// console.log("User canceled the long running operation"); -+// }); -+ -+// progress.report({ increment: 0 }); -+ -+// setTimeout(() => { -+// progress.report({ increment: 10, message: "Still going..." }); -+// }, 1000); -+ -+// setTimeout(() => { -+// progress.report({ increment: 40, message: "Still going even more..." }); -+// }, 2000); -+ -+// setTimeout(() => { -+// progress.report({ increment: 50, message: "I am long running! - almost there..." }); -+// }, 3000); -+ -+// const p = new Promise(resolve => { -+// setTimeout(() => { -+// resolve(); -+// }, 5000); -+// }); -+ -+// return p; -+// }); -+// }); -+ -+// // Show all notifications to show do not disturb behavior -+// const showAllNotifications = vscode.commands.registerCommand('notifications-sample.showAll', () => { -+// vscode.commands.executeCommand('notifications-sample.showInfo'); -+// vscode.commands.executeCommand('notifications-sample.showWarning'); -+// vscode.commands.executeCommand('notifications-sample.showWarningWithActions'); -+// vscode.commands.executeCommand('notifications-sample.showError'); -+// vscode.commands.executeCommand('notifications-sample.showProgress'); -+// vscode.commands.executeCommand('notifications-sample.showInfoAsModal'); -+// }); -+ -+// context.subscriptions.push(showErrorNotification, showProgressNotification, showWarningNotificationWithActions, showAllNotifications); -+// } \ No newline at end of file Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json =================================================================== diff --git a/patches/series b/patches/series index b6f476ca..dfb7a72a 100644 --- a/patches/series +++ b/patches/series @@ -1,3 +1,4 @@ +update-csp.patch sagemaker-extension.diff disable-online-services.diff disable-telemetry.diff From 9280cf6eec521beda0a0dbbfa43081edc9ccaaa8 Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 10 Apr 2025 19:42:55 -0700 Subject: [PATCH 16/20] refactor --- patches/sagemaker-extensions-sync.patch | 134 +++++++++++++++++++++++- 1 file changed, 132 insertions(+), 2 deletions(-) diff --git a/patches/sagemaker-extensions-sync.patch b/patches/sagemaker-extensions-sync.patch index 213b2e1e..efc43013 100644 --- a/patches/sagemaker-extensions-sync.patch +++ b/patches/sagemaker-extensions-sync.patch @@ -177,10 +177,140 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/extension.ts -@@ -0,0 +1,243 @@ +@@ -0,0 +1,101 @@ ++import * as process from "process"; ++import * as vscode from 'vscode'; ++ ++import { ++ ExtensionInfo, ++ IMAGE_EXTENSIONS_DIR, ++ LOG_PREFIX, ++ PERSISTENT_VOLUME_EXTENSIONS_DIR, ++} from "./constants" ++ ++import { ++ getExtensionsFromDirectory, ++ getInstalledExtensions, ++ installExtension, ++ refreshExtensionsMetadata } from "./utils" ++ ++export async function activate() { ++ ++ // this extension will only activate within a sagemaker app ++ const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; ++ if (!isSageMakerApp) { ++ return; ++ } ++ ++ // get installed extensions. this could be different from pvExtensions b/c vscode sometimes doesn't delete the assets ++ // for an old extension when uninstalling or changing versions ++ const installedExtensions = new Set(await getInstalledExtensions()); ++ console.log(`${LOG_PREFIX} Found installed extensions: `, Array.from(installedExtensions)); ++ ++ const prePackagedExtensions: ExtensionInfo[] = await getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR); ++ const prePackagedExtensionsById: Record = {}; ++ prePackagedExtensions.forEach(extension => { ++ prePackagedExtensionsById[extension.identifier] = extension; ++ }); ++ ++ console.log(`${LOG_PREFIX} Found pre-packaged extensions: `, prePackagedExtensions); ++ ++ const pvExtensions = await getExtensionsFromDirectory(PERSISTENT_VOLUME_EXTENSIONS_DIR); ++ const pvExtensionsByName: Record = {}; ++ const pvExtensionsById: Record = {}; ++ pvExtensions.forEach(extension => { ++ if (installedExtensions.has(extension.identifier)) { // only index extensions that are installed ++ pvExtensionsByName[extension.name] = extension; ++ pvExtensionsById[extension.identifier] = extension; ++ } ++ }); ++ console.log(`${LOG_PREFIX} Found extensions in persistent volume: `, JSON.stringify(pvExtensionsById, null, 2)); ++ ++ // check each pre-packaged extension, record if it is not in installed extensions or version mismatch ++ // store unsynced extensions as {identifier pre-packaged ext: currently installed version} ++ const unsyncedExtensions: Record = {} ++ prePackagedExtensions.forEach(extension => { ++ const id = extension.identifier; ++ if (!(installedExtensions.has(id))){ ++ unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version ?? null; ++ } ++ }); ++ console.log(`${LOG_PREFIX} Unsynced extensions: `, JSON.stringify(unsyncedExtensions, null, 2)); ++ ++ if (Object.keys(unsyncedExtensions).length !== 0) { ++ const selection = await vscode.window.showWarningMessage( ++ 'Warning: You have unsynchronized extensions from SageMaker Distribution \ ++ which could result in incompatibilities with Code Editor. Do you want to install them?', ++ "Synchronize Extensions", "Dismiss"); ++ ++ if (selection === "Synchronize Extensions") { ++ const quickPick = vscode.window.createQuickPick(); ++ quickPick.items = Object.keys(unsyncedExtensions).map(extensionId => ({ ++ label: extensionId, ++ description: unsyncedExtensions[extensionId] ? `Currently installed version: ${unsyncedExtensions[extensionId]}` : undefined, ++ })); ++ quickPick.placeholder = 'Select extensions to install'; ++ quickPick.canSelectMany = true; ++ quickPick.ignoreFocusOut = true; ++ ++ quickPick.onDidAccept(async () => { ++ const selectedExtensions = quickPick.selectedItems.map(item => item.label); ++ await Promise.all( ++ selectedExtensions.map(extensionId => { ++ const extensionName = prePackagedExtensionsById[extensionId].name; ++ installExtension(prePackagedExtensionsById[extensionId], pvExtensionsByName[extensionName]); ++ }) ++ ); ++ await refreshExtensionsMetadata(); ++ ++ quickPick.hide(); ++ await vscode.window.showInformationMessage( ++ 'Extensions have been installed. \nWould you like to reload the window?', ++ { modal: true }, ++ 'Reload' ++ ).then(selection => { ++ if (selection === 'Reload') { ++ vscode.commands.executeCommand('workbench.action.reloadWindow'); ++ } ++ }); ++ }); ++ ++ quickPick.show(); ++ } ++ } ++} +\ No newline at end of file +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json +@@ -0,0 +1,10 @@ ++{ ++ "extends": "../tsconfig.base.json", ++ "compilerOptions": { ++ "outDir": "./out" ++ }, ++ "include": [ ++ "../sagemaker-extensions-sync/src/**/*", ++ "../../src/vscode-dts/vscode.d.ts" ++ ] ++} +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/yarn.lock +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/yarn.lock +@@ -0,0 +1,4 @@ ++# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. ++# yarn lockfile v1 ++ ++ +Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/utils.ts +=================================================================== +--- /dev/null ++++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/utils.ts +@@ -0,0 +1,155 @@ +import * as fs from "fs/promises"; +import * as path from "path"; -+import * as process from "process"; +import * as vscode from 'vscode'; +import { execFile } from "child_process"; +import { promisify } from "util"; From ff53ae5e77a7c80eaa153242a0d48859227c11a9 Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 10 Apr 2025 19:49:19 -0700 Subject: [PATCH 17/20] Remove parallel installs remove parallel installs due to race condition with writing obsolete file --- patches/sagemaker-extensions-sync.patch | 136 +++--------------------- 1 file changed, 12 insertions(+), 124 deletions(-) diff --git a/patches/sagemaker-extensions-sync.patch b/patches/sagemaker-extensions-sync.patch index efc43013..d0f67369 100644 --- a/patches/sagemaker-extensions-sync.patch +++ b/patches/sagemaker-extensions-sync.patch @@ -177,7 +177,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/extension.ts -@@ -0,0 +1,101 @@ +@@ -0,0 +1,100 @@ +import * as process from "process"; +import * as vscode from 'vscode'; + @@ -224,7 +224,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + pvExtensionsById[extension.identifier] = extension; + } + }); -+ console.log(`${LOG_PREFIX} Found extensions in persistent volume: `, JSON.stringify(pvExtensionsById, null, 2)); ++ console.log(`${LOG_PREFIX} Found installed extensions in persistent volume: `, pvExtensionsById); + + // check each pre-packaged extension, record if it is not in installed extensions or version mismatch + // store unsynced extensions as {identifier pre-packaged ext: currently installed version} @@ -235,7 +235,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version ?? null; + } + }); -+ console.log(`${LOG_PREFIX} Unsynced extensions: `, JSON.stringify(unsyncedExtensions, null, 2)); ++ console.log(`${LOG_PREFIX} Unsynced extensions: `, unsyncedExtensions); + + if (Object.keys(unsyncedExtensions).length !== 0) { + const selection = await vscode.window.showWarningMessage( @@ -255,12 +255,11 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/ext + + quickPick.onDidAccept(async () => { + const selectedExtensions = quickPick.selectedItems.map(item => item.label); -+ await Promise.all( -+ selectedExtensions.map(extensionId => { -+ const extensionName = prePackagedExtensionsById[extensionId].name; -+ installExtension(prePackagedExtensionsById[extensionId], pvExtensionsByName[extensionName]); -+ }) -+ ); ++ ++ for (const extensionId of selectedExtensions) { ++ const extensionName = prePackagedExtensionsById[extensionId].name; ++ await installExtension(prePackagedExtensionsById[extensionId], pvExtensionsByName[extensionName]); ++ } + await refreshExtensionsMetadata(); + + quickPick.hide(); @@ -317,12 +316,11 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/uti + +import { + ExtensionInfo, -+ IMAGE_EXTENSIONS_DIR, + LOG_PREFIX, + PERSISTENT_VOLUME_EXTENSIONS_DIR, +} from "./constants" + -+async function getExtensionsFromDirectory(directoryPath: string): Promise { ++export async function getExtensionsFromDirectory(directoryPath: string): Promise { + const results: ExtensionInfo[] = []; + try { + const items = await fs.readdir(directoryPath); @@ -357,7 +355,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/uti + return results; +} + -+async function getInstalledExtensions(): Promise { ++export async function getInstalledExtensions(): Promise { + const command = "./scripts/code-server.sh"; + // todo: uncomment correct code + //const command = "sagemaker-code-editor"; @@ -377,7 +375,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/uti + } +} + -+async function refreshExtensionsMetadata(): Promise { ++export async function refreshExtensionsMetadata(): Promise { + const metaDataFile = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, "extensions.json"); + try { + await fs.unlink(metaDataFile); @@ -388,7 +386,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/uti + } +} + -+async function installExtension( ++export async function installExtension( + prePackagedExtensionInfo: ExtensionInfo, installedExtensionInfo?: ExtensionInfo | undefined +): Promise { + if (installedExtensionInfo) { @@ -465,114 +463,4 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/uti + console.error(`${LOG_PREFIX} ${error}`); + } +} -+ -+export async function activate(context: vscode.ExtensionContext) { -+ -+ // this extension will only activate within a sagemaker app -+ const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; -+ if (!isSageMakerApp) { -+ return; -+ } -+ -+ // get installed extensions. this could be different from pvExtensions b/c vscode doesn't delete the assets -+ // for an old extension when uninstalling or changing versions -+ const installedExtensions = new Set(await getInstalledExtensions()); -+ console.log(`${LOG_PREFIX} Found installed extensions: `, Array.from(installedExtensions)); -+ -+ const prePackagedExtensions: ExtensionInfo[] = await getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR); -+ const prePackagedExtensionsById: Record = {}; -+ prePackagedExtensions.forEach(extension => { -+ prePackagedExtensionsById[extension.identifier] = extension; -+ }); -+ -+ console.log(`${LOG_PREFIX} Found pre-packaged extensions: `, prePackagedExtensions); -+ -+ const pvExtensions = await getExtensionsFromDirectory(PERSISTENT_VOLUME_EXTENSIONS_DIR); -+ const pvExtensionsByName: Record = {}; -+ const pvExtensionsById: Record = {}; -+ pvExtensions.forEach(extension => { -+ if (installedExtensions.has(extension.identifier)) { // only index extensions that are installed -+ pvExtensionsByName[extension.name] = extension; -+ pvExtensionsById[extension.identifier] = extension; -+ } -+ }); -+ console.log(`${LOG_PREFIX} Found extensions in persistent volume: `, JSON.stringify(pvExtensionsById, null, 2)); -+ -+ // check each pre-packaged extension, record if it is not in installed extensions or version mismatch -+ // store unsynced extensions as {identifier pre-packaged ext: currently installed version} -+ const unsyncedExtensions: Record = {} -+ prePackagedExtensions.forEach(extension => { -+ const id = extension.identifier; -+ if (!(installedExtensions.has(id))){ -+ unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version ?? null; -+ } -+ }); -+ console.log(`${LOG_PREFIX} Unsynced extensions: `, JSON.stringify(unsyncedExtensions, null, 2)); -+ -+ if (Object.keys(unsyncedExtensions).length !== 0) { -+ const selection = await vscode.window.showWarningMessage( -+ 'Warning: You have unsynchronized extensions from SageMaker Distribution \ -+ which could result in incompatibilities with Code Editor. Do you want to install them?', -+ "Synchronize Extensions", "Dismiss"); -+ -+ if (selection === "Synchronize Extensions") { -+ const quickPick = vscode.window.createQuickPick(); -+ quickPick.items = Object.keys(unsyncedExtensions).map(extensionId => ({ -+ label: extensionId, -+ description: unsyncedExtensions[extensionId] ? `Currently installed version: ${unsyncedExtensions[extensionId]}` : undefined, -+ })); -+ quickPick.placeholder = 'Select extensions to install'; -+ quickPick.canSelectMany = true; -+ quickPick.ignoreFocusOut = true; -+ -+ quickPick.onDidAccept(async () => { -+ const selectedExtensions = quickPick.selectedItems.map(item => item.label); -+ await Promise.all( -+ selectedExtensions.map(extensionId => { -+ const extensionName = prePackagedExtensionsById[extensionId].name; -+ installExtension(prePackagedExtensionsById[extensionId], pvExtensionsByName[extensionName]); -+ }) -+ ); -+ await refreshExtensionsMetadata(); -+ -+ quickPick.hide(); -+ await vscode.window.showInformationMessage( -+ 'Extensions have been installed. Would you like to reload the window?', -+ { modal: true }, -+ 'Reload' -+ ).then(selection => { -+ if (selection === 'Reload') { -+ vscode.commands.executeCommand('workbench.action.reloadWindow'); -+ } -+ }); -+ }); -+ -+ quickPick.show(); -+ } -+ } -+} \ No newline at end of file -Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json -=================================================================== ---- /dev/null -+++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/tsconfig.json -@@ -0,0 +1,10 @@ -+{ -+ "extends": "../tsconfig.base.json", -+ "compilerOptions": { -+ "outDir": "./out" -+ }, -+ "include": [ -+ "../sagemaker-extensions-sync/src/**/*", -+ "../../src/vscode-dts/vscode.d.ts" -+ ] -+} -Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/yarn.lock -=================================================================== ---- /dev/null -+++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/yarn.lock -@@ -0,0 +1,4 @@ -+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -+# yarn lockfile v1 -+ -+ From ca7428b9a10ded2ceac207aa995e88ca3d707032 Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 10 Apr 2025 20:13:53 -0700 Subject: [PATCH 18/20] remove testing code --- patches/sagemaker-extensions-sync.patch | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/patches/sagemaker-extensions-sync.patch b/patches/sagemaker-extensions-sync.patch index d0f67369..5efe0b7d 100644 --- a/patches/sagemaker-extensions-sync.patch +++ b/patches/sagemaker-extensions-sync.patch @@ -148,14 +148,11 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/con =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/constants.ts -@@ -0,0 +1,24 @@ +@@ -0,0 +1,21 @@ +// constants -+//todo: uncomment correct code -+// export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; -+export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/Users/pangestu/.vscode-server-oss-dev/extensions"; -+// export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker-code-editor-server-data/extensions"; -+export const IMAGE_EXTENSIONS_DIR = "/tmp/extensions"; -+export const LOG_PREFIX = "[sagemaker-extensions-sync]" ++export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; ++export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker-code-editor-server-data/extensions"; ++export const LOG_PREFIX = "[sagemaker-extensions-sync]"; + +export class ExtensionInfo { + constructor( @@ -307,7 +304,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/uti =================================================================== --- /dev/null +++ sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/utils.ts -@@ -0,0 +1,155 @@ +@@ -0,0 +1,152 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import * as vscode from 'vscode'; @@ -356,11 +353,8 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/uti +} + +export async function getInstalledExtensions(): Promise { -+ const command = "./scripts/code-server.sh"; -+ // todo: uncomment correct code -+ //const command = "sagemaker-code-editor"; -+ const args = ["--list-extensions", "--show-versions", ]; -+ // "--extensions-dir", PERSISTENT_VOLUME_EXTENSIONS_DIR]; ++ const command = "sagemaker-code-editor"; ++ const args = ["--list-extensions", "--show-versions", "--extensions-dir", PERSISTENT_VOLUME_EXTENSIONS_DIR]; + + const execFileAsync = promisify(execFile); + try { From 4f36d7220eb31d8cba52149332310ae757473d1f Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Thu, 10 Apr 2025 23:26:17 -0700 Subject: [PATCH 19/20] Fix image extensions directory path --- patches/sagemaker-extensions-sync.patch | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/patches/sagemaker-extensions-sync.patch b/patches/sagemaker-extensions-sync.patch index 5efe0b7d..2d6e6315 100644 --- a/patches/sagemaker-extensions-sync.patch +++ b/patches/sagemaker-extensions-sync.patch @@ -151,7 +151,7 @@ Index: sagemaker-code-editor/vscode/extensions/sagemaker-extensions-sync/src/con @@ -0,0 +1,21 @@ +// constants +export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; -+export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker-code-editor-server-data/extensions"; ++export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker/sagemaker-code-editor-server-data/extensions"; +export const LOG_PREFIX = "[sagemaker-extensions-sync]"; + +export class ExtensionInfo { From 1b62e9223eb372086892674fffa19daa1cb66682 Mon Sep 17 00:00:00 2001 From: Samuel Pangestu Date: Fri, 11 Apr 2025 00:52:45 -0700 Subject: [PATCH 20/20] Update patched-vscode --- patched-vscode/build/gulpfile.extensions.js | 1 + patched-vscode/build/npm/dirs.js | 1 + .../sagemaker-extensions-sync/.vscodeignore | 12 ++ .../sagemaker-extensions-sync/README.md | 3 + .../extension-browser.webpack.config.js | 17 ++ .../extension.webpack.config.js | 20 +++ .../sagemaker-extensions-sync/package.json | 44 +++++ .../src/constants.ts | 21 +++ .../src/extension.ts | 100 ++++++++++++ .../sagemaker-extensions-sync/src/utils.ts | 152 ++++++++++++++++++ .../sagemaker-extensions-sync/tsconfig.json | 10 ++ .../sagemaker-extensions-sync/yarn.lock | 4 + 12 files changed, 385 insertions(+) create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/.vscodeignore create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/README.md create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/package.json create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/src/constants.ts create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/src/extension.ts create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/src/utils.ts create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/tsconfig.json create mode 100644 patched-vscode/extensions/sagemaker-extensions-sync/yarn.lock diff --git a/patched-vscode/build/gulpfile.extensions.js b/patched-vscode/build/gulpfile.extensions.js index d5d57f6d..3ec35890 100644 --- a/patched-vscode/build/gulpfile.extensions.js +++ b/patched-vscode/build/gulpfile.extensions.js @@ -62,6 +62,7 @@ const compilations = [ 'extensions/simple-browser/tsconfig.json', 'extensions/sagemaker-extension/tsconfig.json', 'extensions/sagemaker-idle-extension/tsconfig.json', + 'extensions/sagemaker-extensions-sync/tsconfig.json', 'extensions/sagemaker-terminal-crash-mitigation/tsconfig.json', 'extensions/sagemaker-open-notebook-extension/tsconfig.json', 'extensions/tunnel-forwarding/tsconfig.json', diff --git a/patched-vscode/build/npm/dirs.js b/patched-vscode/build/npm/dirs.js index ae459ee0..9f057b3a 100644 --- a/patched-vscode/build/npm/dirs.js +++ b/patched-vscode/build/npm/dirs.js @@ -40,6 +40,7 @@ const dirs = [ 'extensions/php-language-features', 'extensions/references-view', 'extensions/sagemaker-extension', + 'extensions/sagemaker-extensions-sync', 'extensions/sagemaker-idle-extension', 'extensions/sagemaker-terminal-crash-mitigation', 'extensions/sagemaker-open-notebook-extension', diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/.vscodeignore b/patched-vscode/extensions/sagemaker-extensions-sync/.vscodeignore new file mode 100644 index 00000000..56b78554 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/.vscodeignore @@ -0,0 +1,12 @@ +.vscode/** +.vscode-test/** +out/test/** +out/** +test/** +src/** +tsconfig.json +out/test/** +out/** +cgmanifest.json +yarn.lock +preview-src/** diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/README.md b/patched-vscode/extensions/sagemaker-extensions-sync/README.md new file mode 100644 index 00000000..b8dd030e --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/README.md @@ -0,0 +1,3 @@ +# SageMaker Code Editor Extensions Sync + +Notifies users if the extensions directory is missing pre-packaged extensions from SageMaker Distribution and give them the option to sync them. \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js b/patched-vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js new file mode 100644 index 00000000..68271e0e --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/extension-browser.webpack.config.js @@ -0,0 +1,17 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withBrowserDefaults = require('../shared.webpack.config').browser; + +module.exports = withBrowserDefaults({ + context: __dirname, + entry: { + extension: './src/extension.ts' + }, +}); diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js b/patched-vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js new file mode 100644 index 00000000..59852626 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/extension.webpack.config.js @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright Amazon.com Inc. or its affiliates. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +//@ts-check + +'use strict'; + +const withDefaults = require('../shared.webpack.config'); + +module.exports = withDefaults({ + context: __dirname, + resolve: { + mainFields: ['module', 'main'] + }, + entry: { + extension: './src/extension.ts', + } +}); diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/package.json b/patched-vscode/extensions/sagemaker-extensions-sync/package.json new file mode 100644 index 00000000..a0761fa0 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/package.json @@ -0,0 +1,44 @@ +{ + "name": "sagemaker-extensions-sync", + "displayName": "SageMaker Extensions Sync", + "description": "Sync pre-packaged extensions from SageMaker Distribution", + "extensionKind": [ + "workspace" + ], + "version": "1.0.0", + "publisher": "sagemaker", + "license": "MIT", + "engines": { + "vscode": "^1.70.0" + }, + "main": "./out/extension", + "categories": [ + "Other" + ], + "activationEvents": [ + "*" + ], + "capabilities": { + "virtualWorkspaces": true, + "untrustedWorkspaces": { + "supported": true + } + }, + "contributes": { + "commands": [ + { + "command": "extensions-sync.syncExtensions", + "title": "Sync Extensions from SageMaker Distribution", + "category": "Extensions Sync" + } + ] + }, + "scripts": { + "compile": "gulp compile-extension:sagemaker-extensions-sync", + "watch": "npm run build-preview && gulp watch-extension:sagemaker-extensions-sync", + "vscode:prepublish": "npm run build-ext", + "build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:sagemaker-idle-extension ./tsconfig.json" + }, + "dependencies": {}, + "repository": {} +} diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/src/constants.ts b/patched-vscode/extensions/sagemaker-extensions-sync/src/constants.ts new file mode 100644 index 00000000..1a7fdcb8 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/src/constants.ts @@ -0,0 +1,21 @@ +// constants +export const PERSISTENT_VOLUME_EXTENSIONS_DIR = "/home/sagemaker-user/sagemaker-code-editor-server-data/extensions"; +export const IMAGE_EXTENSIONS_DIR = "/opt/amazon/sagemaker/sagemaker-code-editor-server-data/extensions"; +export const LOG_PREFIX = "[sagemaker-extensions-sync]"; + +export class ExtensionInfo { + constructor( + public name: string, + public publisher: string, + public version: string, + public path: string | null + ) {} + + get identifier(): string { + return `${this.publisher}.${this.name}@${this.version}`; + } + + toString(): string { + return `ExtensionInfo: ${this.identifier} (${this.path})`; + } +} diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/src/extension.ts b/patched-vscode/extensions/sagemaker-extensions-sync/src/extension.ts new file mode 100644 index 00000000..f9f44fd5 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/src/extension.ts @@ -0,0 +1,100 @@ +import * as process from "process"; +import * as vscode from 'vscode'; + +import { + ExtensionInfo, + IMAGE_EXTENSIONS_DIR, + LOG_PREFIX, + PERSISTENT_VOLUME_EXTENSIONS_DIR, +} from "./constants" + +import { + getExtensionsFromDirectory, + getInstalledExtensions, + installExtension, + refreshExtensionsMetadata } from "./utils" + +export async function activate() { + + // this extension will only activate within a sagemaker app + const isSageMakerApp = !!process.env?.SAGEMAKER_APP_TYPE_LOWERCASE; + if (!isSageMakerApp) { + return; + } + + // get installed extensions. this could be different from pvExtensions b/c vscode sometimes doesn't delete the assets + // for an old extension when uninstalling or changing versions + const installedExtensions = new Set(await getInstalledExtensions()); + console.log(`${LOG_PREFIX} Found installed extensions: `, Array.from(installedExtensions)); + + const prePackagedExtensions: ExtensionInfo[] = await getExtensionsFromDirectory(IMAGE_EXTENSIONS_DIR); + const prePackagedExtensionsById: Record = {}; + prePackagedExtensions.forEach(extension => { + prePackagedExtensionsById[extension.identifier] = extension; + }); + + console.log(`${LOG_PREFIX} Found pre-packaged extensions: `, prePackagedExtensions); + + const pvExtensions = await getExtensionsFromDirectory(PERSISTENT_VOLUME_EXTENSIONS_DIR); + const pvExtensionsByName: Record = {}; + const pvExtensionsById: Record = {}; + pvExtensions.forEach(extension => { + if (installedExtensions.has(extension.identifier)) { // only index extensions that are installed + pvExtensionsByName[extension.name] = extension; + pvExtensionsById[extension.identifier] = extension; + } + }); + console.log(`${LOG_PREFIX} Found installed extensions in persistent volume: `, pvExtensionsById); + + // check each pre-packaged extension, record if it is not in installed extensions or version mismatch + // store unsynced extensions as {identifier pre-packaged ext: currently installed version} + const unsyncedExtensions: Record = {} + prePackagedExtensions.forEach(extension => { + const id = extension.identifier; + if (!(installedExtensions.has(id))){ + unsyncedExtensions[id] = pvExtensionsByName[extension.name]?.version ?? null; + } + }); + console.log(`${LOG_PREFIX} Unsynced extensions: `, unsyncedExtensions); + + if (Object.keys(unsyncedExtensions).length !== 0) { + const selection = await vscode.window.showWarningMessage( + 'Warning: You have unsynchronized extensions from SageMaker Distribution \ + which could result in incompatibilities with Code Editor. Do you want to install them?', + "Synchronize Extensions", "Dismiss"); + + if (selection === "Synchronize Extensions") { + const quickPick = vscode.window.createQuickPick(); + quickPick.items = Object.keys(unsyncedExtensions).map(extensionId => ({ + label: extensionId, + description: unsyncedExtensions[extensionId] ? `Currently installed version: ${unsyncedExtensions[extensionId]}` : undefined, + })); + quickPick.placeholder = 'Select extensions to install'; + quickPick.canSelectMany = true; + quickPick.ignoreFocusOut = true; + + quickPick.onDidAccept(async () => { + const selectedExtensions = quickPick.selectedItems.map(item => item.label); + + for (const extensionId of selectedExtensions) { + const extensionName = prePackagedExtensionsById[extensionId].name; + await installExtension(prePackagedExtensionsById[extensionId], pvExtensionsByName[extensionName]); + } + await refreshExtensionsMetadata(); + + quickPick.hide(); + await vscode.window.showInformationMessage( + 'Extensions have been installed. \nWould you like to reload the window?', + { modal: true }, + 'Reload' + ).then(selection => { + if (selection === 'Reload') { + vscode.commands.executeCommand('workbench.action.reloadWindow'); + } + }); + }); + + quickPick.show(); + } + } +} \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/src/utils.ts b/patched-vscode/extensions/sagemaker-extensions-sync/src/utils.ts new file mode 100644 index 00000000..e2d34fe0 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/src/utils.ts @@ -0,0 +1,152 @@ +import * as fs from "fs/promises"; +import * as path from "path"; +import * as vscode from 'vscode'; +import { execFile } from "child_process"; +import { promisify } from "util"; + +import { + ExtensionInfo, + LOG_PREFIX, + PERSISTENT_VOLUME_EXTENSIONS_DIR, +} from "./constants" + +export async function getExtensionsFromDirectory(directoryPath: string): Promise { + const results: ExtensionInfo[] = []; + try { + const items = await fs.readdir(directoryPath); + + for (const item of items) { + const itemPath = path.join(directoryPath, item); + try { + const stats = await fs.stat(itemPath); + + if (stats.isDirectory()) { + const packageJsonPath = path.join(itemPath, "package.json"); + + const packageData = JSON.parse(await fs.readFile(packageJsonPath, "utf8")); + + if (packageData.name && packageData.publisher && packageData.version) { + results.push(new ExtensionInfo( + packageData.name, + packageData.publisher, + packageData.version, + itemPath, + )); + } + } + } catch (error) { + // fs.stat will break on dangling simlinks. Just skip to the next file + console.error(`${LOG_PREFIX} Error reading package.json in ${itemPath}:`, error); + } + } + } catch (error) { + console.error(`${LOG_PREFIX} Error reading directory ${directoryPath}:`, error); + } + return results; +} + +export async function getInstalledExtensions(): Promise { + const command = "sagemaker-code-editor"; + const args = ["--list-extensions", "--show-versions", "--extensions-dir", PERSISTENT_VOLUME_EXTENSIONS_DIR]; + + const execFileAsync = promisify(execFile); + try { + const { stdout, stderr } = await execFileAsync(command, args); + if (stderr) { + throw new Error("stderr"); + } + return stdout.split("\n").filter(line => line.trim() !== ""); + } catch (error) { + console.error(`${LOG_PREFIX} Error getting list of installed extensions:`, error); + throw error; + } +} + +export async function refreshExtensionsMetadata(): Promise { + const metaDataFile = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, "extensions.json"); + try { + await fs.unlink(metaDataFile); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + console.error(`${LOG_PREFIX} Error removing metadata file:`, error); + } + } +} + +export async function installExtension( + prePackagedExtensionInfo: ExtensionInfo, installedExtensionInfo?: ExtensionInfo | undefined +): Promise { + if (installedExtensionInfo) { + console.log(`${LOG_PREFIX} Upgrading extension from ${installedExtensionInfo.identifier} to ${prePackagedExtensionInfo.identifier}`); + } else { + console.log(`${LOG_PREFIX} Installing extension ${prePackagedExtensionInfo.identifier}`); + } + try { + if (!prePackagedExtensionInfo.path) { + throw new Error(`Extension path missing for ${prePackagedExtensionInfo.identifier}`); + } + + const targetPath = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, path.basename(prePackagedExtensionInfo.path)); + + // Remove existing symlink or directory if it exists + try { + console.log(`${LOG_PREFIX} Removing existing folder ${targetPath}`); + await fs.unlink(targetPath); + } catch (error) { + if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { + console.error(`${LOG_PREFIX} Error removing existing extension:`, error); + throw error; + } + // if file already doesn't exist then keep going + } + + // Create new symlink + try { + console.log(`${LOG_PREFIX} Adding extension to persistent volume directory`); + await fs.symlink(prePackagedExtensionInfo.path, targetPath, 'dir'); + } catch (error) { + console.error(`${LOG_PREFIX} Error adding extension to persistent volume directory:`, error); + throw error; + } + + // Handle .obsolete file + const OBSOLETE_FILE = path.join(PERSISTENT_VOLUME_EXTENSIONS_DIR, '.obsolete'); + let obsoleteData: Record = {}; + + try { + const obsoleteContent = await fs.readFile(OBSOLETE_FILE, 'utf-8'); + console.log(`${LOG_PREFIX} .obsolete file found`); + obsoleteData = JSON.parse(obsoleteContent); + } catch (error) { + if ((error as NodeJS.ErrnoException).code === 'ENOENT') { + console.log(`${LOG_PREFIX} .obsolete file not found. Creating a new one.`); + } else { + console.warn(`${LOG_PREFIX} Error reading .obsolete file:`, error); + // Backup malformed file + const backupPath = `${OBSOLETE_FILE}.bak`; + await fs.rename(OBSOLETE_FILE, backupPath); + console.log(`${LOG_PREFIX} Backed up malformed .obsolete file to ${backupPath}`); + } + } + + if (installedExtensionInfo?.path) { + const obsoleteBasename = path.basename(installedExtensionInfo.path); + obsoleteData[obsoleteBasename] = true; + } + const obsoleteBasenamePrepackaged = path.basename(prePackagedExtensionInfo.path); + obsoleteData[obsoleteBasenamePrepackaged] = false; + + try { + console.log(`${LOG_PREFIX} Writing to .obsolete file.`); + await fs.writeFile(OBSOLETE_FILE, JSON.stringify(obsoleteData, null, 2)); + } catch (error) { + console.error(`${LOG_PREFIX} Error writing .obsolete file:`, error); + throw error; + } + + console.log(`${LOG_PREFIX} Installed ${prePackagedExtensionInfo.identifier}`); + } catch (error) { + vscode.window.showErrorMessage(`Could not install extension ${prePackagedExtensionInfo.identifier}`); + console.error(`${LOG_PREFIX} ${error}`); + } +} \ No newline at end of file diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/tsconfig.json b/patched-vscode/extensions/sagemaker-extensions-sync/tsconfig.json new file mode 100644 index 00000000..e474d9a5 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out" + }, + "include": [ + "../sagemaker-extensions-sync/src/**/*", + "../../src/vscode-dts/vscode.d.ts" + ] +} diff --git a/patched-vscode/extensions/sagemaker-extensions-sync/yarn.lock b/patched-vscode/extensions/sagemaker-extensions-sync/yarn.lock new file mode 100644 index 00000000..fb57ccd1 --- /dev/null +++ b/patched-vscode/extensions/sagemaker-extensions-sync/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +