coc-tsserver/src/server/utils/modules.ts
2019-06-13 22:52:57 +08:00

108 lines
3.2 KiB
TypeScript

import { exec } from 'child_process'
import path from 'path'
import { Uri, workspace } from 'coc.nvim'
export function runCommand(cmd: string, cwd: string, timeout?: number): Promise<string> {
return new Promise<string>((resolve, reject) => {
let timer: NodeJS.Timer
if (timeout) {
timer = setTimeout(() => {
reject(new Error(`timeout after ${timeout}s`))
}, timeout * 1000)
}
exec(cmd, { cwd }, (err, stdout) => {
if (timer) clearTimeout(timer)
if (err) {
reject(new Error(`exited with ${err.code}`))
return
}
resolve(stdout)
})
})
}
async function getManager(): Promise<string> {
let res = await workspace.findUp(['yarn.lock', 'package-lock.json'])
if (!res) return 'yarn'
return res.endsWith('yarn.lock') ? 'yarn' : 'npm'
}
async function getRoot(): Promise<string | null> {
let res = await workspace.findUp(['package.json'])
if (!res) return null
return path.dirname(res)
}
export async function moduleExists(name: string): Promise<boolean> {
try {
let content = await runCommand(`npm info ${name} --json`, process.cwd())
if (!content) return false
let obj = JSON.parse(content)
if (obj.error != null) return false
return true
} catch (e) {
return false
}
return false
}
/**
* Removes duplicates from the given array. The optional keyFn allows to specify
* how elements are checked for equalness by returning a unique string for each.
*/
export function distinct<T>(array: T[], keyFn?: (t: T) => string): T[] {
if (!keyFn) {
return array.filter((element, position) => {
return array.indexOf(element) === position
})
}
const seen: { [key: string]: boolean } = Object.create(null)
return array.filter(elem => {
const key = keyFn(elem)
if (seen[key]) {
return false
}
seen[key] = true
return true
})
}
export async function installModules(uri: string, names: string[]): Promise<void> {
names = distinct(names)
let root = await getRoot()
if (!root) {
workspace.showMessage(`package.json not found from cwd: ${workspace.cwd}`, 'error')
return
}
let arr = names.concat(names.map(s => `@types/${s}`))
let statusItem = workspace.createStatusBarItem(99, { progress: true })
statusItem.text = `Checking module ${arr.join(' ')}`
statusItem.show()
let exists = await Promise.all(arr.map(name => {
return moduleExists(name).then(exists => {
return exists ? name : null
})
}))
let manager = await getManager()
exists = exists.filter(s => s != null)
if (!exists.length) return
let devs = exists.filter(s => s.startsWith('@types'))
let deps = exists.filter(s => devs.indexOf(s) == -1)
statusItem.text = `Installing ${exists.join(' ')}`
try {
await Promise.all([deps, devs].map((names, i) => {
let cmd = manager == 'npm' ? `npm i ${names.join(' ')}` : `yarn add ${names.join(' ')} --ignore-scripts --no-default-rc`
if (i == 1) cmd = cmd + ' --dev'
return runCommand(cmd, root)
}))
} catch (e) {
statusItem.dispose()
workspace.showMessage(`Install error ${e.message}`, 'error')
return
}
statusItem.dispose()
workspace.showMessage(`Installed: ${exists.join(' ')}`, 'more')
}