Compare commits

...

12 Commits

Author SHA1 Message Date
cdrci
22ddf60dbc Update Code to 1.143.1 (#8069)
* Remove Node patch
* Update patches
Just one conflict, they use user-data-dir now which is something we
already have been doing for a long time.
2026-10-23 22:15:00 +00:00
cdrci
e777c1f963 Update Helm chart and changelog with 4.141.1 (#8071) 2026-10-23 13:49:22 -08:00
Dean Sheather
279b5702aa Merge pull request #8070 from coder/upgrade-node-checksums
Fix Node 24.22.1 checksums
2026-10-24 04:50:00 +11:00
Dean Sheather
27baccf843 Fix Node 24.22.1 checksums 2026-10-23 17:35:56 +00:00
Dean Sheather
8aab1b0d72 Merge pull request #8068 from coder/upgrade-node
Upgrade Node to 24.22.1
2026-10-24 02:35:35 +11:00
Dean Sheather
777c493a9b Upgrade Node to 24.22.1 2026-10-23 15:06:41 +00:00
Harsh Bhanushali
f07405cba3 Expand ~ in path options from the config file (#8055)
The shell expands ~ in command-line arguments, but values read from
config.yaml were passed to path.resolve as-is, so
`user-data-dir: ~/.config/code-server-data` resolved to
`<cwd>/~/.config/code-server-data`.
2026-10-14 13:47:08 -08:00
dependabot[bot]
c5fee2d2f9 Bump brace-expansion from 1.1.18 to 1.1.21 (#8044) 2026-10-13 17:02:46 -08:00
Harsh Bhanushali
83670e50c6 Fix false booleans in config file being parsed as true (#8053)
Options like `enable-remote-storage: false` in config.yaml are converted to
`--enable-remote-storage=false`, but the parser set any boolean option to
true regardless of its value. Booleans set to false are now ignored, the
same way optional strings set to false already are.
2026-10-13 17:01:10 -08:00
KeuqS
41cf6f7eaf Add Turkish locale (#8050) 2026-10-09 22:50:17 +00:00
cdrci
78aad47ce6 Update Helm chart and changelog with 4.141.0 (#8045) 2026-10-08 02:35:52 -08:00
cdrci
8b41915d25 Update Code to 1.141.0 (#8040)
* Update Code to 1.141.0
* Allow pull_request_target
* Add e2e tests for remote storage
* Fix Typescript extension not loading
* Remove unused imports
2026-10-07 22:54:47 -08:00
38 changed files with 488 additions and 88 deletions

View File

@@ -79,6 +79,9 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
submodules: true
# When an update/ branch is merged, a release is automatically created
# which requires secrets.
allow-unsafe-pr-checkout: true
- run: quilt push -a
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
with:
@@ -173,6 +176,9 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
submodules: true
# When an update/ branch is merged, a release is automatically created
# which requires secrets.
allow-unsafe-pr-checkout: true
- run: quilt push -a
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v6
with:
@@ -233,6 +239,9 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v6
with:
submodules: true
# When an update/ branch is merged, a release is automatically created
# which requires secrets.
allow-unsafe-pr-checkout: true
# quilt has no windows build. The patches are ordinary -p1 diffs
# against the repository root, so git applies them in series order.

View File

@@ -1 +1 @@
24.21.0
24.22.1

View File

@@ -22,6 +22,53 @@ Code v99.99.999
## Unreleased
Code v1.143.1
### Changed
- Update to Code 1.143.1
## [4.141.1](https://github.com/coder/code-server/releases/tag/v4.141.1) - 2026-10-23
### Security
- Update bundled Node.js to 24.22.1 to fix a remote code execution bug in the
Brotli decoder (CVE-2026-111497).
- Reject compressed request bodies (`Content-Encoding: br`, `gzip`, or
`deflate`) instead of decompressing them.
### Fixed
- Boolean flags in the `--flag=false` form and in the config file now respect
being set to false.
### Added
- Turkish locale.
### Changed
- Expand values from path options in the config file that start with a tilde.
## [4.141.0](https://github.com/coder/code-server/releases/tag/v4.141.0) - 2026-10-08
Code v1.141.0
### Added
- A new `--enable-remote-storage` flag can be used to make code-server persist
workbench storage on the remote server instead of browser storage. Use this if
you want to share storage between multiple devices.
### Fixed
- The Typescript extension was trying to load as a browser extension even though
we build the remote server version, causing it to fail to activate.
### Changed
- Update to Code 1.141.0
## [4.140.0](https://github.com/coder/code-server/releases/tag/v4.140.0) - 2026-10-02
Code v1.140.0

View File

@@ -58,10 +58,17 @@ main() {
exit 1
fi
# Add the date, our name, links, enable telemetry (this just makes telemetry
# available; telemetry can still be disabled by flag or setting), and
# configure trusted extensions (since some, like github.copilot-chat, never
# ask to be trusted and this is the only way to get auth working).
# Modify the product.json:
# - Add the date
# - Set the code-server name.
# - Add our links
# - Enable telemetry (this just makes telemetry available; telemetry can still
# be disabled by flag or setting)
# - Configure trusted extensions (since some, like github.copilot-chat, never
# ask to be trusted and this is the only way to get auth working).
# - Make Typescript server plugins run in the workspace. We build the remote
# server version, so there is no browser distribution, and this key is not
# added automatically.
#
# This needs to be done before building as Code will read this file and embed
# it into the client-side code.
@@ -103,6 +110,9 @@ main() {
],
"aiConfig": {
"ariaKey": "code-server"
},
"extensionPointExtensionKind": {
"typescriptServerPlugins": ["workspace"]
}
}
EOF

View File

@@ -15,9 +15,9 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 3.55.0
version: 3.56.1
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
appVersion: 4.140.0
appVersion: 4.141.1

View File

@@ -6,7 +6,7 @@ replicaCount: 1
image:
repository: codercom/code-server
tag: '4.140.0'
tag: '4.141.1'
pullPolicy: Always
# Specifies one or more secrets to be used when pulling images from a

12
package-lock.json generated
View File

@@ -1055,9 +1055,9 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "5.0.9",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz",
"integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==",
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz",
"integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1779,9 +1779,9 @@
"license": "BSD-2-Clause"
},
"node_modules/brace-expansion": {
"version": "1.1.18",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz",
"integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==",
"version": "1.1.21",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz",
"integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==",
"dev": true,
"license": "MIT",
"dependencies": {

View File

@@ -48,8 +48,8 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
+ <link rel="manifest" href="{{VS_BASE}}/manifest.json" crossorigin="use-credentials" />
<style id="vscode-css-modules" type="text/css" media="screen"></style>
</head>
@@ -41,7 +41,7 @@
<!-- Startup fallback: shown only if the scripts below never run, e.g. because a proxy,
@@ -92,7 +92,7 @@
<!-- Startup (do not modify order of script tags!) -->
<script nonce="{{WORKBENCH_SCRIPT_NONCE}}">
@@ -85,8 +85,8 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
+ <link rel="manifest" href="{{VS_BASE}}/manifest.json" crossorigin="use-credentials" />
<link rel="stylesheet" href="{{WORKBENCH_WEB_BASE_URL}}/out/vs/code/browser/workbench/workbench.css">
</head>
@@ -37,7 +37,7 @@
<!-- Startup fallback: shown only if the scripts below never run, e.g. because a proxy,
@@ -88,7 +88,7 @@
<!-- Startup (do not modify order of script tags!) -->
<script nonce="{{WORKBENCH_SCRIPT_NONCE}}">
@@ -281,8 +281,8 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
-
(function () {
// Find config by checking for DOM
@@ -610,8 +600,8 @@ function readCookie(name: string): strin
// This script running at all means the page scripts were not blocked or rewritten.
@@ -614,8 +604,8 @@ function readCookie(name: string): strin
if (!configElement || !configElementAttribute) {
throw new Error('Missing web configuration element');
}

View File

@@ -2,7 +2,7 @@ Index: code-server/lib/vscode/build/gulpfile.extensions.ts
===================================================================
--- code-server.orig/lib/vscode/build/gulpfile.extensions.ts
+++ code-server/lib/vscode/build/gulpfile.extensions.ts
@@ -291,6 +291,29 @@ export const compileCopilotExtensionBuil
@@ -294,6 +294,29 @@ export const compileCopilotExtensionBuil
task.task(compileCopilotExtensionBuildTask);
/**

View File

@@ -6,8 +6,8 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
<meta charset="UTF-8">
<meta http-equiv="Content-Security-Policy"
- content="default-src 'none'; script-src 'sha256-FFQoOVVa2tOE3uqUvirwaMNT20TZmrHcL2aaOjJ8BUo=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-24QqbpzqcqSyIGvg/ZwFRy5pPlhmVBa8CkUREEIUnBw=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
- content="default-src 'none'; script-src 'sha256-7cKy9iQfWRXcgRS74x6I4q5cRDkv8nQz51jecqMmQ5w=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
+ content="default-src 'none'; script-src 'sha256-FYdHnaDkSHwSK3TmWDrs/tT+X4bK+aaoa9yy432BONk=' 'self'; frame-src 'self'; style-src 'unsafe-inline';">
<!-- Disable pinch zooming -->
<meta name="viewport"

View File

@@ -7,7 +7,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts
@@ -343,6 +343,10 @@ export class Extension implements IExten
@@ -344,6 +344,10 @@ export class Extension implements IExten
if (this.type === ExtensionType.System && this.productService.quality === 'stable' && !this.productService.builtInExtensionsEnabledWithAutoUpdates?.some(id => id.toLowerCase() === this.identifier.id.toLowerCase())) {
return false;
}

View File

@@ -18,7 +18,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
import { ProtocolConstants } from '../../base/parts/ipc/common/ipc.net.js';
import { createRandomIPCHandle } from '../../base/parts/ipc/node/ipc.net.js';
import { IConfigurationService } from '../../platform/configuration/common/configuration.js';
@@ -418,6 +418,9 @@ export async function setupServerService
@@ -444,6 +444,9 @@ export async function setupServerService
socketServer.registerChannel('mcpManagement', new McpManagementChannel(mcpManagementService, (ctx: RemoteAgentConnectionContext) => getUriTransformer(ctx.remoteAuthority)));
@@ -272,7 +272,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts
@@ -475,9 +475,6 @@ export class InstallAction extends Exten
@@ -479,9 +479,6 @@ export class InstallAction extends Exten
if (this.extension.isBuiltin) {
return;
}
@@ -282,7 +282,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
if (this.extension.state !== ExtensionState.Uninstalled) {
return;
}
@@ -782,7 +779,7 @@ export abstract class InstallInOtherServ
@@ -786,7 +783,7 @@ export abstract class InstallInOtherServ
}
if (isLanguagePackExtension(this.extension.local.manifest)) {
@@ -291,7 +291,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
}
// Prefers to run on UI
@@ -2281,17 +2278,6 @@ export class SetLanguageAction extends E
@@ -2285,17 +2282,6 @@ export class SetLanguageAction extends E
update(): void {
this.enabled = false;
this.class = SetLanguageAction.DisabledClass;
@@ -309,7 +309,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
}
override async run(): Promise<any> {
@@ -2308,7 +2294,6 @@ export class ClearLanguageAction extends
@@ -2312,7 +2298,6 @@ export class ClearLanguageAction extends
private static readonly DisabledClass = `${this.EnabledClass} disabled`;
constructor(
@@ -317,7 +317,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/extensions/browser/extens
@ILocaleService private readonly localeService: ILocaleService,
) {
super(ClearLanguageAction.ID, ClearLanguageAction.TITLE.value, ClearLanguageAction.DisabledClass, false);
@@ -2318,17 +2303,6 @@ export class ClearLanguageAction extends
@@ -2322,17 +2307,6 @@ export class ClearLanguageAction extends
update(): void {
this.enabled = false;
this.class = ClearLanguageAction.DisabledClass;

View File

@@ -282,7 +282,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explo
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts
@@ -65,6 +65,7 @@ import { timeout } from '../../../../../
@@ -62,6 +62,7 @@ import { defaultCountBadgeStyles } from
import { IFilesConfigurationService } from '../../../../services/filesConfiguration/common/filesConfigurationService.js';
import { mainWindow } from '../../../../../base/browser/window.js';
import { IExplorerFileContribution, explorerFileContribRegistry } from '../explorerFileContrib.js';
@@ -290,7 +290,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explo
import { WorkbenchCompressibleAsyncDataTree } from '../../../../../platform/list/browser/listService.js';
import { ISearchService, QueryType, getExcludes, ISearchConfiguration, ISearchComplete, IFileQuery } from '../../../../services/search/common/search.js';
import { CancellationToken } from '../../../../../base/common/cancellation.js';
@@ -1587,7 +1588,8 @@ export class FileDragAndDrop implements
@@ -1515,7 +1516,8 @@ export class FileDragAndDrop implements
@IConfigurationService private configurationService: IConfigurationService,
@IInstantiationService private instantiationService: IInstantiationService,
@IWorkspaceEditingService private workspaceEditingService: IWorkspaceEditingService,
@@ -300,7 +300,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/files/browser/views/explo
) {
const updateDropEnablement = (e: IConfigurationChangeEvent | undefined) => {
if (!e || e.affectsConfiguration('explorer.enableDragAndDrop')) {
@@ -1812,15 +1814,17 @@ export class FileDragAndDrop implements
@@ -1740,15 +1742,17 @@ export class FileDragAndDrop implements
// External file DND (Import/Upload file)
if (data instanceof NativeDragAndDropData) {

View File

@@ -115,7 +115,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/bro
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
+++ code-server/lib/vscode/src/vs/workbench/contrib/welcomeGettingStarted/browser/media/gettingStarted.css
@@ -60,6 +60,15 @@
@@ -63,6 +63,15 @@
display: block;
}

View File

@@ -33,7 +33,7 @@ Index: code-server/lib/vscode/src/vs/server/node/server.main.ts
-delete process.env[agentHostBridgeConnectionTokenEnvironmentVariable];
-
-const REMOTE_DATA_FOLDER = args['server-data-dir'] || process.env['VSCODE_AGENT_FOLDER'] || join(os.homedir(), product.serverDataFolderName || '.vscode-remote');
-const USER_DATA_PATH = join(REMOTE_DATA_FOLDER, 'data');
-const USER_DATA_PATH = args['user-data-dir'] || join(REMOTE_DATA_FOLDER, 'data');
-const APP_SETTINGS_HOME = join(USER_DATA_PATH, 'User');
-const GLOBAL_STORAGE_HOME = join(APP_SETTINGS_HOME, 'globalStorage');
-const LOCAL_HISTORY_HOME = join(APP_SETTINGS_HOME, 'History');
@@ -225,7 +225,7 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench-dev.html
+ <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<style id="vscode-css-modules" type="text/css" media="screen"></style>
</head>
<!-- Startup fallback: shown only if the scripts below never run, e.g. because a proxy,
Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.html
@@ -251,7 +251,7 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.html
+ <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />
<link rel="stylesheet" href="{{WORKBENCH_WEB_BASE_URL}}/out/vs/code/browser/workbench/workbench.css">
</head>
<!-- Startup fallback: shown only if the scripts below never run, e.g. because a proxy,
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts

View File

@@ -110,7 +110,7 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
import type { IURLCallbackProvider } from '../../../workbench/services/url/browser/urlService.js';
import { create } from '../../../workbench/workbench.web.main.internal.js';
@@ -612,6 +613,44 @@ class WorkspaceProvider implements IWork
@@ -616,6 +617,44 @@ class WorkspaceProvider implements IWork
settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined,
workspaceProvider: WorkspaceProvider.create(config),
urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute),
@@ -159,7 +159,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExpl
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
+++ code-server/lib/vscode/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts
@@ -99,8 +99,8 @@ export class ForwardedPortsView extends
@@ -100,8 +100,8 @@ export class ForwardedPortsView extends
private async enableForwardedPortsFeatures() {
this.contextKeyListener.clear();

View File

@@ -3,10 +3,10 @@ Index: code-server/lib/vscode/src/vs/platform/secrets/common/secrets.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/secrets/common/secrets.ts
+++ code-server/lib/vscode/src/vs/platform/secrets/common/secrets.ts
@@ -139,8 +139,13 @@ export class BaseSecretStorageService ex
@@ -144,8 +144,13 @@ export class BaseSecretStorageService ex
return await readEncryptedSecret(
key,
(fullKey) => this.getValueFromStorage(key, fullKey, storageService),
(fullKey) => this.getValueFromStorage(key, fullKey, storageService),
- // If the storage service is in-memory, we don't need to decrypt
- this._type === 'in-memory' ? (v) => Promise.resolve(v) : (v) => this._encryptionService.decrypt(v),
+ // Don't decrypt if storage is in-memory or if encryption service is not available
@@ -19,10 +19,10 @@ Index: code-server/lib/vscode/src/vs/platform/secrets/common/secrets.ts
this._logService,
);
} catch (e) {
@@ -160,8 +165,13 @@ export class BaseSecretStorageService ex
@@ -168,8 +173,13 @@ export class BaseSecretStorageService ex
key,
value,
(fullKey, encrypted) => this.setValueInStorage(key, fullKey, encrypted, storageService),
(fullKey, encrypted) => this.setValueInStorage(key, fullKey, encrypted, storageService),
- // If the storage service is in-memory, we don't need to encrypt
- this._type === 'in-memory' ? (v) => Promise.resolve(v) : (v) => this._encryptionService.encrypt(v),
+ // Don't encrypt if storage is in-memory or if encryption service is not available
@@ -35,7 +35,7 @@ Index: code-server/lib/vscode/src/vs/platform/secrets/common/secrets.ts
this._logService,
);
} catch (e) {
@@ -194,8 +204,9 @@ export class BaseSecretStorageService ex
@@ -224,8 +234,9 @@ export class BaseSecretStorageService ex
private async initialize(): Promise<IStorageService> {
let storageService;
@@ -47,7 +47,7 @@ Index: code-server/lib/vscode/src/vs/platform/secrets/common/secrets.ts
this._type = 'persisted';
storageService = this._storageService;
} else {
@@ -203,7 +214,7 @@ export class BaseSecretStorageService ex
@@ -233,7 +244,7 @@ export class BaseSecretStorageService ex
if (this._type === 'in-memory') {
return this._storageService;
}
@@ -77,10 +77,14 @@ Index: code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/code/browser/workbench/workbench.ts
+++ code-server/lib/vscode/src/vs/code/browser/workbench/workbench.ts
@@ -625,2 +625,2 @@
@@ -655,8 +655,8 @@ class WorkspaceProvider implements IWork
})
}
},
- secretStorageProvider: config.remoteAuthority && !secretStorageKeyPath
- ? undefined /* with a remote without embedder-preferred storage, store on the remote */
+ secretStorageProvider: config.remoteStorageEnabled || (config.remoteAuthority && !secretStorageKeyPath)
+ ? undefined /* with remote storage enabled, or without embedder-prefered storage, store on the remote */
: new LocalStorageSecretStorageProvider(secretStorageCrypto),
});
})();

View File

@@ -1,8 +1,13 @@
Reuse VS Code's Electron storage service for remote persistence
Index: code-server/lib/vscode/src/vs/platform/storage/electron-main/storageMainService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/platform/storage/electron-main/storageMainService.ts
+++ code-server/lib/vscode/src/vs/platform/storage/electron-main/storageMainService.ts
@@ -14 +14,6 @@
@@ -11,7 +11,12 @@ import { IStorage } from '../../../base/
import { INativeEnvironmentService } from '../../environment/common/environment.js';
import { IFileService } from '../../files/common/files.js';
import { createDecorator } from '../../instantiation/common/instantiation.js';
-import { ILifecycleMainService, LifecycleMainPhase, ShutdownReason } from '../../lifecycle/electron-main/lifecycleMainService.js';
+import type { ILifecycleMainService, ShutdownReason } from '../../lifecycle/electron-main/lifecycleMainService.js';
+
@@ -10,44 +15,110 @@ Reuse VS Code's Electron storage service for remote persistence
+// that module imports Electron, which is not present in the code-server server image.
+const LIFECYCLE_MAIN_PHASE_AFTER_WINDOW_OPEN = 3;
+const SHUTDOWN_REASON_KILL = 2;
@@ -101 +101 @@
import { ILogService } from '../../log/common/log.js';
import { AbstractStorageService, isProfileUsingDefaultStorage, IStorageService, StorageScope, StorageTarget } from '../common/storage.js';
import { ApplicationStorageMain, ApplicationSharedStorageMain, ProfileStorageMain, InMemoryStorageMain, IStorageMain, IStorageMainOptions, WorkspaceStorageMain, IStorageChangeEvent } from './storageMain.js';
@@ -92,7 +97,7 @@ export class StorageMainService extends
@ILogService private readonly logService: ILogService,
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
@IUserDataProfilesMainService private readonly userDataProfilesService: IUserDataProfilesMainService,
- @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
+ private readonly lifecycleMainService: ILifecycleMainService,
@@ -117 +117 @@
@IFileService private readonly fileService: IFileService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
) {
@@ -114,7 +119,7 @@ export class StorageMainService extends
// Application Storage: Warmup when any window opens
(async () => {
- await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen);
+ await this.lifecycleMainService.when(LIFECYCLE_MAIN_PHASE_AFTER_WINDOW_OPEN);
@@ -256 +256 @@
this.applicationStorage.init();
this.applicationSharedStorage.init();
@@ -253,7 +258,7 @@ export class StorageMainService extends
}
private createProfileStorage(profile: IUserDataProfile): IStorageMain {
- if (this.shutdownReason === ShutdownReason.KILL) {
+ if (this.shutdownReason === SHUTDOWN_REASON_KILL) {
@@ -295 +295 @@
// Workaround for native crashes that we see when
// SQLite DBs are being created even after shutdown
@@ -292,7 +297,7 @@ export class StorageMainService extends
}
private createWorkspaceStorage(workspace: IAnyWorkspaceIdentifier): IStorageMain {
- if (this.shutdownReason === ShutdownReason.KILL) {
+ if (this.shutdownReason === SHUTDOWN_REASON_KILL) {
// Workaround for native crashes that we see when
// SQLite DBs are being created even after shutdown
Index: code-server/lib/vscode/src/vs/workbench/services/storage/electron-browser/storageService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/services/storage/electron-browser/storageService.ts
+++ code-server/lib/vscode/src/vs/workbench/services/storage/electron-browser/storageService.ts
@@ -6 +6 @@
@@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-import { IMainProcessService } from '../../../../platform/ipc/common/mainProcessService.js';
+import { IRemoteService } from '../../../../platform/ipc/common/services.js';
@@ -22 +22 @@
import { IStorage, Storage, MigratingStorage } from '../../../../base/parts/storage/common/storage.js';
import { RemoteStorageService } from '../../../../platform/storage/common/storageService.js';
import { FallbackApplicationStorageDatabaseClient, ApplicationSharedStorageDatabaseClient } from '../../../../platform/storage/common/storageIpc.js';
@@ -19,10 +19,10 @@ export class NativeWorkbenchStorageServi
workspace: IAnyWorkspaceIdentifier | undefined,
private readonly userDataProfileService: IUserDataProfileService,
userDataProfilesService: IUserDataProfilesService,
- mainProcessService: IMainProcessService,
+ remoteService: Pick<IRemoteService, 'getChannel'>,
@@ -25 +25 @@
private readonly workbenchEnvironmentService: IWorkbenchEnvironmentService,
) {
- super(workspace, { currentProfile: userDataProfileService.currentProfile, defaultProfile: userDataProfilesService.defaultProfile }, mainProcessService, workbenchEnvironmentService);
+ super(workspace, { currentProfile: userDataProfileService.currentProfile, defaultProfile: userDataProfilesService.defaultProfile }, remoteService as IRemoteService, workbenchEnvironmentService);
this.registerListeners();
}
Index: code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
+++ code-server/lib/vscode/src/vs/server/node/serverEnvironmentService.ts
@@ -68,0 +69 @@
@@ -66,6 +66,7 @@ export const serverOptions: OptionDescri
'disable-experiments': OPTIONS['disable-experiments'],
'disable-workspace-trust': OPTIONS['disable-workspace-trust'],
'file-watcher-polling': { type: 'string', deprecates: ['fileWatcherPolling'] },
+ 'enable-remote-storage': { type: 'boolean', cat: 'o', description: nls.localize('enable-remote-storage', 'Persist browser storage (settings, state, etc.) on the server instead of browser IndexedDB. Enables state portability across browsers/devices.') },
@@ -214,0 +216 @@
'log': OPTIONS['log'],
'logsPath': OPTIONS['logsPath'],
'force-disable-user-env': OPTIONS['force-disable-user-env'],
@@ -212,6 +213,7 @@ export interface ServerParsedArgs {
'logsPath'?: string;
'force-disable-user-env'?: boolean;
+ 'enable-remote-storage'?: boolean;
'enable-proposed-api'?: string[];
/* ----- vs code web options ----- */
Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/serverServices.ts
+++ code-server/lib/vscode/src/vs/server/node/serverServices.ts
@@ -107,0 +108,4 @@
@@ -106,6 +106,10 @@ import { McpManagementChannel } from '..
import { AllowedMcpServersService } from '../../platform/mcp/common/allowedMcpServersService.js';
import { IMcpGalleryManifestService } from '../../platform/mcp/common/mcpGalleryManifest.js';
import { McpGalleryManifestIPCService } from '../../platform/mcp/common/mcpGalleryManifestServiceIpc.js';
+import { StorageDatabaseChannel } from '../../platform/storage/electron-main/storageIpc.js';
+import { StorageMainService } from '../../platform/storage/electron-main/storageMainService.js';
+import type { ILifecycleMainService } from '../../platform/lifecycle/electron-main/lifecycleMainService.js';
+import type { IUserDataProfilesMainService } from '../../platform/userDataProfile/electron-main/userDataProfile.js';
@@ -240,0 +245,15 @@
import { SANDBOX_HELPER_CHANNEL_NAME, SandboxHelperChannel } from '../../platform/sandbox/common/sandboxHelperIpc.js';
import { SandboxHelperService } from '../../platform/sandbox/node/sandboxHelper.js';
@@ -244,6 +248,21 @@ export async function setupServerService
services.set(IMcpGatewayService, new SyncDescriptor(McpGatewayService));
const instantiationService: IInstantiationService = new InstantiationService(services);
+ // Reuse the Electron storage service on the remote. The server has no
+ // windows, so its lifecycle is always ready and never loads a window.
+ const storageMainService = disposables.add(new StorageMainService(
@@ -63,7 +134,10 @@ Reuse VS Code's Electron storage service for remote persistence
+ uriIdentityService,
+ ));
+
@@ -419,6 +439,7 @@ export async function setupServerServices(connectionToken: ServerConnectionToken
services.set(ILanguagePackService, instantiationService.createInstance(NativeLanguagePackService));
const ptyHostStarter = instantiationService.createInstance(
@@ -446,6 +465,7 @@ export async function setupServerService
const languagePackChannel = ProxyChannel.fromService<RemoteAgentConnectionContext>(accessor.get(ILanguagePackService), disposables);
socketServer.registerChannel('languagePacks', languagePackChannel);
@@ -71,28 +145,61 @@ Reuse VS Code's Electron storage service for remote persistence
// clean up extensions folder
remoteExtensionsScanner.whenExtensionsReady().then(() => extensionManagementService.cleanUp());
Index: code-server/lib/vscode/src/vs/server/node/webClientServer.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/server/node/webClientServer.ts
+++ code-server/lib/vscode/src/vs/server/node/webClientServer.ts
@@ -411 +411,2 @@
@@ -451,7 +451,8 @@ export class WebClientServer {
folderUri: resolveWorkspaceURI(this._environmentService.args['default-folder']),
workspaceUri: resolveWorkspaceURI(this._environmentService.args['default-workspace']),
productConfiguration,
- callbackRoute: callbackRoute
+ callbackRoute: callbackRoute,
+ remoteStorageEnabled: this._environmentService.args['enable-remote-storage'] ? true : undefined
};
const cookies = cookie.parse(req.headers.cookie || '');
Index: code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.api.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.api.ts
@@ -413,0 +414,5 @@
@@ -411,6 +411,11 @@ export interface IWorkbenchConstructionO
readonly authenticationProviders?: readonly IAuthenticationProvider[];
//#endregion
+ /**
+ * Persist workbench storage on the remote server instead of browser IndexedDB.
+ */
+ readonly remoteStorageEnabled?: boolean;
+
//#region Development options
Index: code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/workbench/browser/web.main.ts
@@ -35,0 +36 @@
@@ -33,6 +33,7 @@ import { ISignService } from '../../plat
import { SignService } from '../../platform/sign/browser/signService.js';
import { IWorkbenchConstructionOptions, IWorkbench, IWorkspace, ITunnel } from './web.api.js';
import { BrowserStorageService } from '../services/storage/browser/storageService.js';
+import { NativeWorkbenchStorageService } from '../services/storage/electron-browser/storageService.js';
@@ -479 +480 @@
import { IStorageService } from '../../platform/storage/common/storage.js';
import { toLocalISOString } from '../../base/common/date.js';
import { isWorkspaceToOpen, isFolderToOpen } from '../../platform/window/common/window.js';
@@ -482,7 +483,7 @@ export class BrowserMain extends Disposa
serviceCollection.set(IWorkbenchConfigurationService, service);
return service;
}),
- this.createStorageService(workspace, logService, userDataProfileService).then(service => {
+ this.createStorageService(workspace, environmentService, logService, userDataProfileService, userDataProfilesService, remoteAgentService).then(service => {
@@ -601 +602,15 @@
serviceCollection.set(IStorageService, service);
return service;
})
@@ -604,7 +605,21 @@ export class BrowserMain extends Disposa
}));
}
- protected async createStorageService(workspace: IAnyWorkspaceIdentifier, logService: ILogService, userDataProfileService: IUserDataProfileService): Promise<IStorageService> {
+ protected async createStorageService(workspace: IAnyWorkspaceIdentifier, environmentService: IBrowserWorkbenchEnvironmentService, logService: ILogService, userDataProfileService: IUserDataProfileService, userDataProfilesService: IUserDataProfilesService, remoteAgentService: IRemoteAgentService): Promise<IStorageService> {
+ const connection = remoteAgentService.getConnection();
@@ -109,14 +216,31 @@ Reuse VS Code's Electron storage service for remote persistence
+ }
+ }
+
const storageService = new BrowserStorageService(workspace, userDataProfileService, logService);
try {
Index: code-server/lib/vscode/src/vs/sessions/browser/web.main.ts
===================================================================
--- code-server.orig/lib/vscode/src/vs/sessions/browser/web.main.ts
+++ code-server/lib/vscode/src/vs/sessions/browser/web.main.ts
@@ -42 +42 @@
@@ -39,9 +39,9 @@ export class SessionsBrowserMain extends
_workspace: IAnyWorkspaceIdentifier,
environmentService: IBrowserWorkbenchEnvironmentService,
userDataProfileService: IUserDataProfileService,
- _userDataProfilesService: BrowserUserDataProfilesService,
+ userDataProfilesService: BrowserUserDataProfilesService,
@@ -44 +44 @@
fileService: FileService,
- _remoteAgentService: IRemoteAgentService,
+ remoteAgentService: IRemoteAgentService,
@@ -85 +85 @@
uriIdentityService: IUriIdentityService,
policyService: IPolicyService,
logService: ILogService,
@@ -82,7 +82,7 @@ export class SessionsBrowserMain extends
serviceCollection.set(IWorkbenchConfigurationService, configurationService);
// Storage
- const storageService = await this.createStorageService(workspaceIdentifier, logService, userDataProfileService);
+ const storageService = await this.createStorageService(workspaceIdentifier, environmentService, logService, userDataProfileService, userDataProfilesService, remoteAgentService);
serviceCollection.set(IStorageService, storageService);
// Workspace Trust Service

View File

@@ -24,6 +24,6 @@ trusted-domains.diff
signature-verification.diff
copilot.diff
app-name.diff
csp-hashes.diff
remote-storage.diff
remote-secret-storage.diff
csp-hashes.diff

View File

@@ -20,7 +20,7 @@ Index: code-server/lib/vscode/src/vs/server/node/serverServices.ts
import { NullPolicyService } from '../../platform/policy/common/policy.js';
import { OneDataSystemAppender } from '../../platform/telemetry/node/1dsAppender.js';
import { LoggerService } from '../../platform/log/node/loggerService.js';
@@ -179,11 +180,23 @@ export async function setupServerService
@@ -184,11 +185,23 @@ export async function setupServerService
const requestService = new RequestService('remote', configurationService, environmentService, logService);
services.set(IRequestService, requestService);

View File

@@ -21,7 +21,7 @@ Index: code-server/lib/vscode/src/vs/workbench/services/storage/browser/storageS
export class BrowserStorageService extends AbstractStorageService {
@@ -354,7 +355,11 @@ export class IndexedDBStorageDatabase ex
@@ -365,7 +366,11 @@ export class IndexedDBStorageDatabase ex
}
static async createWorkspaceStorage(workspaceId: string, logService: ILogService): Promise<IIndexedDBStorageDatabase> {

View File

@@ -53,7 +53,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
===================================================================
--- code-server.orig/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index.html
+++ code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index.html
@@ -253,7 +253,7 @@
@@ -266,7 +266,7 @@
}
const swPath = encodeURI(`service-worker.js?v=${expectedWorkerVersion}&vscode-resource-base-authority=${searchParams.get('vscode-resource-base-authority')}&remoteAuthority=${searchParams.get('remoteAuthority') ?? ''}&platform=${searchParams.get('platform')}`);
@@ -62,7 +62,7 @@ Index: code-server/lib/vscode/src/vs/workbench/contrib/webview/browser/pre/index
.then(async registration => {
if (navigator.serviceWorker.controller) {
// A previous SW is already controlling. Force an update
@@ -332,6 +332,12 @@
@@ -345,6 +345,12 @@
const hostname = location.hostname;

View File

@@ -3,7 +3,7 @@ import * as crypto from "crypto"
import { promises as fs } from "fs"
import { load } from "js-yaml"
import * as path from "path"
import { generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util"
import { expandTilde, generateCertificate, generatePassword, paths, splitOnFirstEquals } from "./util"
import { EditorSessionManagerClient } from "./vscodeSocket"
export enum Feature {
@@ -436,7 +436,11 @@ export const parse = (
const option = options[key]
if (option.type === "boolean") {
;(args[key] as boolean) = true
// Booleans set to false (for example `--flag=false`, which is also how
// false values from the config file are passed in) are ignored.
if (value !== "false") {
;(args[key] as boolean) = true
}
continue
}
@@ -462,7 +466,7 @@ export const parse = (
}
if (option.path) {
value = path.resolve(value)
value = path.resolve(expandTilde(value))
}
switch (option.type) {

View File

@@ -3,6 +3,7 @@ import i18next, { init } from "i18next"
import * as en from "./locales/en.json"
import * as ja from "./locales/ja.json"
import * as th from "./locales/th.json"
import * as tr from "./locales/tr.json"
import * as ur from "./locales/ur.json"
import * as zhCn from "./locales/zh-cn.json"
@@ -16,6 +17,9 @@ const defaultResources = {
th: {
translation: th,
},
tr: {
translation: tr,
},
ja: {
translation: ja,
},

View File

@@ -0,0 +1,13 @@
{
"LOGIN_TITLE": "{{app}} girişi",
"LOGIN_BELOW": "Lütfen aşağıdan giriş yapın.",
"WELCOME": "{{app}} uygulamasına hoş geldiniz",
"LOGIN_PASSWORD": "Parola için yapılandırma dosyasını kontrol edin.",
"LOGIN_USING_ENV_PASSWORD": "Parola $PASSWORD ortam değişkeninden ayarlandı.",
"LOGIN_USING_HASHED_PASSWORD": "Parola $HASHED_PASSWORD ortam değişkeninden ayarlandı.",
"SUBMIT": "GÖNDER",
"PASSWORD_PLACEHOLDER": "PAROLA",
"LOGIN_RATE_LIMIT": "Giriş denemesi sınırına ulaşıldı!",
"MISS_PASSWORD": "Parola eksik",
"INCORRECT_PASSWORD": "Parola yanlış."
}

View File

@@ -132,8 +132,10 @@ export const register = async (
})
})
app.router.use(express.json())
app.router.use(express.urlencoded({ extended: true }))
// Reject compressed request bodies so unauthenticated requests cannot reach
// the zlib/Brotli decoders. Browsers never compress request bodies.
app.router.use(express.json({ inflate: false }))
app.router.use(express.urlencoded({ extended: true, inflate: false }))
app.router.use(
"/_static",

View File

@@ -511,3 +511,18 @@ export function splitOnFirstEquals(str: string): [string, string | undefined] {
const split = str.split(/=(.+)?/, 2)
return [split[0], split[1]]
}
/**
* Expand a leading `~` to the home directory. The shell does this for
* command-line arguments but values from the config file are not expanded.
* `~user` forms are left alone.
*/
export function expandTilde(p: string): string {
if (p === "~") {
return os.homedir()
}
if (p.startsWith("~/") || p.startsWith(`~${path.sep}`)) {
return path.join(os.homedir(), p.slice(2))
}
return p
}

View File

@@ -38,7 +38,7 @@ export async function makeEditorSessionManagerServer(
): Promise<http.Server> {
const router = express()
router.use(express.json())
router.use(express.json({ inflate: false }))
router.get<{}, GetSessionResponse | string | unknown, undefined, { filePath?: string }>(
"/session",

View File

@@ -2,7 +2,8 @@ import * as vscode from "vscode"
export function activate(context: vscode.ExtensionContext) {
vscode.window.showInformationMessage("test extension loaded")
// Test extension
// Prints the current proxy URI into a notification.
context.subscriptions.push(
vscode.commands.registerCommand("codeServerTest.proxyUri", () => {
if (process.env.VSCODE_PROXY_URI) {
@@ -13,7 +14,8 @@ export function activate(context: vscode.ExtensionContext) {
}),
)
// asExternalUri extension
// Asks for a URI, then runs it through asExternalUri and prints the result
// into a notification.
context.subscriptions.push(
vscode.commands.registerCommand("codeServerTest.asExternalUri", async () => {
const input = await vscode.window.showInputBox({
@@ -28,4 +30,29 @@ export function activate(context: vscode.ExtensionContext) {
}
}),
)
const secretKey = "code-server-test-secret"
// Asks for a string, then stores it as a secret.
context.subscriptions.push(
vscode.commands.registerCommand("codeServerTest.setSecret", async () => {
const input = await vscode.window.showInputBox({
prompt: "Secret to store",
})
if (input) {
await context.secrets.store(secretKey, input)
vscode.window.showInformationMessage(`set secret: ${input}`)
} else {
vscode.window.showErrorMessage(`Failed to run test case. No input provided.`)
}
}),
)
// Prints the current secret, if any.
context.subscriptions.push(
vscode.commands.registerCommand("codeServerTest.getSecret", async () => {
vscode.window.showInformationMessage(`get secret: ${await context.secrets.get(secretKey)}`)
}),
)
}

View File

@@ -22,6 +22,16 @@
"command": "codeServerTest.asExternalUri",
"title": "asExternalUri test",
"category": "code-server"
},
{
"command": "codeServerTest.setSecret",
"title": "Set test secret",
"category": "code-server"
},
{
"command": "codeServerTest.getSecret",
"title": "Get test secret",
"category": "code-server"
}
]
},

View File

@@ -363,13 +363,14 @@ export class CodeServerPage {
}
/**
* Checks if the test extension loaded
* Checks if the test extension loaded by waiting for its notification. Once
* it appears, dismiss the notification.
*/
async waitForTestExtensionLoaded(): Promise<void> {
const selector = "text=test extension loaded"
this.codeServer.logger.debug("Waiting for test extension to load...")
await this.page.waitForSelector(selector)
const locator = this.page.getByRole("dialog", { name: "Info: test extension loaded" })
await locator.hover()
await locator.getByLabel("Clear Notification (Del)").click()
}
/**

View File

@@ -1,5 +1,3 @@
import { promises as fs } from "fs"
import * as path from "path"
import { clean } from "../utils/helpers"
import { describe, test, expect } from "./baseFixture"

61
test/e2e/storage.test.ts Normal file
View File

@@ -0,0 +1,61 @@
import { promises as fs } from "fs"
import * as path from "path"
import { describe, test, expect } from "./baseFixture"
const tests = [
{
name: "browser storage",
args: [],
},
{
name: "remote storage",
args: ["--enable-remote-storage"],
},
]
for (const tc of tests) {
const args = ["--disable-workspace-trust", "--extensions-dir", path.join(__dirname, "./extensions"), ...tc.args]
describe(tc.name, args, {}, () => {
test("should store workbench state and secrets", async ({ codeServerPage }) => {
// Create and open a file.
const dir = await codeServerPage.workspaceDir
const file = path.join(dir, "foo")
await fs.writeFile(file, "bar")
await codeServerPage.openFile(file)
// Store a secret.
const secret = "foo-bar-baz"
await codeServerPage.waitForTestExtensionLoaded()
await codeServerPage.executeCommandViaMenus("code-server: Set test secret")
await codeServerPage.page.waitForSelector(".quick-input-widget:focus-within")
await codeServerPage.page.keyboard.type(secret)
await codeServerPage.page.keyboard.press("Enter")
await expect(codeServerPage.page.getByText(`Info: set secret: ${secret}`)).toBeVisible()
// There should be a workspace storage directory.
const userDir = path.join(dir, "User/workspaceStorage")
const dirs = await fs.readdir(userDir)
expect(dirs.length).toBe(1)
const storageFile = path.join(userDir, dirs[0], "state.vscdb")
if (tc.args.includes("--enable-remote-storage")) {
// The sqlite database should exist.
fs.stat(storageFile)
} else {
// But no sqlite database.
expect(() => fs.stat(storageFile)).rejects.toThrow("ENOENT")
}
// Reload using a command so it has a chance to flush storage.
await codeServerPage.executeCommandViaMenus("Developer: Reload Window")
await codeServerPage.waitForTestExtensionLoaded()
// Should see the same file.
await codeServerPage.waitForTab(file)
// Should be able to retrieve the secret.
await codeServerPage.executeCommandViaMenus("code-server: Get test secret")
await expect(codeServerPage.page.getByText(`Info: get secret: ${secret}`)).toBeVisible()
})
})
}

View File

@@ -0,0 +1,19 @@
import { promises as fs } from "fs"
import * as path from "path"
import { describe, test, expect } from "./baseFixture"
const args = ["--disable-workspace-trust"]
describe("typescript extension", args, {}, () => {
test("should load as a workspace extension", async ({ codeServerPage }) => {
const dir = await codeServerPage.workspaceDir
const file = path.join(dir, "foo.ts")
await fs.writeFile(file, 'const n: number = "x"')
await codeServerPage.openFile(file)
await codeServerPage.executeCommandViaMenus("Developer: Show Running Extensions")
await expect(
codeServerPage.page.getByLabel("typescript-language-features").getByText(/localhost:\d+/),
).toBeVisible()
})
})

View File

@@ -1,5 +1,6 @@
import { Level, logger } from "@coder/logger"
import { promises as fs } from "fs"
import * as os from "os"
import * as path from "path"
import {
UserProvidedArgs,
@@ -575,9 +576,33 @@ describe("parser", () => {
"install-extension": ["extension.number1", "extension.number2"],
})
})
it("should expand ~ in path options from the config file", () => {
const configContents = `
user-data-dir: ~/.config/code-server-data
extensions-dir: ~/extensions
`
expect(parseConfigFile(configContents, "/fake-config-path")).toEqual({
config: "/fake-config-path",
"user-data-dir": path.join(os.homedir(), ".config/code-server-data"),
"extensions-dir": path.join(os.homedir(), "extensions"),
})
})
it("should ignore optional strings set to false", async () => {
expect(parse(["--cert=false"])).toEqual({})
})
it("should ignore booleans set to false", async () => {
expect(parse(["--disable-telemetry=false"])).toEqual({})
})
it("should parse booleans set to false in the config file", () => {
const configContents = `
disable-telemetry: false
disable-update-check: true
`
expect(parseConfigFile(configContents, "/fake-config-path")).toEqual({
config: "/fake-config-path",
"disable-update-check": true,
})
})
it("should use last flag", async () => {
expect(parse(["--port", "8081", "--port", "8082"])).toEqual({
port: 8082,

View File

@@ -75,6 +75,17 @@ describe("login", () => {
expect(htmlContent).toContain("Missing password")
})
it("should reject compressed request bodies", async () => {
for (const encoding of ["br", "gzip", "deflate"]) {
const resp = await codeServer().fetch("/login", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded", "Content-Encoding": encoding },
body: "password=test",
})
expect(resp.status).toBe(415)
}
})
it("should return HTML with 'Incorrect password' message", async () => {
const params = new URLSearchParams()
params.append("password", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")

View File

@@ -1,5 +1,6 @@
import * as cp from "child_process"
import { promises as fs } from "fs"
import * as os from "os"
import * as path from "path"
import { generateUuid } from "../../../src/common/util"
import { tmpdir } from "../../../src/node/constants"
@@ -623,3 +624,18 @@ describe("splitOnFirstEquals", () => {
})
})
})
describe("expandTilde", () => {
it("should expand a bare ~", () => {
expect(util.expandTilde("~")).toBe(os.homedir())
})
it("should expand a leading ~/", () => {
expect(util.expandTilde("~/.config/code-server")).toBe(path.join(os.homedir(), ".config/code-server"))
})
it("should leave other paths alone", () => {
expect(util.expandTilde("/tmp/~/data")).toBe("/tmp/~/data")
expect(util.expandTilde("relative/~")).toBe("relative/~")
expect(util.expandTilde("~user/data")).toBe("~user/data")
expect(util.expandTilde("")).toBe("")
})
})