UNPKG

12.6 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3const authentications_1 = require("@bearer/types/lib/authentications");
4const command_1 = require("@oclif/command");
5const fs = require("fs-extra");
6const Listr = require("listr");
7const path = require("path");
8const Case = require("case");
9const os = require("os");
10const globby = require("globby");
11const cli_ux_1 = require("cli-ux");
12const child_process_1 = require("child_process");
13const util = require("util");
14const base_command_1 = require("../base-command");
15const install_dependencies_1 = require("../tasks/install-dependencies");
16const init_views_1 = require("../tasks/init-views");
17const helpers_1 = require("../utils/helpers");
18const component_1 = require("./generate/component");
19const spec_1 = require("./generate/spec");
20const inquirer = require("inquirer");
21const prompts_1 = require("../utils/prompts");
22const locator_1 = require("../utils/locator");
23exports.authTypes = {
24 [authentications_1.Authentications.OAuth1]: { name: 'OAuth1', value: authentications_1.Authentications.OAuth1 },
25 [authentications_1.Authentications.OAuth2]: { name: 'OAuth2', value: authentications_1.Authentications.OAuth2 },
26 [authentications_1.Authentications.Basic]: { name: 'Basic Auth', value: authentications_1.Authentications.Basic },
27 [authentications_1.Authentications.ApiKey]: { name: 'API Key', value: authentications_1.Authentications.ApiKey },
28 [authentications_1.Authentications.NoAuth]: { name: 'NoAuth', value: authentications_1.Authentications.NoAuth },
29 [authentications_1.Authentications.Custom]: { name: 'Custom', value: authentications_1.Authentications.Custom }
30};
31class New extends base_command_1.default {
32 constructor() {
33 super(...arguments);
34 this.createIntegrationStructure = (authType) => async (ctx) => {
35 ctx.files = await helpers_1.copyFiles(this, path.join('init', 'structure'), this.copyDestinationFolder, this.getVars(authType), true);
36 };
37 this.createAuthFiles = (authType) => async (ctx) => {
38 const files = await helpers_1.copyFiles(this, path.join('init', authType), this.copyDestinationFolder, this.getVars(authType), true);
39 ctx.files = [...ctx.files, ...files];
40 };
41 this.createViewsStructure = (authType) => async (_ctx, _task) => {
42 return new Listr([
43 init_views_1.default({
44 cmd: this,
45 vars: Object.assign({}, this.getVars(authType), initViewsVars(authType))
46 }),
47 {
48 title: 'Create integration specification file',
49 task: async (_ctx, _task) => spec_1.default.run(['--path', this.copyDestinationFolder, '--silent'])
50 },
51 {
52 title: 'Create intial components',
53 task: async (_ctx, _task) => component_1.default.run(['feature', '--type', 'root', '--path', this.copyDestinationFolder, '--silent'])
54 }
55 ]);
56 };
57 this.getVars = (authType) => ({
58 integrationTitle: this.name,
59 componentName: Case.pascal(this.name),
60 componentTagName: Case.kebab(this.name),
61 bearerTagVersion: process.env.BEARER_PACKAGE_VERSION || 'latest',
62 bearerRestClient: bearerRestClient(authType)
63 });
64 }
65 async run() {
66 const { args, flags } = this.parse(New);
67 this.debug('url: %j', flags);
68 const { template, directory } = flags;
69 this.path = flags.path;
70 try {
71 this.name = args.IntegrationName || (await prompts_1.askForString('Integration name'));
72 this.copyDestinationFolder = await defineLocationPath(this, {
73 name: this.name,
74 cwd: this.path || process.cwd(),
75 force: flags.force
76 });
77 this.debug('target path: %s', this.copyDestinationFolder);
78 const skipInstall = flags.skipInstall;
79 let files = [];
80 if (template) {
81 const tmp = path.join(os.tmpdir(), Date.now().toString());
82 await cloneRepository(template, tmp, this);
83 const { selected } = await selectFolder(tmp, { selectedPath: directory });
84 const source = path.join(tmp, selected);
85 fs.copySync(source, this.copyDestinationFolder);
86 this.debug('copied from %s to %s', source, this.copyDestinationFolder);
87 files = await globby(['**/*'], { cwd: this.copyDestinationFolder });
88 }
89 else {
90 const authType = flags.authType || (await askForAuthType());
91 const tasks = [
92 {
93 title: 'Generating integration structure',
94 task: this.createIntegrationStructure(authType)
95 },
96 {
97 title: 'Create auth files',
98 task: this.createAuthFiles(authType)
99 },
100 {
101 title: 'Create views related files',
102 enabled: () => flags.withViews,
103 task: this.createViewsStructure(authType)
104 }
105 ];
106 const { files: created } = await new Listr(tasks, { nonTTYRenderer: 'silent' }).run();
107 files = created;
108 if (authType) {
109 this.success(`Integration initialized, name: ${this.name}, authentication type: ${exports.authTypes[authType].name}`);
110 }
111 }
112 if (!skipInstall) {
113 await new Listr([install_dependencies_1.default({ cwd: this.copyDestinationFolder })]).run();
114 }
115 helpers_1.printFiles(this, files);
116 const finalLocation = this.copyDestinationFolder.replace(path.resolve(args.path || process.cwd()), '.');
117 this.log("\nWhat's next?\n");
118 this.log('* read the bearer documentation at https://docs.bearer.sh\n');
119 this.log(`* start your local development environment by running:\n`);
120 this.log(this.colors.bold(` cd ${finalLocation}`));
121 }
122 catch (e) {
123 this.debug('error: %j', e);
124 if (e instanceof NoIntegrationFoundError) {
125 this.error(this.colors.red('No valid integrations found within the cloned git repository'));
126 }
127 else {
128 this.error(e);
129 }
130 }
131 }
132}
133New.description = 'generate integration boilerplate';
134New.flags = Object.assign({}, base_command_1.default.flags, { help: command_1.flags.help({ char: 'h' }), template: command_1.flags.string({
135 char: 't',
136 description: 'Generate an integration from a template (git url)'
137 }), directory: command_1.flags.string({
138 char: 'd',
139 dependsOn: ['template'],
140 description: 'Select a directory as source of the integration'
141 }), skipInstall: command_1.flags.boolean({ hidden: true, description: 'Do not install dependencies' }), withViews: command_1.flags.boolean({ description: 'Experimental - generate views' }), force: command_1.flags.boolean({ description: 'Force copying files', char: 'f' }), authType: command_1.flags.string({
142 char: 'a',
143 description: 'Authorization type',
144 // only allow the value to be from a discrete set
145 options: Object.keys(exports.authTypes).map(auth => exports.authTypes[auth].value)
146 }) });
147New.args = [{ name: 'IntegrationName' }];
148exports.default = New;
149async function askForAuthType() {
150 const { authenticationType } = await inquirer.prompt([
151 {
152 message: 'Select an authentication method for the API you want to consume:',
153 type: 'list',
154 name: 'authenticationType',
155 choices: Object.values(exports.authTypes)
156 }
157 ]);
158 return authenticationType;
159}
160exports.askForAuthType = askForAuthType;
161function bearerRestClient(authType) {
162 switch (authType) {
163 case authentications_1.Authentications.OAuth1:
164 return '"oauth": "^0.9.15"';
165 case authentications_1.Authentications.ApiKey:
166 case authentications_1.Authentications.Basic:
167 case authentications_1.Authentications.NoAuth:
168 case authentications_1.Authentications.OAuth2:
169 case authentications_1.Authentications.Custom:
170 return '"axios": "^0.18.0"';
171 }
172 throw new Error(`Authentication not found: ${authType}`);
173}
174exports.bearerRestClient = bearerRestClient;
175// @ts-ignore
176async function cloneRepository(url, destination, logger) {
177 const asyncExec = util.promisify(child_process_1.exec);
178 cli_ux_1.default.action.start('cloning');
179 try {
180 const { stdout, stderr } = await asyncExec('git --version');
181 // @ts-ignore
182 logger.debug('out: %j\n, err: %j', stdout, stderr);
183 }
184 catch (e) {
185 // @ts-ignore
186 logger.debug('error: %j', e);
187 cli_ux_1.default.action.stop();
188 throw new GitNotAvailable();
189 }
190 await new Promise(async (resolve, reject) => {
191 try {
192 const command = `git clone ${url} --depth=1 ${destination}`;
193 // @ts-ignore
194 logger.debug(`Running ${command}`);
195 await asyncExec(command);
196 resolve();
197 }
198 catch (e) {
199 cli_ux_1.default.action.stop();
200 // @ts-ignore
201 logger.debug(`%j`, e);
202 reject(new CloningError());
203 }
204 });
205 cli_ux_1.default.action.stop();
206}
207exports.cloneRepository = cloneRepository;
208async function selectFolder(location, { selectedPath, integrationRootProof = locator_1.INTEGRATION_PROOF }) {
209 const list = await globby([`**/${integrationRootProof}`], { cwd: location });
210 const choices = list
211 .sort()
212 .map(path => {
213 return {
214 name: path.split(`/${integrationRootProof}`)[0],
215 value: path.split(integrationRootProof)[0].replace(/\/$/, '')
216 };
217 })
218 .filter(choice => {
219 return !Boolean(selectedPath) || choice.value === selectedPath;
220 });
221 switch (choices.length) {
222 case 0: {
223 throw new NoIntegrationFoundError(selectedPath);
224 }
225 case 1: {
226 return { selected: choices[0].value };
227 }
228 default: {
229 return await inquirer.prompt([
230 {
231 choices,
232 name: 'selected',
233 message: 'Select a template',
234 type: 'list'
235 }
236 ]);
237 }
238 }
239}
240exports.selectFolder = selectFolder;
241async function defineLocationPath(logger, { name, cwd, force }) {
242 let location = path.join(cwd, name);
243 let shouldForce = force;
244 while (fs.existsSync(location) && shouldForce !== true) {
245 if (shouldForce === false) {
246 logger.warn(`${location.replace(cwd, '')} already exists\n please provide a different folder name`);
247 const folderName = await prompts_1.askForString('Folder name to use');
248 location = path.join(cwd, folderName);
249 }
250 else {
251 const { override } = await inquirer.prompt([
252 {
253 type: 'confirm',
254 name: 'override',
255 default: false,
256 message: `${location} is not empty, copy files anyway?`
257 }
258 ]);
259 shouldForce = override;
260 }
261 }
262 return location;
263}
264exports.defineLocationPath = defineLocationPath;
265function initViewsVars(authType) {
266 const setup = authType === authentications_1.Authentications.NoAuth
267 ? ''
268 : `
269 {
270 classname: 'SetupAction',
271 isRoot: true,
272 initialTagName: 'setup-action',
273 name: 'setup-action',
274 label: 'Setup Action Component'
275 },
276 {
277 classname: 'SetupView',
278 isRoot: true,
279 initialTagName: 'setup-view',
280 name: 'setup-view',
281 label: 'Setup Display Component'
282 },`;
283 return { setup };
284}
285exports.initViewsVars = initViewsVars;
286/**
287 * Custom errors
288 */
289class NoIntegrationFoundError extends Error {
290 constructor(selectedFolder) {
291 super(`No valid integrations found ${selectedFolder ? `under ${selectedFolder}` : ''}`);
292 }
293}
294class GitNotAvailable extends Error {
295 constructor() {
296 super('git command not found in your path, please install it');
297 }
298}
299class CloningError extends Error {
300 constructor() {
301 super('Error while cloning the repository');
302 }
303}