Skip to content

Commit

Permalink
feat(cli-core): add BunManager
Browse files Browse the repository at this point in the history
BREAKING CHANGE: prompt has changed and can break the third parties like Intellij/Vscode extension to generate Ts.ED project
  • Loading branch information
Romakita committed Dec 20, 2023
1 parent fd2f646 commit c19ebd3
Show file tree
Hide file tree
Showing 61 changed files with 2,093 additions and 729 deletions.
8 changes: 4 additions & 4 deletions packages/cli-core/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ module.exports = {
},
coverageThreshold: {
global: {
statements: 71.34,
branches: 72.93,
functions: 49.63,
lines: 71.34
statements: 73.9,
branches: 73.45,
functions: 52.15,
lines: 73.9
}
},
};
9 changes: 5 additions & 4 deletions packages/cli-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ export {
Container,
DITest
} from "@tsed/di";
export * from "./interfaces";
export * from "./decorators";
export * from "./services";
export * from "./utils";
export * from "./interfaces/index";
export * from "./decorators/index";
export * from "./services/index";
export * from "./packageManagers/index";
export * from "./utils/index";
export * from "./CliCore";
export {Inquirer};

Expand Down
193 changes: 193 additions & 0 deletions packages/cli-core/src/packageManagers/PackageManagersModule.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import {CliPlatformTest} from "@tsed/cli-testing";
import {PackageManagersModule} from "./PackageManagersModule";
import {YarnManager} from "./supports/YarnManager";
import {NpmManager} from "./supports/NpmManager";
import {CliFs} from "../services";
import {PNpmManager} from "./supports/PNpmManager";
import {YarnBerryManager} from "./supports/YarnBerryManager";
import {BunManager} from "./supports/BunManager";

async function getModuleFixture() {
const yarnManager = {
name: "yarn",
has() {
return true;
},
add: jest.fn().mockReturnValue({
pipe: jest.fn()
}),
addDev: jest.fn().mockReturnValue({
pipe: jest.fn()
}),
install: jest.fn().mockReturnValue({
pipe: jest.fn()
}),
init: jest.fn(),
runScript: jest.fn().mockReturnValue({
pipe: jest.fn()
})
};

const npmManager = {
name: "npm",
has() {
return true;
},
add: jest.fn(),
addDev: jest.fn(),
install: jest.fn(),
runScript: jest.fn()
};

const cliFs = {
exists: jest.fn().mockReturnValue(true),
writeFileSync: jest.fn(),
readJsonSync: jest.fn().mockReturnValue({
scripts: {},
dependencies: {},
devDependencies: {}
})
};

const module = await CliPlatformTest.invoke<PackageManagersModule>(PackageManagersModule, [
{
token: CliFs,
use: cliFs
},
{
token: YarnManager,
use: yarnManager
},
{
token: NpmManager,
use: npmManager
},
{
token: PNpmManager,
use: {
has() {
return false;
}
}
},
{
token: BunManager,
use: {
has() {
return false;
}
}
},
{
token: YarnBerryManager,
use: {
has() {
return false;
}
}
}
]);

return {
module,
cliFs,
yarnManager,
npmManager
};
}

describe("PackageManagersModule", () => {
beforeEach(() => CliPlatformTest.create());
afterEach(() => CliPlatformTest.reset());

describe("list()", () => {
it("should return the list of package managers", async () => {
const {module} = await getModuleFixture();

const result = module.list();

expect(result).toEqual(["npm", "yarn"]);
});
});

describe("get()", () => {
it("should return the package manager (default)", async () => {
const module = await CliPlatformTest.invoke<PackageManagersModule>(PackageManagersModule);

const result = module.get();

expect(result?.name).toEqual("yarn");
});
it("should return the package manager (npm)", async () => {
const module = await CliPlatformTest.invoke<PackageManagersModule>(PackageManagersModule);

const result = module.get("npm");

expect(result?.name).toEqual("npm");
});
it("should return the package manager (yarn)", async () => {
const module = await CliPlatformTest.invoke<PackageManagersModule>(PackageManagersModule);

const result = module.get("yarn");

expect(result?.name).toEqual("yarn");
});
it("should return the package manager (yarn-berry unknown)", async () => {
const module = await CliPlatformTest.invoke<PackageManagersModule>(PackageManagersModule);

const result = module.get("yarn-berry");

expect(result?.name).toEqual("npm");
});
});

describe("init()", () => {
it("should init a project", async () => {
const {module, yarnManager, cliFs} = await getModuleFixture();

module.init({});

expect(yarnManager.init).toHaveBeenCalledWith({
cwd: "./tmp",
env: {
...process.env,
GH_TOKEN: undefined
},
packageManager: "yarn"
});
expect(cliFs.writeFileSync).toHaveBeenCalledWith("tmp/package.json", expect.any(String), {encoding: "utf8"});
});
});

describe("install()", () => {
it("should install dependencies", async () => {
const {module, yarnManager} = await getModuleFixture();

const result = await module.install({});

for (const item of result) {
await item.task();
}

expect(yarnManager.install).toHaveBeenCalledWith({
cwd: "./tmp",
env: {
...process.env
},
packageManager: "yarn"
});
});
});

describe("runScript()", () => {
it("should run script", async () => {
const {module, yarnManager} = await getModuleFixture();

await module.runScript("name", {});

expect(yarnManager.runScript).toHaveBeenCalledWith("name", {
cwd: "./tmp"
});
});
});
});
167 changes: 167 additions & 0 deletions packages/cli-core/src/packageManagers/PackageManagersModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import {Inject, Injectable} from "@tsed/di";
import {BaseManager} from "./supports/BaseManager";
import {YarnManager} from "./supports/YarnManager";
import {YarnBerryManager} from "./supports/YarnBerryManager";
import {NpmManager} from "./supports/NpmManager";
import {PNpmManager} from "./supports/PNpmManager";
import {catchError} from "rxjs/operators";
import {EMPTY, throwError} from "rxjs";
import {ProjectPackageJson} from "../services/ProjectPackageJson";
import {isValidVersion} from "../utils/isValidVersion";
import {Options} from "execa";
import {BunManager} from "./supports/BunManager";

function mapPackagesWithInvalidVersion(deps: any) {
const toString = (info: [string, string]) => {
return info[1] === "latest" ? info[0] : info.join("@");
};

return Object.entries(deps)
.filter(([, version]) => !isValidVersion(version as string))
.map(toString);
}

export interface InstallOptions {
packageManager?: string;

[key: string]: any;
}

@Injectable({
imports: [YarnManager, YarnBerryManager, NpmManager, PNpmManager, BunManager]
})
export class PackageManagersModule {
@Inject()
protected projectPackageJson: ProjectPackageJson;

constructor(@Inject("package:manager") protected packageManagers: BaseManager[]) {
this.packageManagers = packageManagers.filter((manager) => manager.has());
}

init(options: InstallOptions = {}) {
const packageManager = this.get(options.packageManager);
options.packageManager = packageManager.name;

options = {
...options,
cwd: this.projectPackageJson.dir,
env: {
...process.env,
GH_TOKEN: this.projectPackageJson.GH_TOKEN
}
};

this.projectPackageJson.write();
this.projectPackageJson.rewrite = true;

return packageManager.init(options as any);
}

install(options: InstallOptions = {}) {
const packageManager = this.get(options.packageManager);
options.packageManager = packageManager.name;

const devDeps = mapPackagesWithInvalidVersion(this.projectPackageJson.devDependencies);
const deps = mapPackagesWithInvalidVersion(this.projectPackageJson.dependencies);

options = {
...options,
cwd: this.projectPackageJson.dir,
env: {
...process.env,
GH_TOKEN: this.projectPackageJson.GH_TOKEN
}
};

const errorPipe = () =>
catchError((error: any) => {
if (error.stderr.startsWith("error Your lockfile needs to be updated")) {
return throwError(
new Error(`yarn.lock file is outdated. Run ${packageManager.name}, commit the updated lockfile and try again.`)
);
}

return throwError(error);
});

return [
{
title: "Write package.json",
enabled: () => this.projectPackageJson.rewrite,
task: () => {
this.projectPackageJson.write();
}
},
{
title: `Installing dependencies using ${packageManager.name}`,
skip: () => !this.projectPackageJson.reinstall,
task: () => packageManager.install(options as any).pipe(errorPipe())
},
{
title: `Add dependencies using ${packageManager.name}`,
skip: () => !deps.length,
task: () => packageManager.add(deps, options as any).pipe(errorPipe())
},
{
title: `Add devDependencies using ${packageManager.name}`,
skip: () => !devDeps.length,
task: () => packageManager.addDev(devDeps, options as any).pipe(errorPipe())
},
{
title: "Refresh",
task: () => {
this.projectPackageJson.refresh();
}
}
];
}

list() {
return this.packageManagers.map((manager) => manager.name);
}

get(name?: string): BaseManager {
if (this.projectPackageJson.preferences.packageManager) {
name = this.projectPackageJson.preferences.packageManager;
}

name = name || "yarn";

let selectedPackageManager = this.packageManagers.find((manager) => manager.name === name);

if (!selectedPackageManager) {
selectedPackageManager = this.packageManagers.find((manager) => manager.name === "npm")!;
}

this.projectPackageJson.setPreference("packageManager", selectedPackageManager.name);

return selectedPackageManager;
}

public runScript(
scriptName: string,
{
ignoreError,
...opts
}: {
ignoreError?: boolean;
} & Options &
Record<string, any> = {}
) {
const options = {
cwd: this.projectPackageJson.dir,
...opts
};

const errorPipe = () =>
catchError((error: any) => {
if (ignoreError) {
return EMPTY;
}

return throwError(error);
});

return this.get().runScript(scriptName, options).pipe(errorPipe());
}
}
5 changes: 5 additions & 0 deletions packages/cli-core/src/packageManagers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from "./supports/BaseManager";
export * from "./supports/NpmManager";
export * from "./supports/YarnManager";
export * from "./supports/PNpmManager";
export * from "./PackageManagersModule";

0 comments on commit c19ebd3

Please sign in to comment.