diff --git a/lib/clients/aws-client/aws-util.js b/lib/clients/aws-client/aws-util.js index d023473e..1733c7ab 100644 --- a/lib/clients/aws-client/aws-util.js +++ b/lib/clients/aws-client/aws-util.js @@ -1,8 +1,9 @@ const aws = require('aws-sdk'); -const fs = require('fs'); +const fs = require('fs-extra'); const os = require('os'); const path = require('path'); -const jsonUtility = require('@src/utils/json-utility'); +const R = require('ramda'); + const CONSTANT = require('@src/utils/constants'); module.exports = { @@ -23,9 +24,8 @@ function getAWSProfile(askProfile) { return CONSTANT.PLACEHOLDER.ENVIRONMENT_VAR.AWS_CREDENTIALS; } } - const awsCredentials = path.join(os.homedir(), '.ask', 'cli_config'); - const propertyPathForAWSProfile = ['profiles', askProfile, 'aws_profile']; - return jsonUtility.getProperty(awsCredentials, propertyPathForAWSProfile); + const askConfig = fs.readJSONSync(path.join(os.homedir(), '.ask', 'cli_config')); + return R.view(R.lensPath(['profiles', askProfile, 'aws_profile']), askConfig); } // We face the same issue as mentioned in https://github.com/aws/aws-sdk-js/issues/2181 diff --git a/lib/clients/smapi-client/resources/skill.js b/lib/clients/smapi-client/resources/skill.js index aa12cb67..53814af4 100644 --- a/lib/clients/smapi-client/resources/skill.js +++ b/lib/clients/smapi-client/resources/skill.js @@ -185,7 +185,7 @@ module.exports = (smapiHandle) => { function listIspForSkill(skillId, stage, queryParams, callback) { const queryObject = R.clone(queryParams); if (queryObject && !queryObject.maxResults) { - queryObject.maxResults = CONSTANTS.ISP.NUMBERS.DEFAULT_ISP_MAX_RESULTS; + queryObject.maxResults = CONSTANTS.SMAPI.DEFAULT_MAX_RESULT_PER_PAGE; } const url = `skills/${skillId}/stages/${stage}/${ISP_URL_BASE}`; smapiHandle( diff --git a/lib/commands/api/account-linking/set-account-linking/helper.js b/lib/commands/api/account-linking/set-account-linking/helper.js index ed180827..9fadb464 100644 --- a/lib/commands/api/account-linking/set-account-linking/helper.js +++ b/lib/commands/api/account-linking/set-account-linking/helper.js @@ -1,7 +1,6 @@ const fs = require('fs'); const stringUtility = require('@src/utils/string-utils'); -const tools = require('@src/utils/tools'); const ui = require('./ui'); @@ -49,19 +48,18 @@ function prepareAccountLinkingPayload(smapiClient, skillId, stage, filePath, cal } function getManifest(smapiClient, skillId, stage, callback) { - smapiClient.skill.manifest.getManifest(skillId, stage, (getManifestErr, data) => { + smapiClient.skill.manifest.getManifest(skillId, stage, (getManifestErr, response) => { if (getManifestErr) { return callback(getManifestErr); } - if (data.statusCode === 303) { + if (response.statusCode === 303) { return callback('[Warn]: The get-manifest request is not ready. Please try again later.'); } - const response = tools.convertDataToJsonObject(data.body); - if (data.statusCode < 300) { - callback(null, response); + if (response.statusCode < 300) { + callback(null, response.body); } else { - callback(response, null); + callback(response.body, null); } }); } diff --git a/lib/commands/api/account-linking/set-account-linking/index.js b/lib/commands/api/account-linking/set-account-linking/index.js index adf6a48d..15c8c7c8 100644 --- a/lib/commands/api/account-linking/set-account-linking/index.js +++ b/lib/commands/api/account-linking/set-account-linking/index.js @@ -54,7 +54,7 @@ class SetAccountLinkingCommand extends AbstractCommand { Messenger.getInstance().error(error); cb(error); } else { - Messenger.getInstance().info(jsonView.toString(response.body)); + Messenger.getInstance().info('Account linking information updated successfully.'); cb(); } }); diff --git a/lib/commands/api/catalog/upload-catalog/index.js b/lib/commands/api/catalog/upload-catalog/index.js index ae287b2c..19533fed 100644 --- a/lib/commands/api/catalog/upload-catalog/index.js +++ b/lib/commands/api/catalog/upload-catalog/index.js @@ -4,7 +4,6 @@ const SmapiClient = require('@src/clients/smapi-client'); const S3Client = require('@src/clients/aws-client/s3-client'); const optionModel = require('@src/commands/option-model'); const profileHelper = require('@src/utils/profile-helper'); -const tools = require('@src/utils/tools'); const helper = require('./helper'); @@ -54,23 +53,21 @@ function _multiPartUploadCatalog(catalogId, filePath, smapiClient, callback) { } helper._confirmOrOverwritePartSize(totalSize, calculatedPartSize, calculatedPartsNumber, (partSize, partsNumber) => { - smapiClient.catalog.createCatalogUpload(catalogId, partsNumber, (createCatalogUploadError, data) => { + smapiClient.catalog.createCatalogUpload(catalogId, partsNumber, (createCatalogUploadError, createResponse) => { if (createCatalogUploadError) { callback(createCatalogUploadError); return; } - const createResponse = tools.convertDataToJsonObject(data.body); - if (!createResponse) { + if (!createResponse.body) { return process.nextTick(() => { callback('[Error]: The response from create-catalog-upload should not be empty.'); }); } - if (!createResponse.id || !createResponse.presignedUploadParts) { + const uploadId = createResponse.body.id; + if (!uploadId || !createResponse.body.presignedUploadParts) { return callback('[Error]: The response of create-catalog-upload is not valid.'); } - - const uploadId = createResponse.id; - const uploadPartsMap = helper._transformUploadArrayToMap(createResponse.presignedUploadParts); + const uploadPartsMap = helper._transformUploadArrayToMap(createResponse.body.presignedUploadParts); if (!uploadPartsMap) { return callback('[Error]: The response from create-catalog-upload is empty.'); } diff --git a/lib/utils/constants.js b/lib/utils/constants.js index ab9e0521..34668e7d 100644 --- a/lib/utils/constants.js +++ b/lib/utils/constants.js @@ -1,5 +1,3 @@ -const DEFAULT_LIST_MAX_RESULT = 50; - module.exports.METRICS = { ENDPOINT: '', // TODO add the official endpoint when we have it NEW_USER_LENGTH_DAYS: 3 @@ -38,12 +36,6 @@ module.exports.SKILL = { } }; -module.exports.ISP = { - NUMBERS: { - DEFAULT_ISP_MAX_RESULTS: 50 - } -}; - module.exports.CONFIGURATION = { JSON_DISPLAY_INDENT: 2, OPN_BROWSER_DELAY: 1000, @@ -271,7 +263,7 @@ module.exports.SMAPI = { GET_TASK: 'get-task', SEARCH_TASK: 'search-task' }, - DEFAULT_MAX_RESULT_PER_PAGE: DEFAULT_LIST_MAX_RESULT + DEFAULT_MAX_RESULT_PER_PAGE: 50 }; module.exports.HTTP_REQUEST = { diff --git a/lib/utils/json-read.js b/lib/utils/json-read.js deleted file mode 100644 index 8a244497..00000000 --- a/lib/utils/json-read.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict'; - -const jsonfile = require('jsonfile'); - -// Public -module.exports = { - readFile: (filePath) => { - let file; - try { - file = jsonfile.readFileSync(filePath); - } catch (e) { - console.error('Invalid json: ' + filePath); - return null; - } - return file; - }, - readString: (string) => { - let jsonObj; - try { - jsonObj = JSON.parse(string); - } catch (e) { - console.error('Invalid json string: ' + string); - return null; - } - return jsonObj; - }, - getProperty: (jsonObject, track) => { - const trackArray = track.split('.').slice(1); - let property = jsonObject; - for (let i = 0; i < trackArray.length; i++) { - if (property.hasOwnProperty(trackArray[i])) { - property = property[trackArray[i]]; - } else { - console.error(`The property "${trackArray[i]}" does not exist.`); - return null; - } - } - return property; - } -}; diff --git a/lib/utils/json-utility.js b/lib/utils/json-utility.js deleted file mode 100644 index ed421232..00000000 --- a/lib/utils/json-utility.js +++ /dev/null @@ -1,110 +0,0 @@ -'use strict'; - -const jsonfile = require('jsonfile'); - -module.exports = { - write: write, - read: read, - getProperty: getProperty, - writeToProperty: writeToProperty, - deleteProperty: deleteProperty, - insertObjectToObject: insertObjectToObject, - deletePropertyFromJsonObject: deletePropertyFromJsonObject, - getPropertyValueFromObject: getPropertyValueFromObject, - addValueToProperty:addValueToProperty -}; - -function read(filePath) { - try { - let content = jsonfile.readFileSync(filePath); - return content; - } catch (e) { - console.error('Invalid json: ' + filePath); - process.exit(1); - } -} - -function write(filePath, jsonObject) { - try { - jsonfile.writeFileSync(filePath, jsonObject, {spaces: 2}); - } catch (e) { - console.error('Invalid file, cannot write to: ' + filePath); - process.exit(1); - } -} - -function writeToProperty(filePath, propertyPathArray, writeObject) { - let jsonObject = read(filePath); - insertObjectToObject(jsonObject, propertyPathArray, writeObject); - write(filePath, jsonObject); -} - -function deleteProperty(filePath, propertyPathArray) { - let jsonObject = read(filePath); - deletePropertyFromJsonObject(jsonObject, propertyPathArray); - write(filePath, jsonObject); - return true; -} - -function getProperty(filePath, propertyPathArray) { - const jsonObject = read(filePath); - return getPropertyValueFromObject(jsonObject, propertyPathArray); -} - -function getPropertyValueFromObject(jsonObject, propertyPathArray) { - let targetObject = jsonObject; - for (let index = 0; index < propertyPathArray.length - 1; index++) { - if (!targetObject || !targetObject[propertyPathArray[index]]) { - return null; - } - targetObject = targetObject[propertyPathArray[index]]; - } - - if (!targetObject) { - return null; - } - - return targetObject[propertyPathArray[propertyPathArray.length - 1]]; -} - -function deletePropertyFromJsonObject(jsonObject, propertyPathArray) { - let targetObject = jsonObject; - for (let index = 0; index < propertyPathArray.length - 1; index++) { - if (!targetObject.hasOwnProperty(propertyPathArray[index])) { - return false; - } - targetObject = targetObject[propertyPathArray[index]]; - } - delete targetObject[propertyPathArray[propertyPathArray.length - 1]]; -} - -function insertObjectToObject(jsonObject, propertyPathArray, writeObject) { - let targetObject = jsonObject; - for (let index = 0; index < propertyPathArray.length - 1; index++) { - if (!targetObject.hasOwnProperty(propertyPathArray[index])) { - if (typeof targetObject !== 'object') { - break; - } - targetObject[propertyPathArray[index]] = {}; - } - targetObject = targetObject[propertyPathArray[index]]; - } - - if (typeof targetObject === 'object') { - targetObject[propertyPathArray[propertyPathArray.length - 1]] = writeObject; - return jsonObject; - } - console.error('[Error]: cannot add property to non-object value. Please correct your target path'); - process.exit(1); -} - - -function addValueToProperty(jsonObject, propertyPathArray, value) { - for (let index = 0; index < propertyPathArray.length-1; index++) { - if (!jsonObject.hasOwnProperty(propertyPathArray[index]) && typeof jsonObject === 'object') { - jsonObject[propertyPathArray[index]] = {}; - } - jsonObject = jsonObject[propertyPathArray[index]]; - } - jsonObject[propertyPathArray[propertyPathArray.length-1]] = value; -} diff --git a/lib/utils/lwa.js b/lib/utils/lwa.js deleted file mode 100644 index 23499798..00000000 --- a/lib/utils/lwa.js +++ /dev/null @@ -1,180 +0,0 @@ -const opn = require('opn'); -const http = require('http'); -const url = require('url'); -const inquirer = require('inquirer'); -const portscanner = require('portscanner'); - -const SpinnerView = require('@src/view/spinner-view'); -const oauthWrapper = require('@src/utils/oauth-wrapper'); -const CONSTANTS = require('@src/utils/constants'); - -const LWA_AUTH_MESSAGE = 'Switch to "Login with Amazon" page and sign-in with your Amazon developer credentials.\n' -+ 'If your browser did not open the page, run the initialization process again with command "ask init --no-browser".'; - -module.exports = { - accessTokenGenerator, - _requestTokens, - _getAuthCode, - _listenResponseFromLWA -}; - -/** -* Use LWA OAuth2 to retrieve access tokens. -* @param needBrowser -* @param lwaOptions contains clientId, clientConfirmation, scopes and state -* @param callback Json object which includes: -* 'access_token', 'refresh_token', 'token_type', 'expires_in', and 'expires_at' -*/ -function accessTokenGenerator(needBrowser, lwaOptions, callback) { - const { - clientId, - clientConfirmation, - scopes = CONSTANTS.LWA.DEFAULT_SCOPES, - state = CONSTANTS.LWA.DEFAULT_STATE - } = lwaOptions; - - const OAuth = oauthWrapper.createOAuth(clientId, clientConfirmation); - - if (!needBrowser) { - // prepare url which the user can use to call LWA - const authorizeUrl = OAuth.authorizationCode.authorizeURL({ - redirect_uri: CONSTANTS.LWA.S3_RESPONSE_PARSER_URL, - scope: scopes, - state - }); - console.log(`Paste the following url to your browser:\n ${authorizeUrl}`); - - _getAuthCode((authCode) => { - _requestTokens(authCode, CONSTANTS.LWA.S3_RESPONSE_PARSER_URL, OAuth, callback); - }); - } else { - const PORT = 9090; - portscanner.checkPortStatus(PORT, (error, status) => { - if (error) { - callback(error); - } else { - if (status === 'closed') { - // if the port haven't being used, start a server and listen to - // lwa response which has the authorization code. - const localServerUrl = `http://127.0.0.1:${PORT}/cb`; - const authorizeUrl = OAuth.authorizationCode.authorizeURL({ - redirect_uri: localServerUrl, - scope: scopes, - state - }); - - // call LWA on behalf of the user - console.log(LWA_AUTH_MESSAGE); - setTimeout(() => { - opn(authorizeUrl); - }, CONSTANTS.CONFIGURATION.OPN_BROWSER_DELAY); - - _listenResponseFromLWA(PORT, (error, authCode) => { - if (error) { - return callback(error); - } - _requestTokens(authCode, localServerUrl, OAuth, callback); - }); - } else { - console.warn('[Warn]: 9090 port on localhost has been occupied, ' - + 'ask-cli cannot start a local server for receiving authorization code.'); - console.warn('Please either abort any processes running on port 9090\n' - + 'or add `--no-browser` flag to the command as an alternative approach.'); - } - } - }); - } -} - -/** -* Ask the auth code from the user. -* @param callback Authorization code -* @private -*/ -function _getAuthCode(callback) { - inquirer.prompt([ - { - type: 'input', - name: 'authCode', - message: 'Please enter the Authorization Code: ', - validate: (value) => { - let pass = value.trim(); - if (!pass) { - return 'Please enter a valid Authorization Code.'; - } - return true; - } - } - ]).then( - (answer) => { - callback(answer.authCode); - } - ); -} - -/** -* Use the auth code to retrieve access token and other associated info from LWA. -* @param authCode -* @param redirect_uri -* @param OAuth -* @param callback Json object which includes: -* 'access_token', 'refresh_token', 'token_type', 'expires_in', and 'expires_at' -* @private -*/ -function _requestTokens(authCode, redirect_uri, OAuth, callback) { - let tokenConfig = { - code: authCode, - redirect_uri: redirect_uri - }; - - - OAuth.authorizationCode.getToken(tokenConfig, (error, result) => { - if (error) { - callback('Cannot obtain access token. ' + error); - } else { - let token = OAuth.accessToken.create(result).token; - callback(null, token); - } - }); -} - -/** -* Start a local server and listen the response from LWA, -* then extract authorization code from it. -* @param PORT -* @param OAuth -* @param callback Authorization code -* @private -*/ -function _listenResponseFromLWA(PORT, callback) { - let listenSpinner; - const server = http.createServer(handleServerRequest); - server.on('connection', (socket) => { - socket.unref(); - return; - }); - server.listen(PORT, () => { - listenSpinner = new SpinnerView(); - listenSpinner.start(` Listening on http://localhost:${PORT}...`); - }); - - function handleServerRequest(request, response) { - response.on('close', () => { - request.socket.destroy(); - }); - const requestQuery = url.parse(request.url, true).query; - listenSpinner.terminate(); - server.close(); - server.unref(); - if (request.url.startsWith('/cb?code')) { - response.end('Sign in was successful. Close browser and return to the command line interface.'); - const authCode = requestQuery.code; - callback(null, authCode); - } else if (request.url.startsWith('/cb?error')) { - response.statusCode = 403; - response.end(`Error: ${requestQuery.error}\nError description: ${requestQuery.error_description}`); - callback('Access not granted. Please verify your account credentials are correct.\nIf this is your first time getting the error, ' - + 'please retry "ask init" to ensure any browser-cached tokens are refreshed.'); - } - } -} diff --git a/lib/utils/oauth-wrapper.js b/lib/utils/oauth-wrapper.js deleted file mode 100644 index 3ee91024..00000000 --- a/lib/utils/oauth-wrapper.js +++ /dev/null @@ -1,173 +0,0 @@ -const oauth2 = require('simple-oauth2'); -const path = require('path'); -const fs = require('fs'); -const os = require('os'); -const stringUtils = require('@src/utils/string-utils'); -const jsonRead = require('@src/utils/json-read'); -const jsonUtility = require('@src/utils/json-utility'); -const CONSTANTS = require('@src/utils/constants'); - -module.exports = { - createOAuth, - tokenRefreshAndRead, - writeToken -}; - -/* If you want to create tools to call SMAPI(Skill Management API), - * please create your own clientId and ClientConfirmation through LWA (login with Amazon). - * https://developer.amazon.com/loginwithamazon/console/site/lwa/overview.html - * - * You can find necessary scopes for LWA to call SMAPI here: - * https://developer.amazon.com/docs/smapi/ask-cli-intro.html#smapi-intro - */ - -function createOAuth(inputClientId, inputClientConfirmation) { - // Set default CLI LWA value - let clientId = inputClientId || CONSTANTS.LWA.CLI_INTERNAL_ONLY_LWA_CLIENT.CLIENT_ID; - let clientConfirmation = inputClientConfirmation || CONSTANTS.LWA.CLI_INTERNAL_ONLY_LWA_CLIENT.CLIENT_CONFIRMATION; - let authorizeHost = 'https://www.amazon.com'; - const authorizePath = '/ap/oa'; - let tokenHost = 'https://api.amazon.com'; - const tokenPath = '/auth/o2/token'; - - // Overrite LWA options from Environmental Variable - const envVarClientId = process.env.ASK_LWA_CLIENT_ID; - if (stringUtils.isNonBlankString(envVarClientId)) { - clientId = envVarClientId; - } - const envVarClientConfirmation = process.env.ASK_LWA_CLIENT_CONFIRMATION; - if (stringUtils.isNonBlankString(envVarClientConfirmation)) { - clientConfirmation = envVarClientConfirmation; - } - const envVarAuthorizeHost = process.env.ASK_LWA_AUTHORIZE_HOST; - if (stringUtils.isNonBlankString(envVarAuthorizeHost)) { - authorizeHost = envVarAuthorizeHost; - } - const envVarTokenHost = process.env.ASK_LWA_TOKEN_HOST; - if (stringUtils.isNonBlankString(envVarTokenHost)) { - tokenHost = envVarTokenHost; - } - - return oauth2.create({ - client: { id: clientId, secret: clientConfirmation }, - auth: { authorizeHost, authorizePath, tokenHost, tokenPath } - }); -} - -function tokenRefreshAndRead(params, profile, callback) { - if (profile === CONSTANTS.PLACEHOLDER.ENVIRONMENT_VAR.PROFILE_NAME) { - // if there's refreshToken, use that first since this profile is using env var, - // cannot find whether accessToken expired or not. - const askRefreshToken = process.env.ASK_REFRESH_TOKEN; - if (stringUtils.isNonBlankString(askRefreshToken)) { - refreshToken(profile, (refreshedAccessToken) => { - params.headers.Authorization = refreshedAccessToken; - callback(); - }); - return; - } - - // if no refreshToken, fallback to accessToken - const askAccessToken = process.env.ASK_ACCESS_TOKEN; - if (stringUtils.isNonBlankString(askAccessToken)) { - params.headers.Authorization = askAccessToken; - callback(); - } - return; - } - if (!fs.existsSync(path.join(os.homedir(), '.ask'))) { - console.warn('Failed to get authorization information.\n' + - 'Please run "ask init" to initialize ASK cli.'); - return; - } - if (!fs.existsSync(path.join(os.homedir(), '.ask', 'cli_config'))) { - console.warn('Failed to get CLI config.\n' + - 'Please run "ask init" to initialize ASK cli.'); - return; - } - if (!isTokenExpired(profile)) { - params.headers.Authorization = readAccessToken(profile); - callback(); - } else { - refreshToken(profile, (refreshedAccessToken) => { - params.headers.Authorization = refreshedAccessToken; - callback(); - }); - } -} - -function isTokenExpired(profile) { - const OAuth = module.exports.createOAuth(); - const token = OAuth.accessToken.create(readToken(profile)); - - return token.expired(); -} - -function readAccessToken(profile) { - const cliConfig = jsonRead.readFile(path.join(os.homedir(), '.ask', 'cli_config')); - if (!cliConfig) { - return; - } - return jsonRead.getProperty(cliConfig, '.profiles.' + profile + '.token.access_token'); -} - -function refreshToken(profile, callback) { - const OAuth = module.exports.createOAuth(); - const oldToken = readToken(profile); - if (!oldToken) { - return; - } - const token = OAuth.accessToken.create(oldToken); - token.refresh((err, result) => { - if (err) { - console.error(err + '\nFailed to refresh access token.'); - return process.exit(1); - } - if (profile === CONSTANTS.PLACEHOLDER.ENVIRONMENT_VAR.PROFILE_NAME) { - callback(result.token.access_token); - } else { - writeToken(result.token, profile); - callback(jsonRead.getProperty(result, '.token.access_token')); - } - }); -} - -function readToken(profile) { - if (profile === CONSTANTS.PLACEHOLDER.ENVIRONMENT_VAR.PROFILE_NAME) { - return { - 'access_token': "ACCESS_TOKEN_PLACE_HOLDER", - 'refresh_token': process.env.ASK_REFRESH_TOKEN, - 'token_type': 'bearer', - 'expires_in': 0, - 'expires_at': 0 - }; - } - const cliConfig = jsonRead.readFile(path.join(os.homedir(), '.ask', 'cli_config')); - if (!cliConfig) { - return; - } - const token = jsonRead.getProperty(cliConfig, '.profiles.' + profile + '.token'); - if (!token) { - return; - } - return { - access_token: token.access_token, - refresh_token: token.refresh_token, - token_type: token.token_type, - expires_in: token.expires_in, - expires_at: token.expires_at - }; -} - -function writeToken(token, profile) { - const configPath = path.join(os.homedir(), '.ask', 'cli_config'); - const configToken = { - access_token: token.access_token, - refresh_token: token.refresh_token, - token_type: token.token_type, - expires_in: token.expires_in, - expires_at: token.expires_at - }; - const propertyPathArray = ['profiles', profile, 'token']; - jsonUtility.writeToProperty(configPath, propertyPathArray, configToken); -} diff --git a/lib/utils/profile-helper.js b/lib/utils/profile-helper.js index d30cfb0c..61fd0fe6 100644 --- a/lib/utils/profile-helper.js +++ b/lib/utils/profile-helper.js @@ -1,22 +1,16 @@ const R = require('ramda'); const fs = require('fs'); +const fse = require('fs-extra'); const os = require('os'); const path = require('path'); -const jsonUtility = require('./json-utility'); -const CONSTANT = require('./constants'); -const FORMATTER_SPACING = 26; +const CONSTANT = require('./constants'); module.exports = { runtimeProfile, checkASKProfileExist, setupProfile, - deleteProfile, - getListProfile, - displayProfile, - stringFormatter, - resolveVendorId, - askProfileSyntaxValidation + resolveVendorId }; function runtimeProfile(profile) { @@ -47,67 +41,10 @@ function checkASKProfileExist(profileName) { } function setupProfile(awsProfile, askProfile) { - const homeConfig = path.join(os.homedir(), '.ask', 'cli_config'); - const propertyPathArray = ['profiles', askProfile, 'aws_profile']; - jsonUtility.writeToProperty(homeConfig, propertyPathArray, awsProfile); -} - -function deleteProfile(profile) { - const configPath = path.join(os.homedir(), '.ask', 'cli_config'); - const targetPath = ['profiles', profile]; - jsonUtility.deleteProperty(configPath, targetPath); -} - -function getListProfile() { - const askConfig = path.join(os.homedir(), '.ask', 'cli_config'); - if (!fs.existsSync(askConfig)) { - return null; - } - const { profiles } = jsonUtility.read(askConfig); - if (!profiles || Object.keys(profiles).length === 0) { - return null; - } - const printOut = []; - for (const profile of Object.getOwnPropertyNames(profiles)) { - printOut.push({ - askProfile: profile, - awsProfile: profiles[profile].aws_profile - }); - } - return printOut; -} - -function displayProfile() { - const HEADER = 'Profile Associated AWS Profile'; - const profileList = stringFormatter(getListProfile()); - if (!profileList) { - console.warn('ask-cli has not set up any profiles yet.'); - return; - } - profileList.splice(0, 0, HEADER); - profileList.forEach((profile) => { - console.log(` ${profile}`); - }); -} - -function stringFormatter(profileList) { - if (!profileList || profileList.length === 0) { - return null; - } - const formattedProfileList = []; - for (const profileObj of profileList) { - const formattedASKProfile = `[${profileObj.askProfile}]`; - let fillingSpace = ' '; - if (formattedASKProfile.length <= FORMATTER_SPACING) { - fillingSpace = ' '.repeat(FORMATTER_SPACING - formattedASKProfile.length); - } - if (!profileObj.awsProfile) { - formattedProfileList.push(`${formattedASKProfile}${fillingSpace}** NULL **`); - continue; - } - formattedProfileList.push(`${formattedASKProfile}${fillingSpace}"${profileObj.awsProfile}"`); - } - return formattedProfileList; + const homeConfigPath = path.join(os.homedir(), '.ask', 'cli_config'); + const homeConfig = fse.readJSONSync(homeConfigPath); + const updatedConfig = R.set(R.lensPath(['profiles', askProfile, 'aws_profile']), awsProfile, homeConfig); + fse.writeJSONSync(homeConfigPath, updatedConfig); } function resolveVendorId(profile) { @@ -123,7 +60,3 @@ function resolveVendorId(profile) { return R.view(R.lensPath(['profiles', profile, 'vendor_id']), cliConfig); } } - -function askProfileSyntaxValidation(profileName) { - return /^[a-zA-Z0-9-_]+$/g.test(profileName); -} diff --git a/lib/utils/tools.js b/lib/utils/tools.js deleted file mode 100644 index 3a18195f..00000000 --- a/lib/utils/tools.js +++ /dev/null @@ -1,20 +0,0 @@ -module.exports = { - convertDataToJsonObject -}; - -function convertDataToJsonObject(data) { - let response = data; - try { - if (typeof data === 'string') { - response = JSON.parse(data); - } - } catch (e) { - if (data.length <= 5000) { - console.error(`Failed to parse the JSON from string ${data}.`); - } else { - console.error('Failed to parse the JSON from string.'); - } - return null; - } - return response; -} diff --git a/package.json b/package.json index 522d43fc..3de6df75 100644 --- a/package.json +++ b/package.json @@ -56,12 +56,10 @@ "pretty-bytes": "^5.1.0", "ramda": "^0.25.0", "request": "^2.79.0", - "retry": "^0.10.1", "rxjs": "^6.5.2", "semver": "^5.3.0", "shelljs": "^0.8.2", "simple-git": "^1.82.0", - "simple-oauth2": "1.0.3", "tmp": "^0.1.0", "uuid": "^3.4.0", "valid-url": "^1.0.9" diff --git a/test/functional/commands/api/test/invoke-skill-test.js b/test/functional/commands/api/test/invoke-skill-test.js index 1a28ad26..baf79496 100644 --- a/test/functional/commands/api/test/invoke-skill-test.js +++ b/test/functional/commands/api/test/invoke-skill-test.js @@ -1,8 +1,8 @@ const { expect } = require('chai'); const fs = require('fs'); +const { readJsonSync } = require('fs-extra'); const tmp = require('tmp'); const jsonView = require('@src/view/json-view'); -const jsonRead = require('@src/utils/json-read'); const CONSTANTS = require('@src/utils/constants'); const ApiCommandBasicTest = require('@test/functional/commands/api'); @@ -16,7 +16,7 @@ describe('Functional test - ask api invoke-skill', () => { const TEST_INVALID_PROFILE = ApiCommandBasicTest.testDataProvider.INVALID_PROFILE; const TEST_DEFAULT_PROFILE_TOKEN = ApiCommandBasicTest.testDataProvider.VALID_TOKEN.DEFAULT_PROFILE_TOKEN; const TEST_FILE_PATH = tmpobj.name; - const TEST_FILE_CONTENT = JSON.stringify(jsonRead.readFile(TEST_FILE_PATH, 'utf8')); + const TEST_FILE_CONTENT = JSON.stringify(readJsonSync(TEST_FILE_PATH)); const TEST_HTTP_RESPONSE_BODY = { TEST: 'RESPONSE_BODY' }; const TEST_ERROR_MESSAGE = 'ERROR_MESSAGE'; const SKILL_COMMAND = `ask api ${operation}`; diff --git a/test/unit/commands/configure/ui-test.js b/test/unit/commands/configure/ui-test.js index 6e2a8a72..e938c664 100644 --- a/test/unit/commands/configure/ui-test.js +++ b/test/unit/commands/configure/ui-test.js @@ -223,7 +223,6 @@ describe('Command: Configure - UI test', () => { it('| invalid profile name entered by user', (done) => { // setup - sinon.stub(profileHelper, 'askProfileSyntaxValidation').returns(false); inquirer.prompt.resolves({ profile: ' %*^@&!' }); // call @@ -316,7 +315,6 @@ describe('Command: Configure - UI test', () => { describe('# createOrUpdateProfile check', () => { beforeEach(() => { sinon.stub(inquirer, 'prompt'); - sinon.stub(profileHelper, 'askProfileSyntaxValidation'); }); it('| returns error | invalid profile name entered', (done) => { @@ -326,7 +324,6 @@ describe('Command: Configure - UI test', () => { 'askProfile2' ]; const createNewProfile = 'Create new profile'; - profileHelper.askProfileSyntaxValidation.withArgs(sinon.match.string).returns(true); inquirer.prompt.resolves({ profile: createNewProfile }); sinon.stub(ui, 'createNewProfile').callsArgWith(0, null, 'askProfile2'); @@ -347,7 +344,6 @@ describe('Command: Configure - UI test', () => { 'askProfile2' ]; const createNewProfile = 'Create new profile'; - profileHelper.askProfileSyntaxValidation.withArgs(sinon.match.string).returns(true); inquirer.prompt.resolves({ profile: createNewProfile }); sinon.stub(stringUtils, 'validateSyntax').returns(true); sinon.stub(ui, 'createNewProfile').callsArgWith(0, null, 'askProfile2'); @@ -368,7 +364,6 @@ describe('Command: Configure - UI test', () => { 'askProfile2', '#$*%#$(%$43' ]; - profileHelper.askProfileSyntaxValidation.withArgs(sinon.match.string).returns(true); inquirer.prompt.resolves({ profile: listOfProfiles[1] }); // call @@ -386,7 +381,6 @@ describe('Command: Configure - UI test', () => { 'askProfile1', '#$*%#$(%$43' ]; - profileHelper.askProfileSyntaxValidation.withArgs(sinon.match.string).returns(false); inquirer.prompt.resolves({ profile: listOfProfiles[1] }); // call diff --git a/test/unit/utils/json-read-test.js b/test/unit/utils/json-read-test.js deleted file mode 100644 index 7b895c35..00000000 --- a/test/unit/utils/json-read-test.js +++ /dev/null @@ -1,81 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const sinon = require('sinon'); -const jsonRead = require('../../../lib/utils/json-read'); - - -describe('utils json-read testing', () => { - describe('# read file', () => { - beforeEach(() => { - sinon.stub(console, 'error'); - }); - - afterEach(() => { - console.error.restore(); - }); - - it('| try to read an invalid json file', () => { - jsonRead.readFile('../fixture/jsonRead/bad.json'); - expect(console.error.getCall(0).args[0]).equal( - 'Invalid json: ../fixture/jsonRead/bad.json' - ); - }); - }); - - describe('# read property from json file', () => { - let testObject; - - before(() => { - testObject = { - "manifest": { - "customInteractionModelInfo": { - "invocationNameByLocale": { - "en-US": "hello world" - }, - "endpointsByRegion": { - "NA": { - "isDefaultRegion": true - } - } - } - } - }; - }); - - it('| return null when try to get a not existed property', () => { - sinon.stub(console, 'log'); - expect(jsonRead.getProperty(testObject, '.invalid.path')).equal(null); - console.log.restore(); - }); - - it('| return correct property for the following example', () => { - let track = '.manifest.customInteractionModelInfo.endpointsByRegion.NA.isDefaultRegion'; - expect(jsonRead.getProperty(testObject, track)).equal(true); - }); - }); - - describe('# read json from string', () => { - beforeEach(() => { - sinon.stub(console, 'error'); - }); - - afterEach(() => { - console.error.restore(); - }); - - it ('| try to read an invalid json string', () => { - let invalidJsonString = '{"number": 10'; - jsonRead.readString(invalidJsonString); - expect(console.error.getCall(0).args[0]).equal( - 'Invalid json string: ' + invalidJsonString - ); - }); - - it ('| try to read a valid json string', () => { - let validJsonString = '{"number": 10}'; - let jsonObj = jsonRead.readString(validJsonString); - expect(jsonObj.number).equal(10); - }); - }); -}); diff --git a/test/unit/utils/json-utility-test.js b/test/unit/utils/json-utility-test.js deleted file mode 100644 index ed4c333e..00000000 --- a/test/unit/utils/json-utility-test.js +++ /dev/null @@ -1,176 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const sinon = require('sinon'); -const jsonUtility = require('../../../lib/utils/json-utility'); -const jsonfile = require('jsonfile'); - - -describe('utils json-utility testing', () => { - describe('# read file', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(console, 'error'); - sandbox.stub(process, 'exit'); - }); - afterEach(() => { - sandbox.restore(); - }); - - it('| read invalid file, return error and exit', () => { - jsonUtility.read('../fixture/jsonRead/bad.json'); - expect(console.error.getCall(0).args[0]).equal( - 'Invalid json: ../fixture/jsonRead/bad.json' - ); - expect(process.exit.getCall(0).args[0]).equal(1); - }); - - it('| read in valid json file', () => { - sinon.stub(jsonfile, 'readFileSync'); - jsonfile.readFileSync.returns({'test': '123'}); - let content = jsonUtility.read('../valid_file_path'); - expect(content).to.be.an('object'); - jsonfile.readFileSync.restore(); - }); - }); - - describe('# write file', () => { - let sandbox; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(console, 'error'); - sandbox.stub(process, 'exit'); - }); - afterEach(() => { - sandbox.restore(); - }); - - it('| read invalid file, return error and exit', () => { - jsonUtility.write('../fixture/jsonRead/bad.json'); - expect(console.error.getCall(0).args[0]).equal( - 'Invalid file, cannot write to: ../fixture/jsonRead/bad.json' - ); - expect(process.exit.getCall(0).args[0]).equal(1); - }); - - it('| read in valid json file', () => { - sinon.stub(jsonfile, 'writeFileSync'); - jsonUtility.write('../valid_file_path'); - expect(jsonfile.writeFileSync.calledOnce).equal(true); - jsonfile.writeFileSync.restore(); - }); - }); - - describe('# get/write/delete property in json object level', () => { - let TEST_JSON_OBJECT; - beforeEach(() => { - TEST_JSON_OBJECT = { - a: { - b: { - c: 123 - } - } - }; - }); - - describe('* getPropertyValueFromObject', () => { - it('| target property not exist, function return null', () => { - let testResult = jsonUtility.getPropertyValueFromObject(TEST_JSON_OBJECT, ['a', 'c', 'b']); - expect(testResult).to.be.null; - }); - - it('| target property exist, return the property value', () => { - let testResult = jsonUtility.getPropertyValueFromObject(TEST_JSON_OBJECT, ['a', 'b', 'c']); - expect(testResult).equal(123); - }); - }); - - describe('* deletePropertyFromJsonObject', () => { - it('| target property not exist, return false/undefinited', () => { - let testResult1 = jsonUtility.deletePropertyFromJsonObject(TEST_JSON_OBJECT, ['a', 'b', 'd', 'e', 'f']); - expect(testResult1).to.be.not.ok; - - let testResult2 = jsonUtility.deletePropertyFromJsonObject(TEST_JSON_OBJECT, ['a', 'c']); - expect(testResult2).to.be.not.ok; - }); - - it('| target property exist, delete that property', () => { - jsonUtility.deletePropertyFromJsonObject(TEST_JSON_OBJECT, ['a', 'b', 'c']); - expect(TEST_JSON_OBJECT.a.b.c).to.be.undefined; - }); - }); - - describe('* insertObjectToObject', () => { - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(console, 'error'); - sandbox.stub(process, 'exit'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('| override property to a non-object property value before hit the last node in the target path', () => { - jsonUtility.insertObjectToObject(TEST_JSON_OBJECT, ['a', 'b', 'c', 'd', 'e', 'f'], 321); - - expect(console.error.getCall(0).args[0]).equal('[Error]: cannot add property to non-object value.' + - ' Please correct your target path'); - expect(process.exit.getCall(0).args[0]).equal(1); - }); - - it('| override property to a non-object property value on the last node', () => { - jsonUtility.insertObjectToObject(TEST_JSON_OBJECT, ['a', 'b', 'c', 'd'], 321); - - expect(console.error.getCall(0).args[0]).equal('[Error]: cannot add property to non-object value.' + - ' Please correct your target path'); - expect(process.exit.getCall(0).args[0]).equal(1); - }); - - it('| target property not exist, function will create property along the given path', () => { - jsonUtility.insertObjectToObject(TEST_JSON_OBJECT, ['z'], 321); - expect(TEST_JSON_OBJECT.z).equal(321); - }) - - it('| target property exist, override the value', () => { - jsonUtility.insertObjectToObject(TEST_JSON_OBJECT, ['a', 'b', 'c'], 321); - expect(TEST_JSON_OBJECT.a.b.c).equal(321); - }); - }); - }); - - describe('# get/write/delete property directly to a file', () => { - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(jsonfile, 'readFileSync'); - sandbox.stub(jsonfile, 'writeFileSync'); - jsonfile.readFileSync.returns({'a': 1}); - }); - - afterEach(() => { - sandbox.restore(); - }); - - it('| write property from file', () => { - jsonUtility.writeToProperty('fake_path', ['c'], {'b': 2}); - expect(jsonfile.readFileSync.calledOnce).to.be.true; - expect(jsonfile.writeFileSync.calledOnce).to.be.true; - }); - - it('| get property from file', () => { - jsonUtility.getProperty('fake_path', ['a']); - expect(jsonfile.readFileSync.calledOnce).to.be.true; - }); - - it('| delete property from file', () => { - jsonUtility.deleteProperty('fake_path', ['a']); - expect(jsonfile.readFileSync.calledOnce).to.be.true; - expect(jsonfile.writeFileSync.calledOnce).to.be.true; - }); - }); -}); diff --git a/test/unit/utils/profile-helper-test.js b/test/unit/utils/profile-helper-test.js deleted file mode 100644 index 213e255a..00000000 --- a/test/unit/utils/profile-helper-test.js +++ /dev/null @@ -1,226 +0,0 @@ -'use strict'; - -const expect = require('chai').expect; -const sinon = require('sinon'); - -const profileHelper = require('../../../lib/utils/profile-helper'); -const jsonUtility = require('../../../lib/utils/json-utility'); -const fs = require('fs'); - -// TODO provide more skill schema samples - -describe('utils profile helper testing', () => { - describe('# get aws profile', () => { - it('| return the aws profile name', () => { - let askProfile = 'test'; - sinon.stub(jsonUtility, 'getProperty'); - jsonUtility.getProperty.withArgs(sinon.match.any, ['profiles', askProfile, 'aws_profile']).returns('success'); - expect(profileHelper.getAWSProfile(askProfile)).equal('success'); - }); - }); - - describe('# check aws profile exists', () => { - const ini = - '[test_success]\naws_access_key_id = ABC\naws_secret_access_key = ABC'; - beforeEach(() => { - sinon.stub(fs, 'readFileSync'); - - }); - afterEach(() => { - fs.readFileSync.restore(); - }); - - it('| have aws credential file and profile exists', () => { - fs.readFileSync.withArgs(sinon.match.any, sinon.match.any).returns(ini); - let input = 'test_success'; - expect(profileHelper.checkAWSProfileExist(input)).equal(true); - }); - - it('| have aws credential file and profile does not exist', () => { - fs.readFileSync.withArgs(sinon.match.any, sinon.match.any).returns(ini); - let input = 'test_fail'; - expect(profileHelper.checkAWSProfileExist(input)).equal(false); - }); - - it('| no aws credential file, error generated', () => { - fs.readFileSync.withArgs(sinon.match.any, sinon.match.any).throws(); - sinon.stub(process, 'exit'); - sinon.stub(console, 'error'); - profileHelper.checkAWSProfileExist('any'); - expect(process.exit.calledOnce).to.be.true; - expect(console.error.calledOnce).to.be.true; - process.exit.restore(); - console.error.restore(); - }); - }); - - describe('# check ask profile exists', () => { - const askProfileObject = { - profiles: { - test_success: {} - } - }; - beforeEach(() => { - sinon.stub(jsonUtility, 'read'); - jsonUtility.read.withArgs(sinon.match.any).returns(askProfileObject); - - }); - afterEach(() => { - jsonUtility.read.restore(); - }); - it('| has profile', () => { - let input = 'test_success'; - expect(profileHelper.checkASKProfileExist(input)).to.be.true; - }); - - it('| invalid profile', () => { - let input = 'test_fail'; - expect(profileHelper.checkASKProfileExist(input)).to.be.false; - }); - }); - - describe('# setupProfile', () => { - it('| write to profile', () => { - let emptyCallback = () => {}; - sinon.stub(jsonUtility, 'writeToProperty'); - profileHelper.setupProfile('aws', 'ask', emptyCallback); - expect(jsonUtility.writeToProperty.calledOnce).to.be.true; - jsonUtility.writeToProperty.restore(); - }); - }); - - describe('# deleteProfile', () => { - it('| delete the profile from config', () => { - sinon.stub(jsonUtility, 'deleteProperty'); - profileHelper.deleteProfile('test'); - expect(jsonUtility.deleteProperty.calledOnce).to.be.true; - jsonUtility.deleteProperty.restore(); - }); - }); - - describe('# getListProfile', () => { - beforeEach(() => { - sinon.stub(fs, 'existsSync'); - sinon.stub(jsonUtility, 'read'); - fs.existsSync.withArgs(sinon.match.any).returns(true); - }); - afterEach(() => { - fs.existsSync.restore(); - jsonUtility.read.restore(); - }); - it('| no askConfig file, return nothing', () => { - fs.existsSync.withArgs(sinon.match.any).returns(false); - expect(profileHelper.getListProfile()).to.be.a('null'); - }); - - it('| return null if no profile inside the config file', () => { - jsonUtility.read.withArgs(sinon.match.any).returns({}); - expect(profileHelper.getListProfile()).to.be.a('null'); - }); - - it('| return null if profile object is empty inside the config file', () => { - jsonUtility.read.withArgs(sinon.match.any).returns({profiles: {}}); - expect(profileHelper.getListProfile()).to.be.a('null'); - }); - - it('| get the list of profile', () => { - let input = { - profiles: { - ask_test: { - aws_profile: 'aws_test' - } - } - }; - let expected_result = [{ - 'askProfile': 'ask_test', - 'awsProfile': 'aws_test' - }]; - jsonUtility.read.withArgs(sinon.match.any).returns(input); - expect(profileHelper.getListProfile()).to.eql(expected_result); - }); - }); - - describe('# displayProfile', () => { - beforeEach(() => { - sinon.stub(fs, 'existsSync'); - sinon.stub(jsonUtility, 'read'); - fs.existsSync.withArgs(sinon.match.any).returns(true); - sinon.stub(console, 'warn'); - sinon.stub(console, 'log'); - }); - afterEach(() => { - fs.existsSync.restore(); - jsonUtility.read.restore(); - console.warn.restore(); - console.log.restore(); - }); - - it('| return warning if no profile list', () => { - let input = {}; - jsonUtility.read.withArgs(sinon.match.any).returns(input); - profileHelper.displayProfile(); - expect(console.warn.calledOnce).to.be.true; - expect(console.log.called).to.be.false; - }); - - it('| print out the list', () => { - let input = { - profiles: { - ask_test: { - aws_profile: 'aws_test' - } - } - }; - jsonUtility.read.withArgs(sinon.match.any).returns(input); - profileHelper.displayProfile(); - expect(console.log.calledTwice).to.be.true; - expect(console.warn.called).to.be.false; - }); - }); - - describe('# stringFormatter', () => { - it('| return null if not profile list', () => { - expect(profileHelper.stringFormatter()).to.be.a('null'); - }); - - it('| return null if profile list is empty', () => { - expect(profileHelper.stringFormatter([])).to.be.a('null'); - }); - - it('| return formatted profile list with no aws profile', () => { - let input = [{ - 'askProfile': 'ask_test', - 'awsProfile': null - }]; - let expect_result = ['[ask_test] ** NULL **']; - expect(profileHelper.stringFormatter(input)).to.eql(expect_result); - }); - - it('| return formatted profile list with aws profile', () => { - let input = [{ - 'askProfile': 'ask_test', - 'awsProfile': 'aws_test' - }]; - let expect_result = ['[ask_test] "aws_test"']; - expect(profileHelper.stringFormatter(input)).to.eql(expect_result); - - }); - }); - - describe('# askProfileSyntaxValidation', () => { - it('| return true if the input string only contains [0-9a-zA-Z-_]', () => { - let input = '0-9a-zA-Z-_'; - expect(profileHelper.askProfileSyntaxValidation(input)).to.be.true; - }); - - it('| return false if the input is empty', () => { - let input = ''; - expect(profileHelper.askProfileSyntaxValidation(input)).to.be.false; - }); - - it('| return false if the input is invalid', () => { - let input = 'kong ge'; - expect(profileHelper.askProfileSyntaxValidation(input)).to.be.false; - }) - }); -});