Skip to content

Commit 141dfe2

Browse files
author
Fatme
authored
Merge pull request #4050 from mflor35/feat/pkg-mngr-impl
feat() - [Yarn Support - Part 1 and 2] Yarn and Package Manager implementation
2 parents 7033085 + 646aa5d commit 141dfe2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+440
-197
lines changed

lib/base-package-manager.ts

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { isInteractive } from "./common/helpers";
2+
3+
export class BasePackageManager {
4+
constructor(
5+
protected $childProcess: IChildProcess,
6+
private $hostInfo: IHostInfo,
7+
private packageManager: string
8+
) { }
9+
10+
protected getPackageManagerExecutableName(): string {
11+
let npmExecutableName = this.packageManager;
12+
13+
if (this.$hostInfo.isWindows) {
14+
npmExecutableName += ".cmd";
15+
}
16+
17+
return npmExecutableName;
18+
}
19+
20+
protected async processPackageManagerInstall(params: string[], opts: { cwd: string }) {
21+
const npmExecutable = this.getPackageManagerExecutableName();
22+
const stdioValue = isInteractive() ? "inherit" : "pipe";
23+
return await this.$childProcess.spawnFromEvent(npmExecutable, params, "close", { cwd: opts.cwd, stdio: stdioValue });
24+
}
25+
26+
protected getFlagsString(config: any, asArray: boolean): any {
27+
const array: Array<string> = [];
28+
for (const flag in config) {
29+
if (flag === "global" && this.packageManager !== 'yarn') {
30+
array.push(`--${flag}`);
31+
array.push(`${config[flag]}`);
32+
} else if (config[flag]) {
33+
if (flag === "dist-tags" || flag === "versions") {
34+
array.push(` ${flag}`);
35+
continue;
36+
}
37+
array.push(`--${flag}`);
38+
}
39+
}
40+
if (asArray) {
41+
return array;
42+
}
43+
44+
return array.join(" ");
45+
}
46+
}

lib/bootstrap.ts

+5-1
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,12 @@ $injector.require("itmsTransporterService", "./services/itmstransporter-service"
7979
$injector.requireCommand("setup|*", "./commands/setup");
8080
$injector.requireCommand(["setup|cloud", "cloud|setup"], "./commands/setup");
8181

82+
$injector.requirePublic("packageManager", "./package-manager");
8283
$injector.requirePublic("npm", "./node-package-manager");
83-
$injector.require("npmInstallationManager", "./npm-installation-manager");
84+
$injector.requirePublic("yarn", "./yarn-package-manager");
85+
$injector.requireCommand("package-manager|set", "./commands/package-manager-set");
86+
87+
$injector.require("packageInstallationManager", "./package-installation-manager");
8488
$injector.require("dynamicHelpProvider", "./dynamic-help-provider");
8589
$injector.require("mobilePlatformsCapabilities", "./mobile-platforms-capabilities");
8690
$injector.require("commandsServiceProvider", "./providers/commands-service-provider");

lib/commands/install.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class InstallCommand implements ICommand {
1313
private $logger: ILogger,
1414
private $fs: IFileSystem,
1515
private $stringParameter: ICommandParameter,
16-
private $npm: INodePackageManager) {
16+
private $packageManager: INodePackageManager) {
1717
this.$projectData.initializeProjectData();
1818
}
1919

@@ -54,7 +54,7 @@ export class InstallCommand implements ICommand {
5454
moduleName = devPrefix + moduleName;
5555
}
5656

57-
await this.$npm.install(moduleName, projectDir, {
57+
await this.$packageManager.install(moduleName, projectDir, {
5858
'save-dev': true,
5959
disableNpmInstall: this.$options.disableNpmInstall,
6060
frameworkPath: this.$options.frameworkPath,

lib/commands/plugin/create-plugin.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export class CreatePluginCommand implements ICommand {
1313
private $fs: IFileSystem,
1414
private $childProcess: IChildProcess,
1515
private $prompter: IPrompter,
16-
private $npm: INodePackageManager) { }
16+
private $packageManager: INodePackageManager) { }
1717

1818
public async execute(args: string[]): Promise<void> {
1919
const pluginRepoName = args[0];
@@ -45,7 +45,7 @@ export class CreatePluginCommand implements ICommand {
4545
try {
4646
spinner.start();
4747
const npmOptions: any = { silent: true };
48-
await this.$npm.install(cwd, cwd, npmOptions);
48+
await this.$packageManager.install(cwd, cwd, npmOptions);
4949
} finally {
5050
spinner.stop();
5151
}

lib/commands/test-init.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class TestInitCommand implements ICommand {
1212
mocha: ['chai']
1313
};
1414

15-
constructor(private $npm: INodePackageManager,
15+
constructor(private $packageManager: INodePackageManager,
1616
private $projectData: IProjectData,
1717
private $errors: IErrors,
1818
private $options: IOptions,
@@ -55,7 +55,7 @@ class TestInitCommand implements ICommand {
5555
if (mod.version) {
5656
moduleToInstall += `@${mod.version}`;
5757
}
58-
await this.$npm.install(moduleToInstall, projectDir, {
58+
await this.$packageManager.install(moduleToInstall, projectDir, {
5959
'save-dev': true,
6060
'save-exact': true,
6161
optional: false,
@@ -76,7 +76,7 @@ class TestInitCommand implements ICommand {
7676
// catch errors when a peerDependency is already installed
7777
// e.g karma is installed; karma-jasmine depends on karma and will try to install it again
7878
try {
79-
await this.$npm.install(`${peerDependency}@${dependencyVersion}`, projectDir, {
79+
await this.$packageManager.install(`${peerDependency}@${dependencyVersion}`, projectDir, {
8080
'save-dev': true,
8181
'save-exact': true,
8282
disableNpmInstall: false,
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
export class PackageManagerCommand implements ICommand {
3+
4+
constructor(private $userSettingsService: IUserSettingsService,
5+
private $errors: IErrors,
6+
private $stringParameter: ICommandParameter) { }
7+
8+
public allowedParameters: ICommandParameter[] = [this.$stringParameter];
9+
10+
public execute(args: string[]): Promise<void> {
11+
if (args[0] === 'yarn' ) {
12+
return this.$userSettingsService.saveSetting("packageManager", "yarn");
13+
} else if ( args[0] === 'npm') {
14+
return this.$userSettingsService.saveSetting("packageManager", "npm");
15+
}
16+
return this.$errors.fail(`${args[0]} is not a valid package manager. Only yarn or npm are supported.`);
17+
}
18+
}
19+
20+
$injector.registerCommand("package-manager|set", PackageManagerCommand);

lib/declarations.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ interface INodePackageManager {
5454
getCachePath(): Promise<string>;
5555
}
5656

57-
interface INpmInstallationManager {
57+
interface IPackageInstallationManager {
5858
install(packageName: string, packageDir: string, options?: INpmInstallOptions): Promise<any>;
5959
getLatestVersion(packageName: string): Promise<string>;
6060
getNextVersion(packageName: string): Promise<string>;
@@ -506,6 +506,7 @@ interface IOptions extends IRelease, IDeviceIdentifier, IJustLaunch, IAvd, IAvai
506506
framework: string;
507507
frameworkName: string;
508508
frameworkVersion: string;
509+
yarn: string,
509510
ipa: string;
510511
tsc: boolean;
511512
ts: boolean;

lib/node-package-manager.ts

+13-99
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,21 @@
11
import * as path from "path";
2+
import { BasePackageManager } from "./base-package-manager";
23
import { exported, cache } from "./common/decorators";
3-
import { isInteractive } from "./common/helpers";
44
import { CACACHE_DIRECTORY_NAME } from "./constants";
55

6-
export class NodePackageManager implements INodePackageManager {
6+
export class NodePackageManager extends BasePackageManager implements INodePackageManager {
77
private static SCOPED_DEPENDENCY_REGEXP = /^(@.+?)(?:@(.+?))?$/;
88
private static DEPENDENCY_REGEXP = /^(.+?)(?:@(.+?))?$/;
99

10-
constructor(private $fs: IFileSystem,
11-
private $hostInfo: IHostInfo,
10+
constructor(
11+
$childProcess: IChildProcess,
1212
private $errors: IErrors,
13-
private $childProcess: IChildProcess,
13+
private $fs: IFileSystem,
14+
$hostInfo: IHostInfo,
1415
private $logger: ILogger,
15-
private $httpClient: Server.IHttpClient) { }
16+
private $httpClient: Server.IHttpClient) {
17+
super($childProcess, $hostInfo, 'npm');
18+
}
1619

1720
@exported("npm")
1821
public async install(packageName: string, pathToSave: string, config: INodePackageManagerInstallOptions): Promise<INpmInstallResultInfo> {
@@ -53,7 +56,7 @@ export class NodePackageManager implements INodePackageManager {
5356
}
5457

5558
try {
56-
const spawnResult: ISpawnResult = await this.getNpmInstallResult(params, cwd);
59+
const spawnResult: ISpawnResult = await this.processPackageManagerInstall(params, { cwd });
5760

5861
// Whenever calling npm install without any arguments (hence installing all dependencies) no output is emitted on stdout
5962
// Luckily, whenever you call npm install to install all dependencies chances are you won't need the name/version of the package you're installing because there is none.
@@ -66,7 +69,7 @@ export class NodePackageManager implements INodePackageManager {
6669
// We cannot use the actual install with --json to get the information because of post-install scripts which may print on stdout
6770
// dry-run install is quite fast when the dependencies are already installed even for many dependencies (e.g. angular) so we can live with this approach
6871
// We need the --prefix here because without it no output is emitted on stdout because all the dependencies are already installed.
69-
const spawnNpmDryRunResult = await this.$childProcess.spawnFromEvent(this.getNpmExecutableName(), params, "close");
72+
const spawnNpmDryRunResult = await this.$childProcess.spawnFromEvent(this.getPackageManagerExecutableName(), params, "close");
7073
return this.parseNpmInstallResult(spawnNpmDryRunResult.stdout, spawnResult.stdout, packageName);
7174
} catch (err) {
7275
if (err.message && err.message.indexOf("EPEERINVALID") !== -1) {
@@ -136,43 +139,12 @@ export class NodePackageManager implements INodePackageManager {
136139
return path.join(cachePath.trim(), CACACHE_DIRECTORY_NAME);
137140
}
138141

139-
private getNpmExecutableName(): string {
140-
let npmExecutableName = "npm";
141-
142-
if (this.$hostInfo.isWindows) {
143-
npmExecutableName += ".cmd";
144-
}
145-
146-
return npmExecutableName;
147-
}
148-
149-
private getFlagsString(config: any, asArray: boolean): any {
150-
const array: Array<string> = [];
151-
for (const flag in config) {
152-
if (flag === "global") {
153-
array.push(`--${flag}`);
154-
array.push(`${config[flag]}`);
155-
} else if (config[flag]) {
156-
if (flag === "dist-tags" || flag === "versions") {
157-
array.push(` ${flag}`);
158-
continue;
159-
}
160-
array.push(`--${flag}`);
161-
}
162-
}
163-
if (asArray) {
164-
return array;
165-
}
166-
167-
return array.join(" ");
168-
}
169-
170142
private parseNpmInstallResult(npmDryRunInstallOutput: string, npmInstallOutput: string, userSpecifiedPackageName: string): INpmInstallResultInfo {
171143
// TODO: Add tests for this functionality
172144
try {
173145
const originalOutput: INpmInstallCLIResult | INpm5InstallCliResult = JSON.parse(npmDryRunInstallOutput);
174-
const npm5Output = <INpm5InstallCliResult> originalOutput;
175-
const npmOutput = <INpmInstallCLIResult> originalOutput;
146+
const npm5Output = <INpm5InstallCliResult>originalOutput;
147+
const npmOutput = <INpmInstallCLIResult>originalOutput;
176148
let name: string;
177149
_.forOwn(npmOutput.dependencies, (peerDependency: INpmPeerDependencyInfo, key: string) => {
178150
if (!peerDependency.required && !peerDependency.peerMissing) {
@@ -239,64 +211,6 @@ export class NodePackageManager implements INodePackageManager {
239211
version
240212
};
241213
}
242-
243-
private async getNpmInstallResult(params: string[], cwd: string): Promise<ISpawnResult> {
244-
return new Promise<ISpawnResult>((resolve, reject) => {
245-
const npmExecutable = this.getNpmExecutableName();
246-
const stdioValue = isInteractive() ? "inherit" : "pipe";
247-
248-
const childProcess = this.$childProcess.spawn(npmExecutable, params, { cwd, stdio: stdioValue });
249-
250-
let isFulfilled = false;
251-
let capturedOut = "";
252-
let capturedErr = "";
253-
254-
if (childProcess.stdout) {
255-
childProcess.stdout.on("data", (data: string) => {
256-
this.$logger.write(data.toString());
257-
capturedOut += data;
258-
});
259-
}
260-
261-
if (childProcess.stderr) {
262-
childProcess.stderr.on("data", (data: string) => {
263-
capturedErr += data;
264-
});
265-
}
266-
267-
childProcess.on("close", (arg: any) => {
268-
const exitCode = typeof arg === "number" ? arg : arg && arg.code;
269-
270-
if (exitCode === 0) {
271-
isFulfilled = true;
272-
const result = {
273-
stdout: capturedOut,
274-
stderr: capturedErr,
275-
exitCode
276-
};
277-
278-
resolve(result);
279-
} else {
280-
let errorMessage = `Command ${npmExecutable} ${params && params.join(" ")} failed with exit code ${exitCode}`;
281-
if (capturedErr) {
282-
errorMessage += ` Error output: \n ${capturedErr}`;
283-
}
284-
285-
if (!isFulfilled) {
286-
isFulfilled = true;
287-
reject(new Error(errorMessage));
288-
}
289-
}
290-
});
291-
292-
childProcess.on("error", (err: Error) => {
293-
if (!isFulfilled) {
294-
isFulfilled = true;
295-
reject(err);
296-
}
297-
});
298-
});
299-
}
300214
}
301215

302216
$injector.register("npm", NodePackageManager);

lib/options.ts

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export class Options {
7171
tsc: { type: OptionType.Boolean },
7272
ts: { type: OptionType.Boolean },
7373
typescript: { type: OptionType.Boolean },
74+
yarn: { type: OptionType.Boolean },
7475
androidTypings: { type: OptionType.Boolean },
7576
bundle: { type: OptionType.String },
7677
all: { type: OptionType.Boolean },

lib/npm-installation-manager.ts renamed to lib/package-installation-manager.ts

+7-6
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ import * as path from "path";
22
import * as semver from "semver";
33
import * as constants from "./constants";
44

5-
export class NpmInstallationManager implements INpmInstallationManager {
6-
constructor(private $npm: INodePackageManager,
5+
export class PackageInstallationManager implements IPackageInstallationManager {
6+
constructor(
7+
private $packageManager: INodePackageManager,
78
private $childProcess: IChildProcess,
89
private $logger: ILogger,
910
private $settingsService: ISettingsService,
@@ -32,7 +33,7 @@ export class NpmInstallationManager implements INpmInstallationManager {
3233
return latestVersion;
3334
}
3435

35-
const data = await this.$npm.view(packageName, { "versions": true });
36+
const data = await this.$packageManager.view(packageName, { "versions": true });
3637

3738
const maxSatisfying = semver.maxSatisfying(data, compatibleVersionRange);
3839
return maxSatisfying || latestVersion;
@@ -144,18 +145,18 @@ export class NpmInstallationManager implements INpmInstallationManager {
144145
npmOptions[dependencyType] = true;
145146
}
146147

147-
return await this.$npm.install(packageName, pathToSave, npmOptions);
148+
return await this.$packageManager.install(packageName, pathToSave, npmOptions);
148149
}
149150

150151
/**
151152
* This function must not be used with packageName being a URL or local file,
152153
* because npm view doens't work with those
153154
*/
154155
private async getVersion(packageName: string, version: string): Promise<string> {
155-
const data: any = await this.$npm.view(packageName, { "dist-tags": true });
156+
const data: any = await this.$packageManager.view(packageName, { "dist-tags": true });
156157
this.$logger.trace("Using version %s. ", data[version]);
157158

158159
return data[version];
159160
}
160161
}
161-
$injector.register("npmInstallationManager", NpmInstallationManager);
162+
$injector.register("packageInstallationManager", PackageInstallationManager);

0 commit comments

Comments
 (0)