UNPKG

8.03 kBJavaScriptView Raw
1"use strict";
2/**
3 * © 2013 Liferay, Inc. <https://liferay.com> and Node GH contributors
4 * (see file: README.md)
5 * SPDX-License-Identifier: BSD-3-Clause
6 */
7Object.defineProperty(exports, "__esModule", { value: true });
8// -- Requires -------------------------------------------------------------------------------------
9const Future = require("fluture");
10const fluture_sanctuary_types_1 = require("fluture-sanctuary-types");
11const fs = require("fs");
12const immer_1 = require("immer");
13const nopt = require("nopt");
14const path = require("path");
15const R = require("ramda");
16const sanctuary_1 = require("sanctuary");
17const updateNotifier = require("update-notifier");
18const base_1 = require("./base");
19const logger = require("./logger");
20const configs_1 = require("./configs");
21const fp_1 = require("./fp");
22const git = require("./git");
23const github_1 = require("./github");
24const testing = process.env.NODE_ENV === 'testing';
25// Make Fluture Play nicely with Sanctuary
26const S = sanctuary_1.create({ checkTypes: true, env: sanctuary_1.env.concat(fluture_sanctuary_types_1.env) });
27// Allow mutation of options when not testing
28// https://immerjs.github.io/immer/docs/freezing
29immer_1.setAutoFreeze(testing);
30Future.debugMode(testing);
31/**
32 * Figure out if cmd is either the Version of Help cmd
33 */
34function tryResolvingByHelpOrVersion({ cooked, remain } = {}) {
35 let cmdName = null;
36 const isVersionCmd = cooked[0] === '--version' || cooked[0] === '-v';
37 const isHelpCmd = !remain.length || cooked.includes('-h') || cooked.includes('--help');
38 if (isVersionCmd) {
39 cmdName = 'version';
40 }
41 else if (isHelpCmd) {
42 cmdName = 'help';
43 }
44 return cmdName ? Future.of(cmdName) : Future.reject(remain[0]);
45}
46exports.tryResolvingByHelpOrVersion = tryResolvingByHelpOrVersion;
47/**
48 * Builds out the absolute path of the non plugin cmd
49 */
50function buildFilePath(filename) {
51 const commandDir = path.join(__dirname, 'cmds');
52 const fullFileName = filename.includes('.') ? filename : `${filename}`;
53 const absolutePath = path.join(commandDir, fullFileName);
54 return absolutePath;
55}
56/**
57 * Try to determine if cmd passed in is a plugin
58 */
59exports.tryResolvingByPlugin = R.pipeK(fp_1.prepend('gh-'), fp_1.safeWhich, fp_1.safeRealpath);
60/**
61 * Checks if cmd is a valid alias
62 */
63function tryResolvingByAlias(name) {
64 const cmdDir = path.join(__dirname, 'cmds');
65 return fp_1.safeReaddir(cmdDir)
66 .chain(filterFiles)
67 .chainRej(() => Future.reject(name));
68 function filterFiles(files) {
69 const cmdFileName = files.filter((file) => {
70 return file.startsWith(name[0]) && file.includes(name[1]);
71 })[0];
72 return cmdFileName ? Future.of(cmdFileName) : Future.reject(name);
73 }
74}
75exports.tryResolvingByAlias = tryResolvingByAlias;
76// Some plugins have the Impl prop housing the main class
77// For backwards compat, we will flatten it if it exists
78function flattenIfImpl(obj) {
79 return obj.Impl ? obj.Impl : obj;
80}
81function loadCommand(args) {
82 return tryResolvingByHelpOrVersion(args)
83 .chainRej(tryResolvingByAlias)
84 .map(buildFilePath)
85 .chainRej(exports.tryResolvingByPlugin)
86 .chain(fp_1.safeImport)
87 .map(flattenIfImpl);
88}
89exports.loadCommand = loadCommand;
90function getCommand(args) {
91 /**
92 * nopt function returns:
93 *
94 * remain: The remaining args after all the parsing has occurred.
95 * original: The args as they originally appeared.
96 * cooked: The args after flags and shorthands are expanded.
97 */
98 const parsed = nopt(args);
99 const remain = parsed.argv.remain;
100 const module = remain[0];
101 const Command = loadCommand(parsed.argv);
102 return Command.fold(() => S.Left(`Cannot find module ${module}`), S.Right);
103}
104function notifyVersion() {
105 const notifier = updateNotifier({ pkg: configs_1.getGlobalPackageJson() });
106 if (notifier.update) {
107 notifier.notify();
108 }
109}
110async function buildOptions(args, cmdName) {
111 const options = immer_1.default(args, async (draft) => {
112 const config = base_1.getConfig();
113 // Gets 2nd positional arg (`gh pr 1` will return 1)
114 const secondArg = [draft.argv.remain[1]];
115 const remote = draft.remote || config.default_remote;
116 const remoteUrl = git.getRemoteUrl(remote);
117 if (cmdName !== 'Help' && cmdName !== 'Version') {
118 // We don't want to boot up Ocktokit if user just wants help or version
119 draft.GitHub = await github_1.getGitHubInstance();
120 }
121 // default the page size to 30
122 draft.allPages = config.page_size === '';
123 draft.pageSize = config.page_size || 30;
124 draft.config = config;
125 draft.remote = remote;
126 draft.number = draft.number || secondArg;
127 draft.loggedUser = base_1.getUser();
128 draft.remoteUser = git.getUserFromRemoteUrl(remoteUrl);
129 draft.repo = draft.repo || git.getRepoFromRemoteURL(remoteUrl);
130 draft.currentBranch = git.getCurrentBranch();
131 draft.github_host = config.github_host;
132 draft.github_gist_host = config.github_gist_host;
133 if (!draft.user) {
134 if (args.repo || args.all) {
135 draft.user = draft.loggedUser;
136 }
137 else {
138 draft.user = process.env.GH_USER || draft.remoteUser || draft.loggedUser;
139 }
140 }
141 /**
142 * Checks if there are aliases in your .gh.json file.
143 * If there are aliases in your .gh.json file, we will attempt to resolve the user, PR forwarder or PR submitter to your alias.
144 */
145 if (config.alias) {
146 draft.fwd = config.alias[draft.fwd] || draft.fwd;
147 draft.submit = config.alias[draft.submit] || draft.submit;
148 draft.user = config.alias[draft.user] || draft.user;
149 }
150 draft.userRepo = logger.colors.green(`${draft.user}/${draft.repo}`);
151 });
152 return options;
153}
154exports.buildOptions = buildOptions;
155/* !! IMPURE CALLING CODE !! */
156async function run() {
157 process.env.GH_PATH = path.join(__dirname, '../');
158 if (!fs.existsSync(configs_1.getUserHomePath())) {
159 configs_1.createGlobalConfig();
160 }
161 notifyVersion();
162 getCommand(process.argv).fork(errMsg => console.log(errMsg), async ({ value: Command }) => {
163 const args = getAvailableArgsOnCmd(Command);
164 let cmdDoneRunning = null;
165 if (testing) {
166 const { prepareTestFixtures } = await Promise.resolve().then(() => require('./test-utils'));
167 // Enable mock apis for e2e's
168 cmdDoneRunning = prepareTestFixtures(Command.name, args.argv.cooked);
169 }
170 const options = await buildOptions(args, Command.name);
171 // Maintain backwards compat with plugins implemented as classes
172 if (typeof Command === 'function') {
173 const Plugin = Command;
174 configs_1.addPluginConfig(Plugin.name);
175 await new Plugin(options).run(cmdDoneRunning);
176 }
177 else {
178 await Command.run(options, cmdDoneRunning);
179 }
180 });
181}
182exports.run = run;
183/**
184 * If you run `gh pr 1 -s node-gh --remote=origin --user protoEvangelion`, nopt will return
185 *
186 * {
187 * remote: 'origin',
188 * submit: 'node-gh',
189 * user: 'protoEvangelion',
190 * argv: {
191 * original: ['pr', '1', '-s', 'pr', 'node-gh', '--remote', 'origin', '--user', 'protoEvangelion'],
192 * remain: ['pr', '1'],
193 * cooked: ['pr', '1', '--submit', 'node-gh', '--remote', 'origin', '--user', 'protoEvangelion'],
194 * },
195 * }
196 *
197 * Historically we passed every arg after 2nd arg (gh pr 1 -s user; everything after 'pr')
198 * and all parsed options to each cmd's payload function to figure out positional args and allow for neat shortcuts like:
199 * gh is 'new issue' 'new issue description'
200 */
201function getAvailableArgsOnCmd(Command) {
202 return nopt(Command.DETAILS.options, Command.DETAILS.shorthands, process.argv, 2);
203}
204//# sourceMappingURL=cmd.js.map
\No newline at end of file