Skip to content

Commit

Permalink
Merge pull request #6440 from desktop/use-fs-admin-for-installing-sho…
Browse files Browse the repository at this point in the history
…rtcut

replace runas with fs-admin to handle installing installing CLI
  • Loading branch information
iAmWillShepherd committed Jan 17, 2019
2 parents 8ea35b1 + 4fc3a17 commit 73a12a8
Show file tree
Hide file tree
Showing 7 changed files with 245 additions and 45 deletions.
2 changes: 1 addition & 1 deletion app/package.json
Expand Up @@ -31,6 +31,7 @@
"event-kit": "^2.0.0",
"file-uri-to-path": "0.0.2",
"file-url": "^2.0.2",
"fs-admin": "^0.1.7",
"fs-extra": "^6.0.0",
"fuzzaldrin-plus": "^0.6.0",
"keytar": "^4.0.4",
Expand All @@ -46,7 +47,6 @@
"react-transition-group": "^1.2.0",
"react-virtualized": "^9.20.0",
"registry-js": "^1.0.7",
"runas": "^3.1.1",
"source-map-support": "^0.4.15",
"strip-ansi": "^4.0.0",
"textarea-caret": "^3.0.2",
Expand Down
98 changes: 67 additions & 31 deletions app/src/ui/lib/install-cli.ts
@@ -1,11 +1,7 @@
import * as Fs from 'fs'
import * as FSE from 'fs-extra'
import * as Path from 'path'

const runas: (
command: string,
args: ReadonlyArray<string>,
options?: { admin: boolean }
) => number = require('runas')
import * as fsAdmin from 'fs-admin'

/** The path for the installed command line tool. */
export const InstalledCLIPath = '/usr/local/bin/github'
Expand All @@ -21,46 +17,86 @@ export async function installCLI(): Promise<void> {
}

try {
symlinkCLI(false)
await symlinkCLI(false)
} catch (e) {
// If we error without running as an admin, try again as an admin.
symlinkCLI(true)
await symlinkCLI(true)
}
}

async function getResolvedInstallPath(): Promise<string | null> {
return new Promise<string | null>((resolve, reject) => {
Fs.readlink(InstalledCLIPath, (err, realpath) => {
if (err) {
resolve(null)
} else {
resolve(realpath)
try {
return await FSE.readlink(InstalledCLIPath)
} catch {
return null
}
}

function removeExistingSymlink(asAdmin: boolean) {
if (!asAdmin) {
return FSE.unlink(InstalledCLIPath)
}

return new Promise<void>((resolve, reject) => {
fsAdmin.unlink(InstalledCLIPath, error => {
if (error !== null) {
reject(
new Error(
`Failed to remove file at ${InstalledCLIPath}. Authorization of GitHub Desktop Helper is required.`
)
)
return
}

resolve()
})
})
}

function symlinkCLI(asAdmin: boolean) {
let exitCode = runas('/bin/rm', ['-f', InstalledCLIPath], { admin: asAdmin })
if (exitCode !== 0) {
throw new Error(
`Failed to remove file at ${InstalledCLIPath}. Authorization of GitHub Desktop Helper is required.`
)
function createDirectories(asAdmin: boolean) {
const path = Path.dirname(InstalledCLIPath)

if (!asAdmin) {
return FSE.mkdirp(path)
}

exitCode = runas('/bin/mkdir', ['-p', Path.dirname(InstalledCLIPath)], {
admin: asAdmin,
return new Promise<void>((resolve, reject) => {
fsAdmin.makeTree(path, error => {
if (error !== null) {
reject(
new Error(
`Failed to create intermediate directories to ${InstalledCLIPath}`
)
)
return
}

resolve()
})
})
if (exitCode !== 0) {
throw new Error(
`Failed to create intermediate directories to ${InstalledCLIPath}`
)
}

function createNewSymlink(asAdmin: boolean) {
if (!asAdmin) {
return FSE.symlink(PackagedPath, InstalledCLIPath)
}

exitCode = runas('/bin/ln', ['-s', PackagedPath, InstalledCLIPath], {
admin: asAdmin,
return new Promise<void>((resolve, reject) => {
fsAdmin.symlink(PackagedPath, InstalledCLIPath, error => {
if (error !== null) {
reject(
new Error(`Failed to symlink ${PackagedPath} to ${InstalledCLIPath}`)
)
return
}

resolve()
})
})
if (exitCode !== 0) {
throw new Error(`Failed to symlink ${PackagedPath} to ${InstalledCLIPath}`)
}
}

async function symlinkCLI(asAdmin: boolean): Promise<void> {
await removeExistingSymlink(asAdmin)
await createDirectories(asAdmin)
await createNewSymlink(asAdmin)
}
17 changes: 17 additions & 0 deletions app/typings/fs-admin/index.d.ts
@@ -0,0 +1,17 @@
declare module 'fs-admin' {
export function makeTree(
path: string | Buffer,
callback: (err: Error | null) => void
): void

export function unlink(
path: string | Buffer,
callback: (err: Error | null) => void
): void

export function symlink(
srcpath: string | Buffer,
dstpath: string | Buffer,
callback: (err: Error | null) => void
): void
}
4 changes: 4 additions & 0 deletions app/webpack.common.ts
Expand Up @@ -88,6 +88,10 @@ export const renderer = merge({}, commonConfig, {
test: /\.(jpe?g|png|gif|ico)$/,
use: ['file?name=[path][name].[ext]'],
},
{
test: /\.cmd$/,
loader: 'file-loader',
},
],
},
plugins: [
Expand Down

0 comments on commit 73a12a8

Please sign in to comment.