Skip to content

Commit

Permalink
feat: detect users package manager
Browse files Browse the repository at this point in the history
  • Loading branch information
field123 committed Sep 23, 2023
1 parent f8d2309 commit c1dcc23
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 12 deletions.
20 changes: 13 additions & 7 deletions packages/composable-cli/src/commands/generate/d2c/d2c-command.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import {
createActiveStoreMiddleware,
createAuthenticationCheckerMiddleware,
} from "../generate-command"
import { detect } from "../../../lib/detect-package-manager"

export function createD2CCommand(
ctx: CommandContext
Expand All @@ -63,8 +64,7 @@ export function createD2CCommand(
})
.option("pkg-manager", {
describe: "node package manager to use",
choices: ["npm", "yarn", "pnpm"] as const,
default: "npm" as const,
choices: ["npm", "yarn", "pnpm", "bun"] as const,
})
.help()
.parserConfiguration({
Expand Down Expand Up @@ -167,8 +167,12 @@ export function createD2CCommandHandler(
return async function generateCommandHandler(args) {
const colors = ansiColors.create()

const { cliOptions, schematicOptions, _, name, pkgManager } =
parseArgs(args)
const detectedPkgManager = await detect()

const { cliOptions, schematicOptions, _, name, pkgManager } = parseArgs(
args,
detectedPkgManager
)

/** Create the DevKit Logger used through the CLI. */
const logger = createConsoleLogger(
Expand Down Expand Up @@ -415,6 +419,7 @@ export function createD2CCommandHandler(
skipGit,
skipInstall,
skipConfig,
packageManager: pkgManager,
...gatheredOptions,
},
allowPrivate: allowPrivate,
Expand Down Expand Up @@ -494,12 +499,13 @@ interface Options {
schematicOptions: Record<string, unknown>
cliOptions: Partial<Record<ElementType<typeof booleanArgs>, boolean | null>>
name: string | null
pkgManager: "npm" | "yarn" | "pnpm"
pkgManager: "npm" | "yarn" | "pnpm" | "bun"
}

/** Parse the command line. */
function parseArgs(
args: yargs.ArgumentsCamelCase<D2CCommandArguments>
args: yargs.ArgumentsCamelCase<D2CCommandArguments>,
detectedPkgManager?: "npm" | "yarn" | "pnpm" | "bun"
): Options {
const { _, $0, name = null, ...options } = args

Expand Down Expand Up @@ -532,7 +538,7 @@ function parseArgs(
schematicOptions,
cliOptions,
name,
pkgManager: args["pkg-manager"],
pkgManager: args["pkg-manager"] ?? detectedPkgManager ?? "npm",
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@ export type D2CCommandError = {

export type D2CCommandArguments = {
name?: string
"pkg-manager": "npm" | "yarn" | "pnpm"
"pkg-manager"?: "npm" | "yarn" | "pnpm" | "bun"
} & GenerateCommandArguments
14 changes: 10 additions & 4 deletions packages/composable-cli/src/commands/ui/generate/d2c-generated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export function D2CGenerated({
skipInstall,
}: {
name: string
nodePkgManager: "yarn" | "npm" | "pnpm"
nodePkgManager: "yarn" | "npm" | "pnpm" | "bun"
skipInstall: boolean
}) {
return (
Expand Down Expand Up @@ -35,7 +35,7 @@ function constructSteps({
skipInstall,
}: {
name: string
nodePkgManager: "yarn" | "npm" | "pnpm"
nodePkgManager: "yarn" | "npm" | "pnpm" | "bun"
skipInstall: boolean
}) {
return [
Expand All @@ -49,24 +49,30 @@ function constructSteps({
]
}

function resolveStartCommand(nodePkgManager: "yarn" | "npm" | "pnpm") {
function resolveStartCommand(nodePkgManager: "yarn" | "npm" | "pnpm" | "bun") {
switch (nodePkgManager) {
case "yarn":
return "yarn run dev"
case "npm":
return "npm run dev"
case "pnpm":
return "pnpm run dev"
case "bun":
return "bun run dev"
}
}

function resolveInstallCommand(nodePkgManager: "yarn" | "npm" | "pnpm") {
function resolveInstallCommand(
nodePkgManager: "yarn" | "npm" | "pnpm" | "bun"
) {
switch (nodePkgManager) {
case "yarn":
return "yarn install"
case "npm":
return "npm install"
case "pnpm":
return "pnpm install"
case "bun":
return "bun install"
}
}
103 changes: 103 additions & 0 deletions packages/composable-cli/src/lib/detect-package-manager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import { promises as fs } from "fs"
import { resolve } from "path"
import { execa } from "execa"

export type PM = "npm" | "yarn" | "pnpm" | "bun"

/**
* Check if a path exists
*/
async function pathExists(p: string) {
try {
await fs.access(p)
return true
} catch {
return false
}
}

const cache = new Map()

/**
* Check if a global pm is available
*/
function hasGlobalInstallation(pm: PM): Promise<boolean> {
const key = `has_global_${pm}`
if (cache.has(key)) {
return Promise.resolve(cache.get(key))
}

return execa(pm, ["--version"])
.then((res) => {
return /^\d+.\d+.\d+$/.test(res.stdout)
})
.then((value) => {
cache.set(key, value)
return value
})
.catch(() => false)
}

function getTypeofLockFile(cwd = "."): Promise<PM | null> {
const key = `lockfile_${cwd}`
if (cache.has(key)) {
return Promise.resolve(cache.get(key))
}

return Promise.all([
pathExists(resolve(cwd, "yarn.lock")),
pathExists(resolve(cwd, "package-lock.json")),
pathExists(resolve(cwd, "pnpm-lock.yaml")),
pathExists(resolve(cwd, "bun.lockb")),
]).then(([isYarn, isNpm, isPnpm, isBun]) => {
let value: PM | null = null

if (isYarn) {
value = "yarn"
} else if (isPnpm) {
value = "pnpm"
} else if (isBun) {
value = "bun"
} else if (isNpm) {
value = "npm"
}

cache.set(key, value)
return value
})
}

const detect = async ({
cwd,
includeGlobalBun,
}: { cwd?: string; includeGlobalBun?: boolean } = {}) => {
const type = await getTypeofLockFile(cwd)
if (type) {
return type
}
const [hasYarn, hasPnpm, hasBun] = await Promise.all([
hasGlobalInstallation("yarn"),
hasGlobalInstallation("pnpm"),
includeGlobalBun && hasGlobalInstallation("bun"),
])
if (hasYarn) {
return "yarn"
}
if (hasPnpm) {
return "pnpm"
}
if (hasBun) {
return "bun"
}
return "npm"
}

export { detect }

export function getNpmVersion(pm: PM) {
return execa(pm || "npm", ["--version"]).then((res) => res.stdout)
}

export function clearCache() {
return cache.clear()
}

0 comments on commit c1dcc23

Please sign in to comment.