Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add commands to buy phone numbers #38

Merged
merged 11 commits into from
Jun 14, 2019
23 changes: 16 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"@oclif/plugin-autocomplete": "^0.1.1",
"@oclif/plugin-help": "^2.2.0",
"@oclif/plugin-plugins": "^1.7.8",
"@twilio/cli-core": "~1.3.1",
"@twilio/cli-core": "~1.4.0",
"chalk": "^2.4.2",
"ngrok": "^3.1.1",
"twilio": "^3.32.0"
Expand All @@ -24,6 +24,7 @@
"@twilio/cli-test": "~1.0.0",
"aws-sdk": "^2.475.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"eslint": "^4.19.1",
"eslint-config-oclif": "^1.5.1",
"eslint-plugin-mocha": "^5.2.1",
Expand Down Expand Up @@ -71,6 +72,7 @@
"hooks": {
"init": [
"./src/hooks/init/twilio-api",
"./src/hooks/init/buy-phone-number",
"./src/hooks/init/plugin-verification"
]
},
Expand Down
112 changes: 9 additions & 103 deletions src/base-commands/twilio-api-command.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
const { flags } = require('@oclif/command');
const { TwilioClientCommand } = require('@twilio/cli-core').baseCommands;
const { doesObjectHaveProperty } = require('@twilio/cli-core').services.JSUtils;
const { validateSchema } = require('../services/api-schema/schema-validator');
const { kebabCase, camelCase } = require('../services/naming-conventions');
const ResourcePathParser = require('../services/resource-path-parser');
const { getActionDescription, isApi2010 } = require('../services/twilio-api');
const { ApiCommandRunner, getActionDescription, isApi2010 } = require('../services/twilio-api');

// Open API type to oclif flag type mapping. For numerical types, we'll do validation elsewhere.
const typeMap = {
Expand All @@ -25,109 +23,17 @@ const typeMap = {
const ACCOUNT_SID_FLAG = 'account-sid';

class TwilioApiCommand extends TwilioClientCommand {
async run() {
await super.run();

// "this.constructor" is the class used for this command.
// Because oclif is constructing the object for us,
// we can't pass the actionDefinition in through
// the constructor, so we make it a static property
// of the command class.
const ThisCommandClass = this.constructor;
const domainName = ThisCommandClass.actionDefinition.domainName;
const versionName = ThisCommandClass.actionDefinition.versionName;
const currentPath = ThisCommandClass.actionDefinition.path;
const actionName = ThisCommandClass.actionDefinition.actionName;

const { flags: receivedFlags } = this.parse(this.constructor);

// TODO: Possible extender event: "beforeValidateParameters"

const camelCasedFlags = {};
const flagErrors = {};
Object.keys(receivedFlags).forEach(key => {
const flagValue = receivedFlags[key];

if (doesObjectHaveProperty(ThisCommandClass.flags[key], 'apiDetails')) {
const schema = ThisCommandClass.flags[key].apiDetails.parameter.schema;
this.logger.debug(`Schema for "${key}": ` + JSON.stringify(schema));

const validationErrors = validateSchema(schema, flagValue, this.logger);

if (validationErrors.length > 0) {
flagErrors[key] = validationErrors;
}
}

camelCasedFlags[camelCase(key)] = flagValue;
});

this.logger.debug('Provided flags: ' + JSON.stringify(receivedFlags));

// If there were any errors validating the flag values, log them by flag
// key and exit with a non-zero code.
if (Object.keys(flagErrors).length > 0) {
this.logger.error('Flag value validation errors:');
Object.keys(flagErrors).forEach(key => {
flagErrors[key].forEach(error => {
this.logger.error(` ${key}: ${error}`);
});
});
this.exit(1);
}
async runCommand() {
const runner = new ApiCommandRunner(
this.twilioClient,
this.constructor.actionDefinition,
this.constructor.flags,
this.flags
);

// TODO: Possible extender event: "afterValidateParameters"

// TODO: Possible extender event: "beforeInvokeApi"

// This converts a path like "/Accounts/{AccountSid}/Calls" to
// the Node.js object in the Twilio Helper library.
// Example: twilioClient.api.v2010.accounts('ACxxxx').calls
const helperVersion = this.twilioClient[domainName][versionName];
const resourcePathParser = new ResourcePathParser(currentPath);
let endpoint = helperVersion;

resourcePathParser.forEachPathNode(pathNode => {
if (resourcePathParser.isPathVariable(pathNode)) {
const paramName = kebabCase(pathNode.replace(/[{}]/g, ''));
let value = '';

if (doesObjectHaveProperty(receivedFlags, paramName)) {
value = receivedFlags[paramName];
} else if (paramName === ACCOUNT_SID_FLAG) {
value = this.twilioClient.accountSid;
}

// Since this part of the path has a parameter, we invoke
// the current endpoint as a function, passing the parameter
// and then use it's result as the new endpoint.
endpoint = endpoint(value);
this.logger.debug(`pathNode=${pathNode}, value=${value}, endpoint=${typeof endpoint}`);
} else {
endpoint = endpoint[camelCase(pathNode)];
this.logger.debug(`pathNode=${pathNode}, endpoint=${typeof endpoint}`);
}
});

this.logger.debug(`actionName=${actionName}, endpoint[actionName]=${typeof endpoint[actionName]}`);

let response;
try {
response = await endpoint[actionName](camelCasedFlags);
} catch (error) {
if (error.moreInfo) {
this.logger.error(`Error ${error.code} response from Twilio: ${error.message}`);
this.logger.info(`See ${error.moreInfo} for more info.`);
} else {
this.logger.error(`Twilio library error: ${error.message}`);
}
this.exit(error.code);
}
const response = await runner.run();

// TODO: Figure out sane default output columns
this.output(response, this.flags.properties);

// TODO: Possible extender event: "afterInvokeApi"
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/commands/phone-numbers/list.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@ const { flags } = require('@oclif/command');
const { TwilioClientCommand } = require('@twilio/cli-core').baseCommands;

class NumberList extends TwilioClientCommand {
async run() {
await super.run();

async runCommand() {
const fullData = await this.twilioClient.incomingPhoneNumbers.list();
this.output(fullData, this.flags.properties);
}
Expand Down
5 changes: 2 additions & 3 deletions src/commands/phone-numbers/update.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ class NumberUpdate extends TwilioClientCommand {
this.ngrok = ngrok || require('ngrok');
}

async run() {
await super.run();
const helper = new IncomingPhoneNumberHelper(this);
async runCommand() {
const helper = new IncomingPhoneNumberHelper(this.twilioClient);
const phoneNumber = await helper.findPhoneNumber(this.args['phone-number']);

const props = this.parseProperties();
Expand Down
4 changes: 2 additions & 2 deletions src/commands/projects/add.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const os = require('os');
const { flags } = require('@oclif/command');

const { BaseCommand, TwilioClientCommand } = require('@twilio/cli-core').baseCommands;
const { CLIRequestClient } = require('@twilio/cli-core').services;
const { CliRequestClient } = require('@twilio/cli-core').services;
const { STORAGE_LOCATIONS } = require('@twilio/cli-core').services.secureStorage;

const helpMessages = require('../../services/messaging/help-messages');
Expand Down Expand Up @@ -141,7 +141,7 @@ class ProjectsAdd extends BaseCommand {
getTwilioClient() {
if (!this.twilioClient) {
this.twilioClient = require('twilio')(this.accountSid, this.authToken, {
httpClient: new CLIRequestClient(this.id, this.logger),
httpClient: new CliRequestClient(this.id, this.logger),
region: this.region
});
}
Expand Down
Loading