@theia/cli 1.18.0-next.d3501165 → 1.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +642 -0
- package/lib/download-plugins.d.ts +0 -1
- package/lib/download-plugins.d.ts.map +1 -1
- package/lib/download-plugins.js +49 -63
- package/lib/download-plugins.js.map +1 -1
- package/lib/theia.js +72 -5
- package/lib/theia.js.map +1 -1
- package/package.json +7 -5
- package/src/download-plugins.ts +50 -66
- package/src/theia.ts +84 -8
package/src/download-plugins.ts
CHANGED
|
@@ -26,7 +26,7 @@ declare global {
|
|
|
26
26
|
import { OVSXClient } from '@theia/ovsx-client/lib/ovsx-client';
|
|
27
27
|
import { green, red, yellow } from 'colors/safe';
|
|
28
28
|
import * as decompress from 'decompress';
|
|
29
|
-
import { createWriteStream,
|
|
29
|
+
import { createWriteStream, promises as fs } from 'fs';
|
|
30
30
|
import { HttpsProxyAgent } from 'https-proxy-agent';
|
|
31
31
|
import fetch, { RequestInit, Response } from 'node-fetch';
|
|
32
32
|
import * as path from 'path';
|
|
@@ -34,13 +34,12 @@ import { getProxyForUrl } from 'proxy-from-env';
|
|
|
34
34
|
import * as stream from 'stream';
|
|
35
35
|
import * as temp from 'temp';
|
|
36
36
|
import { promisify } from 'util';
|
|
37
|
+
import { DEFAULT_SUPPORTED_API_VERSION } from '@theia/application-package/lib/api';
|
|
37
38
|
|
|
38
39
|
const pipelineAsPromised = promisify(stream.pipeline);
|
|
39
40
|
|
|
40
41
|
temp.track();
|
|
41
42
|
|
|
42
|
-
export const extensionPackCacheName = '.packs';
|
|
43
|
-
|
|
44
43
|
/**
|
|
45
44
|
* Available options when downloading.
|
|
46
45
|
*/
|
|
@@ -73,7 +72,7 @@ export default async function downloadPlugins(options: DownloadPluginsOptions =
|
|
|
73
72
|
const {
|
|
74
73
|
packed = false,
|
|
75
74
|
ignoreErrors = false,
|
|
76
|
-
apiVersion =
|
|
75
|
+
apiVersion = DEFAULT_SUPPORTED_API_VERSION,
|
|
77
76
|
apiUrl = 'https://open-vsx.org/api'
|
|
78
77
|
} = options;
|
|
79
78
|
|
|
@@ -96,32 +95,22 @@ export default async function downloadPlugins(options: DownloadPluginsOptions =
|
|
|
96
95
|
return;
|
|
97
96
|
}
|
|
98
97
|
try {
|
|
99
|
-
// Retrieve the cached extension-packs in order to not re-download them.
|
|
100
|
-
const extensionPackCachePath = path.resolve(pluginsDir, extensionPackCacheName);
|
|
101
|
-
const cachedExtensionPacks = new Set<string>(
|
|
102
|
-
existsSync(extensionPackCachePath)
|
|
103
|
-
? await fs.readdir(extensionPackCachePath)
|
|
104
|
-
: []
|
|
105
|
-
);
|
|
106
98
|
console.warn('--- downloading plugins ---');
|
|
107
99
|
// Download the raw plugins defined by the `theiaPlugins` property.
|
|
108
100
|
// This will include both "normal" plugins as well as "extension packs".
|
|
109
101
|
const downloads = [];
|
|
110
102
|
for (const [plugin, pluginUrl] of Object.entries(pck.theiaPlugins)) {
|
|
111
|
-
|
|
112
|
-
if (cachedExtensionPacks.has(plugin) || typeof pluginUrl !== 'string') {
|
|
103
|
+
if (typeof pluginUrl !== 'string') {
|
|
113
104
|
continue;
|
|
114
105
|
}
|
|
115
106
|
downloads.push(downloadPluginAsync(failures, plugin, pluginUrl, pluginsDir, packed));
|
|
116
107
|
}
|
|
117
108
|
await Promise.all(downloads);
|
|
109
|
+
|
|
118
110
|
console.warn('--- collecting extension-packs ---');
|
|
119
111
|
const extensionPacks = await collectExtensionPacks(pluginsDir, excludedIds);
|
|
120
112
|
if (extensionPacks.size > 0) {
|
|
121
|
-
console.warn(`---
|
|
122
|
-
// Move extension-packs to `.packs`
|
|
123
|
-
await cacheExtensionPacks(pluginsDir, extensionPacks);
|
|
124
|
-
console.warn('--- resolving extension-packs ---');
|
|
113
|
+
console.warn(`--- resolving ${extensionPacks.size} extension-packs ---`);
|
|
125
114
|
const client = new OVSXClient({ apiVersion, apiUrl });
|
|
126
115
|
// De-duplicate extension ids to only download each once:
|
|
127
116
|
const ids = new Set<string>(Array.from(extensionPacks.values()).flat());
|
|
@@ -129,10 +118,27 @@ export default async function downloadPlugins(options: DownloadPluginsOptions =
|
|
|
129
118
|
const extension = await client.getLatestCompatibleExtensionVersion(id);
|
|
130
119
|
const downloadUrl = extension?.files.download;
|
|
131
120
|
if (downloadUrl) {
|
|
132
|
-
await downloadPluginAsync(failures, id, downloadUrl, pluginsDir, packed);
|
|
121
|
+
await downloadPluginAsync(failures, id, downloadUrl, pluginsDir, packed, extension?.version);
|
|
133
122
|
}
|
|
134
123
|
}));
|
|
135
124
|
}
|
|
125
|
+
|
|
126
|
+
console.warn('--- collecting extension dependencies ---');
|
|
127
|
+
const pluginDependencies = await collectPluginDependencies(pluginsDir, excludedIds);
|
|
128
|
+
if (pluginDependencies.length > 0) {
|
|
129
|
+
console.warn(`--- resolving ${pluginDependencies.length} extension dependencies ---`);
|
|
130
|
+
const client = new OVSXClient({ apiVersion, apiUrl });
|
|
131
|
+
// De-duplicate extension ids to only download each once:
|
|
132
|
+
const ids = new Set<string>(pluginDependencies);
|
|
133
|
+
await Promise.all(Array.from(ids, async id => {
|
|
134
|
+
const extension = await client.getLatestCompatibleExtensionVersion(id);
|
|
135
|
+
const downloadUrl = extension?.files.download;
|
|
136
|
+
if (downloadUrl) {
|
|
137
|
+
await downloadPluginAsync(failures, id, downloadUrl, pluginsDir, packed, extension?.version);
|
|
138
|
+
}
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
|
|
136
142
|
} finally {
|
|
137
143
|
temp.cleanupSync();
|
|
138
144
|
}
|
|
@@ -153,7 +159,7 @@ export default async function downloadPlugins(options: DownloadPluginsOptions =
|
|
|
153
159
|
* @param packed whether to decompress or not.
|
|
154
160
|
* @param cachedExtensionPacks the list of cached extension packs already downloaded.
|
|
155
161
|
*/
|
|
156
|
-
async function downloadPluginAsync(failures: string[], plugin: string, pluginUrl: string, pluginsDir: string, packed: boolean): Promise<void> {
|
|
162
|
+
async function downloadPluginAsync(failures: string[], plugin: string, pluginUrl: string, pluginsDir: string, packed: boolean, version?: string): Promise<void> {
|
|
157
163
|
if (!plugin) {
|
|
158
164
|
return;
|
|
159
165
|
}
|
|
@@ -162,6 +168,8 @@ async function downloadPluginAsync(failures: string[], plugin: string, pluginUrl
|
|
|
162
168
|
fileExt = '.tar.gz';
|
|
163
169
|
} else if (pluginUrl.endsWith('vsix')) {
|
|
164
170
|
fileExt = '.vsix';
|
|
171
|
+
} else if (pluginUrl.endsWith('theia')) {
|
|
172
|
+
fileExt = '.theia'; // theia plugins.
|
|
165
173
|
} else {
|
|
166
174
|
failures.push(red(`error: '${plugin}' has an unsupported file type: '${pluginUrl}'`));
|
|
167
175
|
return;
|
|
@@ -210,7 +218,7 @@ async function downloadPluginAsync(failures: string[], plugin: string, pluginUrl
|
|
|
210
218
|
return;
|
|
211
219
|
}
|
|
212
220
|
|
|
213
|
-
if (fileExt === '.vsix' && packed === true) {
|
|
221
|
+
if ((fileExt === '.vsix' || fileExt === '.theia') && packed === true) {
|
|
214
222
|
// Download .vsix without decompressing.
|
|
215
223
|
const file = createWriteStream(targetPath);
|
|
216
224
|
await pipelineAsPromised(response.body, file);
|
|
@@ -221,7 +229,7 @@ async function downloadPluginAsync(failures: string[], plugin: string, pluginUrl
|
|
|
221
229
|
await decompress(tempFile.path, targetPath);
|
|
222
230
|
}
|
|
223
231
|
|
|
224
|
-
console.warn(green(`+ ${plugin}: downloaded successfully ${attempts > 1 ? `(after ${attempts} attempts)` : ''}`));
|
|
232
|
+
console.warn(green(`+ ${plugin}${version ? `@${version}` : ''}: downloaded successfully ${attempts > 1 ? `(after ${attempts} attempts)` : ''}`));
|
|
225
233
|
}
|
|
226
234
|
|
|
227
235
|
/**
|
|
@@ -257,8 +265,7 @@ async function collectPackageJsonPaths(pluginDir: string): Promise<string[]> {
|
|
|
257
265
|
// Recursively fetch the list of extension `package.json` files.
|
|
258
266
|
for (const file of files) {
|
|
259
267
|
const filePath = path.join(pluginDir, file);
|
|
260
|
-
|
|
261
|
-
if (!filePath.startsWith(extensionPackCacheName) && (await fs.stat(filePath)).isDirectory()) {
|
|
268
|
+
if ((await fs.stat(filePath)).isDirectory()) {
|
|
262
269
|
packageJsonPathList.push(...await collectPackageJsonPaths(filePath));
|
|
263
270
|
} else if (path.basename(filePath) === 'package.json' && !path.dirname(filePath).includes('node_modules')) {
|
|
264
271
|
packageJsonPathList.push(filePath);
|
|
@@ -280,7 +287,7 @@ async function collectExtensionPacks(pluginDir: string, excludedIds: Set<string>
|
|
|
280
287
|
await Promise.all(packageJsonPaths.map(async packageJsonPath => {
|
|
281
288
|
const json = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
282
289
|
const extensionPack: unknown = json.extensionPack;
|
|
283
|
-
if (
|
|
290
|
+
if (Array.isArray(extensionPack)) {
|
|
284
291
|
extensionPackPaths.set(packageJsonPath, extensionPack.filter(id => {
|
|
285
292
|
if (excludedIds.has(id)) {
|
|
286
293
|
console.log(yellow(`'${id}' referenced by '${json.name}' (ext pack) is excluded because of 'theiaPluginsExcludeIds'`));
|
|
@@ -294,50 +301,27 @@ async function collectExtensionPacks(pluginDir: string, excludedIds: Set<string>
|
|
|
294
301
|
}
|
|
295
302
|
|
|
296
303
|
/**
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* By moving the packs to a subdirectory it should make it invisible to the plugin system, only leaving
|
|
304
|
-
* the plugins that were installed under `pluginsDir` directly.
|
|
305
|
-
*
|
|
306
|
-
* @param extensionPacksPaths the list of extension-pack paths.
|
|
304
|
+
* Get the mapping of paths and their included plugin ids.
|
|
305
|
+
* - If an extension-pack references an explicitly excluded `id` the `id` will be omitted.
|
|
306
|
+
* @param pluginDir the plugin directory.
|
|
307
|
+
* @param excludedIds the list of plugin ids to exclude.
|
|
308
|
+
* @returns the mapping of extension-pack paths and their included plugin ids.
|
|
307
309
|
*/
|
|
308
|
-
async function
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
await Promise.all(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
await fs.rename(oldPath, newPath);
|
|
310
|
+
async function collectPluginDependencies(pluginDir: string, excludedIds: Set<string>): Promise<string[]> {
|
|
311
|
+
const dependencyIds: string[] = [];
|
|
312
|
+
const packageJsonPaths = await collectPackageJsonPaths(pluginDir);
|
|
313
|
+
await Promise.all(packageJsonPaths.map(async packageJsonPath => {
|
|
314
|
+
const json = JSON.parse(await fs.readFile(packageJsonPath, 'utf8'));
|
|
315
|
+
const extensionDependencies: unknown = json.extensionDependencies;
|
|
316
|
+
if (Array.isArray(extensionDependencies)) {
|
|
317
|
+
for (const dependency of extensionDependencies) {
|
|
318
|
+
if (excludedIds.has(dependency)) {
|
|
319
|
+
console.log(yellow(`'${dependency}' referenced by '${json.name}' is excluded because of 'theiaPluginsExcludeIds'`));
|
|
320
|
+
} else {
|
|
321
|
+
dependencyIds.push(dependency);
|
|
322
|
+
}
|
|
322
323
|
}
|
|
323
|
-
} catch (error) {
|
|
324
|
-
console.error(error);
|
|
325
324
|
}
|
|
326
325
|
}));
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Walk back to the root of an extension starting from its `package.json`. e.g.
|
|
331
|
-
*
|
|
332
|
-
* ```ts
|
|
333
|
-
* getExtensionRoot('/a/b/c', '/a/b/c/EXT/d/e/f/package.json') === '/a/b/c/EXT'
|
|
334
|
-
* ```
|
|
335
|
-
*/
|
|
336
|
-
function getExtensionRoot(root: string, packageJsonPath: string): string {
|
|
337
|
-
root = path.resolve(root);
|
|
338
|
-
packageJsonPath = path.resolve(packageJsonPath);
|
|
339
|
-
if (!packageJsonPath.startsWith(root)) {
|
|
340
|
-
throw new Error(`unexpected paths:\n root: ${root}\n package.json: ${packageJsonPath}`);
|
|
341
|
-
}
|
|
342
|
-
return packageJsonPath.substr(0, packageJsonPath.indexOf(path.sep, root.length + 1));
|
|
326
|
+
return dependencyIds;
|
|
343
327
|
}
|
package/src/theia.ts
CHANGED
|
@@ -18,10 +18,11 @@ import * as temp from 'temp';
|
|
|
18
18
|
import * as yargs from 'yargs';
|
|
19
19
|
import yargsFactory = require('yargs/yargs');
|
|
20
20
|
import { ApplicationPackageManager, rebuild } from '@theia/application-manager';
|
|
21
|
-
import { ApplicationProps } from '@theia/application-package';
|
|
21
|
+
import { ApplicationProps, DEFAULT_SUPPORTED_API_VERSION } from '@theia/application-package';
|
|
22
22
|
import checkHoisted from './check-hoisting';
|
|
23
23
|
import downloadPlugins from './download-plugins';
|
|
24
24
|
import runTest from './run-test';
|
|
25
|
+
import { extract } from '@theia/localization-manager';
|
|
25
26
|
|
|
26
27
|
process.on('unhandledRejection', (reason, promise) => {
|
|
27
28
|
throw reason;
|
|
@@ -45,17 +46,46 @@ function toStringArray(argv?: (string | number)[]): string[] | undefined {
|
|
|
45
46
|
: argv.map(arg => String(arg));
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
function rebuildCommand(command: string, target: ApplicationProps.Target): yargs.CommandModule<unknown, {
|
|
49
|
+
function rebuildCommand(command: string, target: ApplicationProps.Target): yargs.CommandModule<unknown, {
|
|
50
|
+
modules: string[]
|
|
51
|
+
cacheRoot?: string
|
|
52
|
+
force?: boolean
|
|
53
|
+
}> {
|
|
49
54
|
return {
|
|
50
55
|
command,
|
|
51
|
-
describe: `Rebuild native node modules for
|
|
56
|
+
describe: `Rebuild/revert native node modules for "${target}"`,
|
|
52
57
|
builder: {
|
|
58
|
+
'cacheRoot': {
|
|
59
|
+
type: 'string',
|
|
60
|
+
describe: 'Root folder where to store the .browser_modules cache'
|
|
61
|
+
},
|
|
53
62
|
'modules': {
|
|
54
|
-
|
|
63
|
+
alias: 'm',
|
|
64
|
+
array: true, // === `--modules/-m` can be specified multiple times
|
|
65
|
+
describe: 'List of modules to rebuild/revert'
|
|
55
66
|
},
|
|
67
|
+
'force': {
|
|
68
|
+
alias: 'f',
|
|
69
|
+
boolean: true,
|
|
70
|
+
describe: 'Rebuild modules for Electron anyway',
|
|
71
|
+
}
|
|
56
72
|
},
|
|
57
|
-
handler:
|
|
58
|
-
|
|
73
|
+
handler: ({ cacheRoot, modules, force }) => {
|
|
74
|
+
// Note: `modules` is actually `string[] | undefined`.
|
|
75
|
+
if (modules) {
|
|
76
|
+
// It is ergonomic to pass arguments as --modules="a,b,c,..."
|
|
77
|
+
// but yargs doesn't parse it this way by default.
|
|
78
|
+
const flattened: string[] = [];
|
|
79
|
+
for (const value of modules) {
|
|
80
|
+
if (value.includes(',')) {
|
|
81
|
+
flattened.push(...value.split(',').map(mod => mod.trim()));
|
|
82
|
+
} else {
|
|
83
|
+
flattened.push(value);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
modules = flattened;
|
|
87
|
+
}
|
|
88
|
+
rebuild(target, { cacheRoot, modules, force });
|
|
59
89
|
}
|
|
60
90
|
};
|
|
61
91
|
}
|
|
@@ -183,7 +213,7 @@ function theiaCli(): void {
|
|
|
183
213
|
'api-version': {
|
|
184
214
|
alias: 'v',
|
|
185
215
|
describe: 'Supported API version for plugins',
|
|
186
|
-
default:
|
|
216
|
+
default: DEFAULT_SUPPORTED_API_VERSION
|
|
187
217
|
},
|
|
188
218
|
'api-url': {
|
|
189
219
|
alias: 'u',
|
|
@@ -194,7 +224,53 @@ function theiaCli(): void {
|
|
|
194
224
|
handler: async ({ packed }) => {
|
|
195
225
|
await downloadPlugins({ packed });
|
|
196
226
|
},
|
|
197
|
-
})
|
|
227
|
+
})
|
|
228
|
+
.command<{
|
|
229
|
+
root: string,
|
|
230
|
+
output: string,
|
|
231
|
+
merge: boolean,
|
|
232
|
+
exclude?: string,
|
|
233
|
+
logs?: string,
|
|
234
|
+
files?: string[]
|
|
235
|
+
}>({
|
|
236
|
+
command: 'nls-extract',
|
|
237
|
+
describe: 'Extract translation key/value pairs from source code',
|
|
238
|
+
builder: {
|
|
239
|
+
'output': {
|
|
240
|
+
alias: 'o',
|
|
241
|
+
describe: 'Output file for the extracted translations',
|
|
242
|
+
demandOption: true
|
|
243
|
+
},
|
|
244
|
+
'root': {
|
|
245
|
+
alias: 'r',
|
|
246
|
+
describe: 'The directory which contains the source code',
|
|
247
|
+
default: '.'
|
|
248
|
+
},
|
|
249
|
+
'merge': {
|
|
250
|
+
alias: 'm',
|
|
251
|
+
describe: 'Whether to merge new with existing translation values',
|
|
252
|
+
boolean: true,
|
|
253
|
+
default: false
|
|
254
|
+
},
|
|
255
|
+
'exclude': {
|
|
256
|
+
alias: 'e',
|
|
257
|
+
describe: 'Allows to exclude translation keys starting with this value'
|
|
258
|
+
},
|
|
259
|
+
'files': {
|
|
260
|
+
alias: 'f',
|
|
261
|
+
describe: 'Glob pattern matching the files to extract from (starting from --root).',
|
|
262
|
+
array: true
|
|
263
|
+
},
|
|
264
|
+
'logs': {
|
|
265
|
+
alias: 'l',
|
|
266
|
+
describe: 'File path to a log file'
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
handler: async options => {
|
|
270
|
+
await extract(options);
|
|
271
|
+
}
|
|
272
|
+
})
|
|
273
|
+
.command<{
|
|
198
274
|
testInspect: boolean,
|
|
199
275
|
testExtension: string[],
|
|
200
276
|
testFile: string[],
|