Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/deno #130

Draft
wants to merge 31 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4cd43df
Add default formatter for vscode
Zeko369 Jan 17, 2023
a5b4723
Better handling of UnsupportedCommand error
Zeko369 Jan 17, 2023
cdaaccf
Wip add deno support (tasks / run)
Zeko369 Jan 18, 2023
4672ced
Refactor agents
Zeko369 Jan 19, 2023
d7f162f
Closer follow eslint with vscode
Zeko369 Jan 19, 2023
c17d5c1
Implement deno tasks
Zeko369 Jan 19, 2023
9171836
Support both json and jsonc
Zeko369 Jan 19, 2023
21dc6ce
Merge branch 'antfu:main' into feature/deno
Zeko369 Jan 20, 2023
b88f63a
Merge branch 'antfu:main' into some-refactors
Zeko369 Jan 20, 2023
7bd7414
Final newline
Zeko369 Jan 20, 2023
6157926
Remove .vscode
Zeko369 Jan 20, 2023
0b19765
Ignore .vscode folder
Zeko369 Jan 20, 2023
a38242a
Add deno specific tests
Zeko369 Jan 21, 2023
d63cdc2
Refactor command runner to support multiple commands and commands wit…
Zeko369 Jan 21, 2023
d59b9e9
Add --reinstall option to ni
Zeko369 Jan 21, 2023
6050904
Rewrite ni tests to share all the logic
Zeko369 Jan 21, 2023
d4cf880
Fix broken test
Zeko369 Jan 21, 2023
7ea0d76
Merge branch 'antfu:main' into feature/ni-reinstall
Zeko369 Jan 21, 2023
34b815c
Fix tests
Zeko369 Jan 21, 2023
5f57cc3
Fix loading scripts for deno
Zeko369 Jan 23, 2023
68edffb
Format
Zeko369 Jan 23, 2023
e5e900a
Remove vscode
Zeko369 Jan 24, 2023
2a62f93
Ignore .vscode
Zeko369 Jan 24, 2023
b19930a
Add ability to install types for a package
Zeko369 Feb 10, 2023
d7de069
Make sure --types works with scoped packages
Zeko369 Feb 10, 2023
e7002ac
Merge branch 'main' into feature/ni-types
Zeko369 Feb 14, 2023
f4dd6d0
Merge pull request #2 from Zeko369/feature/ni-types
Zeko369 Feb 14, 2023
ae3b3b7
Merge branch 'main' into feature/ni-reinstall
Zeko369 Feb 14, 2023
66d8a02
Merge pull request #3 from Zeko369/feature/ni-reinstall
Zeko369 Feb 14, 2023
df51105
Merge branch 'antfu:main' into main
Zeko369 Mar 2, 2023
cc134d9
Merge branch 'main' into feature/deno
Zeko369 Mar 8, 2023
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -83,3 +83,5 @@ _storage.json

# System files
.DS_Store

.vscode
42 changes: 29 additions & 13 deletions src/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,19 @@ const npmRun = (agent: string) => (args: string[]) => {
else return `${agent} run ${args[0]}`
}

const npm = {
'agent': 'npm {0}',
'run': npmRun('npm'),
'install': 'npm i {0}',
'frozen': 'npm ci',
'global': 'npm i -g {0}',
'add': 'npm i {0}',
'upgrade': 'npm update {0}',
'upgrade-interactive': null,
'execute': 'npx {0}',
'uninstall': 'npm uninstall {0}',
'global_uninstall': 'npm uninstall -g {0}',
}
const yarn = {
'agent': 'yarn {0}',
'run': 'yarn run {0}',
Expand Down Expand Up @@ -43,21 +56,22 @@ const bun = {
'uninstall': 'bun remove {0}',
'global_uninstall': 'bun remove -g {0}',
}
const deno = {
'agent': 'deno {0}',
'run': 'deno task {0}',
'install': null,
'frozen': null,
'global': null,
'add': null,
'upgrade': null,
'upgrade-interactive': null,
'execute': 'deno run npm:{0}',
'uninstall': null,
'global_uninstall': null,
}

export const AGENTS = {
'npm': {
'agent': 'npm {0}',
'run': npmRun('npm'),
'install': 'npm i {0}',
'frozen': 'npm ci',
'global': 'npm i -g {0}',
'add': 'npm i {0}',
'upgrade': 'npm update {0}',
'upgrade-interactive': null,
'execute': 'npx {0}',
'uninstall': 'npm uninstall {0}',
'global_uninstall': 'npm uninstall -g {0}',
},
'npm': npm,
'yarn': yarn,
'yarn@berry': {
...yarn,
Expand All @@ -76,6 +90,7 @@ export const AGENTS = {
run: npmRun('pnpm'),
},
'bun': bun,
'deno': deno,
}

export type Agent = keyof typeof AGENTS
Expand All @@ -99,4 +114,5 @@ export const INSTALL_PAGE: Record<Agent, string> = {
'yarn': 'https://classic.yarnpkg.com/en/docs/install',
'yarn@berry': 'https://yarnpkg.com/getting-started/install',
'npm': 'https://docs.npmjs.com/cli/v8/configuring-npm/install',
'deno': 'https://deno.land/manual/getting_started/installation',
}
8 changes: 4 additions & 4 deletions src/commands/nr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import c from 'kleur'
import { Fzf } from 'fzf'
import { dump, load } from '../storage'
import { parseNr } from '../parse'
import { getPackageJSON } from '../fs'
import { getConfig } from '../fs'
import { runCli } from '../runner'

runCli(async (agent, args, ctx) => {
Expand All @@ -19,11 +19,11 @@ runCli(async (agent, args, ctx) => {
}

if (args.length === 0) {
const pkg = getConfig(agent, ctx?.cwd)
const scripts = (agent === 'deno' ? pkg.tasks : pkg.scripts) || {}

// support https://www.npmjs.com/package/npm-scripts-info conventions
const pkg = getPackageJSON(ctx?.cwd)
const scripts = pkg.scripts || {}
const scriptsInfo = pkg['scripts-info'] || {}

const names = Object.entries(scripts) as [string, string][]

if (!names.length)
Expand Down
2 changes: 1 addition & 1 deletion src/detect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export async function detect({ autoInstall, cwd }: DetectOptions = {}) {
console.warn('[ni] Unknown packageManager:', pkg.packageManager)
}
}
catch {}
catch { }
}

// detect based on lock
Expand Down
27 changes: 21 additions & 6 deletions src/fs.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,33 @@
import { resolve } from 'path'
import fs from 'fs'
import fs from 'node:fs'
import { resolve } from 'node:path'
import type { Agent } from './agents'

export function getPackageJSON(cwd = process.cwd()): any {
const path = resolve(cwd, 'package.json')
const getJSONConfigFile = (cwd: string, filename: string) => {
const path = resolve(cwd, filename)

if (fs.existsSync(path)) {
try {
const raw = fs.readFileSync(path, 'utf-8')
const data = JSON.parse(raw)
const data = JSON.parse(raw) as Record<string, any>
return data
}
catch (e) {
console.warn('Failed to parse package.json')
console.warn(`Failed to parse ${filename}`)
process.exit(0)
}
}
}

export function getPackageJSON(cwd = process.cwd()) {
return getJSONConfigFile(cwd, 'package.json') || {}
}

export function getDenoJSON(cwd = process.cwd()) {
return getJSONConfigFile(cwd, 'deno.json') || getJSONConfigFile(cwd, 'deno.jsonc') || {}
}

export function getConfig(agent: Agent, cwd = process.cwd()) {
if (agent === 'deno')
return getDenoJSON(cwd)
return getPackageJSON(cwd)
}
46 changes: 39 additions & 7 deletions src/parse.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import os from 'node:os'
import type { Agent, Command } from './agents'
import { AGENTS } from './agents'
import { exclude } from './utils'
import type { Runner } from './runner'
import type { CommandWithPrompt, Runner } from './runner'

export class UnsupportedCommand extends Error {
constructor({ agent, command }: { agent: Agent; command: Command }) {
Expand Down Expand Up @@ -29,25 +30,56 @@ export function getCommand(
}

export const parseNi = <Runner>((agent, args, ctx) => {
if (args.includes('--types')) {
args = exclude(args, '--types')
args = args.map((i) => {
if (i.startsWith('-'))
return i
if (i.startsWith('@'))
i = i.slice(1).replace('/', '__')

return `@types/${i}`
},
)
args.unshift('-D')
}

// bun use `-d` instead of `-D`, #90
if (agent === 'bun')
args = args.map(i => i === '-D' ? '-d' : i)

if (args.includes('-g'))
return getCommand(agent, 'global', exclude(args, '-g'))
let before_actions: (CommandWithPrompt & { tag: 'clean_node_modules' })[] = []
if (args.includes('--reinstall') || args.includes('-R')) {
args = exclude(args, '--reinstall')
args = exclude(args, '-R')

const node_modules = `${ctx?.cwd || '.'}/node_modules`
const command = os.platform() === 'win32' ? `rmdir /s /q ${node_modules}` : `rm -rf ${node_modules}`

before_actions = [
{ command, tag: 'clean_node_modules', prompt: `Remove ${node_modules} folder?` },
]
}

if (args.includes('-g')) {
if (before_actions.some(action => action.tag === 'clean_node_modules'))
console.warn('`--reinstall` / `-R` is not supported with `-g`')

return [getCommand(agent, 'global', exclude(args, '-g'))]
}

if (args.includes('--frozen-if-present')) {
args = exclude(args, '--frozen-if-present')
return getCommand(agent, ctx?.hasLock ? 'frozen' : 'install', args)
return [...before_actions, getCommand(agent, ctx?.hasLock ? 'frozen' : 'install', args)]
}

if (args.includes('--frozen'))
return getCommand(agent, 'frozen', exclude(args, '--frozen'))
return [...before_actions, getCommand(agent, 'frozen', exclude(args, '--frozen'))]

if (args.length === 0 || args.every(i => i.startsWith('-')))
return getCommand(agent, 'install', args)
return [...before_actions, getCommand(agent, 'install', args)]

return getCommand(agent, 'add', args)
return [...before_actions, getCommand(agent, 'add', args)]
})

export const parseNr = <Runner>((agent, args) => {
Expand Down
36 changes: 27 additions & 9 deletions src/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ export interface RunnerContext {
cwd?: string
}

export type Runner = (agent: Agent, args: string[], ctx?: RunnerContext) => Promise<string | undefined> | string | undefined
export interface CommandWithPrompt { command: string; tag?: string; prompt?: string }
export type RunnerReturn = string | string[] | (string | CommandWithPrompt)[] | undefined

function isObjCommand(cmd: string | CommandWithPrompt): cmd is CommandWithPrompt {
return typeof cmd === 'object'
}

export type Runner = (agent: Agent, args: string[], ctx?: RunnerContext) => Promise<RunnerReturn> | RunnerReturn

export async function runCli(fn: Runner, options: DetectOptions = {}) {
const args = process.argv.slice(2).filter(Boolean)
Expand All @@ -40,7 +47,7 @@ export async function run(fn: Runner, args: string[], options: DetectOptions = {
remove(args, DEBUG_SIGN)

let cwd = process.cwd()
let command
let commands: Awaited<RunnerReturn>

if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
console.log(`@antfu/ni v${version}`)
Expand Down Expand Up @@ -68,7 +75,7 @@ export async function run(fn: Runner, args: string[], options: DetectOptions = {

const isGlobal = args.includes('-g')
if (isGlobal) {
command = await fn(await getGlobalAgent(), args)
commands = await fn(await getGlobalAgent(), args)
}
else {
let agent = await detect({ ...options, cwd }) || await getDefaultAgent()
Expand All @@ -82,23 +89,34 @@ export async function run(fn: Runner, args: string[], options: DetectOptions = {
if (!agent)
return
}
command = await fn(agent as Agent, args, {
commands = await fn(agent as Agent, args, {
hasLock: Boolean(agent),
cwd,
})
}

if (!command)
if (!commands)
return

const voltaPrefix = getVoltaPrefix()
if (voltaPrefix)
command = voltaPrefix.concat(' ').concat(command)
const mappedCommands = (
Array.isArray(commands)
? commands.map(c => (isObjCommand(c) ? c : { command: c }))
: ([{ command: commands }] as CommandWithPrompt[])
).map(c => (voltaPrefix ? { ...c, command: voltaPrefix.concat(' ').concat(c.command) } : c))

if (debug) {
console.log(command)
console.log(commands)
return
}

await execaCommand(command, { stdio: 'inherit', encoding: 'utf-8', cwd })
for (const { command, prompt } of mappedCommands) {
if (prompt) {
const { confirm } = await prompts({ name: 'confirm', type: 'confirm', message: prompt })
if (!confirm)
return
}

await execaCommand(command, { stdio: 'inherit', encoding: 'utf-8', cwd })
}
}
15 changes: 15 additions & 0 deletions test/na/deno.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { expect, test } from 'vitest'
import { parseNa } from '../../src/commands'

const agent = 'deno'
const _ = (arg: string, expected: string) => () => {
expect(
parseNa(agent, arg.split(' ').filter(Boolean)),
).toBe(
expected,
)
}

test('empty', _('', 'deno'))
test('foo', _('foo', 'deno foo'))
test('run test', _('run test', 'deno run test'))
19 changes: 19 additions & 0 deletions test/ni/_base.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { expect } from 'vitest'
import type { RunnerReturn } from '../../src/commands'
import { parseNi } from '../../src/commands'
import type { Agent } from '../../src/agents'

export const parseNaTest = (agent: Agent) => {
return (arg: string, expected: RunnerReturn) => () => {
expect(
parseNi(agent, arg.split(' ').filter(Boolean)),
).toEqual(expected)
}
}

const platformRmDir = process.platform === 'win32' ? 'rmdir /s /q' : 'rm -rf'
export const promptRemoveOfNodeModules = {
prompt: 'Remove ./node_modules folder?',
command: `${platformRmDir} ./node_modules`,
tag: 'clean_node_modules',
}
31 changes: 15 additions & 16 deletions test/ni/bun.spec.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,22 @@
import { expect, test } from 'vitest'
import { parseNi } from '../../src/commands'
import { test } from 'vitest'
import { parseNaTest, promptRemoveOfNodeModules } from './_base'

const agent = 'bun'
const _ = (arg: string, expected: string) => () => {
expect(
parseNi(agent, arg.split(' ').filter(Boolean)),
).toBe(
expected,
)
}
const _ = parseNaTest('bun')

test('empty', _('', 'bun install'))
test('empty', _('', ['bun install']))

test('single add', _('axios', 'bun add axios'))
test('empty reinstall', _('--reinstall', [promptRemoveOfNodeModules, 'bun install']))

test('add dev', _('vite -D', 'bun add vite -d'))
test('single add', _('axios', ['bun add axios']))

test('multiple', _('eslint @types/node', 'bun add eslint @types/node'))
test('-D', _('vite -D', 'bun add vite -d'))

test('global', _('eslint -g', 'bun add -g eslint'))
test('add dev', _('vite -D', ['bun add vite -d']))

test('frozen', _('--frozen', 'bun install --no-save'))
test('multiple', _('eslint @types/node', ['bun add eslint @types/node']))

test('add types', _('--types node react @foo/bar', 'bun add -d @types/node @types/react @types/foo__bar'))

test('global', _('eslint -g', ['bun add -g eslint']))

test('frozen', _('--frozen', ['bun install --no-save']))
Loading