diff --git a/.eslintrc b/.eslintrc index b2ffa2f..815a937 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,7 @@ "mocha": true }, "parserOptions": { - "ecmaVersion": 7, + "ecmaVersion": 8, "sourceType": "module", "ecmaFeatures": { "experimentalObjectRestSpread": true diff --git a/package.json b/package.json index f2150aa..f1bce27 100644 --- a/package.json +++ b/package.json @@ -27,9 +27,6 @@ "@shoutem/fork-terminal": "1.0.0", "archiver": "^3.0.0", "async": "2.6.0", - "babel-eslint": "8.2.3", - "babel-plugin-transform-object-rest-spread": "6.26.0", - "babel-preset-env": "^1.7.0", "babel-register": "6.26.0", "bluebird": "3.5.1", "child-process-promise": "2.2.1", @@ -89,7 +86,11 @@ }, "devDependencies": { "babel-cli": "6.26.0", + "babel-eslint": "8.2.3", "babel-plugin-rewire": "1.1.0", + "babel-plugin-transform-object-rest-spread": "6.26.0", + "babel-preset-env": "^1.7.0", + "babel-preset-es2015": "6.24.1", "chai": "4.1.2", "dos2unix-cli": "1.0.1", "eslint-config-airbnb": "16.1.0", diff --git a/src/cli.js b/src/cli.js index a2c1291..35eb705 100644 --- a/src/cli.js +++ b/src/cli.js @@ -4,9 +4,9 @@ import yargs from 'yargs'; import getHomeDir from './home-dir'; import autoUpdate from './commands/update-cli'; -import * as analytics from './services/analytics'; import { isAscii, containsSpace } from './services/validation'; -import { authorizeRequests, getRefreshToken } from './clients/auth-service'; +import analytics from './services/analytics'; +import authService from './clients/auth-service'; import apiUrls from '../config/services'; require('yargonaut') @@ -28,10 +28,11 @@ analytics.setArgv(process.argv); (async () => { if (await autoUpdate(cliArgs)) { - return null; + return; } - const refreshToken = await getRefreshToken(); - authorizeRequests(refreshToken); + + const refreshToken = await authService.getRefreshToken(); + authService.authorizeRequests(refreshToken); const cli = yargs.usage('Usage: shoutem [command] [-h]') .option('version', { diff --git a/src/cli/builder.js b/src/cli/builder.js index e8cb003..d634ca7 100644 --- a/src/cli/builder.js +++ b/src/cli/builder.js @@ -1,7 +1,7 @@ import opn from 'opn'; import services from '../../config/services'; -import {getPlatformConfig} from "../services/platform"; -import {executeAndHandleError} from "../services/error-handler"; +import { getPlatformConfig } from '../services/platform'; +import { executeAndHandleError } from '../services/error-handler'; export const description = 'Opens the app in the shoutem builder dashboard using default browser'; export const command = 'builder [appId]'; @@ -10,9 +10,11 @@ export const builder = yargs => { .usage(`shoutem ${command} \n\n${description}`); }; -export const handler = args => executeAndHandleError(async () => { - const appId = args.appId || (await getPlatformConfig()).appId; +function openAppInBuilder(args) { + const appId = args.appId || (getPlatformConfig()).appId; const url = `${services.appBuilder}/app/${appId}`; console.log(url); opn(url, { wait: false }); -}); +} + +export const handler = args => executeAndHandleError(openAppInBuilder, args); diff --git a/src/cli/clone.js b/src/cli/clone.js index 8ea2131..3814f15 100644 --- a/src/cli/clone.js +++ b/src/cli/clone.js @@ -29,6 +29,4 @@ export const builder = yargs => { .usage(`shoutem ${command} \n\n${description}`); }; -export async function handler(args) { - await executeAndHandleError(() => clone(args, process.cwd())); -} +export const handler = args => executeAndHandleError(clone, args, process.cwd()); diff --git a/src/cli/configure.js b/src/cli/configure.js index e538b08..6bd14f3 100644 --- a/src/cli/configure.js +++ b/src/cli/configure.js @@ -1,5 +1,10 @@ +import { + configurePlatform, + getPlatformConfig, + getPlatformRootDir, + setPlatformConfig, +} from '../services/platform'; import { executeAndHandleError } from '../services/error-handler'; -import {configurePlatform, getPlatformConfig, getPlatformRootDir, setPlatformConfig} from '../services/platform'; export const description = 'Runs platform\'s configure script to sync with native changes to local extensions'; export const command = 'configure'; @@ -21,16 +26,19 @@ export const builder = yargs => { }) .usage(`shoutem ${command} \n\n${description}`); }; -export async function handler(args) { - await executeAndHandleError(async () => { - const appDir = await getPlatformRootDir(); - await setPlatformConfig(appDir, { - ...await getPlatformConfig(appDir), - release: !!args.release, - production: !!args.production - }); - await configurePlatform(appDir); +export function configure(args) { + const appDir = getPlatformRootDir(); + const config = getPlatformConfig(appDir); + + setPlatformConfig(appDir, { + ...config, + release: !!args.release, + production: !!args.production, }); + + return configurePlatform(appDir); } + +export const handler = args => executeAndHandleError(configure, args); diff --git a/src/cli/extension/add.js b/src/cli/extension/add.js index 1c71650..2cb36ef 100644 --- a/src/cli/extension/add.js +++ b/src/cli/extension/add.js @@ -1,18 +1,19 @@ import path from 'path'; +import 'colors'; + import { addToExtensionsJs, getPlatformConfig, getPlatformExtensionsDir, getPlatformRootDir, - linkLocalExtension -} from "../../services/platform"; -import {executeAndHandleError} from "../../services/error-handler"; -import {initExtension} from "../../commands/init"; -import {uploadExtension} from "../../commands/push"; -import {installLocalExtension} from "../../commands/install"; -import 'colors'; -import {spinify} from "../../services/spinner"; -import {publishExtension} from "../../commands/publish"; + linkLocalExtension, +} from '../../services/platform'; +import { spinify } from '../../services/spinner'; +import { executeAndHandleError } from '../../services/error-handler'; + +import { initExtension } from '../../commands/init'; +import { installLocalExtension } from '../../commands/install'; +import { uploadExtension, publishExtension } from '../../commands/publish'; export const description = 'Create a new extension for the current app'; export const command = 'add '; @@ -43,31 +44,32 @@ const postRunMessage = add a new settings page `; -export const handler = args => executeAndHandleError(() => addExtension(args)); - export async function addExtension({ name, local, externalDestination }) { - const platformDir = externalDestination || await getPlatformRootDir(); - const extensionPath = await initExtension(name, externalDestination || await getPlatformExtensionsDir(platformDir)); + const platformDir = externalDestination || getPlatformRootDir(); + const extensionDir = externalDestination || getPlatformExtensionsDir(platformDir); + const extensionPath = await initExtension(name, extensionDir); if (!local && !externalDestination) { await uploadExtension({ publish: true }, extensionPath); await publishExtension(extensionPath); - const { appId } = await getPlatformConfig(platformDir); + const { appId } = getPlatformConfig(platformDir); await spinify(installLocalExtension(appId, extensionPath), 'Installing it in your app...', 'OK'); } if (!externalDestination) { console.log('\nRunning npm install script:'); await linkLocalExtension(platformDir, extensionPath); - await addToExtensionsJs(platformDir, extensionPath); + addToExtensionsJs(platformDir, extensionPath); console.log(`npm install [${'OK'.bold.green}]`); } - const cdCommand = 'cd ' + path.relative(process.cwd(), extensionPath); + const cdCommand = `cd ${path.relative(process.cwd(), extensionPath)}`; console.log('\nCongratulations, your new extension is ready!'.green.bold); console.log(`You might try doing ${cdCommand.cyan} where you can:`); console.log(postRunMessage); console.log('Success!'.green.bold); console.log('Happy coding!'); } + +export const handler = args => executeAndHandleError(addExtension, args); diff --git a/src/cli/extension/publish.js b/src/cli/extension/publish.js index 45fae91..246646a 100644 --- a/src/cli/extension/publish.js +++ b/src/cli/extension/publish.js @@ -1,45 +1,35 @@ +import fs from 'fs-extra'; import path from 'path'; -import {executeAndHandleError} from "../../services/error-handler"; -import {ensureUserIsLoggedIn} from "../../commands/login"; -import {getPlatformConfig, getPlatformExtensionsDir} from "../../services/platform"; -import { pathExists } from 'fs-extra'; -import {uploadExtension} from "../../commands/push"; -import {publishExtension} from "../../commands/publish"; -import {updateExtension, getInstallation, installExtension} from "../../clients/app-manager"; -import confirmer from "../../services/confirmer"; -import {getExtension} from "../../clients/extension-manager"; + +import confirmer from '../../services/confirmer'; +import { executeAndHandleError } from '../../services/error-handler'; +import { getPlatformConfig, getPlatformExtensionsDir } from '../../services/platform'; +import { ensureUserIsLoggedIn } from '../../commands/login'; +import { uploadExtension } from '../../commands/push'; +import { publishExtension } from '../../commands/publish'; +import { updateExtension, getInstallation, installExtension } from '../../clients/app-manager'; +import { getExtension } from '../../clients/extension-manager'; export const description = 'Publish an extension from the app in the working directory'; export const command = 'publish '; -export const builder = yargs => { - return yargs - .usage(`shoutem ${command}\n\n${description}`); -}; - -export const handler = ({ name }) => executeAndHandleError(async () => { - const dev = await ensureUserIsLoggedIn(); - const extensionPath = path.join(await getPlatformExtensionsDir(), `${dev.name}.${name}`); - - if (!await pathExists(extensionPath)) { - throw new Error(`Path ${path.relative(process.cwd(), extensionPath)} does not exist`); - } - - await uploadExtension({ publish: true }, extensionPath); - const { id: extensionId, version } = await publishExtension(extensionPath); - await offerInstallationUpdate(extensionId, name, version); - console.log('Success'.green.bold); -}); +export const builder = yargs => yargs.usage(`shoutem ${command}\n\n${description}`); export async function offerInstallationUpdate(extensionId, extensionName, newVersion) { - const { appId } = await getPlatformConfig(); + const { appId } = getPlatformConfig(); + const dev = await ensureUserIsLoggedIn(); const canonical = `${dev.name}.${extensionName}`; try { - const { id: installationId, extension: oldExtensionId } = await getInstallation(appId, canonical); + const { + id: installationId, + extension: oldExtensionId, + } = await getInstallation(appId, canonical); const { version: oldVersion } = await getExtension(oldExtensionId); + const versionMsg = `${canonical}@${oldVersion} => @${newVersion}`; const msg = `Update the version used in the current app (${versionMsg})?`; + if (await confirmer(msg)) { await updateExtension(appId, installationId, extensionId); } @@ -47,8 +37,25 @@ export async function offerInstallationUpdate(extensionId, extensionName, newVer if (e.statusCode !== 404) { throw e; } + if (await confirmer(`Do you want to install ${canonical} extension into app ${appId}?`)) { await installExtension(appId, extensionId); } } } + +async function publish(name) { + const dev = await ensureUserIsLoggedIn(); + const extensionPath = path.join(getPlatformExtensionsDir(), `${dev.name}.${name}`); + + if (!fs.existsSync(extensionPath)) { + throw new Error(`Path ${path.relative(process.cwd(), extensionPath)} does not exist`); + } + + await uploadExtension({ publish: true }, extensionPath); + const { id: extensionId, version } = await publishExtension(extensionPath); + await offerInstallationUpdate(extensionId, name, version); + console.log('Success'.green.bold); +} + +export const handler = ({ name }) => executeAndHandleError(publish, name); diff --git a/src/cli/init.js b/src/cli/init.js index ee44ecb..a1a9f0b 100644 --- a/src/cli/init.js +++ b/src/cli/init.js @@ -7,6 +7,7 @@ export const builder = yargs => { return yargs.usage(`shoutem ${command}\n\n${description}`).strict(); }; -export const handler = ({ name }) => executeAndHandleError(async () => { - await addExtension({ name, externalDestination: process.cwd() }); -}); +export const handler = ({ name }) => { + const args = { name, externalDestination: process.cwd() }; + executeAndHandleError(addExtension, args); +}; diff --git a/src/cli/last-error.js b/src/cli/last-error.js index 7140df1..a582a05 100644 --- a/src/cli/last-error.js +++ b/src/cli/last-error.js @@ -1,17 +1,21 @@ import 'colors'; import prettyJson from 'prettyjson'; -import * as cache from '../services/cache-env'; + +import cache from '../services/cache-env'; export const description = null; export const command = 'last-error'; -export async function handler() { - const lastError = await cache.getValue('last-error'); + +export function handler() { + const lastError = cache.getValue('last-error'); + if (lastError) { console.log(prettyJson.render(lastError, { keysColor: 'cyan', - numberColor: 'white' + numberColor: 'white', })); - console.log(`\nIf you think this error is caused by bug in the shoutem command, you can report the issue here: ${"https://github.com/shoutem/cli/issues".bold}`.yellow); + + console.log(`\nIf you think this error is caused by a bug in the Shoutem CLI, please report the issue here: ${'https://github.com/shoutem/cli/issues'.bold}`.yellow); } else { console.log('No error'.green); } diff --git a/src/cli/login.js b/src/cli/login.js index 113461d..8c0b68d 100644 --- a/src/cli/login.js +++ b/src/cli/login.js @@ -4,12 +4,9 @@ import { executeAndHandleError } from '../services/error-handler'; export const description = 'Log in and register as a Shoutem developer.\nProvide credentials in username:password format.'; export const command = 'login [credentials]'; -export function handler(args) { - - return executeAndHandleError(() => loginUser(args)); -} - export function builder(yargs) { return yargs .usage(`shoutem ${command}\n\n${description}`); } + +export const handler = args => executeAndHandleError(loginUser, args); diff --git a/src/cli/logout.js b/src/cli/logout.js index d8fd604..cb15625 100644 --- a/src/cli/logout.js +++ b/src/cli/logout.js @@ -3,9 +3,9 @@ import { executeAndHandleError } from '../services/error-handler'; export const description = 'Erase all locally stored credentials.'; export const command = 'logout'; -export async function handler() { - await executeAndHandleError(logout); -} + export function builder(yargs) { return yargs.usage(`shoutem ${command}\n\n${description}`); } + +export const handler = () => executeAndHandleError(logout); diff --git a/src/cli/page/add.js b/src/cli/page/add.js index ea3be6e..e94bec8 100644 --- a/src/cli/page/add.js +++ b/src/cli/page/add.js @@ -7,14 +7,17 @@ import {offerChanges} from "../../services/diff"; export const description = 'Add a settings page to current extension'; export const command = 'add [name]'; -export const handler = args => executeAndHandleError(async () => { - const extJson = await loadExtensionJson(); - const answers = await askPageCreationQuestions({ ...extJson, defaultName: args.name }); - await createPage(answers, ensureInExtensionDir()); -}); - export async function createPage(opts, extensionPath) { const changes = await instantiateExtensionTemplate('settings-page', { ...opts, extensionPath }); await offerChanges(changes); console.log('Success'.green.bold); } + +async function addPage(args) { + const extJson = loadExtensionJson(); + const answers = await askPageCreationQuestions({ ...extJson, defaultName: args.name }); + + return createPage(answers, ensureInExtensionDir()); +} + +export const handler = args => executeAndHandleError(addPage, args); diff --git a/src/cli/platform/create.js b/src/cli/platform/create.js index 80a44bc..0be156a 100644 --- a/src/cli/platform/create.js +++ b/src/cli/platform/create.js @@ -33,8 +33,6 @@ const postRunPublish = platformId => ` To publish this platform for everyone to use `; -export const handler = args => executeAndHandleError(() => createPlatform(args)); - export async function createPlatform({ url }) { const developer = await ensureUserIsLoggedIn(); @@ -54,7 +52,8 @@ export async function createPlatform({ url }) { published = true; } - const { appId } = await getPlatformConfig(); + const { appId } = getPlatformConfig(); + if (!_.isNil(appId)) { if (await confirmer(`Do you want to install the new platform to this app (${appId})?`)) { await spinify(installPlatform({ app: appId, platform: platformResponse.id })); @@ -76,3 +75,5 @@ export async function createPlatform({ url }) { } } } + +export const handler = args => executeAndHandleError(createPlatform, args); diff --git a/src/cli/platform/install.js b/src/cli/platform/install.js index 39995a9..41f465e 100644 --- a/src/cli/platform/install.js +++ b/src/cli/platform/install.js @@ -27,14 +27,12 @@ export const builder = yargs => yargs }) .usage(`shoutem ${command} [options]\n\n${description}`); -export const handler = args => executeAndHandleError(() => installPlatform(args)); - export async function installPlatform({ app, platform }) { const developer = await ensureUserIsLoggedIn(); let appConfig; - if (await getPlatformRootDir(process.cwd(), { shouldThrow: false })) { - appConfig = await getPlatformConfig(); + if (getPlatformRootDir(process.cwd(), { shouldThrow: false })) { + appConfig = getPlatformConfig(); } // if app ID is not explicitly passed, then try to get the ID from current directory, otherwise ask the user @@ -46,3 +44,5 @@ export async function installPlatform({ app, platform }) { console.log('Your platform is now installed on your app'); console.log('Success!'.green.bold); } + +export const handler = args => executeAndHandleError(installPlatform, args); diff --git a/src/cli/platform/list.js b/src/cli/platform/list.js index a20408f..6a1bf50 100644 --- a/src/cli/platform/list.js +++ b/src/cli/platform/list.js @@ -17,8 +17,6 @@ export const builder = yargs => yargs }) .usage(`shoutem ${command} [options]\n\n${description}`); -export const handler = args => executeAndHandleError(() => listPlatforms(args)); - export async function listPlatforms({ all }) { const developer = await ensureUserIsLoggedIn(); const platforms = await spinify(getAvailablePlatforms(all ? null : 20)); @@ -32,3 +30,5 @@ export async function listPlatforms({ all }) { console.log(`${id}\t${published}\t\t${author}@${version}`); }); } + +export const handler = args => executeAndHandleError(listPlatforms, args); diff --git a/src/cli/platform/publish.js b/src/cli/platform/publish.js index e117139..99796ac 100644 --- a/src/cli/platform/publish.js +++ b/src/cli/platform/publish.js @@ -20,8 +20,6 @@ export const builder = yargs => yargs }) .usage(`shoutem ${command} [options]\n\n${description}`); -export const handler = args => executeAndHandleError(() => publishOwnPlatform(args)); - export async function publishOwnPlatform({ platform }) { const developer = await ensureUserIsLoggedIn(); @@ -31,3 +29,5 @@ export async function publishOwnPlatform({ platform }) { console.log('Success!'.green.bold); console.log('Your platform is now public!'); } + +export const handler = args => executeAndHandleError(publishOwnPlatform, args); diff --git a/src/cli/push.js b/src/cli/push.js index 3a4342e..c393164 100644 --- a/src/cli/push.js +++ b/src/cli/push.js @@ -31,12 +31,17 @@ export const builder = yargs => { .usage(`shoutem ${command} [options]\n\n${description}`); }; -export const handler = args => executeAndHandleError(async () => { - if (!await confirmPush('WARNING: you are about tu push using shoutem developer. Are you sure about that?')) { +async function push(args) { + const developerWarning = 'WARNING: you are about tu push using shoutem developer. Are you sure about that?'; + const deprecationWarning = 'WARNING: shoutem push command is deprecated. Use shoutem publish instead'; + + if (!await confirmPush(developerWarning)) { console.log('Push aborted'.bold.yellow); return null; } - console.log('WARNING: shoutem push command is deprecated. Use shoutem publish instead'.yellow.bold); + + console.log(deprecationWarning.yellow.bold); + if (!args.paths.length) { await uploadExtension(args); console.log(msg.push.complete()); @@ -44,6 +49,9 @@ export const handler = args => executeAndHandleError(async () => { } args.paths = multiglob(args.paths); - await pushAll(args); -}); + + return pushAll(args); +} + +export const handler = args => executeAndHandleError(push, args); diff --git a/src/cli/run.js b/src/cli/run.js index 3e1f9ba..57b5674 100644 --- a/src/cli/run.js +++ b/src/cli/run.js @@ -3,29 +3,26 @@ import path from 'path'; export const description = 'Run shoutem application on using Shoutem preview app'; export const command = 'run'; -export const builder = yargs => { - return yargs - .options({ - local: { - alias: 'l', - description: 'don\'t use tunneling for Shoutem app, connect directly to packager. Note: ' + - 'this computer and iphone/android must be connected to the same network and port 8081 must be opened.', - type: 'boolean' - }, - small: { - alias: 's', - description: 'display smaller ASCII QR code which could be unreadable in some fonts', - type: 'boolean' - } - }) - .usage(`shoutem ${command} [options]\n\n${description}`); -}; +export const builder = yargs => yargs.options({ + local: { + alias: 'l', + description: 'don\'t use tunneling for Shoutem app, connect directly to packager. Note: ' + + 'this computer and iphone/android must be connected to the same network and port 8081 must be opened.', + type: 'boolean', + }, + small: { + alias: 's', + description: 'display smaller ASCII QR code which could be unreadable in some fonts', + type: 'boolean', + }, +}).usage(`shoutem ${command} [options]\n\n${description}`); + export async function handler(args) { const nodeArgs = [ path.join(__dirname, '..', 'scripts', 'shoutem-run.js'), '--local', !!args.local, - '--small', !!args.small + '--small', !!args.small, ]; - forkTerminal('node', nodeArgs, { cwd: process.cwd() }) + forkTerminal('node', nodeArgs, { cwd: process.cwd() }); } diff --git a/src/cli/screen/add.js b/src/cli/screen/add.js index a35193e..8ad088b 100644 --- a/src/cli/screen/add.js +++ b/src/cli/screen/add.js @@ -7,13 +7,16 @@ import {offerChanges} from "../../services/diff"; export const description = 'Add a screen for applications running this extension'; export const command = 'add [name]'; -export const handler = args => executeAndHandleError(async () => { - const extJson = await loadExtensionJson(); - const answers = await askScreenCreationQuestions({ ...extJson, defaultName: args.name }); - await createScreen(answers, ensureInExtensionDir()); -}); - export async function createScreen(opts, extensionPath) { await offerChanges(await instantiateExtensionTemplate('screen', { ...opts, extensionPath })); console.log('Success'.green.bold); } + +async function addScreen(args) { + const extJson = loadExtensionJson(); + const answers = await askScreenCreationQuestions({ ...extJson, defaultName: args.name }); + + return createScreen(answers, ensureInExtensionDir()); +} + +export const handler = args => executeAndHandleError(addScreen, args); diff --git a/src/cli/shortcut/add.js b/src/cli/shortcut/add.js index a31ad87..7dcac3e 100644 --- a/src/cli/shortcut/add.js +++ b/src/cli/shortcut/add.js @@ -6,13 +6,17 @@ import {offerChanges} from "../../services/diff"; export const description = 'Add an application shortcut'; export const command = 'add [name]'; -export const handler = args => executeAndHandleError(async () => { - const extJson = await loadExtensionJson(); - const answers = await askShortcutCreationQuestions({ ...extJson, defaultName: args.name }); - await createShortcut(answers, ensureInExtensionDir()); -}); export async function createShortcut(answers, extensionPath) { await offerChanges(await instantiateExtensionTemplate('shortcut', { ...answers, extensionPath })); console.log('Success!'.bold.green); } + +async function addShortcut(args) { + const extJson = loadExtensionJson(); + const answers = await askShortcutCreationQuestions({ ...extJson, defaultName: args.name }); + + return createShortcut(answers, ensureInExtensionDir()); +} + +export const handler = args => executeAndHandleError(addShortcut, args); diff --git a/src/cli/show.js b/src/cli/show.js index d2b7839..97856b4 100644 --- a/src/cli/show.js +++ b/src/cli/show.js @@ -1,16 +1,18 @@ -import show from '../commands/show.js'; +import show from '../commands/show'; import { executeAndHandleError } from '../services/error-handler'; export const command = 'show'; export const description = 'Shows user status and list of linked extensions'; -export const handler = args => executeAndHandleError(() => show(args)); + export function builder(yargs) { return yargs .options({ all: { type: 'boolean', default: false, - } + }, }) .usage(`shoutem ${command}\n\n${description}`); } + +export const handler = args => executeAndHandleError(show, args); diff --git a/src/cli/uninstall.js b/src/cli/uninstall.js index 4eeb5e9..f05639d 100644 --- a/src/cli/uninstall.js +++ b/src/cli/uninstall.js @@ -1,29 +1,27 @@ import { uninstallExtension, getExtInstallations } from '../clients/app-manager'; -import * as localExtensions from '../clients/local-extensions'; +import { getLocalExtensionCanonicalName } from '../clients/local-extensions'; import { getExtensionId } from '../clients/extension-manager'; -import msg from '../user_messages'; import { handleError } from '../services/error-handler'; +import msg from '../user_messages'; -export const description = `Uninstall current extension from an app.`; +export const description = 'Uninstall current extension from an app.'; export const command = 'uninstall'; -export const builder = yargs => { - return yargs - .options({ - app: { - alias: 'a', - description: 'uninstall local extension from an app', - requiresArg: true, - demand: true - } - }) - .usage(`shoutem ${command} [options]\n\n${description}`); -}; +export const builder = yargs => yargs + .options({ + app: { + alias: 'a', + description: 'uninstall local extension from an app', + requiresArg: true, + demand: true, + }, + }) + .usage(`shoutem ${command} [options]\n\n${description}`); export async function handler(args) { const appId = args.app; try { - const canonicalName = await localExtensions.getExtensionCanonicalName(); + const canonicalName = await getLocalExtensionCanonicalName(); const extensionId = await getExtensionId(canonicalName); if (!extensionId) { diff --git a/src/cli/use.js b/src/cli/use.js index 6944997..da1b4fc 100644 --- a/src/cli/use.js +++ b/src/cli/use.js @@ -1,6 +1,6 @@ import { setHostEnvName } from '../clients/server-env'; +import cache from '../services/cache-env'; import msg from '../user_messages'; -import { getValue } from '../services/cache-env'; export const description = null; export const command = 'use '; @@ -8,37 +8,37 @@ export const command = 'use '; const production = { command: 'production', description: 'Switch to shoutem live env', - async handler() { - await setHostEnvName('production'); - console.log(msg.use.complete('production', await getValue('developer'))); - } + handler() { + setHostEnvName('production'); + console.log(msg.use.complete('production', cache.getValue('developer'))); + }, }; const dev = { command: 'dev', description: 'Switch to sauros dev env', - async handler() { - await setHostEnvName('dev'); - console.log(msg.use.complete('dev', await getValue('developer'))); - } + handler() { + setHostEnvName('dev'); + console.log(msg.use.complete('dev', cache.getValue('developer'))); + }, }; const local = { command: 'local', description: 'Use api endpoints set in OS env variables', - async handler() { - await setHostEnvName('local'); - console.log(msg.use.complete('local', await getValue('developer'))); - } + handler() { + setHostEnvName('local'); + console.log(msg.use.complete('local', cache.getValue('developer'))); + }, }; const qa = { command: 'qa', description: 'Switch to using sauros qa env', - async handler() { - await setHostEnvName('qa'); - console.log(msg.use.complete('qa', await getValue('developer'))); - } + handler() { + setHostEnvName('qa'); + console.log(msg.use.complete('qa', cache.getValue('developer'))); + }, }; export function builder(use) { diff --git a/src/cli/whoami.js b/src/cli/whoami.js index 6d8b735..f000e59 100644 --- a/src/cli/whoami.js +++ b/src/cli/whoami.js @@ -1,11 +1,12 @@ import msg from '../user_messages'; -import { getValue } from '../services/cache-env'; +import cache from '../services/cache-env'; export const command = 'whoami'; export const description = 'Username of the current user.'; -export async function handler() { + +export function handler() { try { - const dev = await getValue('developer'); + const dev = cache.getValue('developer'); if (dev) { console.log(msg.login.complete(dev)); } else { diff --git a/src/clients/_tests_/auth-service.spec.js b/src/clients/_tests_/auth-service.spec.js index 20d0292..3b7132c 100644 --- a/src/clients/_tests_/auth-service.spec.js +++ b/src/clients/_tests_/auth-service.spec.js @@ -1,5 +1,5 @@ import { assert } from 'chai'; -import * as authService from '../auth-service'; +import authService from '../auth-service'; describe('Auth service client integration tests', () => { diff --git a/src/clients/_tests_/extension-manager.js b/src/clients/_tests_/extension-manager.js index 11fec32..42a534d 100644 --- a/src/clients/_tests_/extension-manager.js +++ b/src/clients/_tests_/extension-manager.js @@ -1,19 +1,13 @@ -import { assert } from 'chai'; -import * as authService from '../auth-service'; -import * as extManager from '../extension-manager'; +import authService from '../auth-service'; +import { getDeveloper } from '../extension-manager'; describe('Extension manager client integration tests', () => { - describe('Fetch developer info', () => { - it('should fetch developer info', async () => { const refreshToken = await authService.getRefreshToken({ email: 'cli-test@shoutem.com', password: 'password' }); - await authService.authorizeRequests(refreshToken); - const dev = await extManager.getDeveloper(); + authService.authorizeRequests(refreshToken); + const dev = await getDeveloper(); console.log(dev); }); - - - - }) -}); \ No newline at end of file + }); +}); diff --git a/src/clients/app-manager.js b/src/clients/app-manager.js index 367614b..932c6f8 100644 --- a/src/clients/app-manager.js +++ b/src/clients/app-manager.js @@ -1,13 +1,13 @@ import URI from 'urijs'; -import * as jsonApi from './json-api-client'; +import jsonApi from './json-api-client'; import { appManager } from '../../config/services'; import { getDeveloper } from './extension-manager'; const appManagerUri = new URI(appManager); -export async function installExtension(appId, extensionId) { +export function installExtension(appId, extensionId) { const url = appManagerUri.clone().segment(`/v1/apps/${appId}/installations`); - return await jsonApi.post(url, { + return jsonApi.post(url, { data: { type: 'shoutem.core.installations', attributes: { extension: extensionId }, @@ -15,9 +15,9 @@ export async function installExtension(appId, extensionId) { }); } -export async function updateExtension(appId, installationId, extensionId) { +export function updateExtension(appId, installationId, extensionId) { const url = appManagerUri.clone().segment(`/v1/apps/${appId}/installations/${installationId}`); - return await jsonApi.patch(url, { + return jsonApi.patch(url, { data: { type: 'shoutem.core.installations', attributes: { extension: extensionId }, @@ -25,19 +25,19 @@ export async function updateExtension(appId, installationId, extensionId) { }); } -export async function uninstallExtension(appId, extensionId) { +export function uninstallExtension(appId, extensionId) { const uri = appManagerUri.clone().segment(`/v1/apps/${appId}/installations/${extensionId}`); return jsonApi.del(uri); } -export async function getExtInstallations(appId) { +export function getExtInstallations(appId) { const uri = appManagerUri.clone().segment(`/v1/apps/${appId}/installations`); - return await jsonApi.get(uri); + return jsonApi.get(uri); } -export async function createApp(app) { +export function createApp(app) { const url = appManagerUri.clone().segment('/v1/apps/base/actions/clone'); - return await jsonApi.post(url, { + return jsonApi.post(url, { data: { type: 'shoutem.core.application-clones', attributes: app, @@ -45,19 +45,19 @@ export async function createApp(app) { }); } -export async function getApplicationPlatform(appId, plain = false) { +export function getApplicationPlatform(appId, plain = false) { const url = appManagerUri.clone().segment(`/v1/apps/${appId}/platform`); - return await jsonApi.get(url, { plain }); + return jsonApi.get(url, { plain }); } -export async function getInstallations(appId) { +export function getInstallations(appId) { const url = appManagerUri.clone().segment(`/v1/apps/${appId}/installations`); - return await jsonApi.get(url); + return jsonApi.get(url); } -export async function getInstallation(appId, canonical) { +export function getInstallation(appId, canonical) { const url = appManagerUri.clone().segment(`/v1/apps/${appId}/installations/${canonical}`); - return await jsonApi.get(url); + return jsonApi.get(url); } export async function installApplicationPlatform(appId, platformId) { @@ -66,7 +66,7 @@ export async function installApplicationPlatform(appId, platformId) { const url = appManagerUri.clone().segment(`/v1/apps/${appId}/platform/actions/migrate`); - return await jsonApi.post(url, { + return jsonApi.post(url, { data: { type: 'shoutem.core.platform-installation-migrations', attributes: {}, diff --git a/src/clients/auth-service.js b/src/clients/auth-service.js index 3173a11..1535930 100644 --- a/src/clients/auth-service.js +++ b/src/clients/auth-service.js @@ -1,10 +1,12 @@ import URI from 'urijs'; -import { post } from './json-api-client'; +import { configure, authorize } from '@shoutem/fetch-token-intercept'; + +import logger from '../services/logger'; +import cache from '../services/cache-env'; import services from '../../config/services'; -import * as cache from '../services/cache-env'; -import * as logger from '../services/logger'; +import jsonApi from './json-api-client'; -export class AuthServiceError { +class AuthServiceError { /* Used whenever AuthService misbehaves and returns errors not listed in the API specification. @@ -17,7 +19,7 @@ export class AuthServiceError { } } -export class UnauthorizedError { +class UnauthorizedError { /* Used when bad username or password is supplied. */ @@ -36,16 +38,15 @@ function getBasicAuthHeaderValue(email, password) { return 'Basic ' + new Buffer(`${email}:${password}`).toString('base64'); } -export async function createRefreshToken(email, password) { +async function createRefreshToken(email, password) { try { - const response = await post(tokensUrl, null, { + const response = await jsonApi.post(tokensUrl, null, { headers: { - Authorization: getBasicAuthHeaderValue(email, password) - } + Authorization: getBasicAuthHeaderValue(email, password), + }, }); - const { token } = response; - return token; + return response.token; } catch (err) { if (err.statusCode === 401) { throw new UnauthorizedError(err.url, err.response, err.statusCode); @@ -54,40 +55,41 @@ export async function createRefreshToken(email, password) { } } -export async function createAppAccessToken(appId, refreshToken) { +async function createAppAccessToken(appId, refreshToken) { const body = { data: { type: 'shoutem.auth.tokens', attributes: { tokenType: 'access-token', subjectType: 'application', - subjectId: appId.toString() - } - } + subjectId: appId.toString(), + }, + }, }; - const { token } = await post(appAccessTokenUrl, body, { + const { token } = await jsonApi.post(appAccessTokenUrl, body, { headers: { - Authorization: `Bearer ${refreshToken}` - } + Authorization: `Bearer ${refreshToken}`, + }, }); return token; } -export async function getRefreshToken({ email, password } = {}) { +async function getRefreshToken({ email, password } = {}) { if (email && password) { - const refreshToken = await cache.setValue('refresh-token', await createRefreshToken(email, password)); - await cache.setValue('access-token', null); + const refreshToken = await createRefreshToken(email, password); + cache.setValue('refresh-token', refreshToken); + cache.setValue('access-token', null); return refreshToken; } - return await cache.getValue('refresh-token'); + return cache.getValue('refresh-token'); } -export async function clearTokens() { - await cache.setValue('access-token', null); - await cache.setValue('refresh-token', null); +function clearTokens() { + cache.setValue('access-token', null); + cache.setValue('refresh-token', null); } const authorizationConfig = { @@ -98,22 +100,22 @@ const authorizationConfig = { headers: { Authorization: `Bearer ${refreshToken}`, Accept: 'application/vnd.api+json', - 'Content-Type': 'application/vnd.api+json' + 'Content-Type': 'application/vnd.api+json', }, body: JSON.stringify({ data: { type: 'shoutem.auth.tokens', attributes: { - tokenType: 'access-token' - } - } - }) + tokenType: 'access-token', + }, + }, + }), }); }, async parseAccessToken(response) { if (response.ok) { const { data: { attributes: { token } } } = await response.json(); - await cache.setValue('access-token', token); + cache.setValue('access-token', token); return token; } logger.info('parseAccessToken', response); @@ -133,23 +135,36 @@ const authorizationConfig = { isResponseUnauthorized({ status }) { return status === 401 || status === 403; }, - shouldWaitForTokenRenewal: true + shouldWaitForTokenRenewal: true, }; -export async function authorizeRequests(refreshToken) { +function authorizeRequests(refreshToken) { if (!refreshToken) { - return; + return undefined; } + try { - const intercept = require('@shoutem/fetch-token-intercept'); - intercept.configure(authorizationConfig); - intercept.authorize(refreshToken, await cache.getValue('access-token')); + const accessToken = cache.getValue('access-token'); + configure(authorizationConfig); + authorize(refreshToken, accessToken); return true; } catch (err) { logger.info(err); + if (err.statusCode !== 401) { throw err; } + return false; } } + +export default { + AuthServiceError, + UnauthorizedError, + createRefreshToken, + createAppAccessToken, + getRefreshToken, + clearTokens, + authorizeRequests, +}; diff --git a/src/clients/cli-paths.js b/src/clients/cli-paths.js index fff68b7..cad82cc 100644 --- a/src/clients/cli-paths.js +++ b/src/clients/cli-paths.js @@ -1,15 +1,12 @@ -import mkdirp from 'mkdirp-promise'; -import { sync as mkdirpSync } from 'mkdirp'; -import getHomeDir from '../home-dir'; +const fs = require('fs-extra'); +const path = require('path'); -export async function getLocalStoragePath() { - const storagePath = getHomeDir(); - await mkdirp(storagePath); - return storagePath; -} +const getHomeDir = require(path.resolve(__dirname, '../home-dir.js')); -export function getLocalStoragePathSync() { +function getLocalStoragePath() { const storagePath = getHomeDir(); - mkdirpSync(storagePath); + fs.ensureDirSync(storagePath); return storagePath; } + +module.exports.getLocalStoragePath = getLocalStoragePath; diff --git a/src/clients/extension-manager.js b/src/clients/extension-manager.js index 30b7d10..ad352e7 100644 --- a/src/clients/extension-manager.js +++ b/src/clients/extension-manager.js @@ -2,19 +2,19 @@ import URI from 'urijs'; import FormData from 'form-data'; import { extensionManager } from '../../config/services'; import { listenStream } from '../services/stream-listener'; -import * as jsonApi from './json-api-client'; +import jsonApi from './json-api-client'; const extensionManagerUri = new URI(extensionManager); -export async function getDeveloper() { +export function getDeveloper() { const url = extensionManagerUri.clone().segment('/v1/devs/me'); - return await jsonApi.get(url); + return jsonApi.get(url); } -export async function createDeveloper(devName) { +export function createDeveloper(devName) { const url = extensionManagerUri.clone().segment('/v1/devs'); - return await jsonApi.post(url, { + return jsonApi.post(url, { data: { type: 'shoutem.core.developers', attributes: { name: devName }, @@ -50,24 +50,24 @@ export async function getExtensionId(canonicalName) { return id; } -export async function getExtension(canonicalName) { +export function getExtension(canonicalName) { const url = extensionManagerUri.clone().segment(`/v1/extensions/${canonicalName}`); - return await jsonApi.get(url); + return jsonApi.get(url); } -export async function getPlatform(id) { +export function getPlatform(id) { const url = extensionManagerUri.clone().segment(`/v1/platforms/${id}`); - return await jsonApi.get(url); + return jsonApi.get(url); } -export async function publishExtension(canonicalName) { +export function publishExtension(canonicalName) { const url = extensionManagerUri.clone().segment(`/v1/extensions/${canonicalName}/publish`); - return await jsonApi.post(url); + return jsonApi.post(url); } -export async function getPlatforms() { +export function getPlatforms() { const url = extensionManagerUri.clone().segment('/v1/platforms'); - return await jsonApi.get(url); + return jsonApi.get(url); } export async function uploadPlatform(archiveStream, progressHandler, size) { @@ -92,9 +92,9 @@ export async function uploadPlatform(archiveStream, progressHandler, size) { return response; } -export async function publishPlatform(platformId) { +export function publishPlatform(platformId) { const url = extensionManagerUri.clone().segment(`/v1/platforms/${platformId}/actions/publish`); - return await jsonApi.post(url); + return jsonApi.post(url); } export async function canPublish(canonical) { diff --git a/src/clients/json-api-client.js b/src/clients/json-api-client.js index 945c192..7ea9101 100644 --- a/src/clients/json-api-client.js +++ b/src/clients/json-api-client.js @@ -1,21 +1,22 @@ import _ from 'lodash'; import { Deserializer } from 'jsonapi-serializer'; -import * as logger from '../services/logger'; +import logger from '../services/logger'; const deserializer = new Deserializer({ - keyForAttribute: 'camelCase' + keyForAttribute: 'camelCase', }); -// jsonapi-serializer seems to ignore non-included relationships no matter what its docs say -// therefore, support an option to use our own deserialization when specified(default still jsonapi-serializer) +// jsonapi-serializer seems to ignore non-included relationships +// no matter what its docs say therefore, support an option to +// use our own deserialization when specified (default still jsonapi-serializer) function deserializePlainSingle(json) { - const relationships = json.relationships; + const { relationships } = json; const relationshipKeys = _.keys(relationships); const relationshipIds = {}; - _.forEach(relationshipKeys, key => { + _.forEach(relationshipKeys, (key) => { relationshipIds[key] = _.get(relationships[key], 'data.id'); - }) + }); return { ...relationshipIds, @@ -28,7 +29,7 @@ function deserializePlain(json) { return null; } - const data = json.data; + const { data } = json; if (_.isArray(data)) { return _.map(data, item => deserializePlainSingle(item)); @@ -37,7 +38,7 @@ function deserializePlain(json) { return deserializePlainSingle(data); } -export class JsonApiError { +class JsonApiError { constructor(message, request, body, response, statusCode) { this.message = message; this.request = request; @@ -47,8 +48,8 @@ export class JsonApiError { } } -export async function execute(method, url, opts = {}) { - url = url.toString(); +async function execute(method, _url, opts = {}) { + const url = _url.toString(); const jsonApiHeaders = { Accept: 'application/vnd.api+json', @@ -59,11 +60,11 @@ export async function execute(method, url, opts = {}) { const req = new Request(url, { ...opts, - method: method, + method, headers: { ...jsonApiHeaders, - ...opts.headers - } + ...opts.headers, + }, }); logger.info(`${method} ${url}`, req); @@ -80,7 +81,6 @@ export async function execute(method, url, opts = {}) { } if (response.ok) { - try { const deserialized = await deserializer.deserialize(json); @@ -95,43 +95,53 @@ export async function execute(method, url, opts = {}) { throw new JsonApiError(null, req, json, response, response.status); } -export function get(uri, opts = {}) { +function get(uri, opts = {}) { return execute('get', uri, opts); } -export function put(url, jsonBody = null, opts) { +function put(url, jsonBody = null, opts) { if (jsonBody) { return execute('put', url, { ...opts, - body: JSON.stringify(jsonBody) + body: JSON.stringify(jsonBody), }); } return execute('put', url, opts); } -export function patch(url, jsonBody = null, opts) { +function patch(url, jsonBody = null, opts) { if (jsonBody) { return execute('patch', url, { ...opts, - body: JSON.stringify(jsonBody) + body: JSON.stringify(jsonBody), }); } return execute('patch', url, opts); } -export function del(uri) { +function del(uri) { return execute('delete', uri); } -export function post(url, jsonBody = null, opts = {}) { +function post(url, jsonBody = null, opts = {}) { if (jsonBody) { return execute('post', url, { ...opts, - body: JSON.stringify(jsonBody) + body: JSON.stringify(jsonBody), }); } return execute('post', url, opts); } + +export default { + JsonApiError, + execute, + get, + put, + patch, + del, + post, +}; diff --git a/src/clients/legacy-service.js b/src/clients/legacy-service.js index e2c7b1e..3391bf6 100644 --- a/src/clients/legacy-service.js +++ b/src/clients/legacy-service.js @@ -1,15 +1,16 @@ import URI from 'urijs'; import { legacyService } from '../../config/services'; -import * as jsonApi from './json-api-client'; -import * as logger from '../services/logger'; +import jsonApi from './json-api-client'; +import logger from '../services/logger'; const legacyServiceUri = new URI(legacyService); export async function getLatestApps() { - const body = await jsonApi.get(legacyServiceUri.clone() + const request = legacyServiceUri.clone() .segment('/v1/apps') - .search({ sort: '-modificationTime' }) - ); + .search({ sort: '-modificationTime' }); + + const body = await jsonApi.get(request); logger.info('getLatestApps', body); return body; @@ -17,10 +18,10 @@ export async function getLatestApps() { export async function getApp(appId) { const url = legacyServiceUri.clone().segment(`/v1/apps/${appId}`); - return await jsonApi.get(url); + return jsonApi.get(url); } export async function getPublishingProperties(appId) { - const url = legacyServiceUri.clone().segment(`/api/applications/publishing_properties.json`).search({nid: appId}); - return await jsonApi.get(url); + const url = legacyServiceUri.clone().segment('/api/applications/publishing_properties.json').search({ nid: appId }); + return jsonApi.get(url); } diff --git a/src/clients/local-extensions.js b/src/clients/local-extensions.js index f2106f1..172243a 100644 --- a/src/clients/local-extensions.js +++ b/src/clients/local-extensions.js @@ -1,10 +1,14 @@ -import * as utils from '../services/extension'; -import { ensureInExtensionDir } from '../services/extension'; +import { + loadExtensionJson, + ensureInExtensionDir, + getExtensionCanonicalName, +} from '../services/extension'; + import { ensureUserIsLoggedIn } from '../commands/login'; -export async function getExtensionCanonicalName(extensionRoot = ensureInExtensionDir()) { +export async function getLocalExtensionCanonicalName(extensionRoot = ensureInExtensionDir()) { const dev = await ensureUserIsLoggedIn(); - const { name, version } = await utils.loadExtensionJson(extensionRoot); + const { name, version } = loadExtensionJson(extensionRoot); - return utils.getExtensionCanonicalName(dev.name, name, version); + return getExtensionCanonicalName(dev.name, name, version); } diff --git a/src/clients/server-env.js b/src/clients/server-env.js index 09061c5..4167f65 100644 --- a/src/clients/server-env.js +++ b/src/clients/server-env.js @@ -1,10 +1,12 @@ -import path from 'path'; -import fs from 'fs-extra'; -import { getLocalStoragePathSync } from '../clients/cli-paths'; +const path = require('path'); +const fs = require('fs-extra'); -const serverEnvNamePath = path.join(getLocalStoragePathSync(), 'server-env'); +const cliPaths = require(path.resolve(__dirname, '../clients/cli-paths.js')); -export function getHostEnvName() { +const localStoragePath = cliPaths.getLocalStoragePath(); +const serverEnvNamePath = path.join(localStoragePath, 'server-env'); + +function getHostEnvName() { try { return fs.readFileSync(serverEnvNamePath, 'utf8'); } catch (err) { @@ -12,6 +14,9 @@ export function getHostEnvName() { } } -export async function setHostEnvName(name) { - await fs.writeFile(serverEnvNamePath, name); +function setHostEnvName(name) { + fs.writeFileSync(serverEnvNamePath, name); } + +module.exports.getHostEnvName = getHostEnvName; +module.exports.setHostEnvName = setHostEnvName; diff --git a/src/clients/url-shortener.js b/src/clients/url-shortener.js index 6ad1a22..b1c0fee 100644 --- a/src/clients/url-shortener.js +++ b/src/clients/url-shortener.js @@ -1,5 +1,5 @@ import request from 'request-promise'; -export default async function(url) { - return await request(`http://sh.outem.tk/generate/${url}`); +export default function(url) { + return request(`http://sh.outem.tk/generate/${url}`); } diff --git a/src/commands/clone.js b/src/commands/clone.js index 867b06e..ad6e203 100644 --- a/src/commands/clone.js +++ b/src/commands/clone.js @@ -1,4 +1,3 @@ -import { pathExists, copy } from 'fs-extra'; import fs from 'fs-extra'; import mkdirp from 'mkdirp-promise'; import inquirer from 'inquirer'; @@ -10,7 +9,7 @@ import 'colors'; import { ensureUserIsLoggedIn } from './login'; import { getExtension } from '../clients/extension-manager'; -import * as appManager from '../clients/app-manager'; +import { getInstallations, getApplicationPlatform } from '../clients/app-manager'; import { getApp } from '../clients/legacy-service'; import { createProgressHandler } from '../services/progress-bar'; @@ -27,32 +26,24 @@ import { } from '../services/platform'; export async function pullExtensions(appId, destinationDir) { - const installations = await appManager.getInstallations(appId); + const installations = await getInstallations(appId); const n = installations.length; let i = 0; - for(const inst of installations) { + for (const inst of installations) { i++; - await spinify( - pullExtension(destinationDir, inst), `Downloading extension ${i}/${n}: ${inst.canonicalName}` - ); + await spinify(pullExtension(destinationDir, inst), `Downloading extension ${i}/${n}: ${inst.canonicalName}`,); } } -async function pullExtension(destinationDir, { extension, canonicalName }) { - const url = await getExtensionUrl(extension); - const ext = path.extname(url.split('/').pop()); - const fileName = `extension-${canonicalName}${ext}`; - const destination = path.join(destinationDir, canonicalName); +function removeTrailingSlash(str) { + return str.replace(/\/$/, ''); +} - fs.ensureDirSync(destination); +async function getExtensionUrl(extId) { + const resp = await getExtension(extId); + const { location: { extension } } = resp; - try { - await decompressFromUrl(url, destination, { fileName, deleteArchiveWhenDone: true }); - await unpackExtension(destination, { deleteArchiveWhenDone: true }); - } catch (err) { - err.message = `Could not fetch extension ${canonicalName}\nRequested URL: ${url}\n${err.message}`; - throw err; - } + return `${removeTrailingSlash(extension.package)}/extension.tgz`; } export async function unpackExtension(extensionDir, options) { @@ -65,22 +56,28 @@ export async function unpackExtension(extensionDir, options) { await decompressFile(serverFile, serverDir, options); } -async function getExtensionUrl(extId) { - const resp = await getExtension(extId); - const { location: { extension } } = resp; +async function pullExtension(destinationDir, { extension, canonicalName }) { + const url = await getExtensionUrl(extension); + const ext = path.extname(url.split('/').pop()); + const fileName = `extension-${canonicalName}${ext}`; + const destination = path.join(destinationDir, canonicalName); - return `${removeTrailingSlash(extension.package)}/extension.tgz`; -} + fs.ensureDirSync(destination); -function removeTrailingSlash(str) { - return str.replace(/\/$/, ""); + try { + await decompressFromUrl(url, destination, { fileName, deleteArchiveWhenDone: true }); + await unpackExtension(destination, { deleteArchiveWhenDone: true }); + } catch (err) { + err.message = `Could not fetch extension ${canonicalName}\nRequested URL: ${url}\n${err.message}`; + throw err; + } } function ensurePlatformCompatibility(platform) { - const msg = `Your app is using Shoutem Platform ${platform.version}`+ - `, but cloning is supported only on Shoutem Platform 1.1.2 or later.\n`+ - `Please, update the Platform through Settings -> Shoutem Platform -> Install page on the Builder or install older (and unsupported) version of ` + - `the Shoutem CLI by running 'npm install -g @shoutem/cli@0.0.152'`; + const msg = `Your app is using Shoutem Platform ${platform.version}` + + ', but cloning is supported only on Shoutem Platform 1.1.2 or later.\n' + + 'Please, update the Platform through Settings -> Shoutem Platform -> Install page on the Builder or install older (and unsupported) version of ' + + 'the Shoutem CLI by running \'npm install -g @shoutem/cli@0.0.152\''; if (semver.lte(platform.version, '1.1.1')) { throw new Error(msg); @@ -94,14 +91,14 @@ async function queryPathExistsAction(destinationDir, oldDirectoryName) { message: `Directory ${oldDirectoryName} already exists`, choices: [{ name: 'Overwrite', - value: 'overwrite' + value: 'overwrite', }, { name: 'Abort', value: 'abort', }, { name: 'Different app directory name', - value: 'rename' - }] + value: 'rename', + }], }); if (action === 'overwrite') { @@ -118,17 +115,17 @@ async function queryPathExistsAction(destinationDir, oldDirectoryName) { if (dirName.indexOf(' ') > -1) { return 'No spaces are allowed'; } - if (await pathExists(path.join(destinationDir, dirName))) { - return `Directory ${dirName} already exists.` + if (await fs.pathExists(path.join(destinationDir, dirName))) { + return `Directory ${dirName} already exists.`; } return true; - } + }, }); return { type: 'rename', newDirectoryName, - newAppDir: path.join(destinationDir, newDirectoryName) + newAppDir: path.join(destinationDir, newDirectoryName), }; } @@ -138,6 +135,7 @@ export async function clone(opts, destinationDir) { } await ensureUserIsLoggedIn(); + // eslint-disable-next-line no-param-reassign opts.appId = opts.appId || await selectApp(); const { name } = await getApp(opts.appId); @@ -150,7 +148,7 @@ export async function clone(opts, destinationDir) { await spinify(rmrf(appDir), `Destroying directory ${directoryName}`); } - if (await pathExists(appDir)) { + if (await fs.pathExists(appDir)) { const action = await queryPathExistsAction(destinationDir, directoryName); if (action.type === 'overwrite') { await spinify(rmrf(appDir), `Destroying directory ${directoryName}`); @@ -172,20 +170,20 @@ export async function clone(opts, destinationDir) { console.log(`Cloning \`${name}\` to \`${directoryName}\`...`); if (opts.platform) { - await spinify(copy(opts.platform, appDir), 'Copying platform code'); + await spinify(fs.copy(opts.platform, appDir), 'Copying platform code'); } else { - const platform = await appManager.getApplicationPlatform(opts.appId); + const platform = await getApplicationPlatform(opts.appId); ensurePlatformCompatibility(platform); await downloadApp(opts.appId, appDir, { progress: createProgressHandler({ msg: 'Downloading shoutem platform' }), useCache: !opts.force, deleteArchiveWhenDone: true, - versionCheck: mobileAppVersion => { + versionCheck: (mobileAppVersion) => { if (!semver.gte(mobileAppVersion, '0.58.9')) { throw new Error('This version of CLI only supports platforms containing mobile app 0.58.9 or higher'); } - } + }, }); } @@ -194,9 +192,10 @@ export async function clone(opts, destinationDir) { await fixPlatform(appDir, opts.appId); const config = await createPlatformConfig(appDir, { - appId: opts.appId + appId: opts.appId, }); - await setPlatformConfig(appDir, config); + + setPlatformConfig(appDir, config); if (opts.noconfigure) { console.log('Skipping configure step due to --noconfigure flag'); diff --git a/src/commands/confirm-admin-action.js b/src/commands/confirm-admin-action.js index 40ff9c8..264d0fc 100644 --- a/src/commands/confirm-admin-action.js +++ b/src/commands/confirm-admin-action.js @@ -9,5 +9,5 @@ export default async function(msg) { return true; } - return await confirm(msg, { default: false }); + return confirm(msg, { default: false }); } diff --git a/src/commands/init.js b/src/commands/init.js index 8e1d086..5151b42 100644 --- a/src/commands/init.js +++ b/src/commands/init.js @@ -6,10 +6,10 @@ import path from 'path'; import { ensureUserIsLoggedIn } from '../commands/login'; import msg from '../user_messages'; import { getPlatforms } from '../clients/extension-manager'; -import * as utils from '../services/extension'; -import {instantiateExtensionTemplate} from "../services/extension-template"; -import {offerChanges} from "../services/diff"; -import {stringify} from "../services/data"; +import { getExtensionCanonicalName } from '../services/extension'; +import { instantiateExtensionTemplate } from '../services/extension-template'; +import { offerChanges } from '../services/diff'; +import { stringify } from '../services/data'; function generateNoPatchSemver(version) { @@ -30,9 +30,9 @@ export async function promptExtensionInit(extName) { name: 'version', message: 'Version', default: version, - validate: value => value.match(/^(\d+)\.(\d+)\.(\d+)+$/) - ? true - : 'Version must contain numbers in format X.Y.Z', + validate: value => (value.match(/^(\d+)\.(\d+)\.(\d+)+$/) + ? true + : 'Version must contain numbers in format X.Y.Z'), }, { name: 'description', message: 'Description', @@ -52,7 +52,7 @@ export async function initExtension(extName, extensionPath = process.cwd()) { const developer = await ensureUserIsLoggedIn(); const extJson = await promptExtensionInit(extName); - utils.getExtensionCanonicalName(developer.name, extJson.name, extJson.version); + getExtensionCanonicalName(developer.name, extJson.name, extJson.version); const dirname = `${developer.name}.${extJson.name}`; if (await pathExists(path.join(process.cwd(), dirname))) { diff --git a/src/commands/install.js b/src/commands/install.js index 533a726..d17ca61 100644 --- a/src/commands/install.js +++ b/src/commands/install.js @@ -1,8 +1,8 @@ import inquirer from 'inquirer'; import { installExtension, createApp } from '../clients/app-manager'; import { getLatestApps } from '../clients/legacy-service'; -import { getExtensionCanonicalName } from '../clients/local-extensions'; -import * as extensionManager from '../clients/extension-manager'; +import { getLocalExtensionCanonicalName } from '../clients/local-extensions'; +import { getExtensionId } from '../clients/extension-manager'; import selectApp from '../services/app-selector'; import { ensureInExtensionDir } from '../services/extension'; import msg from '../user_messages'; @@ -31,7 +31,7 @@ export async function promptAppName() { async function getNewApp() { const name = await promptAppName(); - return await createApp({ name }); + return createApp({ name }); } export async function ensureApp() { @@ -39,7 +39,7 @@ export async function ensureApp() { if (appList.length === 0) { if (!await promptCreateNewApp()) { - return await getNewApp(); + return getNewApp(); } } @@ -47,13 +47,13 @@ export async function ensureApp() { return appList.filter(app => app.id === appId)[0] || await getNewApp(); } -export async function createNewApp(name) { - return await createApp({ name }); +export function createNewApp(name) { + return createApp({ name }); } export async function installLocalExtension(appId, extensionRoot = ensureInExtensionDir()) { - const canonicalName = await getExtensionCanonicalName(extensionRoot); - const extensionId = await extensionManager.getExtensionId(canonicalName); + const canonicalName = await getLocalExtensionCanonicalName(extensionRoot); + const extensionId = await getExtensionId(canonicalName); if (extensionId) { await installExtension(appId, extensionId); diff --git a/src/commands/login.js b/src/commands/login.js index 0340d3a..b837352 100644 --- a/src/commands/login.js +++ b/src/commands/login.js @@ -1,21 +1,15 @@ import inquirer from 'inquirer'; import _ from 'lodash'; -import { authorizeRequests, getRefreshToken } from '../clients/auth-service'; +import authService from '../clients/auth-service'; import { getDeveloper, createDeveloper } from '../clients/extension-manager'; import msg from '../user_messages'; import urls from '../../config/services'; -import * as logger from '../services/logger'; -import * as cache from '../services/cache-env'; - -async function resolveCredentials(args) { - if (args.credentials) { - return await parseCredentials(args.credentials) - } - return await promptUserCredentials(args); -} +import logger from '../services/logger'; +import cache from '../services/cache-env'; function parseCredentials(credentials) { const parts = credentials.split(':'); + return { email: _.get(parts, '[0]'), password: _.get(parts, '[1]'), @@ -41,14 +35,27 @@ function promptUserCredentials(args = {}) { return inquirer.prompt(questions); } +function resolveCredentials(args) { + if (args.credentials) { + return parseCredentials(args.credentials); + } + + return promptUserCredentials(args); +} + function promptDeveloperName() { /* eslint no-confusing-arrow: 0 */ console.log('Enter developer name.'); - return inquirer.prompt({ - name: 'devName', - message: 'Developer name', - validate: value => value ? true : 'Developer name cannot be blank.', - }).then(answer => answer.devName); + + return inquirer + .prompt({ + name: 'devName', + message: 'Developer name', + validate(value) { + return value ? true : 'Developer name cannot be blank.'; + }, + }) + .then(answer => answer.devName); } /** @@ -57,10 +64,11 @@ function promptDeveloperName() { */ export async function loginUser(args) { const credentials = await resolveCredentials(args); - const refreshToken = await getRefreshToken(credentials); - await authorizeRequests(refreshToken); - + const refreshToken = await authService.getRefreshToken(credentials); let developer = null; + + authService.authorizeRequests(refreshToken); + try { developer = await getDeveloper(); } catch (err) { @@ -72,6 +80,7 @@ export async function loginUser(args) { } console.log(msg.login.complete(developer)); + logger.info('logged in as developer', developer); return cache.setValue('developer', { ...developer, email: credentials.email }); @@ -79,17 +88,19 @@ export async function loginUser(args) { /** * Asks user for email and password if refreshToken is not already cached - * @param shouldThrow Should an error be thrown if user is not logged in or should user be asked for credentials + * @param shouldThrow Should an error be thrown if user is not logged in + * or should user be asked for credentials */ export async function ensureUserIsLoggedIn(shouldThrow = false) { - const developer = await cache.getValue('developer'); + const developer = cache.getValue('developer'); + if (developer) { return developer; } if (shouldThrow) { throw new Error('Not logged in, use `shoutem login` command to login'); - } else { - return await loginUser(); } + + return loginUser(); } diff --git a/src/commands/logout.js b/src/commands/logout.js index 30917f8..798b32a 100644 --- a/src/commands/logout.js +++ b/src/commands/logout.js @@ -1,9 +1,9 @@ -import * as cache from '../services/cache-env'; -import { clearTokens } from '../clients/auth-service'; +import authService from '../clients/auth-service'; +import cache from '../services/cache-env'; import msg from '../user_messages'; -export default async function logout() { - await cache.setValue('developer', null); - await clearTokens(); +export default function logout() { + cache.setValue('developer', null); + authService.clearTokens(); console.log(msg.logout.complete()); } diff --git a/src/commands/platform.js b/src/commands/platform.js index ba0564c..dde06cb 100644 --- a/src/commands/platform.js +++ b/src/commands/platform.js @@ -29,7 +29,9 @@ export async function uploadPlatformArchive(platformArchiveProvider) { createProgressHandler({ msg: 'Upload progress', total: size, - onFinished: () => spinner = startSpinner('Processing upload...'), + onFinished() { + spinner = startSpinner('Processing upload...'); + }, }), size, ); @@ -50,7 +52,9 @@ export async function getAvailablePlatforms(limit) { let ownPlatforms = _.filter(allPlatforms, platform => _.get(platform, ['author', 'name']) === developer.name); - ownPlatforms.sort((p1, p2) => semver.compare(p1.version, p2.version, true) * -1); // highest versions first + // highest versions first + ownPlatforms.sort((p1, p2) => + semver.compare(p1.version, p2.version, true) * -1); if (_.isNumber(limit)) { ownPlatforms = _.slice(ownPlatforms, 0, limit); diff --git a/src/commands/publish.js b/src/commands/publish.js index 402dfc2..88f25a3 100644 --- a/src/commands/publish.js +++ b/src/commands/publish.js @@ -1,29 +1,29 @@ import * as extensionManager from '../clients/extension-manager'; -import * as utils from '../services/extension'; import { uploadExtension } from '../commands/push'; -import { ensureInExtensionDir } from '../services/extension'; -import { getExtensionCanonicalName } from '../clients/local-extensions'; +import { getLocalExtensionCanonicalName } from '../clients/local-extensions'; import msg from '../user_messages'; -import {spinify} from "../services/spinner"; -import {getPlatformRootDir} from "../services/platform"; -import {offerInstallationUpdate} from "../cli/extension/publish"; -import {loadExtensionJson} from "../services/extension"; +import { spinify } from '../services/spinner'; +import { getPlatformRootDir } from '../services/platform'; +import { offerInstallationUpdate } from '../cli/extension/publish'; +import { loadExtensionJson, ensureInExtensionDir } from '../services/extension'; export async function publishExtension(extDir) { - const extJson = await utils.loadExtensionJson(extDir); - const canonicalName = await getExtensionCanonicalName(extDir); - return await spinify(extensionManager.publishExtension(canonicalName), msg.publish.publishInfo(extJson), 'OK'); + const extJson = await loadExtensionJson(extDir); + const canonicalName = await getLocalExtensionCanonicalName(extDir); + return spinify(extensionManager.publishExtension(canonicalName), msg.publish.publishInfo(extJson), 'OK'); } export async function pushAndPublish(args = {}) { if (!args.nopush) { await uploadExtension({ ...args, publish: true }); } + const extPath = ensureInExtensionDir(); - const { name } = await loadExtensionJson(); + + const { name } = loadExtensionJson(); const { id: extensionId, version } = await publishExtension(extPath); - if (await getPlatformRootDir(extPath, { shouldThrow: false })) { + if (getPlatformRootDir(extPath, { shouldThrow: false })) { await offerInstallationUpdate(extensionId, name, version); } } diff --git a/src/commands/push.js b/src/commands/push.js index 2647abc..5b22253 100644 --- a/src/commands/push.js +++ b/src/commands/push.js @@ -1,64 +1,101 @@ import fs from 'fs-extra'; import { prompt } from 'inquirer'; import semver from 'semver'; +import _ from 'lodash'; + import * as extensionManager from '../clients/extension-manager'; -import {getHostEnvName} from '../clients/server-env'; -import * as local from '../clients/local-extensions'; +import { getHostEnvName } from '../clients/server-env'; +import { getLocalExtensionCanonicalName } from '../clients/local-extensions'; import { ensureInExtensionDir, getExtensionCanonicalName, loadExtensionJson, - saveExtensionJson + saveExtensionJson, } from '../services/extension'; +import depcheck from '../services/depcheck'; import shoutemPack from '../services/packer'; +import { spinify, startSpinner } from '../services/spinner'; +import { createProgressHandler } from '../services/progress-bar'; import msg from '../user_messages'; -import _ from 'lodash'; -import {createProgressHandler} from '../services/progress-bar'; -import {spinify, startSpinner} from '../services/spinner'; -import extLint from '../services/extlint'; -import {ensureUserIsLoggedIn} from "./login"; -import {canPublish} from "../clients/extension-manager"; -import depcheck from '../services/depcheck'; +import { ensureUserIsLoggedIn } from './login'; + +export async function promptPublishableVersion(extJson) { + const dev = await ensureUserIsLoggedIn(); + + while (true) { + const { name, version } = extJson; + const canonical = getExtensionCanonicalName(dev.name, name, version); + const canExtensionBePublished = await spinify(extensionManager.canPublish(canonical), `Checking if version ${version} can be published`); + + if (canExtensionBePublished) { + return; + } + + const { newVersion } = await prompt({ + name: 'newVersion', + default: semver.inc(version, 'patch'), + message: `Version ${version} is already published. Specify another version:`, + validate: v => !!semver.valid(v), + }); + + extJson.version = newVersion; + } +} export async function uploadExtension(opts = {}, extensionDir = ensureInExtensionDir()) { if (!opts.nocheck) { process.stdout.write('Checking the extension code for syntax errors... '); + try { - //await extLint(extensionDir); + // await extLint(extensionDir); console.log(`[${'OK'.green.bold}]`); } catch (err) { err.message = 'Syntax errors detected, aborting push! Use `shoutem push --nocheck` to override'; throw err; } + await spinify(await depcheck(extensionDir), 'Checking for missing shoutem dependencies'); } - const extJson = await loadExtensionJson(extensionDir); + + const extJson = loadExtensionJson(extensionDir); + if (opts.publish) { await promptPublishableVersion(extJson); - await saveExtensionJson(extJson, extensionDir); + saveExtensionJson(extJson, extensionDir); } - const packResult = await shoutemPack(extensionDir, { packToTempDir: true, nobuild: opts.nobuild }); + const packResult = await shoutemPack(extensionDir, { + packToTempDir: true, + nobuild: opts.nobuild, + }); - const { size } = await fs.stat(packResult.package); + const { size } = fs.statSync(packResult.package); const stream = fs.createReadStream(packResult.package); - const id = await local.getExtensionCanonicalName(extensionDir); + const id = await getLocalExtensionCanonicalName(extensionDir); let spinner = null; const extensionId = await extensionManager.uploadExtension( id, stream, - createProgressHandler({ msg: 'Upload progress', total: size, onFinished: () => spinner = startSpinner('Processing upload...') }), - size + createProgressHandler({ + msg: 'Upload progress', + total: size, + onFinished() { + spinner = startSpinner('Processing upload...'); + }, + }), + size, ); + if (spinner) { spinner.stop(true); console.log(`Processing upload... [${'OK'.green.bold}]`); } - console.log(msg.push.uploadingInfo(extJson, getHostEnvName()) + ` [${'OK'.green.bold}]`); - await fs.unlink(packResult.package); + console.log(`${msg.push.uploadingInfo(extJson, getHostEnvName())} [${'OK'.green.bold}]`); + + fs.unlinkSync(packResult.package); const notPacked = _.difference(packResult.allDirs, packResult.packedDirs); if (notPacked.length > 0) { @@ -67,22 +104,3 @@ export async function uploadExtension(opts = {}, extensionDir = ensureInExtensio return { extensionId, packResult, extJson }; } - -export async function promptPublishableVersion(extJson) { - const dev = await ensureUserIsLoggedIn(); - while (true) { - const { name, version } = extJson; - const canonical = getExtensionCanonicalName(dev.name, name, version); - const canExtensionBePublished = await spinify(canPublish(canonical), `Checking if version ${version} can be published`); - if (canExtensionBePublished) { - return; - } - const { newVersion } = await prompt({ - name: 'newVersion', - default: semver.inc(version, 'patch'), - message: `Version ${version} is already published. Specify another version:`, - validate: v => !!semver.valid(v), - }); - extJson.version = newVersion; - } -} diff --git a/src/commands/run.js b/src/commands/run.js index 735a539..7e2b6aa 100644 --- a/src/commands/run.js +++ b/src/commands/run.js @@ -1,39 +1,48 @@ import url from 'url'; import ip from 'ip'; -import * as tunnel from '../services/tunnel'; + +import tunnel from '../services/tunnel'; +import analytics from '../services/analytics'; +import commandExists from '../services/command-exists'; +import { handleError } from '../services/error-handler'; import { startPackager } from '../services/react-native'; +import { getPlatformRootDir, getPlatformConfig } from '../services/platform'; import { printMobilizerQR } from '../commands/qr-generator'; -import * as analytics from '../services/analytics'; -import { handleError } from '../services/error-handler'; import { ensureUserIsLoggedIn } from './login'; -import { getPlatformRootDir, getPlatformConfig } from '../services/platform'; -import commandExists from '../services/command-exists'; export default async function (options) { try { - analytics.setAppId((await getPlatformConfig()).appId); + analytics.setAppId(getPlatformConfig().appId); + await ensureUserIsLoggedIn(); - const { packagerProcess } = await startPackager(await getPlatformRootDir()); + const { packagerProcess } = await startPackager(getPlatformRootDir()); + + let ipAddress = ip.address(); + let portNumber = 8081; if (options.local) { console.log('Make sure that the phone running Shoutem app is connected to the same network as this computer'.yellow); + if (process.platform === 'win32') { console.log('If Shoutem app on your phone fails to load, try opening the 8081 TCP port manually from your Windows Firewall or disabling the firewall temporarily'.yellow); } else { console.log('Make sure that the 8081 TCP port is not blocked on this computer'.yellow); } - await printMobilizerQR(ip.address(), 8081, options); } else { - await printMobilizerQR(url.parse(await tunnel.start(8081)).hostname, 80, options); + ipAddress = url.parse(await tunnel.start(portNumber)).hostname; + portNumber = 80; } + await printMobilizerQR(ipAddress, portNumber, options); + console.log('Keep this process running if app is used in debug mode'.bold.yellow); await packagerProcess; } catch (err) { if (!/^win/.test(process.platform) && !await commandExists('watchman')) { console.log('HINT: You should probably install Facebook\'s `watchman` before running `shoutem run` command'.bold.yellow); } + await tunnel.stop(); await handleError(err); } diff --git a/src/commands/show.js b/src/commands/show.js index e506316..bc4fbaa 100644 --- a/src/commands/show.js +++ b/src/commands/show.js @@ -1,18 +1,19 @@ +import prettyJson from 'prettyjson'; + import { getHostEnvName } from '../clients/server-env'; import apisConfig from '../../config/services'; -import msg from '../user_messages'; -import { getValue } from '../services/cache-env'; +import cache from '../services/cache-env'; import getHomeDir from '../home-dir'; -import prettyJson from 'prettyjson'; +import msg from '../user_messages'; -export default async function(args) { +export default function (args) { const serverEnv = getHostEnvName(); if (args.all || serverEnv !== 'production') { console.log(prettyJson.render({ [msg.use.show(serverEnv)]: apisConfig })); } - const developer = await getValue('developer'); + const developer = cache.getValue('developer'); if (developer) { console.log(msg.login.complete(developer)); } diff --git a/src/commands/theme.js b/src/commands/theme.js index a37d831..2acba06 100644 --- a/src/commands/theme.js +++ b/src/commands/theme.js @@ -10,7 +10,7 @@ const themeUrls = { variables: 'https://raw.githubusercontent.com/shoutem/extensions/master/shoutem.rubicon-theme/server/primeThemeVariables.json' }; -async function promptThemeDetails(themeName) { +function promptThemeDetails(themeName) { console.log('Enter theme information.'); const questions = [{ message: 'Title', @@ -23,7 +23,7 @@ async function promptThemeDetails(themeName) { type: 'input', }]; - return await inquirer.prompt(questions) + return inquirer.prompt(questions) } async function getThemeVariablesContent(themeName) { diff --git a/src/commands/update-cli.js b/src/commands/update-cli.js index 3a20462..7d7b2c1 100644 --- a/src/commands/update-cli.js +++ b/src/commands/update-cli.js @@ -1,20 +1,24 @@ -import { isLatest } from '../services/npmjs'; -import apiUrls from '../../config/services'; -import { spawn } from 'child-process-promise'; +import { execSync } from 'child_process'; +import 'colors'; + import msg from '../user_messages'; import { version } from '../../package.json'; +import apiUrls from '../../config/services'; + +import cache from '../services/cache'; import confirm from '../services/confirmer'; -import * as cache from '../services/cache'; +import { isLatest } from '../services/npmjs'; import { spinify } from '../services/spinner'; -import 'colors'; async function confirmUpdate() { - if (await cache.getValue('updateConfirmed') === false) { + if (cache.getValue('updateConfirmed') === false) { return false; } - const updateConfirmed = await confirm(msg.version.updateRequired()); - await cache.setValue('updateConfirmed', false, 24 * 3600); + const message = msg.version.updateRequired(); + const updateConfirmed = await confirm(message); + + cache.setValue('updateConfirmed', false, 24 * 3600); return updateConfirmed; } @@ -32,19 +36,22 @@ export default async function () { return false; } + const options = { stdio: 'inherit' }; + try { - await spawn('npm', ['install', '-g', '@shoutem/cli'], { stdio: 'inherit' }); + execSync('npm install -g @shoutem/cli', options); } catch (err) { if (process.platform !== 'win32') { console.log('Current user does not have permissions to update shoutem CLI. Using sudo...'); - await spawn('sudo', ['npm', 'install', '-g', '@shoutem/cli'], { stdio: 'inherit' }); + execSync('sudo npm install -g @shoutem/cli', options); } else { throw err; } } console.log('Update complete'); - await spawn('shoutem', process.argv.filter((_, index) => index > 1), { stdio: 'inherit' }); + + execSync('shoutem', process.argv.slice(2), options); return true; } diff --git a/src/services/analytics.js b/src/services/analytics.js index 273286c..bb19bf1 100644 --- a/src/services/analytics.js +++ b/src/services/analytics.js @@ -1,26 +1,37 @@ -import * as cache from './cache'; import analytics from 'universal-analytics'; import uuid from 'uuid/v4'; import _ from 'lodash'; -import { getValue } from './cache-env'; + import { analyticsTrackingId } from '../../config/services'; -import * as logger from './logger'; +import envCache from './cache-env'; +import logger from './logger'; +import cache from './cache'; -async function getClientId() { - return await cache.get('ga-client-id', null, () => uuid()); +const reportData = { + commandName: null, + extensionCanonicalName: null, + appId: null, + argv: [], + reportSent: false, +}; + +function getClientId() { + return cache.get('ga-client-id', null, () => uuid()); } -async function getAnalyticsVisitor() { - const clientId = await getClientId(); +function getAnalyticsVisitor() { + const clientId = getClientId(); const visitor = analytics(analyticsTrackingId, clientId); reportData.clientId = clientId; - const { email } = await getValue('developer') || {}; + const { email } = envCache.getValue('developer') || {}; + if (email) { visitor.set('userId', email); reportData.userId = email; } + visitor.set('isDeveloper', true); reportData.isDeveloper = true; @@ -28,9 +39,9 @@ async function getAnalyticsVisitor() { } async function reportEvent({ category, action, label }) { - const visitor = await getAnalyticsVisitor(); + const visitor = getAnalyticsVisitor(); - await visitor.event(category, action, label).send(err => { + await visitor.event(category, action, label).send((err) => { if (err) { console.error(err); } else { @@ -40,7 +51,7 @@ async function reportEvent({ category, action, label }) { label, clientId: reportData.clientId, userId: reportData.userId, - isDeveloper: reportData.isDeveloper + isDeveloper: reportData.isDeveloper, }); } }); @@ -50,19 +61,27 @@ async function reportCliCommand(commandName, canonicalNameOrAppId) { await reportEvent({ category: 'CLI', action: commandName, - label: canonicalNameOrAppId + label: canonicalNameOrAppId, }); } -const reportData = { - commandName: null, - extensionCanonicalName: null, - appId: null, - argv: [], - reportSent: false -}; +async function finishReport() { + const { + appId, + reportSent, + commandName, + extensionCanonicalName, + } = reportData; -export function setCommandName(name) { + const label = extensionCanonicalName || appId; + + if (commandName && !reportSent) { + await reportCliCommand(commandName, label); + reportData.reportSent = true; + } +} + +function setCommandName(name) { reportData.commandName = name; if (reportData.extensionCanonicalName || reportData.appId) { @@ -71,7 +90,7 @@ export function setCommandName(name) { } } -export function setAppId(appId) { +function setAppId(appId) { reportData.appId = appId; if (reportData.commandName) { @@ -80,7 +99,7 @@ export function setAppId(appId) { } } -export function setExtensionCanonicalName(name) { +function setExtensionCanonicalName(name) { reportData.extensionCanonicalName = name; if (reportData.commandName) { @@ -89,16 +108,13 @@ export function setExtensionCanonicalName(name) { } } -export function setArgv(argv) { +function setArgv(argv) { reportData.argv = _.drop(argv, 2); } -async function finishReport() { - const { commandName, extensionCanonicalName, appId, reportSent } = reportData; - const label = extensionCanonicalName || appId; - - if (commandName && !reportSent) { - await reportCliCommand(commandName, label); - reportData.reportSent = true; - } -} +export default { + setCommandName, + setAppId, + setExtensionCanonicalName, + setArgv, +}; diff --git a/src/services/app-selector.js b/src/services/app-selector.js index a04b214..e5e6a26 100644 --- a/src/services/app-selector.js +++ b/src/services/app-selector.js @@ -1,11 +1,12 @@ +import { prompt } from 'inquirer'; + import { getLatestApps } from '../clients/legacy-service'; import { spinify } from './spinner'; -import { prompt } from 'inquirer'; -import * as logger from './logger'; -import * as cache from './cache-env'; +import logger from './logger'; + +export default async function (_apps = null) { + const apps = _apps || await spinify(getLatestApps(), 'Fetching applications'); -export default async function(apps = null) { - apps = apps || await spinify(getLatestApps(), 'Fetching applications'); logger.info('appSelector', apps); return (await prompt({ @@ -14,8 +15,8 @@ export default async function(apps = null) { message: 'Select your app', choices: apps.map(app => ({ name: `${app.name} (${app.id})`, - value: app.id + value: app.id, })), - pageSize: 20 + pageSize: 20, })).appId; } diff --git a/src/services/cache-env.js b/src/services/cache-env.js index 59268d5..ad3c98b 100644 --- a/src/services/cache-env.js +++ b/src/services/cache-env.js @@ -1,10 +1,15 @@ -import * as cache from './cache'; +import cache from './cache'; import { getHostEnvName } from '../clients/server-env'; -export async function getValue(key) { - return await cache.getValue(`${getHostEnvName()}.${key}`) +function getValue(key) { + return cache.getValue(`${getHostEnvName()}.${key}`); } -export async function setValue(key, value, expirationSeconds) { - return await cache.setValue(`${getHostEnvName()}.${key}`, value, expirationSeconds); +function setValue(key, value, expirationSeconds) { + return cache.setValue(`${getHostEnvName()}.${key}`, value, expirationSeconds); } + +export default { + getValue, + setValue, +}; diff --git a/src/services/cache.js b/src/services/cache.js index 4295a52..f9f3472 100644 --- a/src/services/cache.js +++ b/src/services/cache.js @@ -1,31 +1,42 @@ import path from 'path'; -import mkdirp from 'mkdirp-promise' +import fs from 'fs-extra'; import { readJsonFile, writeJsonFile } from './data'; import { getLocalStoragePath } from '../clients/cli-paths'; -async function getCacheFilePath(key) { - const cacheDir = path.join(await getLocalStoragePath(), 'cache'); - await mkdirp(cacheDir); - return path.join(cacheDir, encodeURIComponent(typeof key === 'string' ? key : JSON.stringify(key))); +function getCacheFilePath(_key) { + const cacheDir = path.join(getLocalStoragePath(), 'cache'); + const key = (typeof _key === 'string') ? _key : JSON.stringify(_key); + + fs.ensureDirSync(cacheDir); + + return path.join(cacheDir, encodeURIComponent(key)); } -export async function getValue(key) { - const cached = await readJsonFile(await getCacheFilePath(key)) || {}; +function getValue(key) { + const cached = readJsonFile(getCacheFilePath(key)) || {}; + if (!cached.expiration || cached.expiration > new Date().getTime()) { return cached.value; - } else { - return null; } + + return null; } -export async function setValue(key, value, expirationSeconds) { - const expiration = expirationSeconds ? new Date().getTime() + expirationSeconds * 1000 : null; +function setValue(key, value, expirationSeconds) { + const expiration = expirationSeconds ? (new Date().getTime() + (expirationSeconds * 1000)) : null; + const filePath = getCacheFilePath(key); - await writeJsonFile({ expiration, value }, await getCacheFilePath(key)); + writeJsonFile(filePath, { expiration, value }); return value; } -export async function get(key, expirationSeconds, func) { - return await getValue(key) || setValue(key, await func(), expirationSeconds); +async function get(key, expirationSeconds, func) { + return getValue(key) || setValue(key, await func(), expirationSeconds); } + +export default { + getValue, + setValue, + get, +}; diff --git a/src/services/data.js b/src/services/data.js index c8d2b57..032cd06 100644 --- a/src/services/data.js +++ b/src/services/data.js @@ -1,8 +1,8 @@ -import fs from "fs-extra"; +import fs from 'fs-extra'; -export async function readJsonFile(filePath) { +export function readJsonFile(filePath) { try { - return JSON.parse(await fs.readFile(filePath, 'utf8')); + return fs.readJsonSync(filePath); } catch (err) { if (err.code === 'ENOENT') { return null; @@ -12,12 +12,15 @@ export async function readJsonFile(filePath) { } } -export async function writeJsonFile(json, filePath) { - const str = stringify(json); - await fs.writeFile(filePath, str, 'utf8'); - return str; -} - export function stringify(json) { return `${JSON.stringify(json, null, 2)}\n`; } + +export function writeJsonFile(filePath, json) { + try { + fs.writeJsonSync(filePath, json); + } catch (err) { + err.message = `Could not write to file ${filePath}\n${err.message}`; + throw err; + } +} diff --git a/src/services/decompress.js b/src/services/decompress.js index fcd58a9..917d7f1 100644 --- a/src/services/decompress.js +++ b/src/services/decompress.js @@ -3,12 +3,12 @@ import fs from 'fs-extra'; import decompress from 'decompress'; import { execSync } from 'child_process'; import downloadCached from 'download-cached'; -import commandExists from 'command-exists'; import extractZip from 'extract-zip'; import request from 'request'; import isGzip from 'is-gzip'; import tar from 'tar'; +import commandExists from './command-exists'; import { pipeDownloadToFile } from './download'; import getHomeDir from '../home-dir'; @@ -105,15 +105,6 @@ export async function decompressZipFromUrl(url, destination, options = {}) { } } -export async function decompressFromUrl(url, destination, options = {}) { - const fileName = options.fileName || url.split('/').pop(); - const filePath = path.join(destination, fileName); - - await pipeDownloadToFile(url, destination, options); - - return decompressFile(filePath, destination, options); -} - export async function decompressFile(filePath, destination, options = {}) { const { stripFirstDir = true, deleteArchiveWhenDone = false } = options; @@ -132,6 +123,15 @@ export async function decompressFile(filePath, destination, options = {}) { Promise.resolve(destination); } +export async function decompressFromUrl(url, destination, options = {}) { + const fileName = options.fileName || url.split('/').pop(); + const filePath = path.join(destination, fileName); + + await pipeDownloadToFile(url, destination, options); + + return decompressFile(filePath, destination, options); +} + export async function decompressFromUrlLegacy(url, destination, options) { if (!options.useCache) { await download.clear(url); diff --git a/src/services/error-handler.js b/src/services/error-handler.js index 83f7e30..e6dbcdb 100644 --- a/src/services/error-handler.js +++ b/src/services/error-handler.js @@ -1,9 +1,10 @@ import 'colors'; +import 'exit-code'; import _ from 'lodash'; import stringify from 'json-stringify-safe'; -import * as cache from './cache-env'; -import * as spinner from './spinner'; -import 'exit-code'; + +import cache from './cache-env'; +import { stopAllSpinners } from './spinner'; function getJsonApiErrorMessage(errors) { const generalDetail = _.upperFirst(_.get(errors, '[0].detail') || _.get(errors, '[0].title')); @@ -33,47 +34,53 @@ export function getErrorMessage(err) { return getJsonApiErrorMessage(err.body.errors); } - if (typeof(_.get(err, 'response.body')) === 'string') { + const msg = 'Unrecognized error. Run `shoutem last-error` for more additional details'; + + if (typeof _.get(err, 'response.body') === 'string') { try { const body = JSON.parse(_.get(err, 'response.body')); + if (body.errors) { return getJsonApiErrorMessage(body.errors); } - } catch (err){ + } catch (err) { + return `${msg}: ${err}`; } } - - return 'Unrecognized error. Run `shoutem last-error` for more additional details' + return msg; } let reportInfoPrinted = false; -export async function handleError(err) { +export function handleError(err) { try { if (err) { process.exitCode = err.code || -1; } - spinner.stopAll(); - console.error(getErrorMessage(err).red.bold); - - const errorJson = JSON.parse(stringify(err)); - errorJson.stack = (err || {}).stack; - errorJson.message = (err || {}).message; - await cache.setValue('last-error', errorJson); - if (!reportInfoPrinted) { - console.error(`\nUse ${'shoutem last-error'.cyan} for more info`); - reportInfoPrinted = true; - } + + stopAllSpinners(); + console.error(getErrorMessage(err).red.bold); + + const errorJson = JSON.parse(stringify(err)); + errorJson.stack = (err || {}).stack; + errorJson.message = (err || {}).message; + + cache.setValue('last-error', errorJson); + + if (!reportInfoPrinted) { + console.error(`\nUse ${'shoutem last-error'.cyan} for more info`); + reportInfoPrinted = true; + } } catch (err) { - console.log(err); + console.log(err); } } -export async function executeAndHandleError(func) { +export async function executeAndHandleError(func, ...funcArgs) { try { - await func(); + await func(...funcArgs); } catch (err) { - await handleError(err); + handleError(err); } } diff --git a/src/services/ext-js-generator.js b/src/services/ext-js-generator.js index 7240883..806d12f 100644 --- a/src/services/ext-js-generator.js +++ b/src/services/ext-js-generator.js @@ -4,6 +4,6 @@ import { instantiateTemplatePath } from './template'; * Generate app/extension.js and server/extension.js files within the extension. * This file is used to export extension's themes and screens. */ -export async function generateExtensionJs(extensionPath) { - return await instantiateTemplatePath('extension-js', extensionPath, {}, { overwrite: () => true }); +export function generateExtensionJs(extensionPath) { + return instantiateTemplatePath('extension-js', extensionPath, {}, { overwrite: () => true }); } diff --git a/src/services/extension-template.js b/src/services/extension-template.js index 357ec00..8ffbc06 100644 --- a/src/services/extension-template.js +++ b/src/services/extension-template.js @@ -1,15 +1,17 @@ -import {loadExtensionJson} from "./extension"; -import * as template from "./template"; +import { loadExtensionJson } from './extension'; +import { instantiateTemplatePath } from './template'; +// eslint-disable-next-line import/prefer-default-export export async function instantiateExtensionTemplate(localTemplatePath, context, opts) { if (!context.extJson && context.extensionPath) { - context.extJson = await loadExtensionJson(context.extensionPath); + // eslint-disable-next-line no-param-reassign + context.extJson = loadExtensionJson(context.extensionPath); } if (!context.extensionPath) { throw new Error(`Missing extension path for extension-template ${localTemplatePath}`); } - await template.instantiateTemplatePath(localTemplatePath, context.extensionPath, context, opts); - return await template.instantiateTemplatePath('extension-js', context.extensionPath, context, opts); + await instantiateTemplatePath(localTemplatePath, context.extensionPath, context, opts); + return instantiateTemplatePath('extension-js', context.extensionPath, context, opts); } diff --git a/src/services/extension.js b/src/services/extension.js index 21f34e4..5abf2c1 100644 --- a/src/services/extension.js +++ b/src/services/extension.js @@ -1,7 +1,8 @@ import fs from 'fs'; import path from 'path'; -import * as analytics from './analytics'; -import {readJsonFile, writeJsonFile} from "./data"; + +import { readJsonFile, writeJsonFile } from './data'; +import analytics from './analytics'; export function getExtensionCanonicalName(devName, extName, extVersion) { const canonicalName = `${devName}.${extName}-${extVersion}`; @@ -48,32 +49,34 @@ export function ensureInExtensionDir() { } export function loadExtensionJsonCallback(callback) { - const root = ensureInExtensionDir(); - - readJsonFile(path.join(root, 'extension.json')) - .then(data => callback(null, data)) - .catch(err => callback(err)); + try { + const extJson = loadExtensionJson(); + callback(null, extJson); + } catch (err) { + callback(err, extJson); + } } /** * Persist extension.json file to extension root directory */ export function saveExtensionJsonCallback(extJson, callback) { - const root = ensureInExtensionDir(); - fs.writeFile(path.join(root, 'extension.json'), - `${JSON.stringify(extJson, null, 2)}\n`, - 'utf8', - err => callback(err, extJson)); + try{ + saveExtensionJson(extJson); + callback(null, extJson); + } catch (err) { + callback(err, extJson); + } } -export function extensionJsonPath(rootPath) { +export function extensionJsonPath(rootPath = ensureInExtensionDir()) { return path.join(rootPath, 'extension.json'); } -export async function loadExtensionJson(rootPath = ensureInExtensionDir()) { - return await readJsonFile(extensionJsonPath(rootPath)); +export function loadExtensionJson(rootPath = ensureInExtensionDir()) { + return readJsonFile(extensionJsonPath(rootPath)); } -export async function saveExtensionJson(json, rootPath = ensureInExtensionDir()){ - return await writeJsonFile(json, extensionJsonPath(rootPath)); +export function saveExtensionJson(json, rootPath = ensureInExtensionDir()) { + return writeJsonFile(extensionJsonPath(rootPath), json); } diff --git a/src/services/extlint/index.js b/src/services/extlint/index.js index ca212cb..8cd8e8d 100644 --- a/src/services/extlint/index.js +++ b/src/services/extlint/index.js @@ -1,7 +1,7 @@ import path from 'path'; -import * as npm from '../npm'; +import npm from '../npm'; -export default async function (extPath) { +export default function (extPath) { const args = [ path.join(extPath, '**/*.js'), path.join(extPath, '**/*.jsx'), @@ -10,5 +10,5 @@ export default async function (extPath) { '--ignore-pattern', 'node_modules' ]; - return await npm.run(__dirname, 'extlint', args, ['--silent']); + return npm.run(__dirname, 'extlint', args, ['--silent']); } diff --git a/src/services/linker.js b/src/services/linker.js index 1b5246d..27a2e26 100644 --- a/src/services/linker.js +++ b/src/services/linker.js @@ -1,53 +1,52 @@ import path from 'path'; -import Promise from 'bluebird'; import _ from 'lodash'; -import { pathExists } from 'fs-extra'; -import * as cache from './cache'; +import fs from 'fs-extra'; +import cache from './cache'; -export async function getLinkedDirectories() { - const allDirectories = await cache.getValue('linked-extensions') || []; - const existingDirectories = await Promise.filter(allDirectories, pathExists); +export function getLinkedDirectories() { + const allDirectories = cache.getValue('linked-extensions') || []; + const existingDirectories = allDirectories.filter(fs.existsSync); if (!_.isEqual(allDirectories, existingDirectories)) { - await setLinkedDirectories(existingDirectories); + setLinkedDirectories(existingDirectories); } return existingDirectories; } -export async function setLinkedDirectories(dirs) { - await cache.setValue('linked-extensions', dirs); +export function setLinkedDirectories(dirs) { + cache.setValue('linked-extensions', dirs); } -export async function linkExtension(extensionDir) { +export function linkExtension(extensionDir) { const fullPath = path.resolve(path.join(extensionDir, 'app')); - if (!await pathExists(path.join(fullPath, 'package.json'))) { + if (!fs.existsSync(path.join(fullPath, 'package.json'))) { throw new Error('Given path does not contain an extension'); } - return await linkDirectory(fullPath); + return linkDirectory(fullPath); } -export async function linkDirectory(dir) { +export function linkDirectory(dir) { const fullPath = path.resolve(dir); - const dirs = await getLinkedDirectories(); + const dirs = getLinkedDirectories(); if (dirs.indexOf(fullPath) >= 0) { return false; } dirs.push(fullPath); - await setLinkedDirectories(dirs); + setLinkedDirectories(dirs); return true; } -export async function unlinkDirectory(dir) { +export function unlinkDirectory(dir) { dir = path.resolve(dir); - const linked = await getLinkedDirectories(); + const linked = getLinkedDirectories(); _.pull(linked, dir, path.join(dir, 'app')); - await setLinkedDirectories(linked); + setLinkedDirectories(linked); } diff --git a/src/services/logger.js b/src/services/logger.js index f66c26c..90c155a 100644 --- a/src/services/logger.js +++ b/src/services/logger.js @@ -1,11 +1,16 @@ import _ from 'lodash'; -export function info(msg, object) { +export function isVerbose() { + return _.includes(process.argv, '--verbose'); +} + +function info(msg, object) { if (isVerbose()) { console.log(msg, object); } } -export function isVerbose() { - return _.includes(process.argv, '--verbose'); -} +export default { + info, + isVerbose, +}; diff --git a/src/services/node.js b/src/services/node.js index ec6968d..811474b 100644 --- a/src/services/node.js +++ b/src/services/node.js @@ -1,10 +1,11 @@ import _ from 'lodash'; -import * as npm from "./npm"; import { exec } from 'child-process-promise' -export async function containsBuildTask(dir) { +import npm from './npm'; + +export function containsBuildTask(dir) { try { - const pkgJson = await npm.getPackageJson(dir); + const pkgJson = npm.getPackageJson(dir); return !!_.get(pkgJson, 'scripts.build'); } catch (err) { return false; @@ -12,7 +13,7 @@ export async function containsBuildTask(dir) { } export async function buildNodeProject(dir) { - if (!await containsBuildTask(dir)) { + if (!containsBuildTask(dir)) { return false; } @@ -22,7 +23,7 @@ export async function buildNodeProject(dir) { } catch (err) { console.log(err.stdout); console.error(err.stderr); - err.message = `${err.message + '\n'}Build failed for ${dir} directory.`; + err.message = `${err.message}\nBuild failed for ${dir} directory.`; throw err; } return true; diff --git a/src/services/npm.js b/src/services/npm.js index 0320178..f5e0afc 100644 --- a/src/services/npm.js +++ b/src/services/npm.js @@ -1,56 +1,66 @@ import path from 'path'; import Promise from 'bluebird'; -import { readJson } from 'fs-extra'; import { spawn } from 'child-process-promise'; -import {writeJsonFile} from "./data"; + +import { writeJsonFile, readJsonFile } from './data'; + const linkLocal = Promise.promisify(require('linklocal')); -export async function install(cwd = process.cwd()) { - await spawn('npm', ['install'], { +function install(cwd = process.cwd()) { + return spawn('npm', ['install'], { cwd, stdio: 'inherit', shell: true, - env: { ...process.env, FORCE_COLOR: true } + env: { ...process.env, FORCE_COLOR: true }, }); } -export async function run(cwd, task, taskArgs = [], npmOptions = []) { +function run(cwd, task, taskArgs = [], npmOptions = []) { const opts = { cwd, stdio: ['ignore', 'inherit', 'inherit'], - shell: true + shell: true, }; const spawned = taskArgs.length ? spawn('npm', ['run', task, ...npmOptions, '--', ...taskArgs], opts) : spawn('npm', ['run', task, ...npmOptions], opts); - return await spawned; + return spawned; } -export async function getPackageJson(npmProjectPath) { - return await readJson(path.join(npmProjectPath, 'package.json')); +function getPackageJson(npmProjectPath) { + return readJsonFile(path.join(npmProjectPath, 'package.json')); } -export async function savePackageJson(npmProjectPath, pkgJson) { - return await writeJsonFile(pkgJson, path.join(npmProjectPath, 'package.json')); +function savePackageJson(npmProjectPath, pkgJson) { + return writeJsonFile(path.join(npmProjectPath, 'package.json'), pkgJson); } -export async function addLocalDependency(npmProjectPath, npmModulePath) { - const { name } = await getPackageJson(npmModulePath); - const packageJson = await getPackageJson(npmProjectPath); +function addLocalDependency(npmProjectPath, npmModulePath) { + const { name } = getPackageJson(npmModulePath); + const packageJson = getPackageJson(npmProjectPath); - const dependencyValue = 'file:' + path.relative(npmProjectPath, npmModulePath); + const dependencyValue = `file:${path.relative(npmProjectPath, npmModulePath)}`; - await savePackageJson(npmProjectPath, { + savePackageJson(npmProjectPath, { ...packageJson, dependencies: { ...packageJson.dependencies, - [name]: dependencyValue - } + [name]: dependencyValue, + }, }); } -export async function linkLocalDependencies(npmProjectPath) { - return await linkLocal(npmProjectPath); +function linkLocalDependencies(npmProjectPath) { + return linkLocal(npmProjectPath); } + +export default { + run, + install, + getPackageJson, + savePackageJson, + addLocalDependency, + linkLocalDependencies, +}; diff --git a/src/services/npmjs.js b/src/services/npmjs.js index 644cfe4..5769374 100644 --- a/src/services/npmjs.js +++ b/src/services/npmjs.js @@ -1,9 +1,9 @@ import request from 'request-promise-native'; import semver from 'semver'; -import * as cache from './cache'; +import cache from './cache'; -export async function getRepoData(npmUrl) { - return await request({ uri: npmUrl, json: true }); +export function getRepoData(npmUrl) { + return request({ uri: npmUrl, json: true }); } async function getNpmjsVersion(npmUrl, tag) { @@ -12,8 +12,8 @@ async function getNpmjsVersion(npmUrl, tag) { return repo['dist-tags'][tag]; } -export async function getVersion(npmUrl, tag) { - return await cache.get({ npmUrl, tag }, 3600 * 48, () => getNpmjsVersion(npmUrl, tag)); +export function getVersion(npmUrl, tag) { + return cache.get({ npmUrl, tag }, 3600 * 48, () => getNpmjsVersion(npmUrl, tag)); } export async function isLatest(npmUrl, currentVersion) { diff --git a/src/services/packer.js b/src/services/packer.js index c37083a..dba1a4c 100644 --- a/src/services/packer.js +++ b/src/services/packer.js @@ -1,7 +1,5 @@ import tar from 'tar'; import path from 'path'; -import zlib from 'zlib'; -import move from 'glob-move'; import tmp from 'tmp-promise'; import Promise from 'bluebird'; import { exec } from 'child-process-promise'; @@ -12,7 +10,7 @@ import { spinify } from './spinner'; import { buildNodeProject } from './node'; import { loadExtensionJson } from './extension'; import { readJsonFile, writeJsonFile } from './data'; -import { getPackageJson, savePackageJson } from './npm'; +import npm from './npm'; import { ensureUserIsLoggedIn } from '../commands/login'; @@ -32,7 +30,7 @@ async function npmPack(dir, destinationDir) { const timestamp = (new Date()).getTime(); packageJson.version = `${packageJson.version}-build${timestamp}`; - await writeJsonFile(packageJson, packageJsonPath); + writeJsonFile(packageJson, packageJsonPath); const { stdout } = await exec('npm pack', { cwd: dir }); const packageFilename = stdout.replace(/\n$/, ''); @@ -41,7 +39,7 @@ async function npmPack(dir, destinationDir) { await mv(packagePath, resultFilename); if (originalFileContent !== null) { - await fs.writeFile(packageJsonPath, originalFileContent, 'utf8'); + fs.writeFileSync(packageJsonPath, originalFileContent, 'utf8'); } } @@ -50,10 +48,10 @@ function hasExtensionsJson(dir) { } async function offerDevNameSync(extensionDir) { - const { name: extensionName } = await loadExtensionJson(extensionDir); + const { name: extensionName } = loadExtensionJson(extensionDir); - const appPackageJson = await getPackageJson(path.join(extensionDir, 'app')); - const serverPackageJson = await getPackageJson(path.join(extensionDir, 'server')); + const appPackageJson = npm.getPackageJson(path.join(extensionDir, 'app')); + const serverPackageJson = npm.getPackageJson(path.join(extensionDir, 'server')); const { name: appModuleName } = appPackageJson; const { name: serverModuleName } = serverPackageJson; @@ -71,8 +69,8 @@ async function offerDevNameSync(extensionDir) { appPackageJson.name = targetModuleName; serverPackageJson.name = targetModuleName; - await savePackageJson(path.join(extensionDir, 'app'), appPackageJson); - await savePackageJson(path.join(extensionDir, 'server'), serverPackageJson); + await npm.savePackageJson(path.join(extensionDir, 'app'), appPackageJson); + await npm.savePackageJson(path.join(extensionDir, 'server'), serverPackageJson); } export default async function shoutemPack(dir, options) { @@ -98,7 +96,7 @@ export default async function shoutemPack(dir, options) { await spinify(buildNodeProject(path.join(dir, 'app')), 'Building the app part...', 'OK'); } - return await spinify(async () => { + return spinify(async () => { for (const partDir of dirsToPack) { await npmPack(partDir, packageDir); } diff --git a/src/services/platform-selector.js b/src/services/platform-selector.js index fabac9f..bb85ee1 100644 --- a/src/services/platform-selector.js +++ b/src/services/platform-selector.js @@ -1,11 +1,11 @@ import _ from 'lodash'; import { prompt } from 'inquirer'; -import * as logger from './logger'; +import logger from './logger'; import { spinify } from './spinner'; import { getAvailablePlatforms } from '../commands/platform'; -export default async function (platforms = null) { - platforms = platforms || await spinify(getAvailablePlatforms(), 'Fetching platforms'); +export default async function (_platforms = null) { + const platforms = _platforms || await spinify(getAvailablePlatforms(), 'Fetching platforms'); logger.info('platformSelector', platforms); return (await prompt({ diff --git a/src/services/platform.js b/src/services/platform.js index 6bba5c4..e7f7b8e 100644 --- a/src/services/platform.js +++ b/src/services/platform.js @@ -1,34 +1,34 @@ import url from 'url'; import path from 'path'; import _ from 'lodash'; +import fs from 'fs-extra'; import replace from 'replace-in-file'; -import * as appManager from '../clients/app-manager'; -import * as extensionManager from '../clients/extension-manager'; -import * as authService from '../clients/auth-service'; -import { decompressFromUrl } from './decompress'; + import cliUrls from '../../config/services'; -import { writeJsonFile} from './data'; -import * as npm from './npm'; -import { ensureYarnInstalled } from './yarn'; -import * as reactNative from './react-native'; -import * as analytics from './analytics'; -import { pathExists, readJson, readFile, writeFile } from 'fs-extra'; +import { getApplicationPlatform } from '../clients/app-manager'; +import { getPlatform } from '../clients/extension-manager'; +import authService from '../clients/auth-service'; +import { readJsonFile, writeJsonFile } from './data'; +import { decompressFromUrl } from './decompress'; import commandExists from './command-exists'; -import {readJsonFile} from "./data"; +import { ensureYarnInstalled } from './yarn'; +import { ensureReactNativeInstalled } from './react-native'; +import analytics from './analytics'; +import npm from './npm'; -async function isPlatformDirectory(dir) { - const { name } = await readJsonFile(path.join(dir, 'package.json')) || {}; +function isPlatformDirectory(dir) { + const { name } = readJsonFile(path.join(dir, 'package.json')) || {}; // platform package was renamed with Platform release v1.1.10 return name === '@shoutem/mobile-app' || name === '@shoutem/platform'; } -export async function getPlatformRootDir(dir = process.cwd(), { shouldThrow = true } = {}) { - if (await isPlatformDirectory(dir)) { +export function getPlatformRootDir(dir = process.cwd(), { shouldThrow = true } = {}) { + if (isPlatformDirectory(dir)) { return dir; } - const parentDir = path.join(dir, '..'); + const parentDir = path.resolve(dir, '..'); if (parentDir === dir) { if (shouldThrow) { @@ -37,19 +37,21 @@ export async function getPlatformRootDir(dir = process.cwd(), { shouldThrow = tr return null; } } - return await getPlatformRootDir(parentDir, { shouldThrow }); + + return getPlatformRootDir(parentDir, { shouldThrow }); } -export async function getPlatformExtensionsDir(dir = null) { - return path.join(dir || await getPlatformRootDir(), 'extensions'); +export function getPlatformExtensionsDir(dir = null) { + return path.join(dir || getPlatformRootDir(), 'extensions'); } export async function createPlatformConfig(platformDir, opts) { - const configTemplate = await readJson(path.join(platformDir, 'config.template.json')); - + const configTemplate = readJsonFile(path.join(platformDir, 'config.template.json')); let authorization; + try { - authorization = await authService.createAppAccessToken(opts.appId, await authService.getRefreshToken()); + const refreshToken = await authService.getRefreshToken(); + authorization = await authService.createAppAccessToken(opts.appId, refreshToken); } catch (err) { if (err.code === 401 || err.code === 403) { err.message = 'Not authorized to create application token. You must log in again using `shoutem login` command.'; @@ -57,33 +59,37 @@ export async function createPlatformConfig(platformDir, opts) { throw err; } + const serverApiEndpoint = url.parse(cliUrls.appManager).hostname; + const legacyApiEndpoint = url.parse(cliUrls.legacyService).hostname; + return { ...configTemplate, ...opts, - serverApiEndpoint: url.parse(cliUrls.appManager).hostname, - legacyApiEndpoint: url.parse(cliUrls.legacyService).hostname, authorization, - configurationFilePath: 'config.json' + serverApiEndpoint, + legacyApiEndpoint, + configurationFilePath: 'config.json', }; } -export async function getPlatformConfig(platformDir = null) { - return await readJson(path.join(platformDir || await getPlatformRootDir(), 'config.json')); +export function getPlatformConfig(platformDir = null) { + return readJsonFile(path.join(platformDir || getPlatformRootDir(), 'config.json')); } -export async function setPlatformConfig(platformDir, mobileConfig) { - await writeJsonFile(mobileConfig, path.join(platformDir, 'config.json')); +export function setPlatformConfig(platformDir, mobileConfig) { + writeJsonFile(path.join(platformDir, 'config.json'), mobileConfig); } export async function configurePlatform(platformDir) { await ensureYarnInstalled(); - await reactNative.ensureInstalled(); + await ensureReactNativeInstalled(); + if (process.platform === 'darwin' && !await commandExists('pod')) { throw new Error('Missing `pods` command. Please install cocoapods and run `shoutem configure` in the ' + `${platformDir} directory`); } - if (!await getPlatformConfig(platformDir)) { + if (!getPlatformConfig(platformDir)) { throw new Error('Missing config.json file'); } @@ -91,71 +97,70 @@ export async function configurePlatform(platformDir) { await npm.run(platformDir, 'configure'); } -export async function fixPlatform(platformDir, appId) { +export function fixPlatform(platformDir, appId) { const appBuilderPath = path.join(platformDir, 'scripts', 'classes', 'app-builder.js'); if (process.platform === 'win32') { try { - await replace({ + replace.sync({ files: appBuilderPath, from: './gradlew', - to: 'gradlew' + to: 'gradlew', }); } catch (err) { console.log('WARN: Could not rename ./gradle to gradle'); } try { - await replace({ + replace.sync({ files: appBuilderPath, from: "const apkPath = path.join('android', 'app', 'build', 'outputs', 'apk');", - to: `const apkPath = path.join('c:/', '${appId}', 'tmp', 'ShoutemApp', 'app', 'outputs', 'apk');` + to: `const apkPath = path.join('c:/', '${appId}', 'tmp', 'ShoutemApp', 'app', 'outputs', 'apk');`, }); } catch (err) { console.log('WARN: Could not adapt client for c:\\tmp build directory'); } try { - await replace({ + replace.sync({ files: path.join(platformDir, 'android', 'build.gradle'), from: '// buildDir = "C:/tmp/', - to: `buildDir = "C:/tmp/${appId}/` - }) + to: `buildDir = "C:/tmp/${appId}/`, + }); } catch (err) { console.log('WARN: Could not set the tmp build directory for android app'); } } } +function pullPlatform(location, version, destination, options) { + const platformUrl = !!location ? location : `${cliUrls.mobileAppUrl}/archive/v${version}.tar.gz`; + return decompressFromUrl(platformUrl, destination, { ...options, strip: 1, useCache: options.useCache }); +} + export async function downloadApp(appId, destinationDir, options = {}) { analytics.setAppId(appId); const versionCheck = options.versionCheck || (() => {}); - const platformInstallationData = await appManager.getApplicationPlatform(appId, true); + const platformInstallationData = await getApplicationPlatform(appId, true); const { platform: platformId, mobileAppVersion } = platformInstallationData; - const platform = await extensionManager.getPlatform(platformId); + const platform = await getPlatform(platformId); await versionCheck(mobileAppVersion); await pullPlatform(platform.location, mobileAppVersion, destinationDir, options); - if (!await pathExists(destinationDir)) { + if (!fs.pathExistsSync(destinationDir)) { throw new Error('Platform code could not be downloaded. Make sure that platform is setup correctly.'); } } -function pullPlatform(location, version, destination, options) { - const url = !!location ? location : `${cliUrls.mobileAppUrl}/archive/v${version}.tar.gz`; - return decompressFromUrl(url, destination, { ...options, strip: 1, useCache: options.useCache }); -} - export async function addToExtensionsJs(platformDir, extensionPath) { - const { name } = await npm.getPackageJson(path.join(extensionPath, 'app')); + const { name } = npm.getPackageJson(path.join(extensionPath, 'app')); const extensionsJsPath = path.join(platformDir, 'extensions.js'); - - let extensionsJsData = await readFile(extensionsJsPath, 'utf8'); + let extensionsJsData = fs.readFileSync(extensionsJsPath, 'utf8'); if (_.includes(extensionsJsData, `'${name}'`)) { return; @@ -164,11 +169,11 @@ export async function addToExtensionsJs(platformDir, extensionPath) { extensionsJsData = extensionsJsData.replace('};', `'${name}': require('${name}'),`); extensionsJsData += ' };\n'; - await writeFile(extensionsJsPath, extensionsJsData); + fs.writeFileSync(extensionsJsPath, extensionsJsData); } export async function linkLocalExtension(platformDir, extensionPath) { - await npm.addLocalDependency(platformDir, path.join(extensionPath, 'app')); + npm.addLocalDependency(platformDir, path.join(extensionPath, 'app')); await npm.linkLocalDependencies(platformDir); await npm.install(platformDir); } diff --git a/src/services/react-native.js b/src/services/react-native.js index fba383d..70c7714 100644 --- a/src/services/react-native.js +++ b/src/services/react-native.js @@ -4,10 +4,10 @@ import msg from '../user_messages'; import commandExists from './command-exists'; import streamMatcher from './stream-matcher'; -export async function ensureInstalled() { +export async function ensureReactNativeInstalled() { try { if (!await commandExists('react-native')) { - await spawn('npm', ['install', '-g', 'react-native-cli'], { stdio: 'inherit', shell: true }) + await spawn('npm', ['install', '-g', 'react-native-cli'], { stdio: 'inherit', shell: true }); } } catch (err) { throw new Error(msg.reactNative.missing()); @@ -16,12 +16,11 @@ export async function ensureInstalled() { export async function startPackager(cwd) { const spawned = spawn('react-native', ['start'], { - stdio: ['inherit', 'pipe', 'inherit'], - cwd, - shell: true, - env: { ...process.env, FORCE_COLOR: true } - } - ); + stdio: ['inherit', 'pipe', 'inherit'], + cwd, + shell: true, + env: { ...process.env, FORCE_COLOR: true }, + }); const { childProcess } = spawned; @@ -31,7 +30,7 @@ export async function startPackager(cwd) { // rejection if react-packager fails before becoming 'ready' await Promise.race([ streamMatcher(childProcess.stdout, 'Loading dependency graph, done'), - spawned + spawned, ]); return { packagerProcess: spawned }; diff --git a/src/services/shortcut.js b/src/services/shortcut.js index a996b6b..56c3579 100644 --- a/src/services/shortcut.js +++ b/src/services/shortcut.js @@ -2,7 +2,7 @@ import _ from 'lodash'; import decamelize from 'decamelize'; import { prompt } from 'inquirer'; import getOrSet from 'lodash-get-or-set'; -import {isVariableName} from "./cli-parsing"; +import { isVariableName } from './cli-parsing'; export function addShortcut(extJson, { name, title, description, screenName, pagesNames }) { const shortcut = { name, title, description }; @@ -68,8 +68,8 @@ function createShortcutCreationQuestions({ shortcuts, parentName, screens, defau }]; } -export async function askShortcutCreationQuestions(opts) { - return await prompt(createShortcutCreationQuestions(opts)); +export function askShortcutCreationQuestions(opts) { + return prompt(createShortcutCreationQuestions(opts)); } export function addShortcutForScreen(extJson, screen, shortcut) { diff --git a/src/services/spinner.js b/src/services/spinner.js index bba560e..5d12683 100644 --- a/src/services/spinner.js +++ b/src/services/spinner.js @@ -1,4 +1,5 @@ -import { Spinner } from 'cli-spinner' +import { Spinner } from 'cli-spinner'; + import { isVerbose } from './logger'; let spinners = []; @@ -6,7 +7,7 @@ let spinners = []; export function startSpinner(msg) { if (isVerbose()) { console.log(msg); - return { stop(){} }; + return { stop() {} }; } const spinner = new Spinner(msg); @@ -16,7 +17,7 @@ export function startSpinner(msg) { return spinner; } -export function stopAll() { +export function stopAllSpinners() { spinners.forEach(s => s.stop(true)); spinners = []; } diff --git a/src/services/template.js b/src/services/template.js index 2de7ace..3a7d571 100644 --- a/src/services/template.js +++ b/src/services/template.js @@ -3,41 +3,47 @@ import Promise from 'bluebird'; import fs from 'fs-extra'; import path from 'path'; import Mustache from 'mustache'; -import { pathExists } from 'fs-extra'; const templatesDirectory = path.join(__dirname, '..', 'templates'); export function load(pathWithSlashes, templateContext) { const p = path.join(templatesDirectory, ...pathWithSlashes.split('/')); const template = fs.readFileSync(p, 'utf8'); + return Mustache.render(template, templateContext); } async function instantiateTemplatePathRec(localTemplatePath, destinationPath, context, opts) { if (localTemplatePath.endsWith('template-init.js')) { - return null; + return; } + + // eslint-disable-next-line no-param-reassign destinationPath = Mustache.render(destinationPath, context); const templatePath = path.join(templatesDirectory, localTemplatePath); - const templatePathState = await fs.lstat(templatePath); + const templatePathState = fs.lstatSync(templatePath); + getOrSet(context, 'diffLog', {}); if (templatePathState.isDirectory()) { - const files = await fs.readdir(templatePath); - await Promise.map(files, file => { + const files = fs.readdirSync(templatePath); + + await Promise.map(files, (file) => { const src = path.join(localTemplatePath, file); const dest = path.join(destinationPath, file); return instantiateTemplatePathRec(src, dest, context, opts); }); } else if (templatePathState.isFile()) { - const templateContent = await fs.readFile(templatePath, 'utf8'); - context.diffLog[destinationPath] = await Mustache.render(templateContent, context); + const templateContent = fs.readFileSync(templatePath, 'utf8'); + // eslint-disable-next-line no-param-reassign + context.diffLog[destinationPath] = Mustache.render(templateContent, context); } } function importName(modulePath, name, defaultValue) { try { + // eslint-disable-next-line import/no-dynamic-require return require(modulePath)[name] || defaultValue; } catch (err) { if (err.code === 'MODULE_NOT_FOUND') { @@ -48,15 +54,16 @@ function importName(modulePath, name, defaultValue) { } export async function instantiateTemplatePath(localTemplatePath, destinationPath, context, opts = {}) { + // eslint-disable-next-line no-param-reassign opts.overwrite = opts.overwrite || (() => false); - const postRunActions = getOrSet(context, 'postRunActions', []); const initPath = path.join(templatesDirectory, localTemplatePath, 'template-init'); + const postRunActions = getOrSet(context, 'postRunActions', []); const before = importName(initPath, 'before', () => {}); const after = importName(initPath, 'after', () => {}); - await before(context); + before(context); await instantiateTemplatePathRec(localTemplatePath, destinationPath, context, opts); await after(context); diff --git a/src/services/tunnel.js b/src/services/tunnel.js index d1d8204..b27f2f9 100644 --- a/src/services/tunnel.js +++ b/src/services/tunnel.js @@ -4,10 +4,15 @@ import ngrok from 'ngrok'; const ngrokConnect = Promise.promisify(ngrok.connect); const ngrokKill = Promise.promisify(ngrok.kill); -export async function start(localPort) { - return await ngrokConnect({ proto: 'http', addr: localPort }); +function start(localPort) { + return ngrokConnect({ proto: 'http', addr: localPort }); } -export async function stop() { - return await ngrokKill(); +function stop() { + return ngrokKill(); } + +export default { + start, + stop, +}; diff --git a/src/services/validation.js b/src/services/validation.js index fd49195..f8c969d 100644 --- a/src/services/validation.js +++ b/src/services/validation.js @@ -1,7 +1,6 @@ -import os from 'os'; import _ from 'lodash'; import semver from 'semver'; -import { getValue } from '../services/cache-env'; +import cache from '../services/cache-env'; export function isValidExtensionName(name) { return /^[a-z]+[a-z0-9\-]*$/.test(name); @@ -28,7 +27,8 @@ export async function validatePlatformArchive(archiveProvider) { throw new Error('platform.json \'version\' must be a valid semantic version'); } - // when publishing from local source, we pack with a correct root directory name, so no need to check that one + // when publishing from local source, we pack with a correct root directory name, + // so no need to check that one if (archiveProvider.getType() === 'remote') { const jsonPath = await archiveProvider.getPlatformJsonPath(); @@ -41,11 +41,11 @@ export async function validatePlatformArchive(archiveProvider) { throw new Error('platform.json \'mobileAppVersion\' must be a valid semantic version'); } - const developer = await getValue('developer'); + const developer = cache.getValue('developer'); const isCustomPlatform = developer.name !== 'shoutem'; const appetizeKey = _.get(platformJson, ['settings', 'appetizeKey']); if (!isCustomPlatform && _.isNil(appetizeKey)) { - throw new Error(`platform.json must contain settings.appetizeKey`); + throw new Error('platform.json must contain settings.appetizeKey'); } } diff --git a/src/services/yarn.js b/src/services/yarn.js index b35ed65..bdfee54 100644 --- a/src/services/yarn.js +++ b/src/services/yarn.js @@ -18,8 +18,8 @@ export async function run(cwd, task, taskArgs = null, stdio = 'inherit') { const opts = {cwd, stdio }; if (taskArgs) { - return await spawn('yarn', ['run', task, '--', ...taskArgs], opts); + return spawn('yarn', ['run', task, '--', ...taskArgs], opts); } else { - return await spawn('yarn', ['run', task], opts); + return spawn('yarn', ['run', task], opts); } } diff --git a/src/templates/extension-js/template-init.js b/src/templates/extension-js/template-init.js index ec8c043..c652036 100644 --- a/src/templates/extension-js/template-init.js +++ b/src/templates/extension-js/template-init.js @@ -1,6 +1,7 @@ import _ from 'lodash'; import decamelize from 'decamelize'; -import {isReactPage} from "../settings-page-react/template-init"; + +import { isReactPage } from "../settings-page-react/template-init"; function importStatements(names, path, directoriesNames = names) { return names.map((name, i) => `import ${name} from '${path}/${directoriesNames[i]}';`).join('\n'); @@ -14,7 +15,7 @@ function indentedNamesList(names) { * Generate app/extension.js file within the extension. * This file is used to export extension's themes and screens. */ -export async function before(context) { +export function before(context) { const { extJson } = context; const screensNamesList = _.map(extJson.screens, 'name'); diff --git a/src/templates/init/template-init.js b/src/templates/init/template-init.js index 6b2ea44..fe7eb44 100644 --- a/src/templates/init/template-init.js +++ b/src/templates/init/template-init.js @@ -1,7 +1,7 @@ import path from 'path'; import 'colors' -export async function before(context) { +export function before(context) { const { devName, extJson, extensionPath } = context; context.extensionPath = path.join(extensionPath, `${devName}.${extJson.name}`); } diff --git a/src/templates/screen/template-init.js b/src/templates/screen/template-init.js index c59b664..1379d3e 100644 --- a/src/templates/screen/template-init.js +++ b/src/templates/screen/template-init.js @@ -1,9 +1,10 @@ + import _ from 'lodash'; import getOrSet from 'lodash-get-or-set'; -import camelcase from 'uppercamelcase'; -import * as shortcut from '../../services/shortcut'; -export async function before(context) { +import { addShortcutForScreen } from '../../services/shortcut'; + +export function before(context) { const { extJson, name } = context; const screens = getOrSet(extJson, 'screens', []); @@ -20,6 +21,6 @@ export async function before(context) { }); if (context.newShortcut) { - shortcut.addShortcutForScreen(extJson, context, context.newShortcut); + addShortcutForScreen(extJson, context, context.newShortcut); } } diff --git a/src/templates/settings-page-html/template-init.js b/src/templates/settings-page-html/template-init.js index 71aa371..931a4d1 100644 --- a/src/templates/settings-page-html/template-init.js +++ b/src/templates/settings-page-html/template-init.js @@ -1,7 +1,7 @@ import _ from 'lodash'; -import getOrSet from 'lodash-get-or-set'; -import {instantiateExtensionTemplate} from "../../services/extension-template"; import decamelize from 'decamelize'; +import getOrSet from 'lodash-get-or-set'; +import { instantiateExtensionTemplate } from '../../services/extension-template'; function isHtmlPage({ type, path }) { return type === 'html' && !_.includes(path, 'server/build'); diff --git a/src/templates/settings-page-react-bin/server/.babelrc b/src/templates/settings-page-react-bin/server/.babelrc index b4e0857..20d0be1 100644 --- a/src/templates/settings-page-react-bin/server/.babelrc +++ b/src/templates/settings-page-react-bin/server/.babelrc @@ -1,6 +1,6 @@ { "presets": [ - ["es2015", { "modules": false }], + ["env", { "modules": false }], "react", "stage-0" ], diff --git a/src/templates/settings-page-react-bin/template-init.js b/src/templates/settings-page-react-bin/template-init.js index 715548e..fc1b591 100644 --- a/src/templates/settings-page-react-bin/template-init.js +++ b/src/templates/settings-page-react-bin/template-init.js @@ -1,71 +1,73 @@ import _ from 'lodash'; import path from 'path'; import getOrSet from 'lodash-get-or-set'; -import {stringify} from "../../services/data"; -import {getPackageJson, install} from "../../services/npm"; + +import { stringify } from '../../services/data'; +import npm from '../../services/npm'; const pkgJsonTemplate = { - "scripts": { - "clean": "rimraf ./build/*", - "build": "npm run clean && cross-env NODE_ENV=production webpack --config ./bin/webpack/webpack.config.js", - "dev-dashboard": "webpack-dashboard -c -- webpack-dev-server --config ./bin/webpack/webpack.config.js", - "dev": "webpack-dev-server --config ./bin/webpack/webpack.config.js" + scripts: { + clean: 'rimraf ./build/*', + build: 'npm run clean && cross-env NODE_ENV=production webpack --config ./bin/webpack/webpack.config.js', + 'dev-dashboard': 'webpack-dashboard -c -- webpack-dev-server --config ./bin/webpack/webpack.config.js', + dev: 'webpack-dev-server --config ./bin/webpack/webpack.config.js', }, - "devDependencies": { - "babel-cli": "^6.24.0", - "babel-core": "^6.24.0", - "babel-loader": "^6.4.1", - "babel-plugin-transform-react-jsx": "^6.23.0", - "babel-preset-es2015": "^6.24.0", - "babel-preset-react": "^6.23.0", - "babel-preset-stage-0": "^6.22.0", - "cross-env": "^4.0.0", - "css-loader": "^0.27.3", - "cssnano": "^3.10.0", - "extract-text-webpack-plugin": "^2.1.0", - "file-loader": "^0.10.1", - "html-webpack-plugin": "^2.28.0", - "node-sass": "^4.5.0", - "postcss-loader": "^1.3.3", - "rimraf": "^2.6.1", - "sass-loader": "^6.0.3", - "style-loader": "^0.14.1", - "url-loader": "^0.5.8", - "webpack": "^2.2.1", - "webpack-dashboard": "^0.3.0", - "webpack-dev-server": "^2.4.2" + devDependencies: { + 'babel-cli': '^6.24.0', + 'babel-core': '^6.24.0', + 'babel-loader': '^6.4.1', + 'babel-plugin-transform-react-jsx': '^6.23.0', + 'babel-preset-es2015': '^6.24.0', + 'babel-preset-react': '^6.23.0', + 'babel-preset-stage-0': '^6.22.0', + 'cross-env': '^4.0.0', + 'css-loader': '^0.27.3', + cssnano: '^3.10.0', + 'extract-text-webpack-plugin': '^2.1.0', + 'file-loader': '^0.10.1', + 'html-webpack-plugin': '^2.28.0', + 'node-sass': '^4.5.0', + 'postcss-loader': '^1.3.3', + rimraf: '^2.6.1', + 'sass-loader': '^6.0.3', + 'style-loader': '^0.14.1', + 'url-loader': '^0.5.8', + webpack: '^2.2.1', + 'webpack-dashboard': '^0.3.0', + 'webpack-dev-server': '^2.4.2', }, - "dependencies": { - "@shoutem/extension-sandbox": "^0.1.4", - "@shoutem/react-web-ui": "0.8.6", - "@shoutem/redux-api-sdk": "^1.1.0", - "@shoutem/redux-composers": "^0.1.5", - "@shoutem/redux-io": "^2.3.0", - "@shoutem/redux-sync-state-engine": "^0.0.2", - "es6-promise": "^4.1.1", - "fetch-everywhere": "^1.0.5", - "lodash": "^4.17.4", - "react": "^15.4.2", - "react-dom": "^15.4.2", - "react-redux": "^5.0.3", - "redux": "^3.6.0", - "redux-thunk": "^2.2.0", - "urijs": "^1.18.9" + dependencies: { + '@shoutem/extension-sandbox': '^0.1.4', + '@shoutem/react-web-ui': '0.8.6', + '@shoutem/redux-api-sdk': '^1.1.0', + '@shoutem/redux-composers': '^0.1.5', + '@shoutem/redux-io': '^2.3.0', + '@shoutem/redux-sync-state-engine': '^0.0.2', + 'es6-promise': '^4.1.1', + 'fetch-everywhere': '^1.0.5', + lodash: '^4.17.4', + react: '^15.4.2', + 'react-dom': '^15.4.2', + 'react-redux': '^5.0.3', + redux: '^3.6.0', + 'redux-thunk': '^2.2.0', + urijs: '^1.18.9', }, - "babel": { - "presets": [ - ["es2015", { "modules": false }], - "react", - "stage-0" + babel: { + presets: [ + ['es2015', { modules: false }], + 'react', + 'stage-0', ], - "plugins": ["transform-runtime"] - } + plugins: ['transform-runtime'], + }, }; -export async function before(context) { +export function before(context) { const { extensionPath } = context; const serverPath = path.join(extensionPath, 'server'); - context.serverJsonString = stringify(_.merge(await getPackageJson(serverPath), pkgJsonTemplate)); + // eslint-disable-next-line no-param-reassign + context.serverJsonString = stringify(_.merge(npm.getPackageJson(serverPath), pkgJsonTemplate)); } export async function after(context) { @@ -74,7 +76,7 @@ export async function after(context) { getOrSet(context, 'postRunActions', []) .push(async () => { console.log('Running npm install on the server dir...'); - await install(path.join(extensionPath, 'server')); - console.log(`npm install... [${'OK'.green.bold}]`) + await npm.install(path.join(extensionPath, 'server')); + console.log(`npm install... [${'OK'.green.bold}]`); }); }