@theia/application-manager 1.48.1 → 1.48.3

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.
@@ -1,263 +1,263 @@
1
- // *****************************************************************************
2
- // Copyright (C) 2017 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 * as path from 'path';
18
- import * as fs from 'fs-extra';
19
- import * as cp from 'child_process';
20
- import * as semver from 'semver';
21
- import { ApplicationPackage, ApplicationPackageOptions } from '@theia/application-package';
22
- import { WebpackGenerator, FrontendGenerator, BackendGenerator } from './generator';
23
- import { ApplicationProcess } from './application-process';
24
- import { GeneratorOptions } from './generator/abstract-generator';
25
- import yargs = require('yargs');
26
-
27
- // Declare missing exports from `@types/semver@7`
28
- declare module 'semver' {
29
- function minVersion(range: string): string;
30
- }
31
-
32
- class AbortError extends Error {
33
- constructor(...args: Parameters<ErrorConstructor>) {
34
- super(...args);
35
- Object.setPrototypeOf(this, AbortError.prototype);
36
- }
37
- }
38
-
39
- export class ApplicationPackageManager {
40
-
41
- static defineGeneratorOptions<T>(cli: yargs.Argv<T>): yargs.Argv<T & {
42
- mode: 'development' | 'production'
43
- splitFrontend?: boolean
44
- }> {
45
- return cli
46
- .option('mode', {
47
- description: 'Generation mode to use',
48
- choices: ['development', 'production'],
49
- default: 'production' as const,
50
- })
51
- .option('split-frontend', {
52
- description: 'Split frontend modules into separate chunks. By default enabled in the `development` mode and disabled in the `production` mode.',
53
- type: 'boolean'
54
- });
55
- }
56
-
57
- readonly pck: ApplicationPackage;
58
- /** application process */
59
- readonly process: ApplicationProcess;
60
- /** manager process */
61
- protected readonly __process: ApplicationProcess;
62
-
63
- constructor(options: ApplicationPackageOptions) {
64
- this.pck = new ApplicationPackage(options);
65
- this.process = new ApplicationProcess(this.pck, options.projectPath);
66
- this.__process = new ApplicationProcess(this.pck, path.join(__dirname, '..'));
67
- }
68
-
69
- protected async remove(fsPath: string): Promise<void> {
70
- if (await fs.pathExists(fsPath)) {
71
- await fs.remove(fsPath);
72
- }
73
- }
74
-
75
- async clean(): Promise<void> {
76
- const webpackGenerator = new WebpackGenerator(this.pck);
77
- await Promise.all([
78
- this.remove(this.pck.lib()),
79
- this.remove(this.pck.srcGen()),
80
- this.remove(webpackGenerator.genConfigPath),
81
- this.remove(webpackGenerator.genNodeConfigPath)
82
- ]);
83
- }
84
-
85
- async prepare(): Promise<void> {
86
- if (this.pck.isElectron()) {
87
- await this.prepareElectron();
88
- }
89
- }
90
-
91
- async generate(options: GeneratorOptions = {}): Promise<void> {
92
- try {
93
- await this.prepare();
94
- } catch (error) {
95
- if (error instanceof AbortError) {
96
- console.warn(error.message);
97
- process.exit(1);
98
- }
99
- throw error;
100
- }
101
- await Promise.all([
102
- new WebpackGenerator(this.pck, options).generate(),
103
- new BackendGenerator(this.pck, options).generate(),
104
- new FrontendGenerator(this.pck, options).generate(),
105
- ]);
106
- }
107
-
108
- async copy(): Promise<void> {
109
- await fs.ensureDir(this.pck.lib('frontend'));
110
- await fs.copy(this.pck.frontend('index.html'), this.pck.lib('frontend', 'index.html'));
111
- }
112
-
113
- async build(args: string[] = [], options: GeneratorOptions = {}): Promise<void> {
114
- await this.generate(options);
115
- await this.copy();
116
- return this.__process.run('webpack', args);
117
- }
118
-
119
- start(args: string[] = []): cp.ChildProcess {
120
- if (this.pck.isElectron()) {
121
- return this.startElectron(args);
122
- } else if (this.pck.isBrowserOnly()) {
123
- return this.startBrowserOnly(args);
124
- }
125
- return this.startBrowser(args);
126
- }
127
-
128
- startBrowserOnly(args: string[]): cp.ChildProcess {
129
- const { command, mainArgs, options } = this.adjustBrowserOnlyArgs(args);
130
- return this.__process.spawnBin(command, mainArgs, options);
131
- }
132
-
133
- adjustBrowserOnlyArgs(args: string[]): Readonly<{ command: string, mainArgs: string[]; options: cp.SpawnOptions }> {
134
- let { mainArgs, options } = this.adjustArgs(args);
135
-
136
- // first parameter: path to generated frontend
137
- // second parameter: disable cache to support watching
138
- mainArgs = ['lib/frontend', '-c-1', ...mainArgs];
139
-
140
- const portIndex = mainArgs.findIndex(v => v.startsWith('--port'));
141
- if (portIndex === -1) {
142
- mainArgs.push('--port=3000');
143
- }
144
-
145
- return { command: 'http-server', mainArgs, options };
146
- }
147
-
148
- startElectron(args: string[]): cp.ChildProcess {
149
- // If possible, pass the project root directory to electron rather than the script file so that Electron
150
- // can determine the app name. This requires that the package.json has a main field.
151
- let appPath = this.pck.projectPath;
152
-
153
- if (!this.pck.pck.main) {
154
- // Try the bundled electron app first
155
- appPath = this.pck.lib('backend', 'electron-main.js');
156
- if (!fs.existsSync(appPath)) {
157
- // Fallback to the generated electron app in src-gen
158
- appPath = this.pck.backend('electron-main.js');
159
- }
160
-
161
- console.warn(
162
- `WARNING: ${this.pck.packagePath} does not have a "main" entry.\n` +
163
- 'Please add the following line:\n' +
164
- ' "main": "lib/backend/electron-main.js"'
165
- );
166
- }
167
-
168
- const { mainArgs, options } = this.adjustArgs([appPath, ...args]);
169
- const electronCli = require.resolve('electron/cli.js', { paths: [this.pck.projectPath] });
170
- return this.__process.fork(electronCli, mainArgs, options);
171
- }
172
-
173
- startBrowser(args: string[]): cp.ChildProcess {
174
- const { mainArgs, options } = this.adjustArgs(args);
175
- // The backend must be a process group leader on UNIX in order to kill the tree later.
176
- // See https://nodejs.org/api/child_process.html#child_process_options_detached
177
- options.detached = process.platform !== 'win32';
178
- // Try the bundled backend app first
179
- let mainPath = this.pck.lib('backend', 'main.js');
180
- if (!fs.existsSync(mainPath)) {
181
- // Fallback to the generated backend file in src-gen
182
- mainPath = this.pck.backend('main.js');
183
- }
184
- return this.__process.fork(mainPath, mainArgs, options);
185
- }
186
-
187
- /**
188
- * Inject Theia's Electron-specific dependencies into the application's package.json.
189
- *
190
- * Only overwrite the Electron range if the current minimum supported version is lower than the recommended one.
191
- */
192
- protected async prepareElectron(): Promise<void> {
193
- let theiaElectron;
194
- try {
195
- theiaElectron = await import('@theia/electron');
196
- } catch (error) {
197
- if (error.code === 'ERR_MODULE_NOT_FOUND') {
198
- throw new AbortError('Please install @theia/electron as part of your Theia Electron application');
199
- }
200
- throw error;
201
- }
202
- const expectedRange = theiaElectron.electronRange;
203
- const appPackageJsonPath = this.pck.path('package.json');
204
- const appPackageJson = await fs.readJSON(appPackageJsonPath) as { devDependencies?: Record<string, string> };
205
- if (!appPackageJson.devDependencies) {
206
- appPackageJson.devDependencies = {};
207
- }
208
- const currentRange: string | undefined = appPackageJson.devDependencies.electron;
209
- if (!currentRange || semver.compare(semver.minVersion(currentRange), semver.minVersion(expectedRange)) < 0) {
210
- // Update the range with the recommended one and write it on disk.
211
- appPackageJson.devDependencies = this.insertAlphabetically(appPackageJson.devDependencies, 'electron', expectedRange);
212
- await fs.writeJSON(appPackageJsonPath, appPackageJson, { spaces: 2 });
213
- throw new AbortError('Updated dependencies, please run "install" again');
214
- }
215
- if (!theiaElectron.electronVersion || !semver.satisfies(theiaElectron.electronVersion, currentRange)) {
216
- throw new AbortError('Dependencies are out of sync, please run "install" again');
217
- }
218
- const ffmpeg = await import('@theia/ffmpeg');
219
- await ffmpeg.replaceFfmpeg();
220
- await ffmpeg.checkFfmpeg();
221
- }
222
-
223
- protected insertAlphabetically<T extends Record<string, string>>(object: T, key: string, value: string): T {
224
- const updated: Record<string, unknown> = {};
225
- for (const property of Object.keys(object)) {
226
- if (property.localeCompare(key) > 0) {
227
- updated[key] = value;
228
- }
229
- updated[property] = object[property];
230
- }
231
- if (!(key in updated)) {
232
- updated[key] = value;
233
- }
234
- return updated as T;
235
- }
236
-
237
- private adjustArgs(args: string[], forkOptions: cp.ForkOptions = {}): Readonly<{ mainArgs: string[]; options: cp.ForkOptions }> {
238
- const options = {
239
- ...this.forkOptions,
240
- forkOptions
241
- };
242
- const mainArgs = [...args];
243
- const inspectIndex = mainArgs.findIndex(v => v.startsWith('--inspect'));
244
- if (inspectIndex !== -1) {
245
- const inspectArg = mainArgs.splice(inspectIndex, 1)[0];
246
- options.execArgv = ['--nolazy', inspectArg];
247
- }
248
- return {
249
- mainArgs,
250
- options
251
- };
252
- }
253
-
254
- private get forkOptions(): cp.ForkOptions {
255
- return {
256
- stdio: [0, 1, 2, 'ipc'],
257
- env: {
258
- ...process.env,
259
- THEIA_PARENT_PID: String(process.pid)
260
- }
261
- };
262
- }
263
- }
1
+ // *****************************************************************************
2
+ // Copyright (C) 2017 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 * as path from 'path';
18
+ import * as fs from 'fs-extra';
19
+ import * as cp from 'child_process';
20
+ import * as semver from 'semver';
21
+ import { ApplicationPackage, ApplicationPackageOptions } from '@theia/application-package';
22
+ import { WebpackGenerator, FrontendGenerator, BackendGenerator } from './generator';
23
+ import { ApplicationProcess } from './application-process';
24
+ import { GeneratorOptions } from './generator/abstract-generator';
25
+ import yargs = require('yargs');
26
+
27
+ // Declare missing exports from `@types/semver@7`
28
+ declare module 'semver' {
29
+ function minVersion(range: string): string;
30
+ }
31
+
32
+ class AbortError extends Error {
33
+ constructor(...args: Parameters<ErrorConstructor>) {
34
+ super(...args);
35
+ Object.setPrototypeOf(this, AbortError.prototype);
36
+ }
37
+ }
38
+
39
+ export class ApplicationPackageManager {
40
+
41
+ static defineGeneratorOptions<T>(cli: yargs.Argv<T>): yargs.Argv<T & {
42
+ mode: 'development' | 'production'
43
+ splitFrontend?: boolean
44
+ }> {
45
+ return cli
46
+ .option('mode', {
47
+ description: 'Generation mode to use',
48
+ choices: ['development', 'production'],
49
+ default: 'production' as const,
50
+ })
51
+ .option('split-frontend', {
52
+ description: 'Split frontend modules into separate chunks. By default enabled in the `development` mode and disabled in the `production` mode.',
53
+ type: 'boolean'
54
+ });
55
+ }
56
+
57
+ readonly pck: ApplicationPackage;
58
+ /** application process */
59
+ readonly process: ApplicationProcess;
60
+ /** manager process */
61
+ protected readonly __process: ApplicationProcess;
62
+
63
+ constructor(options: ApplicationPackageOptions) {
64
+ this.pck = new ApplicationPackage(options);
65
+ this.process = new ApplicationProcess(this.pck, options.projectPath);
66
+ this.__process = new ApplicationProcess(this.pck, path.join(__dirname, '..'));
67
+ }
68
+
69
+ protected async remove(fsPath: string): Promise<void> {
70
+ if (await fs.pathExists(fsPath)) {
71
+ await fs.remove(fsPath);
72
+ }
73
+ }
74
+
75
+ async clean(): Promise<void> {
76
+ const webpackGenerator = new WebpackGenerator(this.pck);
77
+ await Promise.all([
78
+ this.remove(this.pck.lib()),
79
+ this.remove(this.pck.srcGen()),
80
+ this.remove(webpackGenerator.genConfigPath),
81
+ this.remove(webpackGenerator.genNodeConfigPath)
82
+ ]);
83
+ }
84
+
85
+ async prepare(): Promise<void> {
86
+ if (this.pck.isElectron()) {
87
+ await this.prepareElectron();
88
+ }
89
+ }
90
+
91
+ async generate(options: GeneratorOptions = {}): Promise<void> {
92
+ try {
93
+ await this.prepare();
94
+ } catch (error) {
95
+ if (error instanceof AbortError) {
96
+ console.warn(error.message);
97
+ process.exit(1);
98
+ }
99
+ throw error;
100
+ }
101
+ await Promise.all([
102
+ new WebpackGenerator(this.pck, options).generate(),
103
+ new BackendGenerator(this.pck, options).generate(),
104
+ new FrontendGenerator(this.pck, options).generate(),
105
+ ]);
106
+ }
107
+
108
+ async copy(): Promise<void> {
109
+ await fs.ensureDir(this.pck.lib('frontend'));
110
+ await fs.copy(this.pck.frontend('index.html'), this.pck.lib('frontend', 'index.html'));
111
+ }
112
+
113
+ async build(args: string[] = [], options: GeneratorOptions = {}): Promise<void> {
114
+ await this.generate(options);
115
+ await this.copy();
116
+ return this.__process.run('webpack', args);
117
+ }
118
+
119
+ start(args: string[] = []): cp.ChildProcess {
120
+ if (this.pck.isElectron()) {
121
+ return this.startElectron(args);
122
+ } else if (this.pck.isBrowserOnly()) {
123
+ return this.startBrowserOnly(args);
124
+ }
125
+ return this.startBrowser(args);
126
+ }
127
+
128
+ startBrowserOnly(args: string[]): cp.ChildProcess {
129
+ const { command, mainArgs, options } = this.adjustBrowserOnlyArgs(args);
130
+ return this.__process.spawnBin(command, mainArgs, options);
131
+ }
132
+
133
+ adjustBrowserOnlyArgs(args: string[]): Readonly<{ command: string, mainArgs: string[]; options: cp.SpawnOptions }> {
134
+ let { mainArgs, options } = this.adjustArgs(args);
135
+
136
+ // first parameter: path to generated frontend
137
+ // second parameter: disable cache to support watching
138
+ mainArgs = ['lib/frontend', '-c-1', ...mainArgs];
139
+
140
+ const portIndex = mainArgs.findIndex(v => v.startsWith('--port'));
141
+ if (portIndex === -1) {
142
+ mainArgs.push('--port=3000');
143
+ }
144
+
145
+ return { command: 'http-server', mainArgs, options };
146
+ }
147
+
148
+ startElectron(args: string[]): cp.ChildProcess {
149
+ // If possible, pass the project root directory to electron rather than the script file so that Electron
150
+ // can determine the app name. This requires that the package.json has a main field.
151
+ let appPath = this.pck.projectPath;
152
+
153
+ if (!this.pck.pck.main) {
154
+ // Try the bundled electron app first
155
+ appPath = this.pck.lib('backend', 'electron-main.js');
156
+ if (!fs.existsSync(appPath)) {
157
+ // Fallback to the generated electron app in src-gen
158
+ appPath = this.pck.backend('electron-main.js');
159
+ }
160
+
161
+ console.warn(
162
+ `WARNING: ${this.pck.packagePath} does not have a "main" entry.\n` +
163
+ 'Please add the following line:\n' +
164
+ ' "main": "lib/backend/electron-main.js"'
165
+ );
166
+ }
167
+
168
+ const { mainArgs, options } = this.adjustArgs([appPath, ...args]);
169
+ const electronCli = require.resolve('electron/cli.js', { paths: [this.pck.projectPath] });
170
+ return this.__process.fork(electronCli, mainArgs, options);
171
+ }
172
+
173
+ startBrowser(args: string[]): cp.ChildProcess {
174
+ const { mainArgs, options } = this.adjustArgs(args);
175
+ // The backend must be a process group leader on UNIX in order to kill the tree later.
176
+ // See https://nodejs.org/api/child_process.html#child_process_options_detached
177
+ options.detached = process.platform !== 'win32';
178
+ // Try the bundled backend app first
179
+ let mainPath = this.pck.lib('backend', 'main.js');
180
+ if (!fs.existsSync(mainPath)) {
181
+ // Fallback to the generated backend file in src-gen
182
+ mainPath = this.pck.backend('main.js');
183
+ }
184
+ return this.__process.fork(mainPath, mainArgs, options);
185
+ }
186
+
187
+ /**
188
+ * Inject Theia's Electron-specific dependencies into the application's package.json.
189
+ *
190
+ * Only overwrite the Electron range if the current minimum supported version is lower than the recommended one.
191
+ */
192
+ protected async prepareElectron(): Promise<void> {
193
+ let theiaElectron;
194
+ try {
195
+ theiaElectron = await import('@theia/electron');
196
+ } catch (error) {
197
+ if (error.code === 'ERR_MODULE_NOT_FOUND') {
198
+ throw new AbortError('Please install @theia/electron as part of your Theia Electron application');
199
+ }
200
+ throw error;
201
+ }
202
+ const expectedRange = theiaElectron.electronRange;
203
+ const appPackageJsonPath = this.pck.path('package.json');
204
+ const appPackageJson = await fs.readJSON(appPackageJsonPath) as { devDependencies?: Record<string, string> };
205
+ if (!appPackageJson.devDependencies) {
206
+ appPackageJson.devDependencies = {};
207
+ }
208
+ const currentRange: string | undefined = appPackageJson.devDependencies.electron;
209
+ if (!currentRange || semver.compare(semver.minVersion(currentRange), semver.minVersion(expectedRange)) < 0) {
210
+ // Update the range with the recommended one and write it on disk.
211
+ appPackageJson.devDependencies = this.insertAlphabetically(appPackageJson.devDependencies, 'electron', expectedRange);
212
+ await fs.writeJSON(appPackageJsonPath, appPackageJson, { spaces: 2 });
213
+ throw new AbortError('Updated dependencies, please run "install" again');
214
+ }
215
+ if (!theiaElectron.electronVersion || !semver.satisfies(theiaElectron.electronVersion, currentRange)) {
216
+ throw new AbortError('Dependencies are out of sync, please run "install" again');
217
+ }
218
+ const ffmpeg = await import('@theia/ffmpeg');
219
+ await ffmpeg.replaceFfmpeg();
220
+ await ffmpeg.checkFfmpeg();
221
+ }
222
+
223
+ protected insertAlphabetically<T extends Record<string, string>>(object: T, key: string, value: string): T {
224
+ const updated: Record<string, unknown> = {};
225
+ for (const property of Object.keys(object)) {
226
+ if (property.localeCompare(key) > 0) {
227
+ updated[key] = value;
228
+ }
229
+ updated[property] = object[property];
230
+ }
231
+ if (!(key in updated)) {
232
+ updated[key] = value;
233
+ }
234
+ return updated as T;
235
+ }
236
+
237
+ private adjustArgs(args: string[], forkOptions: cp.ForkOptions = {}): Readonly<{ mainArgs: string[]; options: cp.ForkOptions }> {
238
+ const options = {
239
+ ...this.forkOptions,
240
+ forkOptions
241
+ };
242
+ const mainArgs = [...args];
243
+ const inspectIndex = mainArgs.findIndex(v => v.startsWith('--inspect'));
244
+ if (inspectIndex !== -1) {
245
+ const inspectArg = mainArgs.splice(inspectIndex, 1)[0];
246
+ options.execArgv = ['--nolazy', inspectArg];
247
+ }
248
+ return {
249
+ mainArgs,
250
+ options
251
+ };
252
+ }
253
+
254
+ private get forkOptions(): cp.ForkOptions {
255
+ return {
256
+ stdio: [0, 1, 2, 'ipc'],
257
+ env: {
258
+ ...process.env,
259
+ THEIA_PARENT_PID: String(process.pid)
260
+ }
261
+ };
262
+ }
263
+ }