Skip to content

Commit e4feeb2

Browse files
committed
feat(yarn): implement get cache directory path
Remove the last hardcoded `npm` command so yarn will be used all the time when it is set as package manager
1 parent 141dfe2 commit e4feeb2

File tree

5 files changed

+40
-117
lines changed

5 files changed

+40
-117
lines changed

lib/base-package-manager.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export class BasePackageManager {
44
constructor(
55
protected $childProcess: IChildProcess,
66
private $hostInfo: IHostInfo,
7+
private $pacoteService: IPacoteService,
78
private packageManager: string
89
) { }
910

@@ -17,10 +18,23 @@ export class BasePackageManager {
1718
return npmExecutableName;
1819
}
1920

20-
protected async processPackageManagerInstall(params: string[], opts: { cwd: string }) {
21+
protected async processPackageManagerInstall(packageName: string, params: string[], opts: { cwd: string, isInstallingAllDependencies: boolean }): Promise<INpmInstallResultInfo> {
2122
const npmExecutable = this.getPackageManagerExecutableName();
2223
const stdioValue = isInteractive() ? "inherit" : "pipe";
23-
return await this.$childProcess.spawnFromEvent(npmExecutable, params, "close", { cwd: opts.cwd, stdio: stdioValue });
24+
await this.$childProcess.spawnFromEvent(npmExecutable, params, "close", { cwd: opts.cwd, stdio: stdioValue });
25+
26+
// Whenever calling "npm install" or "yarn add" without any arguments (hence installing all dependencies) no output is emitted on stdout
27+
// Luckily, whenever you call "npm install" or "yarn add" to install all dependencies chances are you won't need the name/version of the package you're installing because there is none.
28+
const { isInstallingAllDependencies } = opts;
29+
if (isInstallingAllDependencies) {
30+
return null;
31+
}
32+
33+
const packageMetadata = await this.$pacoteService.manifest(packageName);
34+
return {
35+
name: packageMetadata.name,
36+
version: packageMetadata.version
37+
};
2438
}
2539

2640
protected getFlagsString(config: any, asArray: boolean): any {

lib/definitions/pacote-service.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ declare global {
99
* @param packageName The name of the package
1010
* @param options The provided options can control which properties from package.json file will be returned. In case when fullMetadata option is provided, all data from package.json file will be returned.
1111
*/
12-
manifest(packageName: string, options: IPacoteManifestOptions): Promise<any>;
12+
manifest(packageName: string, options?: IPacoteManifestOptions): Promise<any>;
1313
/**
1414
* Downloads the specified package and extracts it in specified destination directory
1515
* @param packageName The name of the package

lib/node-package-manager.ts

Lines changed: 5 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@ import { exported, cache } from "./common/decorators";
44
import { CACACHE_DIRECTORY_NAME } from "./constants";
55

66
export class NodePackageManager extends BasePackageManager implements INodePackageManager {
7-
private static SCOPED_DEPENDENCY_REGEXP = /^(@.+?)(?:@(.+?))?$/;
8-
private static DEPENDENCY_REGEXP = /^(.+?)(?:@(.+?))?$/;
9-
107
constructor(
118
$childProcess: IChildProcess,
129
private $errors: IErrors,
1310
private $fs: IFileSystem,
1411
$hostInfo: IHostInfo,
1512
private $logger: ILogger,
16-
private $httpClient: Server.IHttpClient) {
17-
super($childProcess, $hostInfo, 'npm');
13+
private $httpClient: Server.IHttpClient,
14+
$pacoteService: IPacoteService) {
15+
super($childProcess, $hostInfo, $pacoteService, 'npm');
1816
}
1917

2018
@exported("npm")
@@ -56,21 +54,8 @@ export class NodePackageManager extends BasePackageManager implements INodePacka
5654
}
5755

5856
try {
59-
const spawnResult: ISpawnResult = await this.processPackageManagerInstall(params, { cwd });
60-
61-
// Whenever calling npm install without any arguments (hence installing all dependencies) no output is emitted on stdout
62-
// 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.
63-
if (isInstallingAllDependencies) {
64-
return null;
65-
}
66-
67-
params = params.concat(["--json", "--dry-run", "--prefix", cwd]);
68-
// After the actual install runs successfully execute a dry-run in order to get information about the package.
69-
// We cannot use the actual install with --json to get the information because of post-install scripts which may print on stdout
70-
// 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
71-
// We need the --prefix here because without it no output is emitted on stdout because all the dependencies are already installed.
72-
const spawnNpmDryRunResult = await this.$childProcess.spawnFromEvent(this.getPackageManagerExecutableName(), params, "close");
73-
return this.parseNpmInstallResult(spawnNpmDryRunResult.stdout, spawnResult.stdout, packageName);
57+
const result = await this.processPackageManagerInstall(packageName, params, { cwd, isInstallingAllDependencies });
58+
return result;
7459
} catch (err) {
7560
if (err.message && err.message.indexOf("EPEERINVALID") !== -1) {
7661
// Not installed peer dependencies are treated by npm 2 as errors, but npm 3 treats them as warnings.
@@ -138,79 +123,6 @@ export class NodePackageManager extends BasePackageManager implements INodePacka
138123
const cachePath = await this.$childProcess.exec(`npm config get cache`);
139124
return path.join(cachePath.trim(), CACACHE_DIRECTORY_NAME);
140125
}
141-
142-
private parseNpmInstallResult(npmDryRunInstallOutput: string, npmInstallOutput: string, userSpecifiedPackageName: string): INpmInstallResultInfo {
143-
// TODO: Add tests for this functionality
144-
try {
145-
const originalOutput: INpmInstallCLIResult | INpm5InstallCliResult = JSON.parse(npmDryRunInstallOutput);
146-
const npm5Output = <INpm5InstallCliResult>originalOutput;
147-
const npmOutput = <INpmInstallCLIResult>originalOutput;
148-
let name: string;
149-
_.forOwn(npmOutput.dependencies, (peerDependency: INpmPeerDependencyInfo, key: string) => {
150-
if (!peerDependency.required && !peerDependency.peerMissing) {
151-
name = key;
152-
return false;
153-
}
154-
});
155-
156-
// Npm 5 return different object after performing `npm install --dry-run`.
157-
// We find the correct dependency by searching for the `userSpecifiedPackageName` in the
158-
// `npm5Output.updated` array and as a fallback, considering that the dependency is already installed,
159-
// we find it as the first element.
160-
if (!name && npm5Output.updated) {
161-
const packageNameWithoutVersion = userSpecifiedPackageName.split('@')[0];
162-
const updatedDependency = _.find(npm5Output.updated, ['name', packageNameWithoutVersion]) || npm5Output.updated[0];
163-
return {
164-
name: updatedDependency.name,
165-
originalOutput,
166-
version: updatedDependency.version
167-
};
168-
}
169-
const dependency = _.pick<INpmDependencyInfo, INpmDependencyInfo | INpmPeerDependencyInfo>(npmOutput.dependencies, name);
170-
return {
171-
name,
172-
originalOutput,
173-
version: dependency[name].version
174-
};
175-
} catch (err) {
176-
this.$logger.trace(`Unable to parse result of npm --dry-run operation. Output is: ${npmDryRunInstallOutput}.`);
177-
this.$logger.trace("Now we'll try to parse the real output of npm install command.");
178-
179-
const npmOutputMatchRegExp = /^.--\s+(?!UNMET)(.*)@((?:\d+\.){2}\d+)/m;
180-
const match = npmInstallOutput.match(npmOutputMatchRegExp);
181-
if (match) {
182-
return {
183-
name: match[1],
184-
version: match[2]
185-
};
186-
}
187-
}
188-
189-
this.$logger.trace("Unable to get information from npm installation, trying to return value specified by user.");
190-
return this.getDependencyInformation(userSpecifiedPackageName);
191-
}
192-
193-
private getDependencyInformation(dependency: string): INpmInstallResultInfo {
194-
const scopeDependencyMatch = dependency.match(NodePackageManager.SCOPED_DEPENDENCY_REGEXP);
195-
let name: string = null;
196-
let version: string = null;
197-
198-
if (scopeDependencyMatch) {
199-
name = scopeDependencyMatch[1];
200-
version = scopeDependencyMatch[2];
201-
} else {
202-
const matches = dependency.match(NodePackageManager.DEPENDENCY_REGEXP);
203-
if (matches) {
204-
name = matches[1];
205-
version = matches[2];
206-
}
207-
}
208-
209-
return {
210-
name,
211-
version
212-
};
213-
}
214126
}
215127

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

lib/services/pacote-service.ts

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import * as pacote from "pacote";
22
import * as tar from "tar";
33
import * as path from "path";
4+
import { cache } from "../common/decorators";
45

56
export class PacoteService implements IPacoteService {
67
constructor(private $fs: IFileSystem,
7-
private $npm: INodePackageManager,
8-
private $proxyService: IProxyService,
9-
private $logger: ILogger) { }
8+
private $injector: IInjector,
9+
private $logger: ILogger,
10+
private $proxyService: IProxyService) { }
11+
12+
@cache()
13+
public get $packageManager(): INodePackageManager {
14+
// need to be resolved here due to cyclic dependency
15+
return this.$injector.resolve("packageManager");
16+
}
1017

1118
public async manifest(packageName: string, options?: IPacoteManifestOptions): Promise<any> {
1219
this.$logger.trace(`Calling pacoteService.manifest for packageName: '${packageName}' and options: ${options}`);
@@ -59,7 +66,7 @@ export class PacoteService implements IPacoteService {
5966

6067
private async getPacoteBaseOptions(): Promise<IPacoteBaseOptions> {
6168
// In case `tns create myapp --template https://github.com/NativeScript/template-hello-world.git` command is executed, pacote module throws an error if cache option is not provided.
62-
const cache = await this.$npm.getCachePath();
69+
const cache = await this.$packageManager.getCachePath();
6370
const pacoteOptions = { cache };
6471
const proxySettings = await this.$proxyService.getCache();
6572
if (proxySettings) {

lib/yarn-package-manager.ts

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ export class YarnPackageManager extends BasePackageManager implements INodePacka
1111
$hostInfo: IHostInfo,
1212
private $httpClient: Server.IHttpClient,
1313
private $logger: ILogger,
14-
private $pacoteService: IPacoteService
14+
$pacoteService: IPacoteService
1515
) {
16-
super($childProcess, $hostInfo, 'yarn');
16+
super($childProcess, $hostInfo, $pacoteService, 'yarn');
1717
}
1818

1919
@exported("yarn")
@@ -39,18 +39,8 @@ export class YarnPackageManager extends BasePackageManager implements INodePacka
3939
const cwd = pathToSave;
4040

4141
try {
42-
await this.processPackageManagerInstall(params, { cwd });
43-
44-
if (isInstallingAllDependencies) {
45-
return null;
46-
}
47-
48-
const packageMetadata = await this.$pacoteService.manifest(packageName, {});
49-
return {
50-
name: packageMetadata.name,
51-
version: packageMetadata.version
52-
};
53-
42+
const result = await this.processPackageManagerInstall(packageName, params, { cwd, isInstallingAllDependencies });
43+
return result;
5444
} catch (e) {
5545
this.$fs.writeJson(packageJsonPath, jsonContentBefore);
5646
throw e;
@@ -102,9 +92,9 @@ export class YarnPackageManager extends BasePackageManager implements INodePacka
10292
}
10393

10494
@exported("yarn")
105-
getCachePath(): Promise<string> {
106-
this.$errors.fail("Method not implemented");
107-
return null;
95+
public async getCachePath(): Promise<string> {
96+
const result = await this.$childProcess.exec(`yarn cache dir`);
97+
return result;
10898
}
10999
}
110100

0 commit comments

Comments
 (0)