-
-
Notifications
You must be signed in to change notification settings - Fork 194
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add experimental bun package manager support (#5791)
Co-authored-by: Igor Randjelovic <rigor789@gmail.com>
- Loading branch information
1 parent
7c87b49
commit f758f6c
Showing
11 changed files
with
533 additions
and
270 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
import * as path from "path"; | ||
import { BasePackageManager } from "./base-package-manager"; | ||
import { exported, cache } from "./common/decorators"; | ||
import { CACACHE_DIRECTORY_NAME } from "./constants"; | ||
import * as _ from "lodash"; | ||
import { | ||
INodePackageManagerInstallOptions, | ||
INpmInstallResultInfo, | ||
INpmsResult, | ||
} from "./declarations"; | ||
import { | ||
IChildProcess, | ||
IErrors, | ||
IFileSystem, | ||
IHostInfo, | ||
Server, | ||
} from "./common/declarations"; | ||
import { injector } from "./common/yok"; | ||
|
||
export class BunPackageManager extends BasePackageManager { | ||
constructor( | ||
$childProcess: IChildProcess, | ||
private $errors: IErrors, | ||
$fs: IFileSystem, | ||
$hostInfo: IHostInfo, | ||
private $logger: ILogger, | ||
private $httpClient: Server.IHttpClient, | ||
$pacoteService: IPacoteService | ||
) { | ||
super($childProcess, $fs, $hostInfo, $pacoteService, "bun"); | ||
} | ||
|
||
@exported("bun") | ||
public async install( | ||
packageName: string, | ||
pathToSave: string, | ||
config: INodePackageManagerInstallOptions | ||
): Promise<INpmInstallResultInfo> { | ||
if (config.disableNpmInstall) { | ||
return; | ||
} | ||
if (config.ignoreScripts) { | ||
config["ignore-scripts"] = true; | ||
} | ||
|
||
const packageJsonPath = path.join(pathToSave, "package.json"); | ||
const jsonContentBefore = this.$fs.readJson(packageJsonPath); | ||
|
||
const flags = this.getFlagsString(config, true); | ||
// TODO: Confirm desired behavior. The npm version uses --legacy-peer-deps | ||
// by default, we could use `--no-peer` for Bun if similar is needed; the | ||
// pnpm version uses `--shamefully-hoist`, but Bun has no similar flag. | ||
let params = ["install", "--legacy-peer-deps"]; | ||
const isInstallingAllDependencies = packageName === pathToSave; | ||
if (!isInstallingAllDependencies) { | ||
params.push(packageName); | ||
} | ||
|
||
params = params.concat(flags); | ||
const cwd = pathToSave; | ||
|
||
try { | ||
const result = await this.processPackageManagerInstall( | ||
packageName, | ||
params, | ||
{ cwd, isInstallingAllDependencies } | ||
); | ||
return result; | ||
} catch (err) { | ||
// Revert package.json contents to preserve valid state | ||
this.$fs.writeJson(packageJsonPath, jsonContentBefore); | ||
throw err; | ||
} | ||
} | ||
|
||
@exported("bun") | ||
public async uninstall( | ||
packageName: string, | ||
config?: any, | ||
cwd?: string | ||
): Promise<string> { | ||
const flags = this.getFlagsString(config, false); | ||
return this.$childProcess.exec(`bun remove ${packageName} ${flags}`, { | ||
cwd, | ||
}); | ||
} | ||
|
||
// Bun does not have a `view` command; use npm. | ||
@exported("bun") | ||
public async view(packageName: string, config: Object): Promise<any> { | ||
const wrappedConfig = _.extend({}, config, { json: true }); // always require view response as JSON | ||
|
||
const flags = this.getFlagsString(wrappedConfig, false); | ||
let viewResult: any; | ||
try { | ||
viewResult = await this.$childProcess.exec( | ||
`npm view ${packageName} ${flags}` | ||
); | ||
} catch (e) { | ||
this.$errors.fail(e.message); | ||
} | ||
|
||
try { | ||
return JSON.parse(viewResult); | ||
} catch (err) { | ||
return null; | ||
} | ||
} | ||
|
||
// Bun does not have a `search` command; use npm. | ||
@exported("bun") | ||
public async search(filter: string[], config: any): Promise<string> { | ||
const flags = this.getFlagsString(config, false); | ||
return this.$childProcess.exec(`npm search ${filter.join(" ")} ${flags}`); | ||
} | ||
|
||
public async searchNpms(keyword: string): Promise<INpmsResult> { | ||
// Bugs with npms.io: | ||
// 1. API returns no results when a valid package name contains @ or / | ||
// even if using encodeURIComponent(). | ||
// 2. npms.io's API no longer returns updated results; see | ||
// https://github.com/npms-io/npms-api/issues/112. Better to switch to | ||
// https://registry.npmjs.org/<query> | ||
const httpRequestResult = await this.$httpClient.httpRequest( | ||
`https://api.npms.io/v2/search?q=keywords:${keyword}` | ||
); | ||
const result: INpmsResult = JSON.parse(httpRequestResult.body); | ||
return result; | ||
} | ||
|
||
// Bun does not have a command analogous to `npm config get registry`; Bun | ||
// uses `bunfig.toml` to define custom registries. | ||
// - TODO: read `bunfig.toml`, if it exists, and return the registry URL. | ||
public async getRegistryPackageData(packageName: string): Promise<any> { | ||
const registry = await this.$childProcess.exec(`npm config get registry`); | ||
const url = registry.trim() + packageName; | ||
this.$logger.trace( | ||
`Trying to get data from npm registry for package ${packageName}, url is: ${url}` | ||
); | ||
const responseData = (await this.$httpClient.httpRequest(url)).body; | ||
this.$logger.trace( | ||
`Successfully received data from npm registry for package ${packageName}. Response data is: ${responseData}` | ||
); | ||
const jsonData = JSON.parse(responseData); | ||
this.$logger.trace( | ||
`Successfully parsed data from npm registry for package ${packageName}.` | ||
); | ||
return jsonData; | ||
} | ||
|
||
@cache() | ||
public async getCachePath(): Promise<string> { | ||
const cachePath = await this.$childProcess.exec(`bun pm cache`); | ||
return path.join(cachePath.trim(), CACACHE_DIRECTORY_NAME); | ||
} | ||
} | ||
|
||
injector.register("bun", BunPackageManager); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -492,4 +492,5 @@ export enum PackageManagers { | |
pnpm = "pnpm", | ||
yarn = "yarn", | ||
yarn2 = "yarn2", | ||
bun = "bun", | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { Yok } from "../lib/common/yok"; | ||
import * as stubs from "./stubs"; | ||
import { assert } from "chai"; | ||
import { BunPackageManager } from "../lib/bun-package-manager"; | ||
import { IInjector } from "../lib/common/definitions/yok"; | ||
|
||
function createTestInjector(configuration: {} = {}): IInjector { | ||
const injector = new Yok(); | ||
injector.register("hostInfo", {}); | ||
injector.register("errors", stubs.ErrorsStub); | ||
injector.register("logger", stubs.LoggerStub); | ||
injector.register("childProcess", stubs.ChildProcessStub); | ||
injector.register("httpClient", {}); | ||
injector.register("fs", stubs.FileSystemStub); | ||
injector.register("bun", BunPackageManager); | ||
injector.register("pacoteService", { | ||
manifest: () => Promise.resolve(), | ||
}); | ||
|
||
return injector; | ||
} | ||
|
||
describe("node-package-manager", () => { | ||
describe("getPackageNameParts", () => { | ||
[ | ||
{ | ||
name: "should return both name and version when valid fullName passed", | ||
templateFullName: "some-template@1.0.0", | ||
expectedVersion: "1.0.0", | ||
expectedName: "some-template", | ||
}, | ||
{ | ||
name: "should return both name and version when valid fullName with scope passed", | ||
templateFullName: "@nativescript/some-template@1.0.0", | ||
expectedVersion: "1.0.0", | ||
expectedName: "@nativescript/some-template", | ||
}, | ||
{ | ||
name: "should return only name when version is not specified and the template is scoped", | ||
templateFullName: "@nativescript/some-template", | ||
expectedVersion: "", | ||
expectedName: "@nativescript/some-template", | ||
}, | ||
{ | ||
name: "should return only name when version is not specified", | ||
templateFullName: "some-template", | ||
expectedVersion: "", | ||
expectedName: "some-template", | ||
}, | ||
].forEach((testCase) => { | ||
it(testCase.name, async () => { | ||
const testInjector = createTestInjector(); | ||
const npm = testInjector.resolve<BunPackageManager>("bun"); | ||
const templateNameParts = await npm.getPackageNameParts( | ||
testCase.templateFullName | ||
); | ||
assert.strictEqual(templateNameParts.name, testCase.expectedName); | ||
assert.strictEqual(templateNameParts.version, testCase.expectedVersion); | ||
}); | ||
}); | ||
}); | ||
|
||
describe("getPackageFullName", () => { | ||
[ | ||
{ | ||
name: "should return name and version when specified", | ||
templateName: "some-template", | ||
templateVersion: "1.0.0", | ||
expectedFullName: "some-template@1.0.0", | ||
}, | ||
{ | ||
name: "should return only the github url when no version specified", | ||
templateName: | ||
"https://github.com/NativeScript/template-drawer-navigation-ng#master", | ||
templateVersion: "", | ||
expectedFullName: | ||
"https://github.com/NativeScript/template-drawer-navigation-ng#master", | ||
}, | ||
{ | ||
name: "should return only the name when no version specified", | ||
templateName: "some-template", | ||
templateVersion: "", | ||
expectedFullName: "some-template", | ||
}, | ||
].forEach((testCase) => { | ||
it(testCase.name, async () => { | ||
const testInjector = createTestInjector(); | ||
const npm = testInjector.resolve<BunPackageManager>("bun"); | ||
const templateFullName = await npm.getPackageFullName({ | ||
name: testCase.templateName, | ||
version: testCase.templateVersion, | ||
}); | ||
assert.strictEqual(templateFullName, testCase.expectedFullName); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.