Skip to content

Commit 4904ab7

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 2dd6df5 commit 4904ab7

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
@@ -87,7 +87,7 @@ export class InstallCommand implements ICommand {
8787
}
8888

8989
await this.$packageManager.install(moduleName, projectDir, {
90-
"save-dev": true,
90+
dev: true,
9191
disableNpmInstall: this.$options.disableNpmInstall,
9292
frameworkPath: this.$options.frameworkPath,
9393
ignoreScripts: this.$options.ignoreScripts,

lib/commands/plugin/create-plugin.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,7 @@ export class CreatePluginCommand implements ICommand {
7676
const cwd = path.join(projectDir, "src");
7777
try {
7878
spinner.start();
79-
const npmOptions: any = { silent: true };
80-
await this.$packageManager.install(cwd, cwd, npmOptions);
79+
await this.$packageManager.install(cwd, cwd, { silent: true });
8180
} finally {
8281
spinner.stop();
8382
}

lib/commands/preview.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ export class PreviewCommand implements ICommand {
3636
`${PREVIEW_CLI_PACKAGE}@latest`,
3737
this.$projectData.projectDir,
3838
{
39-
"save-dev": true,
40-
"save-exact": true,
41-
} as any,
39+
dev: true,
40+
exact: true,
41+
},
4242
);
4343
}
4444

lib/commands/test-init.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,8 @@ class TestInitCommand implements ICommand {
148148
await this.$packageManager.install(moduleToInstall, projectDir, {
149149
// Packages with native code must land in "dependencies" — the CLI
150150
// integrates plugin platform files (pods, aars) only from there.
151-
...(mod.saveInDependencies ? { save: true } : { "save-dev": true }),
152-
"save-exact": true,
153-
optional: false,
151+
dev: !mod.saveInDependencies,
152+
exact: true,
154153
disableNpmInstall: this.$options.disableNpmInstall,
155154
frameworkPath: this.$options.frameworkPath,
156155
ignoreScripts: this.$options.ignoreScripts,
@@ -205,8 +204,8 @@ class TestInitCommand implements ICommand {
205204
`${peerDependency}@${dependencyVersion}`,
206205
projectDir,
207206
{
208-
"save-dev": true,
209-
"save-exact": true,
207+
dev: true,
208+
exact: true,
210209
disableNpmInstall: false,
211210
frameworkPath: this.$options.frameworkPath,
212211
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)