Skip to content

Commit d53badc

Browse files
rigor789edusperoni
authored andcommitted
feat(package-managers): resolve installed packages through the package manager
Add getInstalledPackagePath(packageName, fromDir) to the package manager contract. The base implementation walks node_modules the way Node does; a package manager with a different on-disk layout can override it. Services whose call chains are already async now ask the package manager where a package lives instead of resolving it themselves: plugins-service, doctor short-import scan, versions-service, prepare-controller's runtime package.json lookup, android-plugin-build-service's local gradle versions, the preview command, test-init and PackageInstallationManager. Sites reached only from synchronous code (getRuntimePackage in project-data-service, the bundler executable lookup, the vitest and karma readiness checks, the transitive walk in node-modules-dependencies-builder) keep using the resolution helper directly.
1 parent 0029a6d commit d53badc

20 files changed

Lines changed: 239 additions & 135 deletions

‎PublicAPI.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -571,6 +571,26 @@ tns.npm.search(["nativescript", "cloud"]).then(output => {
571571
});
572572
```
573573
574+
### getInstalledPackagePath
575+
Locates a package the way the selected package manager laid it out on disk, so callers never have to assume a `node_modules` layout.
576+
577+
* Definition:
578+
```TypeScript
579+
/**
580+
* @param {string} packageName The name of the package.
581+
* @param {string} fromDir The directory whose dependencies are searched, usually the project directory.
582+
* @return {Promise<string>} The absolute path of the package directory, or null when it is not installed.
583+
*/
584+
getInstalledPackagePath(packageName: string, fromDir: string): Promise<string>;
585+
```
586+
587+
* Usage:
588+
```JavaScript
589+
tns.packageManager.getInstalledPackagePath("@nativescript/core", "/tmp/myProject").then(pathToPackage => {
590+
console.log(pathToPackage ? `Installed at ${pathToPackage}` : "Not installed");
591+
});
592+
```
593+
574594
### view
575595
Provides information about a given package.
576596

‎lib/commands/preview.ts‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import { resolvePackagePath } from "@rigor789/resolve-package-path";
21
import * as path from "path";
32
import { color } from "../color";
43
import { IChildProcess } from "../common/declarations";
@@ -39,7 +38,7 @@ export class PreviewCommand extends Command({
3938
await this.installLatestPreviewCLI();
4039
}
4140

42-
const previewCLIPath = this.getPreviewCLIPath();
41+
const previewCLIPath = await this.getPreviewCLIPath();
4342

4443
if (!previewCLIPath) {
4544
await this.failMissingPreviewCLI();
@@ -60,10 +59,11 @@ export class PreviewCommand extends Command({
6059
);
6160
}
6261

63-
private getPreviewCLIPath(): string {
64-
return resolvePackagePath(PREVIEW_CLI_PACKAGE, {
65-
paths: [this.$projectData.projectDir],
66-
});
62+
private getPreviewCLIPath(): Promise<string> {
63+
return this.$packageManager.getInstalledPackagePath(
64+
PREVIEW_CLI_PACKAGE,
65+
this.$projectData.projectDir,
66+
);
6767
}
6868

6969
private async failMissingPreviewCLI(): Promise<void> {

‎lib/commands/test-init.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import * as path from "path";
22
import * as _ from "lodash";
33
import { TESTING_FRAMEWORKS, ProjectTypes } from "../constants";
44
import { fromWindowsRelativePathToUnix } from "../common/helpers";
5-
import { resolvePackageJSONPath } from "../helpers/package-path-helper";
65
import { ITestInitializationService } from "../definitions/project";
76
import { INodePackageManager } from "../declarations";
87
import { IPluginsService } from "../definitions/plugins";
@@ -135,8 +134,12 @@ export class TestInitCommand extends Command({
135134
path: this.options.path,
136135
});
137136

137+
const modulePath = await this.$packageManager.getInstalledPackagePath(
138+
mod.name,
139+
projectDir,
140+
);
138141
const modulePackageJsonContent = this.$fs.readJson(
139-
resolvePackageJSONPath(mod.name, { paths: [projectDir] }),
142+
path.join(modulePath, "package.json"),
140143
);
141144
const modulePeerDependencies =
142145
modulePackageJsonContent.peerDependencies || {};

‎lib/contracts/doctor-service.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,7 @@ export abstract class DoctorService {
3434
}): Promise<boolean>;
3535

3636
/** Checks and notifies users of deprecated short imports in their app. */
37-
abstract checkForDeprecatedShortImportsInAppDir(projectDir: string): void;
37+
abstract checkForDeprecatedShortImportsInAppDir(
38+
projectDir: string,
39+
): Promise<void>;
3840
}

‎lib/contracts/package-manager.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,17 @@ export abstract class PackageManager {
9999
*/
100100
abstract getCachePath(): Promise<string>;
101101

102+
/**
103+
* Locates a package the way the package manager laid it out on disk.
104+
* @param {string} packageName The name of the package.
105+
* @param {string} fromDir The directory whose dependencies are searched, usually the project directory.
106+
* @return {Promise<string>} The absolute path of the package directory, or null when it is not installed.
107+
*/
108+
abstract getInstalledPackagePath(
109+
packageName: string,
110+
fromDir: string,
111+
): Promise<string>;
112+
102113
/**
103114
* Gets the name of the package manager used for the current process.
104115
* It can be read from the user settings or by passing -- option.

‎lib/controllers/prepare-controller.ts‎

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ import {
2525
SupportedPlatform,
2626
TrackActionNames,
2727
} from "../constants";
28-
import { IOptions, IWatchIgnoreListService } from "../declarations";
28+
import {
29+
IOptions,
30+
IWatchIgnoreListService,
31+
IPackageManager,
32+
} from "../declarations";
2933
import {
3034
INodeModulesDependenciesBuilder,
3135
IPlatformController,
@@ -40,7 +44,6 @@ import {
4044
IProjectDataService,
4145
IProjectService,
4246
} from "../definitions/project";
43-
import { resolvePackageJSONPath } from "@rigor789/resolve-package-path";
4447

4548
interface IPlatformWatcherData {
4649
hasWebpackCompilerProcess: boolean;
@@ -82,6 +85,7 @@ export class PrepareController
8285
private $markingModeService: IMarkingModeService,
8386
private $projectConfigService: IProjectConfigService,
8487
private $projectService: IProjectService,
88+
private $packageManager: IPackageManager,
8589
) {
8690
super();
8791
}
@@ -493,16 +497,15 @@ export class PrepareController
493497
SCOPED_ANDROID_RUNTIME_NAME;
494498
}
495499
// try reading from installed runtime first before reading from the npm registry...
496-
const installedRuntimePackageJSONPath = resolvePackageJSONPath(
497-
runtimePackageName,
498-
{
499-
paths: [projectData.projectDir],
500-
},
501-
);
500+
const installedRuntimePath =
501+
await this.$packageManager.getInstalledPackagePath(
502+
runtimePackageName,
503+
projectData.projectDir,
504+
);
502505

503-
if (installedRuntimePackageJSONPath) {
506+
if (installedRuntimePath) {
504507
installedRuntimePackageJSON = this.$fs.readJson(
505-
installedRuntimePackageJSONPath,
508+
path.join(installedRuntimePath, "package.json"),
506509
);
507510
}
508511
const packageData: any = {

‎lib/declarations.d.ts‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,17 @@ interface INodePackageManager {
111111
* @returns {string} The full path to npm cache directory
112112
*/
113113
getCachePath(): Promise<string>;
114+
115+
/**
116+
* Locates a package the way the package manager laid it out on disk.
117+
* @param {string} packageName The name of the package.
118+
* @param {string} fromDir The directory whose dependencies are searched, usually the project directory.
119+
* @return {Promise<string>} The absolute path of the package directory, or null when it is not installed.
120+
*/
121+
getInstalledPackagePath(
122+
packageName: string,
123+
fromDir: string,
124+
): Promise<string>;
114125
}
115126

116127
/** @deprecated Kept so existing annotations compile; use the {@link PackageManager} contract. */

‎lib/package-managers/base-package-manager.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { isInteractive } from "../common/helpers";
2+
import { resolvePackagePath } from "../helpers/package-path-helper";
23
import {
34
INodePackageManager,
45
IPackageInstallOptions,
@@ -144,6 +145,13 @@ export abstract class BasePackageManager implements INodePackageManager {
144145
};
145146
}
146147

148+
public async getInstalledPackagePath(
149+
packageName: string,
150+
fromDir: string,
151+
): Promise<string> {
152+
return resolvePackagePath(packageName, { paths: [fromDir] }) || null;
153+
}
154+
147155
protected getInstallFlags(options: IPackageInstallOptions): string[] {
148156
return this.mapFlags(options, this.installFlags);
149157
}

‎lib/package-managers/index.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ export class PackageManager implements IPackageManager {
108108
return this.packageManager.getCachePath();
109109
}
110110

111+
@exported("packageManager")
112+
@invokeInit()
113+
public getInstalledPackagePath(
114+
packageName: string,
115+
fromDir: string
116+
): Promise<string> {
117+
return this.packageManager.getInstalledPackagePath(packageName, fromDir);
118+
}
119+
111120
public async getTagVersion(
112121
packageName: string,
113122
tag: string

‎lib/package-managers/package-installation-manager.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import * as path from "path";
22
import * as constants from "../constants";
3-
import { resolvePackagePath } from "../helpers/package-path-helper";
43
import {
54
INpmInstallOptions,
65
INpmInstallResultInfo,
@@ -190,9 +189,10 @@ export class PackageInstallationManager implements IPackageInstallationManager {
190189
projectDir: string
191190
): Promise<string> {
192191
// local installation takes precedence over cache
193-
const inspectorPath = resolvePackagePath(inspectorNpmPackageName, {
194-
paths: [projectDir],
195-
});
192+
const inspectorPath = await this.$packageManager.getInstalledPackagePath(
193+
inspectorNpmPackageName,
194+
projectDir
195+
);
196196
if (inspectorPath) {
197197
return inspectorPath;
198198
}
@@ -281,7 +281,10 @@ export class PackageInstallationManager implements IPackageInstallationManager {
281281
version,
282282
dev
283283
);
284-
return resolvePackagePath(installResultInfo.name, { paths: [pathToSave] });
284+
return this.$packageManager.getInstalledPackagePath(
285+
installResultInfo.name,
286+
pathToSave
287+
);
285288
}
286289

287290
private async npmInstall(

0 commit comments

Comments
 (0)