1 | "use strict";
|
2 | Object.defineProperty(exports, "__esModule", { value: true });
|
3 | const authentications_1 = require("@bearer/types/lib/authentications");
|
4 | const command_1 = require("@oclif/command");
|
5 | const fs = require("fs-extra");
|
6 | const Listr = require("listr");
|
7 | const path = require("path");
|
8 | const Case = require("case");
|
9 | const os = require("os");
|
10 | const globby = require("globby");
|
11 | const cli_ux_1 = require("cli-ux");
|
12 | const child_process_1 = require("child_process");
|
13 | const util = require("util");
|
14 | const base_command_1 = require("../base-command");
|
15 | const install_dependencies_1 = require("../tasks/install-dependencies");
|
16 | const init_views_1 = require("../tasks/init-views");
|
17 | const helpers_1 = require("../utils/helpers");
|
18 | const component_1 = require("./generate/component");
|
19 | const spec_1 = require("./generate/spec");
|
20 | const inquirer = require("inquirer");
|
21 | const prompts_1 = require("../utils/prompts");
|
22 | const locator_1 = require("../utils/locator");
|
23 | exports.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 | };
|
31 | class 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 | }
|
133 | New.description = 'generate integration boilerplate';
|
134 | New.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 |
|
145 | options: Object.keys(exports.authTypes).map(auth => exports.authTypes[auth].value)
|
146 | }) });
|
147 | New.args = [{ name: 'IntegrationName' }];
|
148 | exports.default = New;
|
149 | async 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 | }
|
160 | exports.askForAuthType = askForAuthType;
|
161 | function 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 | }
|
174 | exports.bearerRestClient = bearerRestClient;
|
175 |
|
176 | async 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 |
|
182 | logger.debug('out: %j\n, err: %j', stdout, stderr);
|
183 | }
|
184 | catch (e) {
|
185 |
|
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 |
|
194 | logger.debug(`Running ${command}`);
|
195 | await asyncExec(command);
|
196 | resolve();
|
197 | }
|
198 | catch (e) {
|
199 | cli_ux_1.default.action.stop();
|
200 |
|
201 | logger.debug(`%j`, e);
|
202 | reject(new CloningError());
|
203 | }
|
204 | });
|
205 | cli_ux_1.default.action.stop();
|
206 | }
|
207 | exports.cloneRepository = cloneRepository;
|
208 | async 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 | }
|
240 | exports.selectFolder = selectFolder;
|
241 | async 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 | }
|
264 | exports.defineLocationPath = defineLocationPath;
|
265 | function 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 | }
|
285 | exports.initViewsVars = initViewsVars;
|
286 |
|
287 |
|
288 |
|
289 | class NoIntegrationFoundError extends Error {
|
290 | constructor(selectedFolder) {
|
291 | super(`No valid integrations found ${selectedFolder ? `under ${selectedFolder}` : ''}`);
|
292 | }
|
293 | }
|
294 | class GitNotAvailable extends Error {
|
295 | constructor() {
|
296 | super('git command not found in your path, please install it');
|
297 | }
|
298 | }
|
299 | class CloningError extends Error {
|
300 | constructor() {
|
301 | super('Error while cloning the repository');
|
302 | }
|
303 | }
|