forked from desktop/desktop
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'master' into bump-to-npm-5-finally
- Loading branch information
Showing
24 changed files
with
481 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import * as QueryString from 'querystring' | ||
import { URL } from 'url' | ||
|
||
import { CommandError } from '../util' | ||
import { openDesktop } from '../open-desktop' | ||
import { ICommandModule, mriArgv } from '../load-commands' | ||
|
||
interface ICloneArgs extends mriArgv { | ||
readonly branch?: string | ||
} | ||
|
||
const command: ICommandModule = { | ||
command: 'clone <url|slug>', | ||
description: 'Clone a repository', | ||
args: [ | ||
{ | ||
name: 'url|slug', | ||
required: true, | ||
description: 'The URL or the GitHub owner/name alias to clone', | ||
type: 'string', | ||
}, | ||
], | ||
options: { | ||
branch: { | ||
type: 'string', | ||
aliases: ['b'], | ||
description: 'The branch to checkout after cloning', | ||
}, | ||
}, | ||
handler({ _: [cloneUrl], branch }: ICloneArgs) { | ||
if (!cloneUrl) { | ||
throw new CommandError('Clone URL must be specified') | ||
} | ||
try { | ||
const _ = new URL(cloneUrl) | ||
_.toString() // don’t mark as unused | ||
} catch (e) { | ||
// invalid URL, assume a GitHub repo | ||
cloneUrl = `https://github.com/${cloneUrl}` | ||
} | ||
const url = `openRepo/${cloneUrl}?${QueryString.stringify({ | ||
branch, | ||
})}` | ||
openDesktop(url) | ||
}, | ||
} | ||
export = command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,80 @@ | ||
import * as chalk from 'chalk' | ||
|
||
import { commands, ICommandModule, IOption } from '../load-commands' | ||
|
||
import { dasherizeOption, printTable } from '../util' | ||
|
||
const command: ICommandModule = { | ||
command: 'help [command]', | ||
description: 'Show the help page for a command', | ||
handler({ _: [command] }) { | ||
if (command) { | ||
printCommandHelp(command, commands[command]) | ||
} else { | ||
printHelp() | ||
} | ||
}, | ||
} | ||
export = command | ||
|
||
function printHelp() { | ||
console.log(chalk.underline('Commands:')) | ||
const table: string[][] = [] | ||
for (const commandName of Object.keys(commands)) { | ||
const command = commands[commandName] | ||
table.push([chalk.bold(command.command), command.description]) | ||
} | ||
printTable(table) | ||
console.log( | ||
`\nRun ${chalk.bold( | ||
`github help ${chalk.gray('<command>')}` | ||
)} for details about each command` | ||
) | ||
} | ||
|
||
function printCommandHelp(name: string, command: ICommandModule) { | ||
if (!command) { | ||
console.log(`Unrecognized command: ${chalk.bold.red.underline(name)}`) | ||
printHelp() | ||
return | ||
} | ||
console.log(`${chalk.gray('github')} ${command.command}`) | ||
if (command.aliases) { | ||
for (const alias of command.aliases) { | ||
console.log(chalk.gray(`github ${alias}`)) | ||
} | ||
} | ||
console.log() | ||
const [title, body] = command.description.split('\n', 1) | ||
console.log(chalk.bold(title)) | ||
if (body) { | ||
console.log(body) | ||
} | ||
const { options, args } = command | ||
if (options) { | ||
console.log(chalk.underline('\nOptions:')) | ||
printTable( | ||
Object.keys(options) | ||
.map(k => [k, options[k]] as [string, IOption]) | ||
.map(([optionName, option]) => [ | ||
[optionName, ...(option.aliases || [])] | ||
.map(dasherizeOption) | ||
.map(x => chalk.bold.blue(x)) | ||
.join(chalk.gray(', ')), | ||
option.description, | ||
chalk.gray(`[${chalk.underline(option.type)}]`), | ||
]) | ||
) | ||
} | ||
if (args && args.length) { | ||
console.log(chalk.underline('\nArguments:')) | ||
printTable( | ||
args.map(arg => [ | ||
(arg.required ? chalk.bold : chalk).blue(arg.name), | ||
arg.required ? chalk.gray('(required)') : '', | ||
arg.description, | ||
chalk.gray(`[${chalk.underline(arg.type)}]`), | ||
]) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import * as Path from 'path' | ||
|
||
import { ICommandModule, mriArgv } from '../load-commands' | ||
import { openDesktop } from '../open-desktop' | ||
|
||
interface IOpenArgs extends mriArgv { | ||
readonly path: string | ||
} | ||
|
||
const command: ICommandModule = { | ||
command: 'open <path>', | ||
aliases: ['<path>'], | ||
description: 'Open a git repository in GitHub Desktop', | ||
args: [ | ||
{ | ||
name: 'path', | ||
description: 'The path to the repository to open', | ||
type: 'string', | ||
required: false, | ||
}, | ||
], | ||
handler({ _: [pathArg] }: IOpenArgs) { | ||
if (!pathArg) { | ||
// just open Desktop | ||
openDesktop() | ||
return | ||
} | ||
const repositoryPath = Path.resolve(process.cwd(), pathArg) | ||
const url = `openLocalRepo/${encodeURIComponent(repositoryPath)}` | ||
openDesktop(url) | ||
}, | ||
} | ||
export = command |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
const Fs = require('fs') | ||
const path = require('path') | ||
|
||
const distInfo = require('../../../script/dist-info') | ||
|
||
global.__CLI_COMMANDS__ = distInfo.getCLICommands() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
import { Argv as mriArgv } from 'mri' | ||
|
||
import { TypeName } from './util' | ||
|
||
type StringArray = ReadonlyArray<string> | ||
|
||
export type CommandHandler = (args: mriArgv, argv: StringArray) => void | ||
export { mriArgv } | ||
|
||
export interface IOption { | ||
readonly type: TypeName | ||
readonly aliases?: StringArray | ||
readonly description: string | ||
readonly default?: any | ||
} | ||
|
||
interface IArgument { | ||
readonly name: string | ||
readonly required: boolean | ||
readonly description: string | ||
readonly type: TypeName | ||
} | ||
|
||
export interface ICommandModule { | ||
name?: string | ||
readonly command: string | ||
readonly description: string | ||
readonly handler: CommandHandler | ||
readonly aliases?: StringArray | ||
readonly options?: { [flag: string]: IOption } | ||
readonly args?: ReadonlyArray<IArgument> | ||
readonly unknownOptionHandler?: (flag: string) => void | ||
} | ||
|
||
function loadModule(name: string): ICommandModule { | ||
return require(`./commands/${name}.ts`) | ||
} | ||
|
||
interface ICommands { | ||
[command: string]: ICommandModule | ||
} | ||
export const commands: ICommands = {} | ||
|
||
for (const fileName of __CLI_COMMANDS__) { | ||
const mod = loadModule(fileName) | ||
if (!mod.name) { | ||
mod.name = fileName | ||
} | ||
commands[mod.name] = mod | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,105 @@ | ||
import * as ChildProcess from 'child_process' | ||
import * as Path from 'path' | ||
|
||
const args = process.argv.slice(2) | ||
|
||
// At some point we may have other command line options, but for now we assume | ||
// the first arg is the path to open. | ||
const pathArg = args.length > 0 ? args[0] : null | ||
const repositoryPath = pathArg ? Path.resolve(process.cwd(), pathArg) : '' | ||
const url = `x-github-client://openLocalRepo/${encodeURIComponent( | ||
repositoryPath | ||
)}` | ||
|
||
const env = { ...process.env } | ||
// NB: We're gonna launch Desktop and we definitely don't want to carry over | ||
// `ELECTRON_RUN_AS_NODE`. This seems to only happen on Windows. | ||
delete env['ELECTRON_RUN_AS_NODE'] | ||
|
||
if (__DARWIN__) { | ||
ChildProcess.spawn('open', [url], { env }) | ||
} else if (__WIN32__) { | ||
ChildProcess.spawn('cmd', ['/c', 'start', url], { env }) | ||
import * as mri from 'mri' | ||
import * as chalk from 'chalk' | ||
|
||
import { dasherizeOption, CommandError } from './util' | ||
import { commands } from './load-commands' | ||
const defaultCommand = 'open' | ||
|
||
let args = process.argv.slice(2) | ||
if (!args[0]) { | ||
args[0] = '.' | ||
} | ||
const commandArg = args[0] | ||
args = args.slice(1) | ||
|
||
// tslint:disable-next-line whitespace | ||
;(function attemptRun(name: string) { | ||
try { | ||
if (supportsCommand(name)) { | ||
runCommand(name) | ||
} else if (name.startsWith('--')) { | ||
attemptRun(name.slice(2)) | ||
} else { | ||
try { | ||
args.unshift(commandArg) | ||
runCommand(defaultCommand) | ||
} catch (err) { | ||
logError(err) | ||
args = [] | ||
runCommand('help') | ||
} | ||
} | ||
} catch (err) { | ||
logError(err) | ||
args = [name] | ||
runCommand('help') | ||
} | ||
})(commandArg) | ||
|
||
function logError(err: CommandError) { | ||
console.log(chalk.bgBlack.red('ERR!'), err.message) | ||
if (err.stack && !err.pretty) { | ||
console.log(chalk.gray(err.stack)) | ||
} | ||
} | ||
|
||
console.log() // nice blank line before the command prompt | ||
|
||
interface IMRIOpts extends mri.Options { | ||
alias: mri.DictionaryObject<mri.ArrayOrString> | ||
boolean: Array<string> | ||
default: mri.DictionaryObject | ||
string: Array<string> | ||
} | ||
|
||
function runCommand(name: string) { | ||
const command = commands[name] | ||
const opts: IMRIOpts = { | ||
alias: {}, | ||
boolean: [], | ||
default: {}, | ||
string: [], | ||
} | ||
if (command.options) { | ||
for (const flag of Object.keys(command.options)) { | ||
const flagOptions = command.options[flag] | ||
if (flagOptions.aliases) { | ||
opts.alias[flag] = flagOptions.aliases | ||
} | ||
if (flagOptions.hasOwnProperty('default')) { | ||
opts.default[flag] = flagOptions.default | ||
} | ||
switch (flagOptions.type) { | ||
case 'string': | ||
opts.string.push(flag) | ||
break | ||
case 'boolean': | ||
opts.boolean.push(flag) | ||
break | ||
} | ||
} | ||
opts.unknown = command.unknownOptionHandler | ||
} | ||
const parsedArgs = mri(args, opts) | ||
if (command.options) { | ||
for (const flag of Object.keys(parsedArgs)) { | ||
if (!(flag in command.options)) { | ||
continue | ||
} | ||
|
||
const value = parsedArgs[flag] | ||
const expectedType = command.options[flag].type | ||
if (typeof value !== expectedType) { | ||
throw new CommandError( | ||
`Value passed to flag ${dasherizeOption( | ||
flag | ||
)} was of type ${typeof value}, but was expected to be of type ${expectedType}` | ||
) | ||
} | ||
} | ||
} | ||
command.handler(parsedArgs, args) | ||
} | ||
function supportsCommand(name: string) { | ||
return Object.prototype.hasOwnProperty.call(commands, name) | ||
} |
Oops, something went wrong.