Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions PublicAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -479,7 +479,7 @@ tns.settingsService.setSettings({ userAgentName: "myUserAgent", profileDir: "cus
`npm` module provides a way to interact with npm specifically the use of install, uninstall, search and view commands.

### install
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.
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.
* Auxiliary interfaces:
```TypeScript
/**
Expand Down Expand Up @@ -533,11 +533,11 @@ Uninstalls a specified package.
/**
* Uninstalls a dependency
* @param {string} packageName The name of the dependency.
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate uninstallation.
* @param {IPackageUninstallOptions} options Package-manager-agnostic uninstallation options (`save`).
* @param {string} path The destination of the uninstallation.
* @return {Promise<any>} The output of the uninstallation.
*/
uninstall(packageName: string, config?: IDictionary<string | boolean>, path?: string): Promise<string>;
uninstall(packageName: string, options?: IPackageUninstallOptions, path?: string): Promise<string>;
```

* Usage:
Expand All @@ -556,39 +556,57 @@ Searches for a package using keywords.
```TypeScript
/**
* Searches for a package.
* @param {string[]} filter Keywords with which to perform the search.
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate search.
* @return {Promise<string>} The output of the uninstallation.
* @param {string[]} filter Keywords with which to perform the search.
* @return {Promise<string>} The raw search output.
*/
search(filter: string[], config: IDictionary<string | boolean>): Promise<string>;
search(filter: string[]): Promise<string>;
```

* Usage:
```JavaScript
tns.npm.search(["nativescript", "cloud"], { silent: true }).then(output => {
tns.npm.search(["nativescript", "cloud"]).then(output => {
console.log(`Found: ${output}`);
}, err => {
console.log("An error occurred during searching", err);
});
```

### getInstalledPackagePath
Locates a package the way the selected package manager laid it out on disk, so callers never have to assume a `node_modules` layout.

* Definition:
```TypeScript
/**
* @param {string} packageName The name of the package.
* @param {string} fromDir The directory whose dependencies are searched, usually the project directory.
* @return {string} The absolute path of the package directory, or null when it is not installed.
*/
getInstalledPackagePath(packageName: string, fromDir: string): string;
```

* Usage:
```JavaScript
const pathToPackage = tns.packageManager.getInstalledPackagePath("@nativescript/core", "/tmp/myProject");
console.log(pathToPackage ? `Installed at ${pathToPackage}` : "Not installed");
```

### view
Provides information about a given package.

* Definition
```TypeScript
/**
* Provides information about a given package.
* @param {string} packageName The name of the package.
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate view.
* @return {Promise<any>} Object, containing information about the package.
* Provides registry information about a package.
* @param {string} packageName The name of the package, optionally with a version.
* @param {string} field Optional single registry field (e.g. "versions" or "dist-tags") to return instead of the whole document.
* @return {Promise<any>} The parsed registry data.
*/
view(packageName: string, config: Object): Promise<any>;
view(packageName: string, field?: string): Promise<any>;
```

* Usage:
```JavaScript
tns.npm.view(["nativescript"], {}).then(result => {
tns.npm.view("nativescript").then(result => {
console.log(`${result.name}'s latest version is ${result["dist-tags"].latest}`);
}, err => {
console.log("An error occurred during viewing", err);
Expand Down
14 changes: 7 additions & 7 deletions lib/bootstrap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -379,12 +379,12 @@ registerBuiltInCommand<
typeof import("./commands/setup").setupCommandDefinition
>("setup|*", () => require("./commands/setup").setupCommandDefinition);

injector.requirePublic("packageManager", "./package-manager");
injector.requirePublic("npm", "./node-package-manager");
injector.requirePublic("yarn", "./yarn-package-manager");
injector.requirePublic("yarn2", "./yarn2-package-manager");
injector.requirePublic("pnpm", "./pnpm-package-manager");
injector.requirePublic("bun", "./bun-package-manager");
injector.requirePublic("packageManager", "./package-managers/index");
injector.requirePublic("npm", "./package-managers/npm");
injector.requirePublic("yarn", "./package-managers/yarn");
injector.requirePublic("yarn2", "./package-managers/yarn2");
injector.requirePublic("pnpm", "./package-managers/pnpm");
injector.requirePublic("bun", "./package-managers/bun");
registerBuiltInCommand<
typeof import("./common/commands/package-manager-get").packageManagerGetCommandDefinition
>(
Expand All @@ -404,7 +404,7 @@ registerBuiltInCommand<

injector.require(
"packageInstallationManager",
"./package-installation-manager",
"./package-managers/package-installation-manager",
);

injector.require("deviceLogProvider", "./common/mobile/device-log-provider");
Expand Down
2 changes: 1 addition & 1 deletion lib/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ async function installModule(
}

await $packageManager.install(moduleName, projectDir, {
"save-dev": true,
dev: true,
disableNpmInstall: context.options.disableNpmInstall,
frameworkPath: context.options.frameworkPath,
ignoreScripts: context.options.ignoreScripts,
Expand Down
3 changes: 1 addition & 2 deletions lib/commands/plugin/create-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,8 +180,7 @@ export class CreatePluginCommand extends Command({
const cwd = path.join(projectDir, "src");
try {
spinner.start();
const npmOptions: any = { silent: true };
await this.$packageManager.install(cwd, cwd, npmOptions);
await this.$packageManager.install(cwd, cwd, { silent: true });
} finally {
spinner.stop();
}
Expand Down
14 changes: 7 additions & 7 deletions lib/commands/preview.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { resolvePackagePath } from "@rigor789/resolve-package-path";
import * as path from "path";
import { color } from "../color";
import { IChildProcess, IErrors } from "../common/declarations";
Expand Down Expand Up @@ -53,16 +52,17 @@ export class PreviewCommand extends Command({
`${PREVIEW_CLI_PACKAGE}@latest`,
this.$projectData.projectDir,
{
"save-dev": true,
"save-exact": true,
} as any,
dev: true,
exact: true,
},
);
}

private getPreviewCLIPath(): string {
return resolvePackagePath(PREVIEW_CLI_PACKAGE, {
paths: [this.$projectData.projectDir],
});
return this.$packageManager.getInstalledPackagePath(
PREVIEW_CLI_PACKAGE,
this.$projectData.projectDir,
);
}

private async failMissingPreviewCLI(): Promise<void> {
Expand Down
19 changes: 11 additions & 8 deletions lib/commands/test-init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,18 +129,21 @@ export class TestInitCommand extends Command({
await this.$packageManager.install(moduleToInstall, projectDir, {
// Packages with native code must land in "dependencies" — the CLI
// integrates plugin platform files (pods, aars) only from there.
...(mod.saveInDependencies ? { save: true } : { "save-dev": true }),
"save-exact": true,
optional: false,
dev: !mod.saveInDependencies,
exact: true,
disableNpmInstall: this.$options.disableNpmInstall,
frameworkPath: this.$options.frameworkPath,
ignoreScripts: this.$options.ignoreScripts,
path: this.$options.path,
});

const modulePath = path.join(projectDir, "node_modules", mod.name);
const modulePackageJsonPath = path.join(modulePath, "package.json");
const modulePackageJsonContent = this.$fs.readJson(modulePackageJsonPath);
const modulePath = this.$packageManager.getInstalledPackagePath(
mod.name,
projectDir,
);
const modulePackageJsonContent = this.$fs.readJson(
path.join(modulePath, "package.json"),
);
const modulePeerDependencies =
modulePackageJsonContent.peerDependencies || {};
const modulePeerDependenciesMeta =
Expand Down Expand Up @@ -186,8 +189,8 @@ export class TestInitCommand extends Command({
`${peerDependency}@${dependencyVersion}`,
projectDir,
{
"save-dev": true,
"save-exact": true,
dev: true,
exact: true,
disableNpmInstall: false,
frameworkPath: this.$options.frameworkPath,
ignoreScripts: this.$options.ignoreScripts,
Expand Down
5 changes: 5 additions & 0 deletions lib/common/definitions/json-file-settings-service.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ interface IJsonFileSettingsService {
settingName: string,
cacheOpts?: ICacheTimeoutOpts
): Promise<T>;
/**
* Reads a setting without taking the settings lock. Suitable for values that
* only change through explicit user commands, where a torn read is harmless.
*/
getSettingValueSync<T>(settingName: string): T;
saveSetting<T>(
key: string,
value: T,
Expand Down
22 changes: 22 additions & 0 deletions lib/common/services/json-file-settings-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,28 @@ export class JsonFileSettingsService implements IJsonFileSettingsService {
);
}

public getSettingValueSync<T>(settingName: string): T {
if (!this.jsonSettingsData && this.$fs.exists(this.jsonSettingsFilePath)) {
try {
this.jsonSettingsData = parseJson(
this.$fs.readText(this.jsonSettingsFilePath)
);
} catch (err) {
this.$logger.trace(
`Error while trying to parse ${this.jsonSettingsFilePath}. Err is: ${err}`
);
return null;
}
}

if (this.jsonSettingsData && _.has(this.jsonSettingsData, settingName)) {
const data = this.jsonSettingsData[settingName];
return data.modifiedByCacheMechanism ? data.value : data;
}

return null;
}

public async saveSetting<T>(
key: string,
value: T,
Expand Down
31 changes: 31 additions & 0 deletions lib/common/test/unit-tests/services/json-file-settings-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,37 @@ describe("jsonFileSettingsService", () => {
Date.now = originalDateNow;
});

describe("getSettingValueSync", () => {
it("returns the stored value without going through the lock", () => {
const testInjector = createTestInjector();
dataInFile[jsonFileSettingsPath] = { prop1: "value1" };
const lockService = testInjector.resolve("lockService");
lockService.executeActionWithLock = () => {
throw new Error("lock must not be used for sync reads");
};

const jsonFileSettingsService =
testInjector.resolve<IJsonFileSettingsService>(
"jsonFileSettingsService",
{ jsonFileSettingsPath }
);
assert.equal(jsonFileSettingsService.getSettingValueSync("prop1"), "value1");
assert.isNull(jsonFileSettingsService.getSettingValueSync("missing"));
});

it("returns null when the settings file does not exist", () => {
const testInjector = createTestInjector();
const fs = testInjector.resolve("fs");
fs.exists = () => false;
const jsonFileSettingsService =
testInjector.resolve<IJsonFileSettingsService>(
"jsonFileSettingsService",
{ jsonFileSettingsPath }
);
assert.isNull(jsonFileSettingsService.getSettingValueSync("prop1"));
});
});

describe("getSettingValue", () => {
it("returns correct data without cache", async () => {
dataInFile = { [jsonFileSettingsPath]: { prop1: 1 } };
Expand Down
7 changes: 0 additions & 7 deletions lib/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,6 @@ export const TemplatesV2PackageJsonKeysToRemove: Array<String> = [
"nativescript",
];

export class SaveOptions {
static PRODUCTION = "save";
static DEV = "save-dev";
static OPTIONAL = "save-optional";
static EXACT = "save-exact";
}

export class ReleaseType {
static MAJOR = "major";
static PREMAJOR = "premajor";
Expand Down
41 changes: 24 additions & 17 deletions lib/contracts/package-manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Contract } from "../common/di/contract";
import type { IDictionary } from "../common/declarations";
import type {
INodePackageManagerInstallOptions,
IPackageInstallOptions,
IPackageUninstallOptions,
INpmInstallResultInfo,
INpmPackageNameParts,
INpmsResult,
Expand All @@ -17,35 +17,35 @@ export abstract class PackageManager {
* Installs dependency
* @param {string} packageName The name of the dependency - can be a path, a url or a string.
* @param {string} pathToSave The destination of the installation.
* @param {INodePackageManagerInstallOptions} config Additional options that can be passed to manipulate installation.
* @param {IPackageInstallOptions} options Package-manager-agnostic installation options.
* @return {Promise<INpmInstallResultInfo>} Information about installed package.
*/
abstract install(
packageName: string,
pathToSave: string,
config: INodePackageManagerInstallOptions,
options: IPackageInstallOptions,
): Promise<INpmInstallResultInfo>;

/**
* Uninstalls a dependency
* @param {string} packageName The name of the dependency.
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate uninstallation.
* @param {IPackageUninstallOptions} options Package-manager-agnostic uninstallation options.
* @param {string} path The destination of the uninstallation.
* @return {Promise<string>} The output of the uninstallation.
*/
abstract uninstall(
packageName: string,
config?: IDictionary<string | boolean>,
options?: IPackageUninstallOptions,
path?: string,
): Promise<string>;

/**
* Provides information about a given package.
* @param {string} packageName The name of the package.
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate view.
* @return {Promise<any>} Object, containing information about the package.
* @param {string} packageName The name of the package, optionally with a version.
* @param {string} field @optional A single registry field (e.g. "versions" or "dist-tags") to return instead of the whole document.
* @return {Promise<any>} The parsed registry data, or null when it cannot be parsed.
*/
abstract view(packageName: string, config: Object): Promise<any>;
abstract view(packageName: string, field?: string): Promise<any>;

/**
* Checks if the specified string is name of a packaged published in the NPM registry.
Expand Down Expand Up @@ -74,14 +74,10 @@ export abstract class PackageManager {

/**
* Searches for a package.
* @param {string[]} filter Keywords with which to perform the search.
* @param {IDictionary<string | boolean>} config Additional options that can be passed to manipulate search.
* @return {Promise<string>} The output of the uninstallation.
* @param {string[]} filter Keywords with which to perform the search.
* @return {Promise<string>} The raw search output.
*/
abstract search(
filter: string[],
config: IDictionary<string | boolean>,
): Promise<string>;
abstract search(filter: string[]): Promise<string>;

/**
* Searches for npm packages in npms by keyword.
Expand All @@ -103,6 +99,17 @@ export abstract class PackageManager {
*/
abstract getCachePath(): Promise<string>;

/**
* Locates a package the way the package manager laid it out on disk.
* @param {string} packageName The name of the package.
* @param {string} fromDir The directory whose dependencies are searched, usually the project directory.
* @return {string} The absolute path of the package directory, or null when it is not installed.
*/
abstract getInstalledPackagePath(
packageName: string,
fromDir: string,
): string;

/**
* Gets the name of the package manager used for the current process.
* It can be read from the user settings or by passing -- option.
Expand Down
Loading