Skip to content

Commit 1157cae

Browse files
committed
refactor: type package manager install and uninstall options
Replace the untyped npm flag bag passed to install/uninstall with IPackageInstallOptions and IPackageUninstallOptions (save, dev, optional, exact, silent, ignoreScripts plus the CLI-internal options). Each package manager declares how it spells each option, and options a manager has no flag for are dropped instead of leaking npm syntax onto its command line. This fixes yarn berry receiving --save-dev / --save-exact (silently dropped, so platforms landed in dependencies) and --ignore-scripts (an unknown option that aborted the install); it now gets --dev, --exact and --mode=skip-build. bun receives its own --dev / --exact instead of npm's. Also settle the implementation class names on NpmPackageManager, YarnPackageManager, Yarn2PackageManager, PnpmPackageManager and BunPackageManager.
1 parent f40ff39 commit 1157cae

30 files changed

Lines changed: 556 additions & 208 deletions

‎PublicAPI.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ tns.settingsService.setSettings({ userAgentName: "myUserAgent", profileDir: "cus
479479
`npm` module provides a way to interact with npm specifically the use of install, uninstall, search and view commands.
480480
481481
### install
482-
Installs specified package. Note that you can use the third argument in order to pass different options to the installation like `ignore-scripts`, `save` or `save-exact` which work exactly like they would if you would execute npm from the command line and pass them as `--` flags.
482+
Installs specified package. The third argument takes package-manager-agnostic options (`dev`, `exact`, `save`, `optional`, `silent`, `ignoreScripts`); the selected package manager maps them onto its own command line flags.
483483
* Auxiliary interfaces:
484484
```TypeScript
485485
/**
@@ -533,11 +533,11 @@ Uninstalls a specified package.
533533
/**
534534
* Uninstalls a dependency
535535
* @param {string} packageName The name of the dependency.
536-
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate uninstallation.
536+
* @param {IPackageUninstallOptions} options Package-manager-agnostic uninstallation options (`save`).
537537
* @param {string} path The destination of the uninstallation.
538538
* @return {Promise<any>} The output of the uninstallation.
539539
*/
540-
uninstall(packageName: string, config?: IDictionary<string | boolean>, path?: string): Promise<string>;
540+
uninstall(packageName: string, options?: IPackageUninstallOptions, path?: string): Promise<string>;
541541
```
542542
543543
* Usage:

‎lib/commands/install.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ async function installModule(
9595
}
9696

9797
await $packageManager.install(moduleName, projectDir, {
98-
"save-dev": true,
98+
dev: true,
9999
disableNpmInstall: context.options.disableNpmInstall,
100100
frameworkPath: context.options.frameworkPath,
101101
ignoreScripts: context.options.ignoreScripts,

‎lib/commands/plugin/create-plugin.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,8 +180,7 @@ export class CreatePluginCommand extends Command({
180180
const cwd = path.join(projectDir, "src");
181181
try {
182182
spinner.start();
183-
const npmOptions: any = { silent: true };
184-
await this.$packageManager.install(cwd, cwd, npmOptions);
183+
await this.$packageManager.install(cwd, cwd, { silent: true });
185184
} finally {
186185
spinner.stop();
187186
}

‎lib/commands/preview.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ export class PreviewCommand extends Command({
5353
`${PREVIEW_CLI_PACKAGE}@latest`,
5454
this.$projectData.projectDir,
5555
{
56-
"save-dev": true,
57-
"save-exact": true,
58-
} as any,
56+
dev: true,
57+
exact: true,
58+
},
5959
);
6060
}
6161

‎lib/commands/test-init.ts‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,8 @@ export class TestInitCommand extends Command({
129129
await this.$packageManager.install(moduleToInstall, projectDir, {
130130
// Packages with native code must land in "dependencies" — the CLI
131131
// integrates plugin platform files (pods, aars) only from there.
132-
...(mod.saveInDependencies ? { save: true } : { "save-dev": true }),
133-
"save-exact": true,
134-
optional: false,
132+
dev: !mod.saveInDependencies,
133+
exact: true,
135134
disableNpmInstall: this.$options.disableNpmInstall,
136135
frameworkPath: this.$options.frameworkPath,
137136
ignoreScripts: this.$options.ignoreScripts,
@@ -186,8 +185,8 @@ export class TestInitCommand extends Command({
186185
`${peerDependency}@${dependencyVersion}`,
187186
projectDir,
188187
{
189-
"save-dev": true,
190-
"save-exact": true,
188+
dev: true,
189+
exact: true,
191190
disableNpmInstall: false,
192191
frameworkPath: this.$options.frameworkPath,
193192
ignoreScripts: this.$options.ignoreScripts,

‎lib/constants.ts‎

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,13 +126,6 @@ export const TemplatesV2PackageJsonKeysToRemove: Array<String> = [
126126
"nativescript",
127127
];
128128

129-
export class SaveOptions {
130-
static PRODUCTION = "save";
131-
static DEV = "save-dev";
132-
static OPTIONAL = "save-optional";
133-
static EXACT = "save-exact";
134-
}
135-
136129
export class ReleaseType {
137130
static MAJOR = "major";
138131
static PREMAJOR = "premajor";

‎lib/contracts/package-manager.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { Contract } from "../common/di/contract";
22
import type { IDictionary } from "../common/declarations";
33
import type {
4-
INodePackageManagerInstallOptions,
4+
IPackageInstallOptions,
5+
IPackageUninstallOptions,
56
INpmInstallResultInfo,
67
INpmPackageNameParts,
78
INpmsResult,
@@ -17,25 +18,25 @@ export abstract class PackageManager {
1718
* Installs dependency
1819
* @param {string} packageName The name of the dependency - can be a path, a url or a string.
1920
* @param {string} pathToSave The destination of the installation.
20-
* @param {INodePackageManagerInstallOptions} config Additional options that can be passed to manipulate installation.
21+
* @param {IPackageInstallOptions} options Package-manager-agnostic installation options.
2122
* @return {Promise<INpmInstallResultInfo>} Information about installed package.
2223
*/
2324
abstract install(
2425
packageName: string,
2526
pathToSave: string,
26-
config: INodePackageManagerInstallOptions,
27+
options: IPackageInstallOptions,
2728
): Promise<INpmInstallResultInfo>;
2829

2930
/**
3031
* Uninstalls a dependency
3132
* @param {string} packageName The name of the dependency.
32-
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate uninstallation.
33+
* @param {IPackageUninstallOptions} options Package-manager-agnostic uninstallation options.
3334
* @param {string} path The destination of the uninstallation.
3435
* @return {Promise<string>} The output of the uninstallation.
3536
*/
3637
abstract uninstall(
3738
packageName: string,
38-
config?: IDictionary<string | boolean>,
39+
options?: IPackageUninstallOptions,
3940
path?: string,
4041
): Promise<string>;
4142

‎lib/declarations.d.ts‎

Lines changed: 38 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,25 +27,25 @@ interface INodePackageManager {
2727
* Installs dependency
2828
* @param {string} packageName The name of the dependency - can be a path, a url or a string.
2929
* @param {string} pathToSave The destination of the installation.
30-
* @param {INodePackageManagerInstallOptions} config Additional options that can be passed to manipulate installation.
30+
* @param {IPackageInstallOptions} options Package-manager-agnostic installation options.
3131
* @return {Promise<INpmInstallResultInfo>} Information about installed package.
3232
*/
3333
install(
3434
packageName: string,
3535
pathToSave: string,
36-
config: INodePackageManagerInstallOptions,
36+
options: IPackageInstallOptions,
3737
): Promise<INpmInstallResultInfo>;
3838

3939
/**
4040
* Uninstalls a dependency
4141
* @param {string} packageName The name of the dependency.
42-
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate uninstallation.
42+
* @param {IPackageUninstallOptions} options Package-manager-agnostic uninstallation options.
4343
* @param {string} path The destination of the uninstallation.
4444
* @return {Promise<string>} The output of the uninstallation.
4545
*/
4646
uninstall(
4747
packageName: string,
48-
config?: IDictionary<string | boolean>,
48+
options?: IPackageUninstallOptions,
4949
path?: string,
5050
): Promise<string>;
5151

@@ -167,18 +167,42 @@ interface IPackageInstallationManager {
167167
}
168168

169169
/**
170-
* Describes options that can be passed to manipulate package installation.
170+
* Package-manager-agnostic installation options. Each package manager maps
171+
* these onto its own command line flags; options a manager has no flag for
172+
* are dropped rather than passed through.
171173
*/
172-
interface INodePackageManagerInstallOptions
173-
extends INpmInstallConfigurationOptions, IDictionary<string | boolean> {
174-
/**
175-
* Destination of the installation.
176-
* @type {string}
177-
* @optional
178-
*/
174+
interface IPackageInstallOptions {
175+
/**
176+
* Record the package in package.json. Every supported package manager
177+
* does this by default, so only `false` changes behaviour.
178+
*/
179+
save?: boolean;
180+
/** Record the package under devDependencies. */
181+
dev?: boolean;
182+
/** Record the package under optionalDependencies. */
183+
optional?: boolean;
184+
/** Pin the exact resolved version instead of a semver range. */
185+
exact?: boolean;
186+
/** Suppress the package manager's own output. */
187+
silent?: boolean;
188+
/** Do not run lifecycle scripts. */
189+
ignoreScripts?: boolean;
190+
/** Skip the installation entirely (the --disable-npm-install CLI flag). */
191+
disableNpmInstall?: boolean;
192+
/** Local runtime location (the --frameworkPath CLI flag). */
193+
frameworkPath?: string;
194+
/** Destination of the installation (the --path CLI flag). */
179195
path?: string;
180196
}
181197

198+
/**
199+
* Package-manager-agnostic uninstallation options.
200+
*/
201+
interface IPackageUninstallOptions {
202+
/** Remove the package from package.json. */
203+
save?: boolean;
204+
}
205+
182206
/**
183207
* Describes information about dependency packages.
184208
*/
@@ -396,7 +420,8 @@ interface INpmInstallResultInfo {
396420
interface INpmInstallOptions {
397421
pathToSave?: string;
398422
version?: string;
399-
dependencyType?: string;
423+
/** Record the package under devDependencies. */
424+
dev?: boolean;
400425
}
401426

402427
/**

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

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { isInteractive } from "../common/helpers";
22
import {
33
INodePackageManager,
4-
INodePackageManagerInstallOptions,
4+
IPackageInstallOptions,
5+
IPackageUninstallOptions,
56
INpmInstallResultInfo,
67
INpmsResult,
78
INpmPackageNameParts,
@@ -13,15 +14,33 @@ import {
1314
IHostInfo,
1415
} from "../common/declarations";
1516

17+
/**
18+
* How one package manager spells each IPackageInstallOptions flag on its
19+
* command line. A missing entry means the manager has no such flag and the
20+
* option is dropped rather than passed through.
21+
*/
22+
export interface IPackageManagerFlags {
23+
save?: string;
24+
noSave?: string;
25+
dev?: string;
26+
optional?: string;
27+
exact?: string;
28+
silent?: string;
29+
ignoreScripts?: string;
30+
}
31+
1632
export abstract class BasePackageManager implements INodePackageManager {
33+
protected abstract readonly installFlags: IPackageManagerFlags;
34+
protected abstract readonly uninstallFlags: IPackageManagerFlags;
35+
1736
public abstract install(
1837
packageName: string,
1938
pathToSave: string,
20-
config: INodePackageManagerInstallOptions,
39+
options: IPackageInstallOptions,
2140
): Promise<INpmInstallResultInfo>;
2241
public abstract uninstall(
2342
packageName: string,
24-
config?: IDictionary<string | boolean>,
43+
options?: IPackageUninstallOptions,
2544
path?: string,
2645
): Promise<string>;
2746
public abstract view(packageName: string, config: Object): Promise<any>;
@@ -133,6 +152,37 @@ export abstract class BasePackageManager implements INodePackageManager {
133152
};
134153
}
135154

155+
protected getInstallFlags(options: IPackageInstallOptions): string[] {
156+
return this.mapFlags(options, this.installFlags);
157+
}
158+
159+
protected getUninstallFlags(options: IPackageUninstallOptions): string[] {
160+
return this.mapFlags(options, this.uninstallFlags);
161+
}
162+
163+
private mapFlags(
164+
options: IPackageInstallOptions,
165+
flags: IPackageManagerFlags,
166+
): string[] {
167+
const result: string[] = [];
168+
if (!options) {
169+
return result;
170+
}
171+
const push = (flag?: string) => {
172+
if (flag) {
173+
result.push(flag);
174+
}
175+
};
176+
if (options.save === true) push(flags.save);
177+
if (options.save === false) push(flags.noSave);
178+
if (options.dev) push(flags.dev);
179+
if (options.optional) push(flags.optional);
180+
if (options.exact) push(flags.exact);
181+
if (options.silent) push(flags.silent);
182+
if (options.ignoreScripts) push(flags.ignoreScripts);
183+
return result;
184+
}
185+
136186
protected getFlagsString(config: any, asArray: boolean): any {
137187
const array: Array<string> = [];
138188
for (const flag in config) {

‎lib/package-managers/bun.ts‎

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { exported, cache } from "../common/decorators";
44
import { CACACHE_DIRECTORY_NAME } from "../constants";
55
import * as _ from "lodash";
66
import {
7-
INodePackageManagerInstallOptions,
7+
IPackageInstallOptions,
8+
IPackageUninstallOptions,
89
INpmInstallResultInfo,
910
INpmsResult,
1011
} from "../declarations";
@@ -17,7 +18,21 @@ import {
1718
} from "../common/declarations";
1819
import { injector } from "../common/yok";
1920

20-
export class Bun extends BasePackageManager {
21+
export class BunPackageManager extends BasePackageManager {
22+
protected readonly installFlags = {
23+
save: "--save",
24+
noSave: "--no-save",
25+
dev: "--dev",
26+
optional: "--optional",
27+
exact: "--exact",
28+
silent: "--silent",
29+
ignoreScripts: "--ignore-scripts",
30+
};
31+
protected readonly uninstallFlags = {
32+
save: "--save",
33+
noSave: "--no-save",
34+
};
35+
2136
constructor(
2237
$childProcess: IChildProcess,
2338
private $errors: IErrors,
@@ -34,19 +49,16 @@ export class Bun extends BasePackageManager {
3449
public async install(
3550
packageName: string,
3651
pathToSave: string,
37-
config: INodePackageManagerInstallOptions
52+
options: IPackageInstallOptions
3853
): Promise<INpmInstallResultInfo> {
39-
if (config.disableNpmInstall) {
54+
if (options.disableNpmInstall) {
4055
return;
4156
}
42-
if (config.ignoreScripts) {
43-
config["ignore-scripts"] = true;
44-
}
4557

4658
const packageJsonPath = path.join(pathToSave, "package.json");
4759
const jsonContentBefore = this.$fs.readJson(packageJsonPath);
4860

49-
const flags = this.getFlagsString(config, true);
61+
const flags = this.getInstallFlags(options);
5062
let params = ["install"];
5163
const isInstallingAllDependencies = packageName === pathToSave;
5264
if (!isInstallingAllDependencies) {
@@ -73,10 +85,10 @@ export class Bun extends BasePackageManager {
7385
@exported("bun")
7486
public async uninstall(
7587
packageName: string,
76-
config?: any,
88+
options?: IPackageUninstallOptions,
7789
cwd?: string
7890
): Promise<string> {
79-
const flags = this.getFlagsString(config, false);
91+
const flags = this.getUninstallFlags(options).join(" ");
8092
return this.$childProcess.exec(`bun remove ${packageName} ${flags}`, {
8193
cwd,
8294
});
@@ -152,4 +164,4 @@ export class Bun extends BasePackageManager {
152164
}
153165
}
154166

155-
injector.register("bun", Bun);
167+
injector.register("bun", BunPackageManager);

0 commit comments

Comments
 (0)