Skip to content

Commit c8d2a28

Browse files
committed
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 f3f4e1b commit c8d2a28

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, IErrors } from "../common/declarations";
@@ -38,7 +37,7 @@ export class PreviewCommand extends Command({
3837
await this.installLatestPreviewCLI();
3938
}
4039

41-
const previewCLIPath = this.getPreviewCLIPath();
40+
const previewCLIPath = await this.getPreviewCLIPath();
4241

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

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

6868
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 {
76
IProjectData,
87
ITestInitializationService,
@@ -138,8 +137,12 @@ export class TestInitCommand extends Command({
138137
path: this.$options.path,
139138
});
140139

140+
const modulePath = await this.$packageManager.getInstalledPackagePath(
141+
mod.name,
142+
projectDir,
143+
);
141144
const modulePackageJsonContent = this.$fs.readJson(
142-
resolvePackageJSONPath(mod.name, { paths: [projectDir] }),
145+
path.join(modulePath, "package.json"),
143146
);
144147
const modulePeerDependencies =
145148
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
}
@@ -490,16 +494,15 @@ export class PrepareController
490494
SCOPED_ANDROID_RUNTIME_NAME;
491495
}
492496
// try reading from installed runtime first before reading from the npm registry...
493-
const installedRuntimePackageJSONPath = resolvePackageJSONPath(
494-
runtimePackageName,
495-
{
496-
paths: [projectData.projectDir],
497-
},
498-
);
497+
const installedRuntimePath =
498+
await this.$packageManager.getInstalledPackagePath(
499+
runtimePackageName,
500+
projectData.projectDir,
501+
);
499502

500-
if (installedRuntimePackageJSONPath) {
503+
if (installedRuntimePath) {
501504
installedRuntimePackageJSON = this.$fs.readJson(
502-
installedRuntimePackageJSONPath,
505+
path.join(installedRuntimePath, "package.json"),
503506
);
504507
}
505508
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)