Skip to content

Commit 98bc3e3

Browse files
committed
Use preferences for node download template
1 parent f75d0e1 commit 98bc3e3

9 files changed

+126
-33
lines changed

packages/remote/src/electron-browser/remote-frontend-module.ts

+3
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import { RemoteService } from './remote-service';
2525
import { RemoteStatusService, RemoteStatusServicePath } from '../electron-common/remote-status-service';
2626
import { ElectronFileDialogService } from '@theia/filesystem/lib/electron-browser/file-dialog/electron-file-dialog-service';
2727
import { RemoteElectronFileDialogService } from './remote-electron-file-dialog-service';
28+
import { bindRemotePreferences } from './remote-preferences';
2829

2930
export default new ContainerModule((bind, _, __, rebind) => {
3031
bind(RemoteFrontendContribution).toSelf().inSingletonScope();
@@ -35,6 +36,8 @@ export default new ContainerModule((bind, _, __, rebind) => {
3536
bind(RemoteSSHContribution).toSelf().inSingletonScope();
3637
bind(RemoteRegistryContribution).toService(RemoteSSHContribution);
3738

39+
bindRemotePreferences(bind);
40+
3841
rebind(ElectronFileDialogService).to(RemoteElectronFileDialogService).inSingletonScope();
3942

4043
bind(RemoteService).toSelf().inSingletonScope();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// *****************************************************************************
2+
// Copyright (C) 2023 TypeFox and others.
3+
//
4+
// This program and the accompanying materials are made available under the
5+
// terms of the Eclipse Public License v. 2.0 which is available at
6+
// http://www.eclipse.org/legal/epl-2.0.
7+
//
8+
// This Source Code may also be made available under the following Secondary
9+
// Licenses when the conditions for such availability set forth in the Eclipse
10+
// Public License v. 2.0 are satisfied: GNU General Public License, version 2
11+
// with the GNU Classpath Exception which is available at
12+
// https://www.gnu.org/software/classpath/license.html.
13+
//
14+
// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15+
// *****************************************************************************
16+
17+
import { interfaces } from '@theia/core/shared/inversify';
18+
import {
19+
PreferenceProxy,
20+
PreferenceSchema,
21+
PreferenceContribution
22+
} from '@theia/core/lib/browser/preferences';
23+
import { nls } from '@theia/core/lib/common/nls';
24+
import { PreferenceProxyFactory } from '@theia/core/lib/browser/preferences/injectable-preference-proxy';
25+
26+
const nodeDownloadTemplateParts = [
27+
nls.localize('theia/remote/nodeDownloadTemplateVersion', '`{version}` for the used node version'),
28+
nls.localize('theia/remote/nodeDownloadTemplateOS', '`{os}` for the remote operating system. Either `win`, `linux` or `darwin`.'),
29+
nls.localize('theia/remote/nodeDownloadTemplateArch', '`{arch}` for the remote system architecture.'),
30+
nls.localize('theia/remote/nodeDownloadTemplateExt', '`{ext}` for the file extension. Either `zip`, `tar.xz` or `tar.xz`, depending on the operating system.')
31+
];
32+
33+
export const RemotePreferenceSchema: PreferenceSchema = {
34+
'type': 'object',
35+
properties: {
36+
'remote.nodeDownloadTemplate': {
37+
type: 'string',
38+
default: '',
39+
markdownDescription: nls.localize(
40+
'theia/remote/nodeDownloadTemplate',
41+
'Controls the template used to download the node.js binaries for the remote backend. Points to the official node.js website by default. Uses multiple placeholders:'
42+
) + '\n- ' + nodeDownloadTemplateParts.join('\n- ')
43+
},
44+
}
45+
};
46+
47+
export interface RemoteConfiguration {
48+
'remote.nodeDownloadTemplate': string;
49+
}
50+
51+
export const RemotePreferenceContribution = Symbol('RemotePreferenceContribution');
52+
export const RemotePreferences = Symbol('GettingStartedPreferences');
53+
export type RemotePreferences = PreferenceProxy<RemoteConfiguration>;
54+
55+
export function bindRemotePreferences(bind: interfaces.Bind): void {
56+
bind(RemotePreferences).toDynamicValue(ctx => {
57+
const factory = ctx.container.get<PreferenceProxyFactory>(PreferenceProxyFactory);
58+
return factory(RemotePreferenceSchema);
59+
}).inSingletonScope();
60+
bind(RemotePreferenceContribution).toConstantValue({ schema: RemotePreferenceSchema });
61+
bind(PreferenceContribution).toService(RemotePreferenceContribution);
62+
}

packages/remote/src/electron-browser/remote-ssh-contribution.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { Command, MessageService, nls, QuickInputService } from '@theia/core';
1818
import { inject, injectable } from '@theia/core/shared/inversify';
1919
import { RemoteSSHConnectionProvider } from '../electron-common/remote-ssh-connection-provider';
2020
import { AbstractRemoteRegistryContribution, RemoteRegistry } from './remote-registry-contribution';
21+
import { RemotePreferences } from './remote-preferences';
2122

2223
export namespace RemoteSSHCommands {
2324
export const CONNECT: Command = Command.toLocalizedCommand({
@@ -44,6 +45,9 @@ export class RemoteSSHContribution extends AbstractRemoteRegistryContribution {
4445
@inject(MessageService)
4546
protected readonly messageService: MessageService;
4647

48+
@inject(RemotePreferences)
49+
protected readonly remotePreferences: RemotePreferences;
50+
4751
registerRemoteCommands(registry: RemoteRegistry): void {
4852
registry.registerCommand(RemoteSSHCommands.CONNECT, {
4953
execute: () => this.connect(true)
@@ -89,6 +93,10 @@ export class RemoteSSHContribution extends AbstractRemoteRegistryContribution {
8993
}
9094

9195
async sendSSHConnect(host: string, user: string): Promise<string> {
92-
return this.sshConnectionProvider.establishConnection(host, user);
96+
return this.sshConnectionProvider.establishConnection({
97+
host,
98+
user,
99+
nodeDownloadTemplate: this.remotePreferences['remote.nodeDownloadTemplate']
100+
});
93101
}
94102
}

packages/remote/src/electron-common/remote-ssh-connection-provider.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,12 @@ export const RemoteSSHConnectionProviderPath = '/remote/ssh';
1818

1919
export const RemoteSSHConnectionProvider = Symbol('RemoteSSHConnectionProvider');
2020

21-
export interface RemoteSSHConnectionOptions {
22-
user?: string;
23-
host?: string;
21+
export interface RemoteSSHConnectionProviderOptions {
22+
user: string;
23+
host: string;
24+
nodeDownloadTemplate?: string;
2425
}
2526

2627
export interface RemoteSSHConnectionProvider {
27-
establishConnection(host: string, user: string): Promise<string>;
28-
isConnectionAlive(remoteId: string): Promise<boolean>;
28+
establishConnection(options: RemoteSSHConnectionProviderOptions): Promise<string>;
2929
}

packages/remote/src/electron-node/setup/remote-node-setup-service.ts

+23-11
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,10 @@ export class RemoteNodeSetupService {
4141
protected readonly scriptService: RemoteSetupScriptService;
4242

4343
getNodeDirectoryName(platform: RemotePlatform): string {
44+
return `node-v${REMOTE_NODE_VERSION}-${this.getPlatformName(platform)}-${platform.arch}`;
45+
}
46+
47+
protected getPlatformName(platform: RemotePlatform): string {
4448
let platformId: string;
4549
if (platform.os === OS.Type.Windows) {
4650
platformId = 'win';
@@ -49,8 +53,7 @@ export class RemoteNodeSetupService {
4953
} else {
5054
platformId = 'linux';
5155
}
52-
const dirName = `node-v${REMOTE_NODE_VERSION}-${platformId}-${platform.arch}`;
53-
return dirName;
56+
return platformId;
5457
}
5558

5659
protected validatePlatform(platform: RemotePlatform): void {
@@ -67,7 +70,7 @@ export class RemoteNodeSetupService {
6770
throw new Error(`Invalid architecture for ${platform.os}: '${platform.arch}'. Only ${supportedArch} are supported.`);
6871
}
6972

70-
getNodeFileName(platform: RemotePlatform): string {
73+
protected getNodeFileExtension(platform: RemotePlatform): string {
7174
let fileExtension: string;
7275
if (platform.os === OS.Type.Windows) {
7376
fileExtension = 'zip';
@@ -76,16 +79,20 @@ export class RemoteNodeSetupService {
7679
} else {
7780
fileExtension = 'tar.xz';
7881
}
79-
return `${this.getNodeDirectoryName(platform)}.${fileExtension}`;
82+
return fileExtension;
8083
}
8184

82-
async downloadNode(platform: RemotePlatform): Promise<string> {
85+
getNodeFileName(platform: RemotePlatform): string {
86+
return `${this.getNodeDirectoryName(platform)}.${this.getNodeFileExtension(platform)}`;
87+
}
88+
89+
async downloadNode(platform: RemotePlatform, downloadTemplate?: string): Promise<string> {
8390
this.validatePlatform(platform);
8491
const fileName = this.getNodeFileName(platform);
8592
const tmpdir = os.tmpdir();
8693
const localPath = path.join(tmpdir, fileName);
8794
if (!await fs.pathExists(localPath)) {
88-
const downloadPath = this.getDownloadPath(fileName);
95+
const downloadPath = this.getDownloadPath(platform, downloadTemplate);
8996
const downloadResult = await this.requestService.request({
9097
url: downloadPath
9198
});
@@ -94,18 +101,23 @@ export class RemoteNodeSetupService {
94101
return localPath;
95102
}
96103

97-
generateDownloadScript(platform: RemotePlatform, targetPath: string): string {
104+
generateDownloadScript(platform: RemotePlatform, targetPath: string, downloadTemplate?: string): string {
98105
this.validatePlatform(platform);
99106
const fileName = this.getNodeFileName(platform);
100-
const downloadPath = this.getDownloadPath(fileName);
107+
const downloadPath = this.getDownloadPath(platform, downloadTemplate);
101108
const zipPath = this.scriptService.joinPath(platform, targetPath, fileName);
102109
const download = this.scriptService.downloadFile(platform, downloadPath, zipPath);
103110
const unzip = this.scriptService.unzip(platform, zipPath, targetPath);
104111
return this.scriptService.joinScript(platform, download, unzip);
105112
}
106113

107-
protected getDownloadPath(fileName: string): string {
108-
return `https://nodejs.org/dist/v${REMOTE_NODE_VERSION}/${fileName}`;
114+
protected getDownloadPath(platform: RemotePlatform, downloadTemplate?: string): string {
115+
const template = downloadTemplate || 'https://nodejs.org/dist/v{version}/node-v{version}-{os}-{arch}.{ext}';
116+
const downloadPath = template
117+
.replace(/{version}/g, REMOTE_NODE_VERSION)
118+
.replace(/{os}/g, this.getPlatformName(platform))
119+
.replace(/{arch}/g, platform.arch)
120+
.replace(/{ext}/g, this.getNodeFileExtension(platform));
121+
return downloadPath;
109122
}
110-
111123
}

packages/remote/src/electron-node/setup/remote-setup-service.ts

+13-2
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ import { OS, THEIA_VERSION } from '@theia/core';
2323
import { RemoteNodeSetupService } from './remote-node-setup-service';
2424
import { RemoteSetupScriptService } from './remote-setup-script-service';
2525

26+
export interface RemoteSetupOptions {
27+
connection: RemoteConnection;
28+
report: RemoteStatusReport;
29+
nodeDownloadTemplate?: string;
30+
}
31+
2632
@injectable()
2733
export class RemoteSetupService {
2834

@@ -41,7 +47,12 @@ export class RemoteSetupService {
4147
@inject(ApplicationPackage)
4248
protected readonly applicationPackage: ApplicationPackage;
4349

44-
async setup(connection: RemoteConnection, report: RemoteStatusReport): Promise<void> {
50+
async setup(options: RemoteSetupOptions): Promise<void> {
51+
const {
52+
connection,
53+
report,
54+
nodeDownloadTemplate
55+
} = options;
4556
report('Identifying remote system...');
4657
// 1. Identify remote platform
4758
const platform = await this.detectRemotePlatform(connection);
@@ -57,7 +68,7 @@ export class RemoteSetupService {
5768
if (!nodeDirExists) {
5869
report('Downloading and installing Node.js on remote...');
5970
// Download the binaries locally and move it via SSH
60-
const nodeArchive = await this.nodeSetupService.downloadNode(platform);
71+
const nodeArchive = await this.nodeSetupService.downloadNode(platform, nodeDownloadTemplate);
6172
const remoteNodeZip = this.scriptService.joinPath(platform, applicationDirectory, nodeFileName);
6273
await connection.copy(nodeArchive, remoteNodeZip);
6374
await this.unzipRemote(connection, platform, remoteNodeZip, applicationDirectory);

packages/remote/src/electron-node/ssh/remote-ssh-connection-provider.ts

+8-9
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import * as fs from '@theia/core/shared/fs-extra';
2020
import SftpClient = require('ssh2-sftp-client');
2121
import { Emitter, Event, MessageService, QuickInputService } from '@theia/core';
2222
import { inject, injectable } from '@theia/core/shared/inversify';
23-
import { RemoteSSHConnectionProvider } from '../../electron-common/remote-ssh-connection-provider';
23+
import { RemoteSSHConnectionProvider, RemoteSSHConnectionProviderOptions } from '../../electron-common/remote-ssh-connection-provider';
2424
import { RemoteConnectionService } from '../remote-connection-service';
2525
import { RemoteProxyServerProvider } from '../remote-proxy-server-provider';
2626
import { RemoteConnection, RemoteExecOptions, RemoteExecResult, RemoteExecTester, RemoteStatusReport } from '../remote-types';
@@ -53,15 +53,19 @@ export class RemoteSSHConnectionProviderImpl implements RemoteSSHConnectionProvi
5353
protected passwordRetryCount = 3;
5454
protected passphraseRetryCount = 3;
5555

56-
async establishConnection(host: string, user: string): Promise<string> {
56+
async establishConnection(options: RemoteSSHConnectionProviderOptions): Promise<string> {
5757
const progress = await this.messageService.showProgress({
5858
text: 'Remote SSH'
5959
});
6060
const report: RemoteStatusReport = message => progress.report({ message });
6161
report('Connecting to remote system...');
6262
try {
63-
const remote = await this.establishSSHConnection(host, user);
64-
await this.remoteSetup.setup(remote, report);
63+
const remote = await this.establishSSHConnection(options.host, options.user);
64+
await this.remoteSetup.setup({
65+
connection: remote,
66+
report,
67+
nodeDownloadTemplate: options.nodeDownloadTemplate
68+
});
6569
const registration = this.remoteConnectionService.register(remote);
6670
const server = await this.serverProvider.getProxyServer(socket => {
6771
remote.forwardOut(socket);
@@ -230,11 +234,6 @@ export class RemoteSSHConnectionProviderImpl implements RemoteSSHConnectionProvi
230234
callback(END_AUTH);
231235
};
232236
}
233-
234-
isConnectionAlive(remoteId: string): Promise<boolean> {
235-
return Promise.resolve(Boolean(this.remoteConnectionService.getConnection(remoteId)));
236-
}
237-
238237
}
239238

240239
export interface RemoteSSHConnectionOptions {

packages/remote/tsconfig.json

+3
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
"references": [
1212
{
1313
"path": "../core"
14+
},
15+
{
16+
"path": "../filesystem"
1417
}
1518
]
1619
}

yarn.lock

-5
Original file line numberDiff line numberDiff line change
@@ -8034,11 +8034,6 @@ [email protected]:
80348034
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.3.tgz#fd8e8b7aa761fe807dba2d1b98fb7241bb724a25"
80358035
integrity sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==
80368036

8037-
8038-
version "3.3.4"
8039-
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab"
8040-
integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==
8041-
80428037
nanoid@^3.3.6:
80438038
version "3.3.6"
80448039
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"

0 commit comments

Comments
 (0)