UI for Zipcoin Blue

link.js 22KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. const tslib_1 = require("tslib");
  4. const chalk_1 = require("chalk");
  5. const cli_utils_1 = require("@ionic/cli-utils");
  6. const command_1 = require("@ionic/cli-utils/lib/command");
  7. const errors_1 = require("@ionic/cli-utils/lib/errors");
  8. const CHOICE_CREATE_NEW_APP = 'createNewApp';
  9. const CHOICE_NEVERMIND = 'nevermind';
  10. const CHOICE_IONIC = 'ionic';
  11. const CHOICE_GITHUB = 'github';
  12. const CHOICE_MASTER_ONLY = 'master';
  13. const CHOICE_SPECIFIC_BRANCHES = 'specific';
  14. let LinkCommand = class LinkCommand extends command_1.Command {
  15. preRun(inputs, options) {
  16. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  17. const { create } = options;
  18. if (inputs[0] && create) {
  19. throw new errors_1.FatalException(`Sorry--cannot use both ${chalk_1.default.green('app_id')} and ${chalk_1.default.green('--create')}. You must either link an existing app or create a new one.`);
  20. }
  21. let proAppId = options['pro-id'] || '';
  22. const config = yield this.env.config.load();
  23. if (proAppId) {
  24. if (config.backend !== cli_utils_1.BACKEND_PRO) {
  25. yield this.env.runCommand(['config', 'set', '-g', 'backend', 'pro'], { showExecution: false });
  26. this.env.log.nl();
  27. this.env.log.info(`${chalk_1.default.bold(chalk_1.default.blue.underline('Welcome to Ionic Pro!') + ' The CLI is now set up to use Ionic Pro services.')}\n` +
  28. `You can revert back to Ionic Cloud (legacy) services at any time:\n\n` +
  29. `${chalk_1.default.green('ionic config set -g backend legacy')}\n`);
  30. }
  31. inputs[0] = proAppId;
  32. }
  33. });
  34. }
  35. run(inputs, options) {
  36. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  37. const { promptToLogin } = yield Promise.resolve().then(() => require('@ionic/cli-utils/lib/session'));
  38. let [appId] = inputs;
  39. let { create, name } = options;
  40. const config = yield this.env.config.load();
  41. const project = yield this.env.project.load();
  42. const appUtil = yield this.getAppClient();
  43. if (project.app_id) {
  44. if (project.app_id === appId) {
  45. this.env.log.info(`Already linked with app ${chalk_1.default.green(appId)}.`);
  46. return;
  47. }
  48. const msg = appId ?
  49. `Are you sure you want to link it to ${chalk_1.default.green(appId)} instead?` :
  50. `Would you like to link it to a different app?`;
  51. const confirm = yield this.env.prompt({
  52. type: 'confirm',
  53. name: 'confirm',
  54. message: `App ID ${chalk_1.default.green(project.app_id)} is already set up with this app. ${msg}`,
  55. });
  56. if (!confirm) {
  57. this.env.log.info('Not linking.');
  58. return;
  59. }
  60. }
  61. if (!(yield this.env.session.isLoggedIn())) {
  62. yield promptToLogin(this.env);
  63. }
  64. if (appId) {
  65. this.env.tasks.next(`Looking up app ${chalk_1.default.green(appId)}`);
  66. yield appUtil.load(appId);
  67. this.env.tasks.end();
  68. }
  69. else if (!create) {
  70. this.env.tasks.next(`Looking up your apps`);
  71. let apps = [];
  72. const paginator = yield appUtil.paginate();
  73. for (let r of paginator) {
  74. const res = yield r;
  75. apps = apps.concat(res.data);
  76. }
  77. this.env.tasks.end();
  78. const createAppChoice = {
  79. name: 'Create a new app',
  80. id: CHOICE_CREATE_NEW_APP,
  81. };
  82. const neverMindChoice = {
  83. name: 'Nevermind',
  84. id: CHOICE_NEVERMIND,
  85. };
  86. const linkedApp = yield this.env.prompt({
  87. type: 'list',
  88. name: 'linkedApp',
  89. message: `Which app would you like to link`,
  90. choices: [createAppChoice, ...apps, neverMindChoice].map((app) => ({
  91. name: [CHOICE_CREATE_NEW_APP, CHOICE_NEVERMIND].includes(app.id) ? chalk_1.default.bold(app.name) : `${app.name} (${app.id})`,
  92. value: app.id
  93. }))
  94. });
  95. appId = linkedApp;
  96. }
  97. if (create || appId === CHOICE_CREATE_NEW_APP) {
  98. if (config.backend === cli_utils_1.BACKEND_PRO) {
  99. if (!name) {
  100. name = yield this.env.prompt({
  101. type: 'input',
  102. name: 'name',
  103. message: 'Please enter a name for your new app:',
  104. });
  105. }
  106. const app = yield appUtil.create({ name: String(name) });
  107. appId = app.id;
  108. yield this.linkApp(app);
  109. }
  110. else {
  111. const opn = yield Promise.resolve().then(() => require('opn'));
  112. const dashUrl = yield this.env.config.getDashUrl();
  113. const token = yield this.env.session.getUserToken();
  114. opn(`${dashUrl}/?user_token=${token}`, { wait: false });
  115. this.env.log.info(`Rerun ${chalk_1.default.green(`ionic link`)} to link to the new app.`);
  116. }
  117. }
  118. else if (appId === CHOICE_NEVERMIND) {
  119. this.env.log.info('Not linking app.');
  120. }
  121. else {
  122. const app = yield appUtil.load(appId);
  123. yield this.linkApp(app);
  124. }
  125. yield Promise.all([this.env.config.save(), this.env.project.save()]);
  126. });
  127. }
  128. getAppClient() {
  129. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  130. const { App } = yield Promise.resolve().then(() => require('@ionic/cli-utils/lib/app'));
  131. const token = yield this.env.session.getUserToken();
  132. return new App(token, this.env.client);
  133. });
  134. }
  135. getUserClient() {
  136. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  137. const { UserClient } = yield Promise.resolve().then(() => require('@ionic/cli-utils/lib/user'));
  138. const token = yield this.env.session.getUserToken();
  139. return new UserClient({ token, client: this.env.client });
  140. });
  141. }
  142. linkApp(app) {
  143. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  144. // TODO: load connections
  145. // TODO: check for git availability before this
  146. this.env.log.nl();
  147. this.env.log.info(`${chalk_1.default.bold(`Ionic Pro uses a git-based workflow to manage app updates.`)}\n` +
  148. `You will be prompted to set up the git host and repository for this new app. See the docs${chalk_1.default.bold('[1]')} for more information.\n\n` +
  149. `${chalk_1.default.bold('[1]')}: ${chalk_1.default.cyan('https://ionicframework.com/docs/pro/basics/git/')}`);
  150. const service = yield this.env.prompt({
  151. type: 'list',
  152. name: 'gitService',
  153. message: 'Which git host would you like to use?',
  154. choices: [
  155. {
  156. name: 'GitHub',
  157. value: CHOICE_GITHUB,
  158. },
  159. {
  160. name: 'Ionic Pro',
  161. value: CHOICE_IONIC,
  162. },
  163. ],
  164. });
  165. let githubUrl;
  166. if (service === CHOICE_IONIC) {
  167. const config = yield this.env.config.load();
  168. if (!config.git.setup) {
  169. yield this.env.runCommand(['ssh', 'setup']);
  170. }
  171. yield this.env.runCommand(['config', 'set', 'app_id', `"${app.id}"`, '--json']);
  172. yield this.env.runCommand(['git', 'remote']);
  173. }
  174. else {
  175. if (service === CHOICE_GITHUB) {
  176. githubUrl = yield this.linkGithub(app);
  177. }
  178. yield this.env.runCommand(['config', 'set', 'app_id', `"${app.id}"`, '--json']);
  179. }
  180. this.env.log.ok(`Project linked with app ${chalk_1.default.green(app.id)}!`);
  181. if (service === CHOICE_GITHUB) {
  182. this.env.log.info(`Here are some additional links that can help you with you first push to GitHub:\n` +
  183. `${chalk_1.default.bold('Adding GitHub as a remote')}:\n\t${chalk_1.default.cyan('https://help.github.com/articles/adding-a-remote/')}\n\n` +
  184. `${chalk_1.default.bold('Pushing to a remote')}:\n\t${chalk_1.default.cyan('https://help.github.com/articles/pushing-to-a-remote/')}\n\n` +
  185. `${chalk_1.default.bold('Working with branches')}:\n\t${chalk_1.default.cyan('https://guides.github.com/introduction/flow/')}\n\n` +
  186. `${chalk_1.default.bold('More comfortable with a GUI? Try GitHub Desktop!')}\n\t${chalk_1.default.cyan('https://desktop.github.com/')}`);
  187. if (githubUrl) {
  188. this.env.log.info(`${chalk_1.default.bold('You can now push to one of your branches on GitHub to trigger a build in Ionic Pro!')}\n` +
  189. `If you haven't added GitHub as your origin you can do so by running:\n\n` +
  190. `${chalk_1.default.green('git remote add origin ' + githubUrl)}\n\n` +
  191. `You can find additional links above to help if you're having issues.`);
  192. }
  193. }
  194. });
  195. }
  196. linkGithub(app) {
  197. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  198. const { id } = yield this.env.session.getUser();
  199. const userClient = yield this.getUserClient();
  200. const user = yield userClient.load(id, { fields: ['oauth_identities'] });
  201. if (!user.oauth_identities || !user.oauth_identities.github) {
  202. yield this.oAuthProcess(id);
  203. }
  204. if (yield this.needsAssociation(app, user.id)) {
  205. yield this.confirmGithubRepoExists();
  206. const repoId = yield this.selectGithubRepo();
  207. const branches = yield this.selectGithubBranches(repoId);
  208. return this.connectGithub(app, repoId, branches);
  209. }
  210. });
  211. }
  212. confirmGithubRepoExists() {
  213. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  214. let confirm = false;
  215. this.env.log.nl();
  216. this.env.log.info(chalk_1.default.bold(`In order to link to a GitHub repository the repository must already exist on GitHub.`));
  217. this.env.log.info(`${chalk_1.default.bold('If the repository does not exist please create one now before continuing.')}\n` +
  218. `If you're not familiar with Git you can learn how to set it up with GitHub here:\n\n` +
  219. chalk_1.default.cyan(`https://help.github.com/articles/set-up-git/ \n\n`) +
  220. `You can find documentation on how to create a repository on GitHub and push to it here:\n\n` +
  221. chalk_1.default.cyan(`https://help.github.com/articles/create-a-repo/`));
  222. confirm = yield this.env.prompt({
  223. type: 'confirm',
  224. name: 'confirm',
  225. message: 'Does the repository exist on GitHub?',
  226. });
  227. if (!confirm) {
  228. throw new errors_1.FatalException('Repo Must exist on GitHub in order to link.');
  229. }
  230. });
  231. }
  232. oAuthProcess(userId) {
  233. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  234. const opn = yield Promise.resolve().then(() => require('opn'));
  235. const userClient = yield this.getUserClient();
  236. let confirm = false;
  237. this.env.log.nl();
  238. this.env.log.info(`${chalk_1.default.bold('GitHub OAuth setup required.')}\n` +
  239. `To continue, we need you to authorize Ionic Pro with your GitHub account. ` +
  240. `A browser will open and prompt you to complete the authorization request. ` +
  241. `When finished, please return to the CLI to continue linking your app.`);
  242. confirm = yield this.env.prompt({
  243. type: 'confirm',
  244. name: 'ready',
  245. message: 'Open browser:',
  246. });
  247. if (!confirm) {
  248. throw new errors_1.FatalException('Aborting.');
  249. }
  250. const url = yield userClient.oAuthGithubLogin(userId);
  251. opn(url, { wait: false });
  252. confirm = yield this.env.prompt({
  253. type: 'confirm',
  254. name: 'ready',
  255. message: 'Authorized and ready to continue:',
  256. });
  257. if (!confirm) {
  258. throw new errors_1.FatalException('Aborting.');
  259. }
  260. });
  261. }
  262. needsAssociation(app, userId) {
  263. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  264. const appClient = yield this.getAppClient();
  265. if (app.association && app.association.repository.html_url) {
  266. this.env.log.msg(`App ${chalk_1.default.green(app.id)} already connected to ${chalk_1.default.bold(app.association.repository.html_url)}`);
  267. const confirm = yield this.env.prompt({
  268. type: 'confirm',
  269. name: 'confirm',
  270. message: 'Would you like to connect a different repo?',
  271. });
  272. if (!confirm) {
  273. return false;
  274. }
  275. try {
  276. // TODO: maybe we can use a PUT instead of DELETE now + POST later?
  277. yield appClient.deleteAssociation(app.id);
  278. }
  279. catch (e) {
  280. if (cli_utils_1.isSuperAgentError(e)) {
  281. if (e.response.status === 401) {
  282. yield this.oAuthProcess(userId);
  283. yield appClient.deleteAssociation(app.id);
  284. return true;
  285. }
  286. else if (e.response.status === 404) {
  287. return true;
  288. }
  289. }
  290. throw e;
  291. }
  292. }
  293. return true;
  294. });
  295. }
  296. connectGithub(app, repoId, branches) {
  297. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  298. const appClient = yield this.getAppClient();
  299. try {
  300. const association = yield appClient.createAssociation(app.id, { repoId, type: 'github', branches });
  301. this.env.log.ok(`App ${chalk_1.default.green(app.id)} connected to ${chalk_1.default.bold(association.repository.html_url)}`);
  302. return association.repository.clone_url;
  303. }
  304. catch (e) {
  305. if (cli_utils_1.isSuperAgentError(e) && e.response.status === 403) {
  306. throw new errors_1.FatalException(e.response.body.error.message);
  307. }
  308. else {
  309. throw e;
  310. }
  311. }
  312. });
  313. }
  314. formatRepoName(fullName) {
  315. const [org, name] = fullName.split('/');
  316. return `${chalk_1.default.dim(`${org} /`)} ${name}`;
  317. }
  318. selectGithubRepo() {
  319. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  320. const user = yield this.env.session.getUser();
  321. const userClient = yield this.getUserClient();
  322. this.env.tasks.next('Looking up your GitHub repositories');
  323. const paginator = userClient.paginateGithubRepositories(user.id);
  324. const repos = [];
  325. try {
  326. for (const r of paginator) {
  327. const res = yield r;
  328. repos.push(...res.data);
  329. this.env.tasks.updateMsg(`Looking up your GitHub repositories: ${chalk_1.default.bold(String(repos.length))} found`);
  330. }
  331. }
  332. catch (e) {
  333. this.env.tasks.fail();
  334. if (cli_utils_1.isSuperAgentError(e) && e.response.status === 401) {
  335. yield this.oAuthProcess(user.id);
  336. return this.selectGithubRepo();
  337. }
  338. throw e;
  339. }
  340. this.env.tasks.end();
  341. const repoId = yield this.env.prompt({
  342. type: 'list',
  343. name: 'githubRepo',
  344. message: 'Which GitHub repository would you like to link?',
  345. choices: repos.map(repo => ({
  346. name: this.formatRepoName(repo.full_name),
  347. value: String(repo.id),
  348. })),
  349. });
  350. return Number(repoId);
  351. });
  352. }
  353. selectGithubBranches(repoId) {
  354. return tslib_1.__awaiter(this, void 0, void 0, function* () {
  355. this.env.log.nl();
  356. this.env.log.info(chalk_1.default.bold(`By default Ionic Pro links only to the ${chalk_1.default.green('master')} branch.`));
  357. this.env.log.info(`${chalk_1.default.bold('If you\'d like to link to another branch or multiple branches you\'ll need to select each branch to connect to.')}\n` +
  358. `If you're not familiar with on working with branches in GitHub you can read about them here:\n\n` +
  359. chalk_1.default.cyan(`https://guides.github.com/introduction/flow/ \n\n`));
  360. const choice = yield this.env.prompt({
  361. type: 'list',
  362. name: 'githubMultipleBranches',
  363. message: 'Which would you like to do?',
  364. choices: [
  365. {
  366. name: `Link to master branch only`,
  367. value: CHOICE_MASTER_ONLY,
  368. },
  369. {
  370. name: `Link to specific branches`,
  371. value: CHOICE_SPECIFIC_BRANCHES,
  372. },
  373. ],
  374. });
  375. switch (choice) {
  376. case CHOICE_MASTER_ONLY:
  377. return ['master'];
  378. case CHOICE_SPECIFIC_BRANCHES:
  379. // fall through and begin prompting to choose branches
  380. break;
  381. default:
  382. throw new errors_1.FatalException('Aborting. No branch choice specified.');
  383. }
  384. const user = yield this.env.session.getUser();
  385. const userClient = yield this.getUserClient();
  386. const paginator = userClient.paginateGithubBranches(user.id, repoId);
  387. this.env.tasks.next('Looking for available branches');
  388. const availableBranches = [];
  389. try {
  390. for (const r of paginator) {
  391. const res = yield r;
  392. availableBranches.push(...res.data);
  393. this.env.tasks.updateMsg(`Looking up the available branches on your GitHub repository: ${chalk_1.default.bold(String(availableBranches.length))} found`);
  394. }
  395. }
  396. catch (e) {
  397. this.env.tasks.fail();
  398. throw e;
  399. }
  400. this.env.tasks.end();
  401. const choices = availableBranches.map(branch => ({
  402. name: branch.name,
  403. value: branch.name,
  404. checked: branch.name === 'master',
  405. }));
  406. if (choices.length === 0) {
  407. this.env.log.warn('No branches found for the repository...linking to master branch.');
  408. return ['master'];
  409. }
  410. const selectedBranches = yield this.env.prompt({
  411. type: 'checkbox',
  412. name: 'githubBranches',
  413. message: 'Which branch would you like to link?',
  414. choices: choices,
  415. default: ['master'],
  416. });
  417. return selectedBranches;
  418. });
  419. }
  420. };
  421. LinkCommand = tslib_1.__decorate([
  422. command_1.CommandMetadata({
  423. name: 'link',
  424. type: 'project',
  425. backends: [cli_utils_1.BACKEND_LEGACY, cli_utils_1.BACKEND_PRO],
  426. description: 'Connect your local app to Ionic',
  427. longDescription: `
  428. If you have an app on Ionic, you can link it to this local Ionic project with this command.
  429. Excluding the ${chalk_1.default.green('app_id')} argument looks up your apps on Ionic and prompts you to select one.
  430. This command simply sets the ${chalk_1.default.bold('app_id')} property in ${chalk_1.default.bold('ionic.config.json')} for other commands to read.
  431. `,
  432. exampleCommands: ['', 'a1b2c3d4'],
  433. inputs: [
  434. {
  435. name: 'app_id',
  436. description: `The ID of the app to link (e.g. ${chalk_1.default.green('a1b2c3d4')})`,
  437. required: false,
  438. },
  439. ],
  440. options: [
  441. {
  442. name: 'name',
  443. description: 'The app name to use during the linking of a new app',
  444. },
  445. {
  446. name: 'create',
  447. description: 'Create a new app on Ionic and link it with this local Ionic project',
  448. backends: [cli_utils_1.BACKEND_PRO],
  449. type: Boolean,
  450. visible: false,
  451. },
  452. {
  453. name: 'pro-id',
  454. description: 'Specify an app ID from the Ionic Dashboard to link',
  455. visible: false,
  456. },
  457. ],
  458. })
  459. ], LinkCommand);
  460. exports.LinkCommand = LinkCommand;