From dc62b20d93d175ac9b42ed492a5518cac3dc2f97 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Tue, 3 Nov 2020 18:01:22 +0300 Subject: [PATCH 01/27] refactor(file-structure): changed file structure of sasjs commands and its tests --- src/{sasjs-add => commands/add}/index.js | 14 +- .../context}/create.js | 2 +- .../context}/edit.js | 2 +- .../context}/export.js | 4 +- .../context}/index.js | 6 +- .../context}/list.js | 2 +- .../context}/remove.js | 2 +- .../create}/index.js | 8 +- src/{sasjs-db => commands/db}/index.js | 6 +- .../deploy}/index.js | 14 +- .../folder}/create.js | 2 +- .../folder}/index.js | 4 +- src/{sasjs-folder => commands/folder}/move.js | 2 +- .../folder}/remove.js | 2 +- src/{sasjs-help => commands/help}/index.js | 0 src/{sasjs-job => commands/job}/execute.js | 4 +- src/{sasjs-job => commands/job}/index.js | 6 +- .../request}/index.js | 8 +- src/{sasjs-run => commands/run}/index.js | 6 +- .../servicepack}/deploy.js | 8 +- .../servicepack}/index.js | 3 +- .../version}/index.js | 0 src/{sasjs-web => commands/web}/index.js | 6 +- src/{sasjs-web => commands/web}/sasjsout.js | 0 src/main.js | 30 +- src/sasjs-build/index.js | 851 ------------------ src/utils/config-utils.js | 9 +- .../add/{sasjs-add.spec.js => add.spec.js} | 0 .../cbd/{sasjs-cbd.spec.js => cbd.spec.js} | 0 .../compile/getDependencyPaths.spec.js | 2 +- .../compile/getProgramDependencies.spec.js | 2 +- ...{sasjs-context.spec.js => context.spec.js} | 2 +- .../{sasjs-create.spec.js => create.spec.js} | 0 .../db/{sasjs-db.spec.js => db.spec.js} | 0 .../job/{sasjs-job.spec.js => job.spec.js} | 4 +- ...{sasjs-request.spec.js => request.spec.js} | 0 .../run/run.spec.js} | 2 +- ...ervicepack.spec.js => servicepack.spec.js} | 2 +- 38 files changed, 89 insertions(+), 926 deletions(-) rename src/{sasjs-add => commands/add}/index.js (95%) rename src/{sasjs-context => commands/context}/create.js (94%) rename src/{sasjs-context => commands/context}/edit.js (93%) rename src/{sasjs-context => commands/context}/export.js (91%) rename src/{sasjs-context => commands/context}/index.js (95%) rename src/{sasjs-context => commands/context}/list.js (97%) rename src/{sasjs-context => commands/context}/remove.js (92%) rename src/{sasjs-create => commands/create}/index.js (90%) rename src/{sasjs-db => commands/db}/index.js (95%) rename src/{sasjs-deploy => commands/deploy}/index.js (97%) rename src/{sasjs-folder => commands/folder}/create.js (95%) rename src/{sasjs-folder => commands/folder}/index.js (94%) rename src/{sasjs-folder => commands/folder}/move.js (94%) rename src/{sasjs-folder => commands/folder}/remove.js (90%) rename src/{sasjs-help => commands/help}/index.js (100%) rename src/{sasjs-job => commands/job}/execute.js (97%) rename src/{sasjs-job => commands/job}/index.js (92%) rename src/{sasjs-request => commands/request}/index.js (92%) rename src/{sasjs-run => commands/run}/index.js (96%) rename src/{sasjs-servicepack => commands/servicepack}/deploy.js (92%) rename src/{sasjs-servicepack => commands/servicepack}/index.js (96%) rename src/{sasjs-version => commands/version}/index.js (100%) rename src/{sasjs-web => commands/web}/index.js (98%) rename src/{sasjs-web => commands/web}/sasjsout.js (100%) delete mode 100644 src/sasjs-build/index.js rename test/commands/add/{sasjs-add.spec.js => add.spec.js} (100%) rename test/commands/cbd/{sasjs-cbd.spec.js => cbd.spec.js} (100%) rename test/commands/context/{sasjs-context.spec.js => context.spec.js} (98%) rename test/commands/create/{sasjs-create.spec.js => create.spec.js} (100%) rename test/commands/db/{sasjs-db.spec.js => db.spec.js} (100%) rename test/commands/job/{sasjs-job.spec.js => job.spec.js} (96%) rename test/commands/request/{sasjs-request.spec.js => request.spec.js} (100%) rename test/{sasjs-run.spec.js => commands/run/run.spec.js} (85%) rename test/commands/servicepack/{sasjs-servicepack.spec.js => servicepack.spec.js} (95%) diff --git a/src/sasjs-add/index.js b/src/commands/add/index.js similarity index 95% rename from src/sasjs-add/index.js rename to src/commands/add/index.js index 21016c983..b8e30ec86 100644 --- a/src/sasjs-add/index.js +++ b/src/commands/add/index.js @@ -1,5 +1,5 @@ -import { create } from '../sasjs-create' -import { getAndValidateField } from '../utils/input-utils' +import { create } from '../create' +import { getAndValidateField } from '../../utils/input-utils' import chalk from 'chalk' import path from 'path' import SASjs from '@sasjs/adapter/node' @@ -8,9 +8,9 @@ import { getGlobalRcFile, saveGlobalRcFile, getConfiguration -} from '../utils/config-utils' -import { createFile } from '../utils/file-utils' -import { getNewAccessToken } from '../utils/auth-utils' +} from '../../utils/config-utils' +import { createFile } from '../../utils/file-utils' +import { getNewAccessToken } from '../../utils/auth-utils' export async function addTarget() { const scope = await getAndValidateScope() @@ -61,7 +61,7 @@ export async function addTarget() { } async function getLocalConfig() { - const buildSourceFolder = require('../constants').buildSourceFolder + const buildSourceFolder = require('../../constants').buildSourceFolder const config = await getConfiguration( path.join(buildSourceFolder, 'sasjsconfig.json') ) @@ -70,7 +70,7 @@ async function getLocalConfig() { } async function saveToLocalConfig(buildTarget) { - const buildSourceFolder = require('../constants').buildSourceFolder + const buildSourceFolder = require('../../constants').buildSourceFolder let config = await getLocalConfig() if (config) { if (config.targets && config.targets.length) { diff --git a/src/sasjs-context/create.js b/src/commands/context/create.js similarity index 94% rename from src/sasjs-context/create.js rename to src/commands/context/create.js index bbd2fffbd..8c59986f5 100644 --- a/src/sasjs-context/create.js +++ b/src/commands/context/create.js @@ -1,4 +1,4 @@ -import { displayResult } from '../utils/displayResult' +import { displayResult } from '../../utils/displayResult' /** * Creates compute context using provided config. diff --git a/src/sasjs-context/edit.js b/src/commands/context/edit.js similarity index 93% rename from src/sasjs-context/edit.js rename to src/commands/context/edit.js index 0c9401b4a..a9ba1ea50 100644 --- a/src/sasjs-context/edit.js +++ b/src/commands/context/edit.js @@ -1,4 +1,4 @@ -import { displayResult } from '../utils/displayResult' +import { displayResult } from '../../utils/displayResult' /** * Edits existing compute context. diff --git a/src/sasjs-context/export.js b/src/commands/context/export.js similarity index 91% rename from src/sasjs-context/export.js rename to src/commands/context/export.js index 5842d2399..4c1e2e368 100644 --- a/src/sasjs-context/export.js +++ b/src/commands/context/export.js @@ -1,6 +1,6 @@ -import { displayResult } from '../utils/displayResult' +import { displayResult } from '../../utils/displayResult' import path from 'path' -import { createFile, sanitizeFileName } from '../utils/file-utils' +import { createFile, sanitizeFileName } from '../../utils/file-utils' /** * Export compute context to json file in current folder. diff --git a/src/sasjs-context/index.js b/src/commands/context/index.js similarity index 95% rename from src/sasjs-context/index.js rename to src/commands/context/index.js index 01e5209b9..aebffce4d 100644 --- a/src/sasjs-context/index.js +++ b/src/commands/context/index.js @@ -4,9 +4,9 @@ import { edit } from './edit' import { remove } from './remove' import { list } from './list' import { exportContext } from './export' -import { fileExists, readFile } from '../utils/file-utils' -import { getBuildTarget, getAccessToken } from '../utils/config-utils' -import { displayResult } from '../utils/displayResult' +import { fileExists, readFile } from '../../utils/file-utils' +import { getBuildTarget, getAccessToken } from '../../utils/config-utils' +import { displayResult } from '../../utils/displayResult' import SASjs from '@sasjs/adapter/node' export async function processContext(commandLine) { diff --git a/src/sasjs-context/list.js b/src/commands/context/list.js similarity index 97% rename from src/sasjs-context/list.js rename to src/commands/context/list.js index 72213488d..2c46dbaa4 100644 --- a/src/sasjs-context/list.js +++ b/src/commands/context/list.js @@ -1,6 +1,6 @@ import chalk from 'chalk' import ora from 'ora' -import { displayResult } from '../utils/displayResult' +import { displayResult } from '../../utils/displayResult' /** * Lists all accessible and inaccessible compute contexts. diff --git a/src/sasjs-context/remove.js b/src/commands/context/remove.js similarity index 92% rename from src/sasjs-context/remove.js rename to src/commands/context/remove.js index 2e9534694..88e879a6e 100644 --- a/src/sasjs-context/remove.js +++ b/src/commands/context/remove.js @@ -1,4 +1,4 @@ -import { displayResult } from '../utils/displayResult' +import { displayResult } from '../../utils/displayResult' /** * Removes compute context. diff --git a/src/sasjs-create/index.js b/src/commands/create/index.js similarity index 90% rename from src/sasjs-create/index.js rename to src/commands/create/index.js index 6b5058c64..6d72a1c8f 100644 --- a/src/sasjs-create/index.js +++ b/src/commands/create/index.js @@ -7,19 +7,19 @@ import { createReactApp, createAngularApp, createMinimalApp -} from '../utils/utils' -import { getFolders, getConfiguration } from '../utils/config-utils' +} from '../../utils/utils' +import { getFolders, getConfiguration } from '../../utils/config-utils' import { createFolderStructure, createFolder, createFile, fileExists -} from '../utils/file-utils' +} from '../../utils/file-utils' import chalk from 'chalk' export async function create(parentFolderName = '.', appType = '') { const configPath = - appType === 'sasonly' ? '../config-sasonly.json' : '../config.json' + appType === 'sasonly' ? '../../config-sasonly.json' : '../../config.json' const config = await getConfiguration(path.join(__dirname, configPath)) const fileStructure = await getFileStructure(appType === 'sasonly') console.log(chalk.greenBright('Creating folders and files...')) diff --git a/src/sasjs-db/index.js b/src/commands/db/index.js similarity index 95% rename from src/sasjs-db/index.js rename to src/commands/db/index.js index f0aeb9fbb..a487a0fca 100644 --- a/src/sasjs-db/index.js +++ b/src/commands/db/index.js @@ -9,8 +9,8 @@ import { createFolder, deleteFolder, fileExists -} from '../utils/file-utils' -import { asyncForEach } from '../utils/utils' +} from '../../utils/file-utils' +import { asyncForEach } from '../../utils/utils' const whiteListedDBExtensions = ['ddl', 'sas'] let buildDestinationFolder = '' @@ -18,7 +18,7 @@ let buildSourceDBFolder = '' let buildDestinationDBFolder = '' export async function buildDB() { - const CONSTANTS = require('../constants') + const CONSTANTS = require('../../constants') buildDestinationFolder = CONSTANTS.buildDestinationFolder buildSourceDBFolder = CONSTANTS.buildSourceDBFolder buildDestinationDBFolder = CONSTANTS.buildDestinationDBFolder diff --git a/src/sasjs-deploy/index.js b/src/commands/deploy/index.js similarity index 97% rename from src/sasjs-deploy/index.js rename to src/commands/deploy/index.js index d81e7d36b..cad05f921 100644 --- a/src/sasjs-deploy/index.js +++ b/src/commands/deploy/index.js @@ -1,20 +1,24 @@ import path from 'path' import SASjs from '@sasjs/adapter/node' import chalk from 'chalk' -import { findTargetInConfiguration } from '../utils/config-utils' -import { asyncForEach, executeShellScript, getVariable } from '../utils/utils' +import { findTargetInConfiguration } from '../../utils/config-utils' +import { + asyncForEach, + executeShellScript, + getVariable +} from '../../utils/utils' import { isSasFile, isShellScript, readFile, folderExists, createFile -} from '../utils/file-utils' +} from '../../utils/file-utils' import { getAccessToken, isAccessTokenExpiring, refreshTokens -} from '../utils/auth-utils' +} from '../../utils/auth-utils' let targetToBuild = null let executionSession @@ -160,7 +164,7 @@ async function deployToSasViyaWithServicePack(buildTarget, isForced) { serverType: buildTarget.serverType }) - const CONSTANTS = require('../constants') + const CONSTANTS = require('../../constants') const buildDestinationFolder = CONSTANTS.buildDestinationFolder const finalFilePathJSON = path.join( buildDestinationFolder, diff --git a/src/sasjs-folder/create.js b/src/commands/folder/create.js similarity index 95% rename from src/sasjs-folder/create.js rename to src/commands/folder/create.js index c856860c5..59761fda2 100644 --- a/src/sasjs-folder/create.js +++ b/src/commands/folder/create.js @@ -1,5 +1,5 @@ import chalk from 'chalk' -import { displayResult } from '../utils/displayResult' +import { displayResult } from '../../utils/displayResult' /** * Creates folder. diff --git a/src/sasjs-folder/index.js b/src/commands/folder/index.js similarity index 94% rename from src/sasjs-folder/index.js rename to src/commands/folder/index.js index 5f477fc77..08be13feb 100644 --- a/src/sasjs-folder/index.js +++ b/src/commands/folder/index.js @@ -1,7 +1,7 @@ import SASjs from '@sasjs/adapter/node' import chalk from 'chalk' -import { getBuildTarget, getAccessToken } from '../utils/config-utils' -import { displayResult } from '../utils/displayResult' +import { getBuildTarget, getAccessToken } from '../../utils/config-utils' +import { displayResult } from '../../utils/displayResult' import { create } from './create' import { move } from './move' import { remove } from './remove' diff --git a/src/sasjs-folder/move.js b/src/commands/folder/move.js similarity index 94% rename from src/sasjs-folder/move.js rename to src/commands/folder/move.js index f9ba94573..2a3d7f0b9 100644 --- a/src/sasjs-folder/move.js +++ b/src/commands/folder/move.js @@ -1,4 +1,4 @@ -import { displayResult } from '../utils/displayResult' +import { displayResult } from '../../utils/displayResult' /** * Moves folder to a new location. diff --git a/src/sasjs-folder/remove.js b/src/commands/folder/remove.js similarity index 90% rename from src/sasjs-folder/remove.js rename to src/commands/folder/remove.js index b6cb0663e..8e438286b 100644 --- a/src/sasjs-folder/remove.js +++ b/src/commands/folder/remove.js @@ -1,4 +1,4 @@ -import { displayResult } from '../utils/displayResult' +import { displayResult } from '../../utils/displayResult' /** * Deletes folder. diff --git a/src/sasjs-help/index.js b/src/commands/help/index.js similarity index 100% rename from src/sasjs-help/index.js rename to src/commands/help/index.js diff --git a/src/sasjs-job/execute.js b/src/commands/job/execute.js similarity index 97% rename from src/sasjs-job/execute.js rename to src/commands/job/execute.js index 598a645e4..3d361f83c 100644 --- a/src/sasjs-job/execute.js +++ b/src/commands/job/execute.js @@ -1,7 +1,7 @@ import chalk from 'chalk' import ora from 'ora' -import { displayResult } from '../utils/displayResult' -import { createFile, createFolder, folderExists } from '../utils/file-utils' +import { displayResult } from '../../utils/displayResult' +import { createFile, createFolder, folderExists } from '../../utils/file-utils' import path from 'path' /** diff --git a/src/sasjs-job/index.js b/src/commands/job/index.js similarity index 92% rename from src/sasjs-job/index.js rename to src/commands/job/index.js index 171269992..74716a663 100644 --- a/src/sasjs-job/index.js +++ b/src/commands/job/index.js @@ -3,11 +3,11 @@ import { getBuildTarget, getAccessToken, sanitizeAppLoc -} from '../utils/config-utils' -import { displayResult } from '../utils/displayResult' +} from '../../utils/config-utils' +import { displayResult } from '../../utils/displayResult' import SASjs from '@sasjs/adapter/node' import { execute } from './execute' -import { Command } from '../utils/command' +import { Command } from '../../utils/command' export async function processJob(commandLine) { const command = new Command(commandLine) diff --git a/src/sasjs-request/index.js b/src/commands/request/index.js similarity index 92% rename from src/sasjs-request/index.js rename to src/commands/request/index.js index 0144ea989..f95300c51 100644 --- a/src/sasjs-request/index.js +++ b/src/commands/request/index.js @@ -1,14 +1,14 @@ import path from 'path' import SASjs from '@sasjs/adapter/node' -import { findTargetInConfiguration } from '../utils/config-utils' +import { findTargetInConfiguration } from '../../utils/config-utils' import { readFile, folderExists, createFile, createFolder -} from '../utils/file-utils' -import { getAccessToken } from '../utils/config-utils' -import { displayResult } from '../utils/displayResult' +} from '../../utils/file-utils' +import { getAccessToken } from '../../utils/config-utils' +import { displayResult } from '../../utils/displayResult' export async function runSasJob( sasJobLocation, diff --git a/src/sasjs-run/index.js b/src/commands/run/index.js similarity index 96% rename from src/sasjs-run/index.js rename to src/commands/run/index.js index d5fb6b5cb..c1c9a2750 100644 --- a/src/sasjs-run/index.js +++ b/src/commands/run/index.js @@ -4,9 +4,9 @@ import SASjs from '@sasjs/adapter/node' import { findTargetInConfiguration, getAccessToken -} from '../utils/config-utils' -import { readFile, createFile } from '../utils/file-utils' -import { getVariable, generateTimestamp } from '../utils/utils' +} from '../../utils/config-utils' +import { readFile, createFile } from '../../utils/file-utils' +import { getVariable, generateTimestamp } from '../../utils/utils' /** * Runs SAS code from a given file on the specified target. diff --git a/src/sasjs-servicepack/deploy.js b/src/commands/servicepack/deploy.js similarity index 92% rename from src/sasjs-servicepack/deploy.js rename to src/commands/servicepack/deploy.js index 7ed63e815..a750c7633 100644 --- a/src/sasjs-servicepack/deploy.js +++ b/src/commands/servicepack/deploy.js @@ -1,12 +1,12 @@ import path from 'path' import SASjs from '@sasjs/adapter/node' import chalk from 'chalk' -import { readFile } from '../utils/file-utils' -import { displayResult } from '../utils/displayResult' +import { readFile } from '../../utils/file-utils' +import { displayResult } from '../../utils/displayResult' import { getAccessToken, findTargetInConfiguration -} from '../utils/config-utils' +} from '../../utils/config-utils' export async function servicePackDeploy( jsonFilePath = null, @@ -67,7 +67,7 @@ async function deployToSasViyaWithServicePack( serverType: buildTarget.serverType }) - const CONSTANTS = require('../constants') + const CONSTANTS = require('../../constants') const buildDestinationFolder = CONSTANTS.buildDestinationFolder const finalFilePathJSON = path.join( diff --git a/src/sasjs-servicepack/index.js b/src/commands/servicepack/index.js similarity index 96% rename from src/sasjs-servicepack/index.js rename to src/commands/servicepack/index.js index 826a2cfd4..5ef80c29b 100644 --- a/src/sasjs-servicepack/index.js +++ b/src/commands/servicepack/index.js @@ -3,7 +3,7 @@ import { getCommandParameter, getCommandParameterLastMultiWord, isFlagPresent -} from '../utils/command-utils' +} from '../../utils/command-utils' import chalk from 'chalk' @@ -47,6 +47,7 @@ export async function processServicepack(commandLine) { const isForced = isFlagPresent('-f', commandLine) output = await servicePackDeploy(jsonFilePath, targetName, isForced) + break } diff --git a/src/sasjs-version/index.js b/src/commands/version/index.js similarity index 100% rename from src/sasjs-version/index.js rename to src/commands/version/index.js diff --git a/src/sasjs-web/index.js b/src/commands/web/index.js similarity index 98% rename from src/sasjs-web/index.js rename to src/commands/web/index.js index 423661044..04ce24c9b 100644 --- a/src/sasjs-web/index.js +++ b/src/commands/web/index.js @@ -1,5 +1,5 @@ -import { findTargetInConfiguration } from '../utils/config-utils' -import { asyncForEach, chunk } from '../utils/utils' +import { findTargetInConfiguration } from '../../utils/config-utils' +import { asyncForEach, chunk } from '../../utils/utils' import { readFile, base64EncodeFile, @@ -9,7 +9,7 @@ import { createFile, deleteFolder, getFilesInFolder -} from '../utils/file-utils' +} from '../../utils/file-utils' import path from 'path' import chalk from 'chalk' import jsdom from 'jsdom' diff --git a/src/sasjs-web/sasjsout.js b/src/commands/web/sasjsout.js similarity index 100% rename from src/sasjs-web/sasjsout.js rename to src/commands/web/sasjsout.js diff --git a/src/main.js b/src/main.js index 8729197ca..058ed15f9 100644 --- a/src/main.js +++ b/src/main.js @@ -1,17 +1,17 @@ -import { build } from './sasjs-build' -import { deploy } from './sasjs-deploy' -import { processServicepack } from './sasjs-servicepack' -import { buildDB } from './sasjs-db' -import { create } from './sasjs-create' -import { printHelpText } from './sasjs-help' -import { printVersion } from './sasjs-version' -import { createWebAppServices } from './sasjs-web' -import { addTarget } from './sasjs-add' -import { runSasCode } from './sasjs-run' -import { runSasJob } from './sasjs-request' -import { processContext } from './sasjs-context' -import { folder } from './sasjs-folder' -import { processJob } from './sasjs-job' +import { build } from './commands/build' +import { deploy } from './commands/deploy' +import { processServicepack } from './commands/servicepack' +import { buildDB } from './commands/db' +import { create } from './commands/create' +import { printHelpText } from './commands/help' +import { printVersion } from './commands/version' +import { createWebAppServices } from './commands/web' +import { addTarget } from './commands/add' +import { runSasCode } from './commands/run' +import { runSasJob } from './commands/request' +import { processContext } from './commands/context' +import { folder } from './commands/folder' +import { processJob } from './commands/job' import chalk from 'chalk' import { displayResult } from './utils/displayResult' @@ -270,6 +270,7 @@ export async function buildWebApp(targetName) { export async function add(resourceType = 'target') { let result = false + if (resourceType === 'target') { await addTarget() .then(() => { @@ -281,6 +282,7 @@ export async function add(resourceType = 'target') { result = err }) } + return result } diff --git a/src/sasjs-build/index.js b/src/sasjs-build/index.js deleted file mode 100644 index ff8b2c543..000000000 --- a/src/sasjs-build/index.js +++ /dev/null @@ -1,851 +0,0 @@ -import find from 'find' -import path from 'path' -import chalk from 'chalk' -import uniqBy from 'lodash.uniqby' -import groupBy from 'lodash.groupby' -import { deploy } from '../sasjs-deploy' -import { createWebAppServices } from '../sasjs-web' -import { - readFile, - getSubFoldersInFolder, - getFilesInFolder, - createFile, - createFolder, - deleteFolder, - fileExists, - folderExists, - copy -} from '../utils/file-utils' -import { asyncForEach, removeComments, chunk, diff } from '../utils/utils' -import { - getSourcePaths, - getConfiguration, - findTargetInConfiguration, - getTargetSpecificFile, - getMacroCorePath, - getProgramFolders -} from '../utils/config-utils' - -let buildSourceFolder = '' -let buildDestinationFolder = '' -let buildDestinationServ = '' -let targetToBuild = null - -export async function build( - targetName = null, - compileOnly = false, - compileBuildOnly = false, - compileBuildDeployOnly = false, - isForced = false -) { - const CONSTANTS = require('../constants') - buildSourceFolder = CONSTANTS.buildSourceFolder - buildDestinationFolder = CONSTANTS.buildDestinationFolder - buildDestinationServ = CONSTANTS.buildDestinationServ - const { target } = await findTargetInConfiguration(targetName) - targetToBuild = target - - if (compileBuildDeployOnly) { - await compile(targetName) - await createFinalSasFiles() - return await deploy(targetName, targetToBuild, isForced) - } - if (compileBuildOnly) { - await compile(targetName) - return await createFinalSasFiles() - } - if (compileOnly) return await compile(targetName) - - const servicesToCompile = await getAllServices( - path.join(buildSourceFolder, 'sasjsconfig.json') - ) - const serviceNamesToCompile = servicesToCompile.map((s) => s.split('/').pop()) - const serviceNamesToCompileUniq = [...new Set(serviceNamesToCompile)] - const result = await validCompiled(serviceNamesToCompileUniq) - if (result.compiled) { - // no need to compile again - console.log(chalk.greenBright(result.message)) - console.log(chalk.white('Skipping compiling of build folders...')) - } else { - console.log(chalk.redBright(result.message)) - await compile() - } - await createFinalSasFiles() -} - -async function compile(targetName) { - await copyFilesToBuildFolder() - - const servicesToCompile = await getAllServices( - path.join(buildSourceFolder, 'sasjsconfig.json') - ) - - const serviceNamesToCompile = servicesToCompile.map((s) => s.split('/').pop()) - const serviceNamesToCompileUniq = [...new Set(serviceNamesToCompile)] - - const tgtMacros = targetToBuild ? targetToBuild.tgtMacros : [] - const programFolders = await getProgramFolders(targetName) - - const errors = [] - - await asyncForEach(serviceNamesToCompileUniq, async (buildFolder) => { - const folderPath = path.join(buildDestinationServ, buildFolder) - const subFolders = await getSubFoldersInFolder(folderPath) - const filesNamesInPath = await getFilesInFolder(folderPath) - - await asyncForEach(filesNamesInPath, async (fileName) => { - const filePath = path.join(folderPath, fileName) - - const dependencies = await loadDependencies( - filePath, - tgtMacros, - programFolders - ).catch((err) => { - errors.push(err) - }) - - if (dependencies) await createFile(filePath, dependencies) - }) - - await asyncForEach(subFolders, async (subFolder) => { - const fileNames = await getFilesInFolder(path.join(folderPath, subFolder)) - - await asyncForEach(fileNames, async (fileName) => { - const filePath = path.join(folderPath, subFolder, fileName) - - const dependencies = await loadDependencies( - filePath, - tgtMacros, - programFolders - ).catch((err) => { - errors.push(err) - }) - - if (dependencies) await createFile(filePath, dependencies) - }) - }) - }) - - if (errors.length) throw errors -} - -async function createFinalSasFiles() { - const { - buildOutputFileName, - appLoc, - serverType, - streamWeb, - tgtMacros, - name: tgtName - } = targetToBuild - if (streamWeb) { - await createWebAppServices(null, targetToBuild) - .then(() => - console.log( - chalk.greenBright.bold.italic( - `Web app services have been successfully built!` - ) - ) - ) - .catch((err) => { - console.log( - chalk.redBright( - 'An error has occurred when building web app services.', - err - ) - ) - }) - } - await createFinalSasFile( - buildOutputFileName, - appLoc, - serverType, - tgtMacros, - tgtName - ) -} - -async function createFinalSasFile( - fileName = 'build.sas', - appLoc, - serverType, - tgtMacros = [], - tgtName = 'target' -) { - console.log( - chalk.greenBright(`Creating final ${chalk.cyanBright(fileName)} file`) - ) - let finalSasFileContent = '' - const finalFilePath = path.join(buildDestinationFolder, fileName) - const finalFilePathJSON = path.join(buildDestinationFolder, `${tgtName}.json`) - const buildConfig = await getBuildConfig( - appLoc, - serverType, - tgtMacros - ).catch((_) => {}) - - if (!buildConfig) return - - finalSasFileContent += `\n${buildConfig}` - - const { content: buildInit, path: buildInitPath } = await getBuildInit() - const { - content: buildTermContent, - path: buildTermPath - } = await getBuildTerm() - - console.log(chalk.greenBright(' Loading dependencies for:')) - console.log( - ' BuildInit -', - chalk.greenBright(chalk.cyanBright(buildInitPath)) - ) - console.log( - ' BuildTerm -', - chalk.greenBright(chalk.cyanBright(buildTermPath)) - ) - const dependencyFilePaths = await getDependencyPaths( - `${buildTermContent}\n${buildInit}`, - tgtMacros - ) - const dependenciesContent = await getDependencies(dependencyFilePaths) - - finalSasFileContent += `\n${dependenciesContent}\n\n${buildInit}\n` - - console.log(chalk.greenBright(' - Compiling Services')) - const { folderContent, folderContentJSON } = await getFolderContent( - serverType - ) - finalSasFileContent += `\n${folderContent}` - - finalSasFileContent += `\n${buildTermContent}` - finalSasFileContent = removeComments(finalSasFileContent) - await createFile(finalFilePath, finalSasFileContent) - await createFile( - finalFilePathJSON, - JSON.stringify(folderContentJSON, null, 1) - ) -} - -async function getBuildConfig(appLoc, serverType, tgtMacros = []) { - let buildConfig = '' - const createWebServiceScript = await getCreateWebServiceScript(serverType) - buildConfig += `${createWebServiceScript}\n` - const dependencyFilePaths = await getDependencyPaths(buildConfig, tgtMacros) - const dependenciesContent = await getDependencies(dependencyFilePaths) - const buildVars = await getBuildVars() - return `%global appLoc;\n%let appLoc=%sysfunc(coalescec(&appLoc,${appLoc})); /* metadata or files service location of your app */\n%let syscc=0;\noptions ps=max noquotelenmax;\n${buildVars}\n${dependenciesContent}\n${buildConfig}\n` -} - -async function getCreateWebServiceScript(serverType) { - switch (serverType.toUpperCase()) { - case 'SASVIYA': - return await readFile( - `${getMacroCorePath()}/viya/mv_createwebservice.sas` - ) - - case 'SAS9': - return await readFile( - `${getMacroCorePath()}/meta/mm_createwebservice.sas` - ) - - default: - throw new Error( - `Invalid server type: valid options are ${chalk.cyanBright( - 'SASVIYA' - )} and ${chalk.cyanBright('SAS9')}` - ) - } -} - -function getWebServiceScriptInvocation(serverType) { - switch (serverType.toUpperCase()) { - case 'SASVIYA': - return '%mv_createwebservice(path=&appLoc/&path, name=&service, code=sascode ,replace=yes)' - case 'SAS9': - return '%mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode ,replace=yes)' - default: - throw new Error( - `Invalid server type: valid options are ${chalk.cyanBright( - 'SASVIYA' - )} and ${chalk.cyanBright('SAS9')}` - ) - } -} - -async function getFolderContent(serverType) { - const buildSubFolders = await getSubFoldersInFolder(buildDestinationFolder) - let folderContent = '' - let folderContentJSON = { members: [] } - await asyncForEach(buildSubFolders, async (subFolder) => { - const { content, contentJSON } = await getContentFor( - path.join(buildDestinationFolder, subFolder), - subFolder, - serverType - ) - folderContent += `\n${content}` - folderContentJSON.members.push(contentJSON) - }) - return { folderContent, folderContentJSON } -} - -async function getPreCodeForServicePack(serverType) { - let content = '' - switch (serverType.toUpperCase()) { - case 'SASVIYA': - content += await readFile(`${getMacroCorePath()}/base/mf_getuser.sas`) - content += await readFile(`${getMacroCorePath()}/base/mp_jsonout.sas`) - content += await readFile(`${getMacroCorePath()}/viya/mv_webout.sas`) - content += - '/* if calling viya service with _job param, _program will conflict */\n' + - '/* so we provide instead as __program */\n' + - '%global __program _program;\n' + - '%let _program=%sysfunc(coalescec(&__program,&_program));\n' + - '%macro webout(action,ds,dslabel=,fmt=);\n' + - '%mv_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)\n' + - '%mend;\n' - break - - case 'SAS9': - content += await readFile(`${getMacroCorePath()}/base/mf_getuser.sas`) - content += await readFile(`${getMacroCorePath()}/base/mp_jsonout.sas`) - content += await readFile(`${getMacroCorePath()}/meta/mm_webout.sas`) - content += - ' %macro webout(action,ds,dslabel=,fmt=);\n' + - ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)\n' + - ' %mend;\n' - break - - default: - throw new Error( - `Invalid server type: valid options are ${chalk.cyanBright( - 'SASVIYA' - )} and ${chalk.cyanBright('SAS9')}` - ) - } - return content -} - -async function getContentFor(folderPath, folderName, serverType) { - let content = `\n%let path=${folderName === 'services' ? '' : folderName};\n` - const contentJSON = { - name: folderName, - type: 'folder', - members: [] - } - const files = await getFilesInFolder(folderPath) - const preCode = await getPreCodeForServicePack(serverType) - await asyncForEach(files, async (file) => { - const fileContent = await readFile(path.join(folderPath, file)) - const transformedContent = getServiceText(file, fileContent, serverType) - content += `\n${transformedContent}\n` - contentJSON.members.push({ - name: file.replace('.sas', ''), - type: 'service', - code: removeComments(`${preCode}\n${fileContent}`) - }) - }) - const subFolders = await getSubFoldersInFolder(folderPath) - await asyncForEach(subFolders, async (subFolder) => { - const { - content: childContent, - contentJSON: childContentJSON - } = await getContentFor( - path.join(folderPath, subFolder), - subFolder, - serverType - ) - contentJSON.members.push(childContentJSON) - content += childContent - }) - return { content, contentJSON } -} - -function getServiceText(serviceFileName, fileContent, serverType) { - const serviceName = serviceFileName.replace('.sas', '') - const sourceCodeLines = getLines(removeComments(fileContent)) - let content = `` - sourceCodeLines.forEach((line) => { - const escapedLine = line.split("'").join("''") - if (escapedLine.trim()) { - content += `\n put '${escapedLine.trim()}';` - } - }) - return `%let service=${serviceName}; -filename sascode temp lrecl=32767; -data _null_; -file sascode; -${content}\n -run; -${getWebServiceScriptInvocation(serverType)} -filename sascode clear; -` -} - -function getLines(text) { - let lines = text - .replace(/\r\n/g, '\n') - .split('\n') - .map((l) => l.trim()) - return lines -} - -async function copyFilesToBuildFolder() { - await recreateBuildFolder() - console.log(chalk.greenBright('Copying files to build folder...')) - const servicesToCompile = await getAllServices( - path.join(buildSourceFolder, 'sasjsconfig.json') - ) - await asyncForEach(servicesToCompile, async (buildFolder) => { - const sourcePath = path.join(buildSourceFolder, buildFolder) - const buildFolderName = buildFolder.split('/').pop() - const destinationPath = path.join(buildDestinationServ, buildFolderName) - await copy(sourcePath, destinationPath) - }) -} - -async function recreateBuildFolder() { - console.log(chalk.greenBright('Recreating to build folder...')) - const pathExists = await fileExists(buildDestinationFolder) - if (pathExists) { - // delete everything other than, db folder - const subFolders = await getSubFoldersInFolder(buildDestinationFolder) - const subFiles = await getFilesInFolder(buildDestinationFolder) - await asyncForEach([...subFolders, ...subFiles], async (subFolder) => { - if (subFolder == 'db') return - const subFolderPath = path.join(buildDestinationFolder, subFolder) - await deleteFolder(subFolderPath) - }) - } else await createFolder(buildDestinationFolder) - await createFolder(path.join(buildDestinationServ)) -} - -export async function loadDependencies(filePath, tgtMacros, programFolders) { - console.log( - chalk.greenBright('Loading dependencies for', chalk.cyanBright(filePath)) - ) - let fileContent = await readFile(filePath) - const serviceVars = await getServiceVars() - const serviceInit = await getServiceInit() - const serviceTerm = await getServiceTerm() - const dependencyFilePaths = await getDependencyPaths( - `${fileContent}\n${serviceInit}\n${serviceTerm}`, - tgtMacros - ) - const programDependencies = await getProgramDependencies( - fileContent, - programFolders, - buildSourceFolder - ) - - const dependenciesContent = await getDependencies(dependencyFilePaths) - fileContent = `* Service Variables start;\n${serviceVars}\n*Service Variables end;\n* Dependencies start;\n${dependenciesContent}\n* Dependencies end;\n* Programs start;\n${programDependencies}\n*Programs end;\n* ServiceInit start;\n${serviceInit}\n* ServiceInit end;\n* Service start;\n${fileContent}\n* Service end;\n* ServiceTerm start;\n${serviceTerm}\n* ServiceTerm end;` - - return fileContent -} - -async function getBuildInit() { - return await getTargetSpecificFile('BuildInit', targetToBuild) -} - -async function getServiceInit() { - return (await getTargetSpecificFile('ServiceInit', targetToBuild)).content -} - -async function getServiceTerm() { - return (await getTargetSpecificFile('ServiceTerm', targetToBuild)).content -} - -async function getBuildTerm() { - return await getTargetSpecificFile('BuildTerm', targetToBuild) -} - -async function getTargetSpecificVars(typeOfVars) { - let variables = {} - const configuration = await getConfiguration( - path.join(buildSourceFolder, 'sasjsconfig.json') - ) - - if (configuration && configuration[`cmn${typeOfVars}`]) - variables = { ...configuration[`cmn${typeOfVars}`] } - - if (targetToBuild && targetToBuild[`tgt${typeOfVars}`]) - variables = { ...variables, ...targetToBuild[`tgt${typeOfVars}`] } - - const entries = Object.entries(variables) - let varsContent = '\n' - for (const [name, value] of entries) { - const chunks = chunk(value) - const chunkedString = chunks.join('%trim(\n)') - varsContent += `%let ${name}=${chunkedString};\n` - } - - return varsContent -} - -export async function getServiceVars() { - return await getTargetSpecificVars('ServiceVars') -} - -export async function getBuildVars() { - return await getTargetSpecificVars('BuildVars') -} - -async function getDependencies(filePaths) { - let dependenciesContent = [] - await asyncForEach(filePaths, async (filePath) => { - const depFileContent = await readFile(filePath) - dependenciesContent.push(depFileContent) - }) - - return dependenciesContent.join('\n') -} - -export function getProgramList(fileContent) { - let fileHeader - try { - const hasFileHeader = fileContent.split('/**')[0] !== fileContent - if (!hasFileHeader) return [] - fileHeader = fileContent.split('/**')[1].split('**/')[0] - } catch (e) { - console.error( - chalk.redBright( - 'File header parse error.\nPlease make sure your file header is in the correct format.' - ) - ) - } - - let programsSection - - try { - programsSection = fileHeader.split(/\ SAS Programs \<\/h4\>/i)[1] - - if (!programsSection) return [] - } catch (e) { - console.error( - chalk.redBright( - 'File header parse error.\nPlease make sure your SAS program dependencies are specified in the correct format.' - ) - ) - } - - const programsList = programsSection - .replace(/\r\n/g, '\n') - .split('\n') - .filter((l) => !!l) - .map((l) => l.trim()) - .filter((l) => l.startsWith('@li')) - .map((l) => l.replace(/\@li/g, '').trim()) - .map((l) => { - const [fileName, fileRef] = l.split(' ') - - if (!fileName) { - throw new Error( - `SAS Program ${fileName} is missing file name. Please specify SAS program dependencies in the format: @li ` - ) - } - - if (fileName && !fileRef) { - throw new Error( - `SAS Program ${fileName} is missing fileref. Please specify SAS program dependencies in the format: @li ` - ) - } - - validateFileRef(fileRef) - return { fileName, fileRef } - }) - - validateProgramsList(programsList) - - return uniqBy(programsList, 'fileName') -} - -export function validateProgramsList(programsList) { - const areFileRefsUnique = - uniqBy( - programsList.map((p) => p.fileRef), - (x) => x.toLocaleUpperCase() - ).length === programsList.length - - if (areFileRefsUnique) { - return true - } - - const duplicatePrograms = [] - programsList.forEach((program, index, list) => { - const duplicates = list.filter( - (p, i) => - i !== index && - p.fileRef.toLocaleUpperCase() === program.fileRef.toLocaleUpperCase() && - !duplicatePrograms.some( - (d) => - d.fileName === p.fileName && - d.fileRef.toLocaleUpperCase() === p.fileRef.toLocaleUpperCase() - ) - ) - duplicatePrograms.push(...duplicates) - }) - const groupedDuplicates = groupBy(duplicatePrograms, (x) => - x.fileRef.toLocaleUpperCase() - ) - let errorMessage = '' - Object.keys(groupedDuplicates).forEach((fileRef) => { - errorMessage += `The following files have duplicate fileref '${fileRef}':\n${groupedDuplicates[ - fileRef - ] - .map((d) => d.fileName) - .join(', ')}\n` - }) - throw new Error(errorMessage) -} - -export function validateFileRef(fileRef) { - if (!fileRef) { - throw new Error('Missing file ref.') - } - - if (fileRef.length > 8) { - throw new Error( - 'File ref is too long. File refs can have a maximum of 8 characters.' - ) - } - - if (!/^[_a-zA-Z][_a-zA-Z0-9]*/.test(fileRef)) { - throw new Error( - 'Invalid file ref. File refs can only start with a letter or an underscore, and contain only letters, numbers and underscores.' - ) - } - - return true -} - -export async function getProgramDependencies( - fileContent, - programFolders, - buildSourceFolder -) { - programFolders = uniqBy(programFolders) - const programs = getProgramList(fileContent) - if (programs.length) { - const foundPrograms = [] - await asyncForEach(programFolders, async (programFolder) => { - await asyncForEach(programs, async (program) => { - const filePath = path.join(buildSourceFolder, programFolder) - const filePaths = find.fileSync(program.fileName, filePath) - if (filePaths.length) { - const fileContent = await readFile(filePaths[0]) - if (!fileContent) { - console.log( - chalk.yellowBright(`File ${program.fileName} is empty.`) - ) - } - const programDependencyContent = getProgramDependencyText( - fileContent, - program.fileRef - ) - foundPrograms.push(programDependencyContent) - } else { - console.log( - chalk.yellowBright( - `Skipping ${program.fileName} as program file was not found. Please check your SAS program dependencies.\n` - ) - ) - } - }) - }) - - return foundPrograms.join('\n') - } - - return '' -} - -function getProgramDependencyText(fileContent, fileRef) { - let output = `filename ${fileRef} temp;\ndata _null_;\nfile ${fileRef} lrecl=32767;\n` - - const sourceLines = fileContent - .replace(/\r\n/g, '\n') - .split('\n') - .filter((l) => !!l) - - sourceLines.forEach((line) => { - const chunkedLines = chunk(line) - if (chunkedLines.length === 1) { - output += `put '${chunkedLines[0].split("'").join("''")}';\n` - } else { - let combinedLines = '' - chunkedLines.forEach((chunkedLine, index) => { - let text = `put '${chunkedLine.split("'").join("''")}'` - if (index !== chunkedLines.length - 1) { - text += '@;\n' - } else { - text += ';\n' - } - combinedLines += text - }) - output += combinedLines - } - }) - - output += 'run;' - - return output -} - -export async function getDependencyPaths(fileContent, tgtMacros = []) { - const sourcePaths = await getSourcePaths(buildSourceFolder) - if (tgtMacros.length) { - tgtMacros.forEach((tm) => { - const tgtMacroPath = path.join(buildSourceFolder, tm) - sourcePaths.push(tgtMacroPath) - }) - } - const dependenciesStart = fileContent.split('

Dependencies

') - let dependencies = [] - if (dependenciesStart.length > 1) { - let count = 1 - while (count < dependenciesStart.length) { - let dependency = dependenciesStart[count] - .split('**/')[0] - .replace(/\r\n/g, '\n') - .split('\n') - .filter((d) => !!d) - .map((d) => d.replace(/\@li/g, '').replace(/ /g, '')) - .filter((d) => d.endsWith('.sas')) - dependencies = [...dependencies, ...dependency] - count++ - } - - let dependencyPaths = [] - const foundDependencies = [] - - await asyncForEach(sourcePaths, async (sourcePath) => { - if (await folderExists(sourcePath)) { - await asyncForEach(dependencies, async (dep) => { - const filePaths = find.fileSync(dep, sourcePath) - if (filePaths.length) { - const fileContent = await readFile(filePaths[0]) - foundDependencies.push(dep) - dependencyPaths.push( - ...(await getDependencyPaths(fileContent, tgtMacros)) - ) - } - dependencyPaths.push(...filePaths) - }) - } else { - const errorMessage = `Source path ${sourcePath} does not exist.` - - console.log(chalk.redBright(errorMessage)) - - const unFoundDependencies = diff(dependencies, foundDependencies) - - if (unFoundDependencies.length) { - console.log( - `${chalk.redBright( - 'Unable to locate dependencies: ' + unFoundDependencies.join(', ') - )}` - ) - } - - throw errorMessage - } - }) - - dependencyPaths = prioritiseDependencyOverrides( - dependencies, - dependencyPaths, - tgtMacros - ) - - return [...new Set(dependencyPaths)] - } else { - return [] - } -} - -export function prioritiseDependencyOverrides( - dependencyNames, - dependencyPaths, - tgtMacros = [] -) { - dependencyNames.forEach((depFileName) => { - const paths = dependencyPaths.filter((p) => p.includes(`/${depFileName}`)) - - let overriddenDependencyPaths = paths.filter( - (p) => !p.includes('node_modules') - ) - if (tgtMacros.length) { - const foundInTgtMacros = overriddenDependencyPaths.filter((p) => { - const pathExist = tgtMacros.find((tm) => p.includes(tgtMacros)) - return pathExist ? true : false - }) - if (foundInTgtMacros.length) overriddenDependencyPaths = foundInTgtMacros - } - - if ( - overriddenDependencyPaths.length && - overriddenDependencyPaths.length != paths.length - ) { - const pathsToRemove = paths.filter( - (el) => overriddenDependencyPaths.indexOf(el) < 0 - ) - dependencyPaths = dependencyPaths.filter( - (el) => pathsToRemove.indexOf(el) < 0 - ) - if (overriddenDependencyPaths.length > 1) { - // remove duplicates - dependencyPaths = dependencyPaths.filter( - (p) => p != overriddenDependencyPaths[0] - ) - dependencyPaths.push(overriddenDependencyPaths[0]) - } - } - }) - - return dependencyPaths -} - -async function getAllServices(pathToFile) { - const configuration = await getConfiguration(pathToFile) - let allServices = [] - - if (configuration && configuration.cmnServices) - allServices = [...allServices, ...configuration.cmnServices] - - if (targetToBuild && targetToBuild.tgtServices) - allServices = [...allServices, ...targetToBuild.tgtServices] - - return Promise.resolve(allServices) -} - -async function validCompiled(buildFolders) { - const pathExists = await fileExists(buildDestinationFolder) - if (!pathExists) - return { - compiled: false, - message: `Build Folder doesn't exists: ${buildDestinationFolder}` - } - - const subFolders = await getSubFoldersInFolder(buildDestinationServ) - const present = buildFolders.every((folder) => subFolders.includes(folder)) - if (present) { - let returnObj = { - compiled: true, - message: `All services are already present.` - } - await asyncForEach(buildFolders, async (buildFolder) => { - if (returnObj.compiled) { - const folderPath = path.join(buildDestinationServ, buildFolder) - const subFolders = await getSubFoldersInFolder(folderPath) - const filesNamesInPath = await getFilesInFolder(folderPath) - if (subFolders.length == 0 && filesNamesInPath.length == 0) { - returnObj = { - compiled: false, - message: `Service folder ${buildFolder} is empty.` - } - } - } - }) - return returnObj - } - return { compiled: false, message: 'All services are not present.' } -} diff --git a/src/utils/config-utils.js b/src/utils/config-utils.js index f3ac86880..ec5137edd 100644 --- a/src/utils/config-utils.js +++ b/src/utils/config-utils.js @@ -10,11 +10,18 @@ import path from 'path' import chalk from 'chalk' export async function getConfiguration(pathToFile) { - const config = await readFile(pathToFile, false, true).catch(() => null) + console.log(`[pathToFile]`, pathToFile) + const config = await readFile(pathToFile, false, true).catch((err) => + console.log(err) + ) + + console.log(`[config]`, config) + if (config) { const configJson = JSON.parse(config) return Promise.resolve(configJson.config ? configJson.config : configJson) } + return Promise.resolve(null) } diff --git a/test/commands/add/sasjs-add.spec.js b/test/commands/add/add.spec.js similarity index 100% rename from test/commands/add/sasjs-add.spec.js rename to test/commands/add/add.spec.js diff --git a/test/commands/cbd/sasjs-cbd.spec.js b/test/commands/cbd/cbd.spec.js similarity index 100% rename from test/commands/cbd/sasjs-cbd.spec.js rename to test/commands/cbd/cbd.spec.js diff --git a/test/commands/compile/getDependencyPaths.spec.js b/test/commands/compile/getDependencyPaths.spec.js index 88e0bff26..467cf46ac 100644 --- a/test/commands/compile/getDependencyPaths.spec.js +++ b/test/commands/compile/getDependencyPaths.spec.js @@ -3,7 +3,7 @@ import { readFile } from '../../../src/utils/file-utils' import { getDependencyPaths, prioritiseDependencyOverrides -} from '../../../src/sasjs-build/index' +} from '../../../src/commands/build' process.projectDir = path.join(process.cwd()) describe('getDependencyPaths', () => { diff --git a/test/commands/compile/getProgramDependencies.spec.js b/test/commands/compile/getProgramDependencies.spec.js index eaffbb96d..77c06e23b 100644 --- a/test/commands/compile/getProgramDependencies.spec.js +++ b/test/commands/compile/getProgramDependencies.spec.js @@ -5,7 +5,7 @@ import { getProgramList, validateFileRef, validateProgramsList -} from '../../../src/sasjs-build/index' +} from '../../../src/commands/build' process.projectDir = path.join(process.cwd()) diff --git a/test/commands/context/sasjs-context.spec.js b/test/commands/context/context.spec.js similarity index 98% rename from test/commands/context/sasjs-context.spec.js rename to test/commands/context/context.spec.js index 78752aac9..eeee22140 100644 --- a/test/commands/context/sasjs-context.spec.js +++ b/test/commands/context/context.spec.js @@ -1,6 +1,6 @@ import dotenv from 'dotenv' import path from 'path' -import { processContext } from '../../../src/sasjs-context/index' +import { processContext } from '../../../src/commands/context/index' import { sanitizeFileName, readFile, diff --git a/test/commands/create/sasjs-create.spec.js b/test/commands/create/create.spec.js similarity index 100% rename from test/commands/create/sasjs-create.spec.js rename to test/commands/create/create.spec.js diff --git a/test/commands/db/sasjs-db.spec.js b/test/commands/db/db.spec.js similarity index 100% rename from test/commands/db/sasjs-db.spec.js rename to test/commands/db/db.spec.js diff --git a/test/commands/job/sasjs-job.spec.js b/test/commands/job/job.spec.js similarity index 96% rename from test/commands/job/sasjs-job.spec.js rename to test/commands/job/job.spec.js index a277805f6..a2439321c 100644 --- a/test/commands/job/sasjs-job.spec.js +++ b/test/commands/job/job.spec.js @@ -1,7 +1,7 @@ import dotenv from 'dotenv' import path from 'path' -import { processJob } from '../../../src/sasjs-job/index' -import { processContext } from '../../../src/sasjs-context/index' +import { processJob } from '../../../src/commands/job/index' +import { processContext } from '../../../src/commands/context/index' import { folderExists, fileExists } from '../../../src/utils/file-utils' describe('sasjs job', () => { diff --git a/test/commands/request/sasjs-request.spec.js b/test/commands/request/request.spec.js similarity index 100% rename from test/commands/request/sasjs-request.spec.js rename to test/commands/request/request.spec.js diff --git a/test/sasjs-run.spec.js b/test/commands/run/run.spec.js similarity index 85% rename from test/sasjs-run.spec.js rename to test/commands/run/run.spec.js index 923f5f04e..99209a004 100644 --- a/test/sasjs-run.spec.js +++ b/test/commands/run/run.spec.js @@ -1,4 +1,4 @@ -import { runSasCode } from '../src/sasjs-run/index' +import { runSasCode } from '../../../src/commands/run' describe('sasjs run', () => { describe('runSasCode', () => { diff --git a/test/commands/servicepack/sasjs-servicepack.spec.js b/test/commands/servicepack/servicepack.spec.js similarity index 95% rename from test/commands/servicepack/sasjs-servicepack.spec.js rename to test/commands/servicepack/servicepack.spec.js index 9c4349a22..6cae7c0c9 100644 --- a/test/commands/servicepack/sasjs-servicepack.spec.js +++ b/test/commands/servicepack/servicepack.spec.js @@ -1,6 +1,6 @@ import dotenv from 'dotenv' import path from 'path' -import { processServicepack } from '../../../src/sasjs-servicepack/index' +import { processServicepack } from '../../../src/commands/servicepack' describe('sasjs servicepack', () => { const targetName = 'cli-tests-servicepack' From 2071460ca0e299d5ac196c70df0db7448479af3c Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 4 Nov 2020 09:25:07 +0300 Subject: [PATCH 02/27] refactor(sasjs-commands): centralized commands export --- src/commands/{add/index.js => add.js} | 14 +- src/commands/build.js | 851 ++++++++++++++++++ src/commands/{create/index.js => create.js} | 8 +- src/commands/{db/index.js => db.js} | 6 +- src/commands/{deploy/index.js => deploy.js} | 14 +- src/commands/{help/index.js => help.js} | 0 src/commands/index.js | 38 + src/commands/{request/index.js => request.js} | 8 +- src/commands/{run/index.js => run.js} | 6 +- src/commands/{version/index.js => version.js} | 0 src/main.js | 30 +- 11 files changed, 931 insertions(+), 44 deletions(-) rename src/commands/{add/index.js => add.js} (95%) create mode 100644 src/commands/build.js rename src/commands/{create/index.js => create.js} (90%) rename src/commands/{db/index.js => db.js} (95%) rename src/commands/{deploy/index.js => deploy.js} (97%) rename src/commands/{help/index.js => help.js} (100%) create mode 100644 src/commands/index.js rename src/commands/{request/index.js => request.js} (92%) rename src/commands/{run/index.js => run.js} (96%) rename src/commands/{version/index.js => version.js} (100%) diff --git a/src/commands/add/index.js b/src/commands/add.js similarity index 95% rename from src/commands/add/index.js rename to src/commands/add.js index b8e30ec86..9f7f49573 100644 --- a/src/commands/add/index.js +++ b/src/commands/add.js @@ -1,5 +1,5 @@ -import { create } from '../create' -import { getAndValidateField } from '../../utils/input-utils' +import { create } from './create' +import { getAndValidateField } from '../utils/input-utils' import chalk from 'chalk' import path from 'path' import SASjs from '@sasjs/adapter/node' @@ -8,9 +8,9 @@ import { getGlobalRcFile, saveGlobalRcFile, getConfiguration -} from '../../utils/config-utils' -import { createFile } from '../../utils/file-utils' -import { getNewAccessToken } from '../../utils/auth-utils' +} from '../utils/config-utils' +import { createFile } from '../utils/file-utils' +import { getNewAccessToken } from '../utils/auth-utils' export async function addTarget() { const scope = await getAndValidateScope() @@ -61,7 +61,7 @@ export async function addTarget() { } async function getLocalConfig() { - const buildSourceFolder = require('../../constants').buildSourceFolder + const buildSourceFolder = require('../constants').buildSourceFolder const config = await getConfiguration( path.join(buildSourceFolder, 'sasjsconfig.json') ) @@ -70,7 +70,7 @@ async function getLocalConfig() { } async function saveToLocalConfig(buildTarget) { - const buildSourceFolder = require('../../constants').buildSourceFolder + const buildSourceFolder = require('../constants').buildSourceFolder let config = await getLocalConfig() if (config) { if (config.targets && config.targets.length) { diff --git a/src/commands/build.js b/src/commands/build.js new file mode 100644 index 000000000..b1c1a3e59 --- /dev/null +++ b/src/commands/build.js @@ -0,0 +1,851 @@ +import find from 'find' +import path from 'path' +import chalk from 'chalk' +import uniqBy from 'lodash.uniqby' +import groupBy from 'lodash.groupby' +import { deploy } from './deploy' +import { createWebAppServices } from './web' +import { + readFile, + getSubFoldersInFolder, + getFilesInFolder, + createFile, + createFolder, + deleteFolder, + fileExists, + folderExists, + copy +} from '../utils/file-utils' +import { asyncForEach, removeComments, chunk, diff } from '../utils/utils' +import { + getSourcePaths, + getConfiguration, + findTargetInConfiguration, + getTargetSpecificFile, + getMacroCorePath, + getProgramFolders +} from '../utils/config-utils' + +let buildSourceFolder = '' +let buildDestinationFolder = '' +let buildDestinationServ = '' +let targetToBuild = null + +export async function build( + targetName = null, + compileOnly = false, + compileBuildOnly = false, + compileBuildDeployOnly = false, + isForced = false +) { + const CONSTANTS = require('../constants') + buildSourceFolder = CONSTANTS.buildSourceFolder + buildDestinationFolder = CONSTANTS.buildDestinationFolder + buildDestinationServ = CONSTANTS.buildDestinationServ + const { target } = await findTargetInConfiguration(targetName) + targetToBuild = target + + if (compileBuildDeployOnly) { + await compile(targetName) + await createFinalSasFiles() + return await deploy(targetName, targetToBuild, isForced) + } + if (compileBuildOnly) { + await compile(targetName) + return await createFinalSasFiles() + } + if (compileOnly) return await compile(targetName) + + const servicesToCompile = await getAllServices( + path.join(buildSourceFolder, 'sasjsconfig.json') + ) + const serviceNamesToCompile = servicesToCompile.map((s) => s.split('/').pop()) + const serviceNamesToCompileUniq = [...new Set(serviceNamesToCompile)] + const result = await validCompiled(serviceNamesToCompileUniq) + if (result.compiled) { + // no need to compile again + console.log(chalk.greenBright(result.message)) + console.log(chalk.white('Skipping compiling of build folders...')) + } else { + console.log(chalk.redBright(result.message)) + await compile() + } + await createFinalSasFiles() +} + +async function compile(targetName) { + await copyFilesToBuildFolder() + + const servicesToCompile = await getAllServices( + path.join(buildSourceFolder, 'sasjsconfig.json') + ) + + const serviceNamesToCompile = servicesToCompile.map((s) => s.split('/').pop()) + const serviceNamesToCompileUniq = [...new Set(serviceNamesToCompile)] + + const tgtMacros = targetToBuild ? targetToBuild.tgtMacros : [] + const programFolders = await getProgramFolders(targetName) + + const errors = [] + + await asyncForEach(serviceNamesToCompileUniq, async (buildFolder) => { + const folderPath = path.join(buildDestinationServ, buildFolder) + const subFolders = await getSubFoldersInFolder(folderPath) + const filesNamesInPath = await getFilesInFolder(folderPath) + + await asyncForEach(filesNamesInPath, async (fileName) => { + const filePath = path.join(folderPath, fileName) + + const dependencies = await loadDependencies( + filePath, + tgtMacros, + programFolders + ).catch((err) => { + errors.push(err) + }) + + if (dependencies) await createFile(filePath, dependencies) + }) + + await asyncForEach(subFolders, async (subFolder) => { + const fileNames = await getFilesInFolder(path.join(folderPath, subFolder)) + + await asyncForEach(fileNames, async (fileName) => { + const filePath = path.join(folderPath, subFolder, fileName) + + const dependencies = await loadDependencies( + filePath, + tgtMacros, + programFolders + ).catch((err) => { + errors.push(err) + }) + + if (dependencies) await createFile(filePath, dependencies) + }) + }) + }) + + if (errors.length) throw errors +} + +async function createFinalSasFiles() { + const { + buildOutputFileName, + appLoc, + serverType, + streamWeb, + tgtMacros, + name: tgtName + } = targetToBuild + if (streamWeb) { + await createWebAppServices(null, targetToBuild) + .then(() => + console.log( + chalk.greenBright.bold.italic( + `Web app services have been successfully built!` + ) + ) + ) + .catch((err) => { + console.log( + chalk.redBright( + 'An error has occurred when building web app services.', + err + ) + ) + }) + } + await createFinalSasFile( + buildOutputFileName, + appLoc, + serverType, + tgtMacros, + tgtName + ) +} + +async function createFinalSasFile( + fileName = 'build.sas', + appLoc, + serverType, + tgtMacros = [], + tgtName = 'target' +) { + console.log( + chalk.greenBright(`Creating final ${chalk.cyanBright(fileName)} file`) + ) + let finalSasFileContent = '' + const finalFilePath = path.join(buildDestinationFolder, fileName) + const finalFilePathJSON = path.join(buildDestinationFolder, `${tgtName}.json`) + const buildConfig = await getBuildConfig( + appLoc, + serverType, + tgtMacros + ).catch((_) => {}) + + if (!buildConfig) return + + finalSasFileContent += `\n${buildConfig}` + + const { content: buildInit, path: buildInitPath } = await getBuildInit() + const { + content: buildTermContent, + path: buildTermPath + } = await getBuildTerm() + + console.log(chalk.greenBright(' Loading dependencies for:')) + console.log( + ' BuildInit -', + chalk.greenBright(chalk.cyanBright(buildInitPath)) + ) + console.log( + ' BuildTerm -', + chalk.greenBright(chalk.cyanBright(buildTermPath)) + ) + const dependencyFilePaths = await getDependencyPaths( + `${buildTermContent}\n${buildInit}`, + tgtMacros + ) + const dependenciesContent = await getDependencies(dependencyFilePaths) + + finalSasFileContent += `\n${dependenciesContent}\n\n${buildInit}\n` + + console.log(chalk.greenBright(' - Compiling Services')) + const { folderContent, folderContentJSON } = await getFolderContent( + serverType + ) + finalSasFileContent += `\n${folderContent}` + + finalSasFileContent += `\n${buildTermContent}` + finalSasFileContent = removeComments(finalSasFileContent) + await createFile(finalFilePath, finalSasFileContent) + await createFile( + finalFilePathJSON, + JSON.stringify(folderContentJSON, null, 1) + ) +} + +async function getBuildConfig(appLoc, serverType, tgtMacros = []) { + let buildConfig = '' + const createWebServiceScript = await getCreateWebServiceScript(serverType) + buildConfig += `${createWebServiceScript}\n` + const dependencyFilePaths = await getDependencyPaths(buildConfig, tgtMacros) + const dependenciesContent = await getDependencies(dependencyFilePaths) + const buildVars = await getBuildVars() + return `%global appLoc;\n%let appLoc=%sysfunc(coalescec(&appLoc,${appLoc})); /* metadata or files service location of your app */\n%let syscc=0;\noptions ps=max noquotelenmax;\n${buildVars}\n${dependenciesContent}\n${buildConfig}\n` +} + +async function getCreateWebServiceScript(serverType) { + switch (serverType.toUpperCase()) { + case 'SASVIYA': + return await readFile( + `${getMacroCorePath()}/viya/mv_createwebservice.sas` + ) + + case 'SAS9': + return await readFile( + `${getMacroCorePath()}/meta/mm_createwebservice.sas` + ) + + default: + throw new Error( + `Invalid server type: valid options are ${chalk.cyanBright( + 'SASVIYA' + )} and ${chalk.cyanBright('SAS9')}` + ) + } +} + +function getWebServiceScriptInvocation(serverType) { + switch (serverType.toUpperCase()) { + case 'SASVIYA': + return '%mv_createwebservice(path=&appLoc/&path, name=&service, code=sascode ,replace=yes)' + case 'SAS9': + return '%mm_createwebservice(path=&appLoc/&path, name=&service, code=sascode ,replace=yes)' + default: + throw new Error( + `Invalid server type: valid options are ${chalk.cyanBright( + 'SASVIYA' + )} and ${chalk.cyanBright('SAS9')}` + ) + } +} + +async function getFolderContent(serverType) { + const buildSubFolders = await getSubFoldersInFolder(buildDestinationFolder) + let folderContent = '' + let folderContentJSON = { members: [] } + await asyncForEach(buildSubFolders, async (subFolder) => { + const { content, contentJSON } = await getContentFor( + path.join(buildDestinationFolder, subFolder), + subFolder, + serverType + ) + folderContent += `\n${content}` + folderContentJSON.members.push(contentJSON) + }) + return { folderContent, folderContentJSON } +} + +async function getPreCodeForServicePack(serverType) { + let content = '' + switch (serverType.toUpperCase()) { + case 'SASVIYA': + content += await readFile(`${getMacroCorePath()}/base/mf_getuser.sas`) + content += await readFile(`${getMacroCorePath()}/base/mp_jsonout.sas`) + content += await readFile(`${getMacroCorePath()}/viya/mv_webout.sas`) + content += + '/* if calling viya service with _job param, _program will conflict */\n' + + '/* so we provide instead as __program */\n' + + '%global __program _program;\n' + + '%let _program=%sysfunc(coalescec(&__program,&_program));\n' + + '%macro webout(action,ds,dslabel=,fmt=);\n' + + '%mv_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)\n' + + '%mend;\n' + break + + case 'SAS9': + content += await readFile(`${getMacroCorePath()}/base/mf_getuser.sas`) + content += await readFile(`${getMacroCorePath()}/base/mp_jsonout.sas`) + content += await readFile(`${getMacroCorePath()}/meta/mm_webout.sas`) + content += + ' %macro webout(action,ds,dslabel=,fmt=);\n' + + ' %mm_webout(&action,ds=&ds,dslabel=&dslabel,fmt=&fmt)\n' + + ' %mend;\n' + break + + default: + throw new Error( + `Invalid server type: valid options are ${chalk.cyanBright( + 'SASVIYA' + )} and ${chalk.cyanBright('SAS9')}` + ) + } + return content +} + +async function getContentFor(folderPath, folderName, serverType) { + let content = `\n%let path=${folderName === 'services' ? '' : folderName};\n` + const contentJSON = { + name: folderName, + type: 'folder', + members: [] + } + const files = await getFilesInFolder(folderPath) + const preCode = await getPreCodeForServicePack(serverType) + await asyncForEach(files, async (file) => { + const fileContent = await readFile(path.join(folderPath, file)) + const transformedContent = getServiceText(file, fileContent, serverType) + content += `\n${transformedContent}\n` + contentJSON.members.push({ + name: file.replace('.sas', ''), + type: 'service', + code: removeComments(`${preCode}\n${fileContent}`) + }) + }) + const subFolders = await getSubFoldersInFolder(folderPath) + await asyncForEach(subFolders, async (subFolder) => { + const { + content: childContent, + contentJSON: childContentJSON + } = await getContentFor( + path.join(folderPath, subFolder), + subFolder, + serverType + ) + contentJSON.members.push(childContentJSON) + content += childContent + }) + return { content, contentJSON } +} + +function getServiceText(serviceFileName, fileContent, serverType) { + const serviceName = serviceFileName.replace('.sas', '') + const sourceCodeLines = getLines(removeComments(fileContent)) + let content = `` + sourceCodeLines.forEach((line) => { + const escapedLine = line.split("'").join("''") + if (escapedLine.trim()) { + content += `\n put '${escapedLine.trim()}';` + } + }) + return `%let service=${serviceName}; +filename sascode temp lrecl=32767; +data _null_; +file sascode; +${content}\n +run; +${getWebServiceScriptInvocation(serverType)} +filename sascode clear; +` +} + +function getLines(text) { + let lines = text + .replace(/\r\n/g, '\n') + .split('\n') + .map((l) => l.trim()) + return lines +} + +async function copyFilesToBuildFolder() { + await recreateBuildFolder() + console.log(chalk.greenBright('Copying files to build folder...')) + const servicesToCompile = await getAllServices( + path.join(buildSourceFolder, 'sasjsconfig.json') + ) + await asyncForEach(servicesToCompile, async (buildFolder) => { + const sourcePath = path.join(buildSourceFolder, buildFolder) + const buildFolderName = buildFolder.split('/').pop() + const destinationPath = path.join(buildDestinationServ, buildFolderName) + await copy(sourcePath, destinationPath) + }) +} + +async function recreateBuildFolder() { + console.log(chalk.greenBright('Recreating to build folder...')) + const pathExists = await fileExists(buildDestinationFolder) + if (pathExists) { + // delete everything other than, db folder + const subFolders = await getSubFoldersInFolder(buildDestinationFolder) + const subFiles = await getFilesInFolder(buildDestinationFolder) + await asyncForEach([...subFolders, ...subFiles], async (subFolder) => { + if (subFolder == 'db') return + const subFolderPath = path.join(buildDestinationFolder, subFolder) + await deleteFolder(subFolderPath) + }) + } else await createFolder(buildDestinationFolder) + await createFolder(path.join(buildDestinationServ)) +} + +export async function loadDependencies(filePath, tgtMacros, programFolders) { + console.log( + chalk.greenBright('Loading dependencies for', chalk.cyanBright(filePath)) + ) + let fileContent = await readFile(filePath) + const serviceVars = await getServiceVars() + const serviceInit = await getServiceInit() + const serviceTerm = await getServiceTerm() + const dependencyFilePaths = await getDependencyPaths( + `${fileContent}\n${serviceInit}\n${serviceTerm}`, + tgtMacros + ) + const programDependencies = await getProgramDependencies( + fileContent, + programFolders, + buildSourceFolder + ) + + const dependenciesContent = await getDependencies(dependencyFilePaths) + fileContent = `* Service Variables start;\n${serviceVars}\n*Service Variables end;\n* Dependencies start;\n${dependenciesContent}\n* Dependencies end;\n* Programs start;\n${programDependencies}\n*Programs end;\n* ServiceInit start;\n${serviceInit}\n* ServiceInit end;\n* Service start;\n${fileContent}\n* Service end;\n* ServiceTerm start;\n${serviceTerm}\n* ServiceTerm end;` + + return fileContent +} + +async function getBuildInit() { + return await getTargetSpecificFile('BuildInit', targetToBuild) +} + +async function getServiceInit() { + return (await getTargetSpecificFile('ServiceInit', targetToBuild)).content +} + +async function getServiceTerm() { + return (await getTargetSpecificFile('ServiceTerm', targetToBuild)).content +} + +async function getBuildTerm() { + return await getTargetSpecificFile('BuildTerm', targetToBuild) +} + +async function getTargetSpecificVars(typeOfVars) { + let variables = {} + const configuration = await getConfiguration( + path.join(buildSourceFolder, 'sasjsconfig.json') + ) + + if (configuration && configuration[`cmn${typeOfVars}`]) + variables = { ...configuration[`cmn${typeOfVars}`] } + + if (targetToBuild && targetToBuild[`tgt${typeOfVars}`]) + variables = { ...variables, ...targetToBuild[`tgt${typeOfVars}`] } + + const entries = Object.entries(variables) + let varsContent = '\n' + for (const [name, value] of entries) { + const chunks = chunk(value) + const chunkedString = chunks.join('%trim(\n)') + varsContent += `%let ${name}=${chunkedString};\n` + } + + return varsContent +} + +export async function getServiceVars() { + return await getTargetSpecificVars('ServiceVars') +} + +export async function getBuildVars() { + return await getTargetSpecificVars('BuildVars') +} + +async function getDependencies(filePaths) { + let dependenciesContent = [] + await asyncForEach(filePaths, async (filePath) => { + const depFileContent = await readFile(filePath) + dependenciesContent.push(depFileContent) + }) + + return dependenciesContent.join('\n') +} + +export function getProgramList(fileContent) { + let fileHeader + try { + const hasFileHeader = fileContent.split('/**')[0] !== fileContent + if (!hasFileHeader) return [] + fileHeader = fileContent.split('/**')[1].split('**/')[0] + } catch (e) { + console.error( + chalk.redBright( + 'File header parse error.\nPlease make sure your file header is in the correct format.' + ) + ) + } + + let programsSection + + try { + programsSection = fileHeader.split(/\ SAS Programs \<\/h4\>/i)[1] + + if (!programsSection) return [] + } catch (e) { + console.error( + chalk.redBright( + 'File header parse error.\nPlease make sure your SAS program dependencies are specified in the correct format.' + ) + ) + } + + const programsList = programsSection + .replace(/\r\n/g, '\n') + .split('\n') + .filter((l) => !!l) + .map((l) => l.trim()) + .filter((l) => l.startsWith('@li')) + .map((l) => l.replace(/\@li/g, '').trim()) + .map((l) => { + const [fileName, fileRef] = l.split(' ') + + if (!fileName) { + throw new Error( + `SAS Program ${fileName} is missing file name. Please specify SAS program dependencies in the format: @li ` + ) + } + + if (fileName && !fileRef) { + throw new Error( + `SAS Program ${fileName} is missing fileref. Please specify SAS program dependencies in the format: @li ` + ) + } + + validateFileRef(fileRef) + return { fileName, fileRef } + }) + + validateProgramsList(programsList) + + return uniqBy(programsList, 'fileName') +} + +export function validateProgramsList(programsList) { + const areFileRefsUnique = + uniqBy( + programsList.map((p) => p.fileRef), + (x) => x.toLocaleUpperCase() + ).length === programsList.length + + if (areFileRefsUnique) { + return true + } + + const duplicatePrograms = [] + programsList.forEach((program, index, list) => { + const duplicates = list.filter( + (p, i) => + i !== index && + p.fileRef.toLocaleUpperCase() === program.fileRef.toLocaleUpperCase() && + !duplicatePrograms.some( + (d) => + d.fileName === p.fileName && + d.fileRef.toLocaleUpperCase() === p.fileRef.toLocaleUpperCase() + ) + ) + duplicatePrograms.push(...duplicates) + }) + const groupedDuplicates = groupBy(duplicatePrograms, (x) => + x.fileRef.toLocaleUpperCase() + ) + let errorMessage = '' + Object.keys(groupedDuplicates).forEach((fileRef) => { + errorMessage += `The following files have duplicate fileref '${fileRef}':\n${groupedDuplicates[ + fileRef + ] + .map((d) => d.fileName) + .join(', ')}\n` + }) + throw new Error(errorMessage) +} + +export function validateFileRef(fileRef) { + if (!fileRef) { + throw new Error('Missing file ref.') + } + + if (fileRef.length > 8) { + throw new Error( + 'File ref is too long. File refs can have a maximum of 8 characters.' + ) + } + + if (!/^[_a-zA-Z][_a-zA-Z0-9]*/.test(fileRef)) { + throw new Error( + 'Invalid file ref. File refs can only start with a letter or an underscore, and contain only letters, numbers and underscores.' + ) + } + + return true +} + +export async function getProgramDependencies( + fileContent, + programFolders, + buildSourceFolder +) { + programFolders = uniqBy(programFolders) + const programs = getProgramList(fileContent) + if (programs.length) { + const foundPrograms = [] + await asyncForEach(programFolders, async (programFolder) => { + await asyncForEach(programs, async (program) => { + const filePath = path.join(buildSourceFolder, programFolder) + const filePaths = find.fileSync(program.fileName, filePath) + if (filePaths.length) { + const fileContent = await readFile(filePaths[0]) + if (!fileContent) { + console.log( + chalk.yellowBright(`File ${program.fileName} is empty.`) + ) + } + const programDependencyContent = getProgramDependencyText( + fileContent, + program.fileRef + ) + foundPrograms.push(programDependencyContent) + } else { + console.log( + chalk.yellowBright( + `Skipping ${program.fileName} as program file was not found. Please check your SAS program dependencies.\n` + ) + ) + } + }) + }) + + return foundPrograms.join('\n') + } + + return '' +} + +function getProgramDependencyText(fileContent, fileRef) { + let output = `filename ${fileRef} temp;\ndata _null_;\nfile ${fileRef} lrecl=32767;\n` + + const sourceLines = fileContent + .replace(/\r\n/g, '\n') + .split('\n') + .filter((l) => !!l) + + sourceLines.forEach((line) => { + const chunkedLines = chunk(line) + if (chunkedLines.length === 1) { + output += `put '${chunkedLines[0].split("'").join("''")}';\n` + } else { + let combinedLines = '' + chunkedLines.forEach((chunkedLine, index) => { + let text = `put '${chunkedLine.split("'").join("''")}'` + if (index !== chunkedLines.length - 1) { + text += '@;\n' + } else { + text += ';\n' + } + combinedLines += text + }) + output += combinedLines + } + }) + + output += 'run;' + + return output +} + +export async function getDependencyPaths(fileContent, tgtMacros = []) { + const sourcePaths = await getSourcePaths(buildSourceFolder) + if (tgtMacros.length) { + tgtMacros.forEach((tm) => { + const tgtMacroPath = path.join(buildSourceFolder, tm) + sourcePaths.push(tgtMacroPath) + }) + } + const dependenciesStart = fileContent.split('

Dependencies

') + let dependencies = [] + if (dependenciesStart.length > 1) { + let count = 1 + while (count < dependenciesStart.length) { + let dependency = dependenciesStart[count] + .split('**/')[0] + .replace(/\r\n/g, '\n') + .split('\n') + .filter((d) => !!d) + .map((d) => d.replace(/\@li/g, '').replace(/ /g, '')) + .filter((d) => d.endsWith('.sas')) + dependencies = [...dependencies, ...dependency] + count++ + } + + let dependencyPaths = [] + const foundDependencies = [] + + await asyncForEach(sourcePaths, async (sourcePath) => { + if (await folderExists(sourcePath)) { + await asyncForEach(dependencies, async (dep) => { + const filePaths = find.fileSync(dep, sourcePath) + if (filePaths.length) { + const fileContent = await readFile(filePaths[0]) + foundDependencies.push(dep) + dependencyPaths.push( + ...(await getDependencyPaths(fileContent, tgtMacros)) + ) + } + dependencyPaths.push(...filePaths) + }) + } else { + const errorMessage = `Source path ${sourcePath} does not exist.` + + console.log(chalk.redBright(errorMessage)) + + const unFoundDependencies = diff(dependencies, foundDependencies) + + if (unFoundDependencies.length) { + console.log( + `${chalk.redBright( + 'Unable to locate dependencies: ' + unFoundDependencies.join(', ') + )}` + ) + } + + throw errorMessage + } + }) + + dependencyPaths = prioritiseDependencyOverrides( + dependencies, + dependencyPaths, + tgtMacros + ) + + return [...new Set(dependencyPaths)] + } else { + return [] + } +} + +export function prioritiseDependencyOverrides( + dependencyNames, + dependencyPaths, + tgtMacros = [] +) { + dependencyNames.forEach((depFileName) => { + const paths = dependencyPaths.filter((p) => p.includes(`/${depFileName}`)) + + let overriddenDependencyPaths = paths.filter( + (p) => !p.includes('node_modules') + ) + if (tgtMacros.length) { + const foundInTgtMacros = overriddenDependencyPaths.filter((p) => { + const pathExist = tgtMacros.find((tm) => p.includes(tgtMacros)) + return pathExist ? true : false + }) + if (foundInTgtMacros.length) overriddenDependencyPaths = foundInTgtMacros + } + + if ( + overriddenDependencyPaths.length && + overriddenDependencyPaths.length != paths.length + ) { + const pathsToRemove = paths.filter( + (el) => overriddenDependencyPaths.indexOf(el) < 0 + ) + dependencyPaths = dependencyPaths.filter( + (el) => pathsToRemove.indexOf(el) < 0 + ) + if (overriddenDependencyPaths.length > 1) { + // remove duplicates + dependencyPaths = dependencyPaths.filter( + (p) => p != overriddenDependencyPaths[0] + ) + dependencyPaths.push(overriddenDependencyPaths[0]) + } + } + }) + + return dependencyPaths +} + +async function getAllServices(pathToFile) { + const configuration = await getConfiguration(pathToFile) + let allServices = [] + + if (configuration && configuration.cmnServices) + allServices = [...allServices, ...configuration.cmnServices] + + if (targetToBuild && targetToBuild.tgtServices) + allServices = [...allServices, ...targetToBuild.tgtServices] + + return Promise.resolve(allServices) +} + +async function validCompiled(buildFolders) { + const pathExists = await fileExists(buildDestinationFolder) + if (!pathExists) + return { + compiled: false, + message: `Build Folder doesn't exists: ${buildDestinationFolder}` + } + + const subFolders = await getSubFoldersInFolder(buildDestinationServ) + const present = buildFolders.every((folder) => subFolders.includes(folder)) + if (present) { + let returnObj = { + compiled: true, + message: `All services are already present.` + } + await asyncForEach(buildFolders, async (buildFolder) => { + if (returnObj.compiled) { + const folderPath = path.join(buildDestinationServ, buildFolder) + const subFolders = await getSubFoldersInFolder(folderPath) + const filesNamesInPath = await getFilesInFolder(folderPath) + if (subFolders.length == 0 && filesNamesInPath.length == 0) { + returnObj = { + compiled: false, + message: `Service folder ${buildFolder} is empty.` + } + } + } + }) + return returnObj + } + return { compiled: false, message: 'All services are not present.' } +} diff --git a/src/commands/create/index.js b/src/commands/create.js similarity index 90% rename from src/commands/create/index.js rename to src/commands/create.js index 6d72a1c8f..6b5058c64 100644 --- a/src/commands/create/index.js +++ b/src/commands/create.js @@ -7,19 +7,19 @@ import { createReactApp, createAngularApp, createMinimalApp -} from '../../utils/utils' -import { getFolders, getConfiguration } from '../../utils/config-utils' +} from '../utils/utils' +import { getFolders, getConfiguration } from '../utils/config-utils' import { createFolderStructure, createFolder, createFile, fileExists -} from '../../utils/file-utils' +} from '../utils/file-utils' import chalk from 'chalk' export async function create(parentFolderName = '.', appType = '') { const configPath = - appType === 'sasonly' ? '../../config-sasonly.json' : '../../config.json' + appType === 'sasonly' ? '../config-sasonly.json' : '../config.json' const config = await getConfiguration(path.join(__dirname, configPath)) const fileStructure = await getFileStructure(appType === 'sasonly') console.log(chalk.greenBright('Creating folders and files...')) diff --git a/src/commands/db/index.js b/src/commands/db.js similarity index 95% rename from src/commands/db/index.js rename to src/commands/db.js index a487a0fca..f0aeb9fbb 100644 --- a/src/commands/db/index.js +++ b/src/commands/db.js @@ -9,8 +9,8 @@ import { createFolder, deleteFolder, fileExists -} from '../../utils/file-utils' -import { asyncForEach } from '../../utils/utils' +} from '../utils/file-utils' +import { asyncForEach } from '../utils/utils' const whiteListedDBExtensions = ['ddl', 'sas'] let buildDestinationFolder = '' @@ -18,7 +18,7 @@ let buildSourceDBFolder = '' let buildDestinationDBFolder = '' export async function buildDB() { - const CONSTANTS = require('../../constants') + const CONSTANTS = require('../constants') buildDestinationFolder = CONSTANTS.buildDestinationFolder buildSourceDBFolder = CONSTANTS.buildSourceDBFolder buildDestinationDBFolder = CONSTANTS.buildDestinationDBFolder diff --git a/src/commands/deploy/index.js b/src/commands/deploy.js similarity index 97% rename from src/commands/deploy/index.js rename to src/commands/deploy.js index cad05f921..d81e7d36b 100644 --- a/src/commands/deploy/index.js +++ b/src/commands/deploy.js @@ -1,24 +1,20 @@ import path from 'path' import SASjs from '@sasjs/adapter/node' import chalk from 'chalk' -import { findTargetInConfiguration } from '../../utils/config-utils' -import { - asyncForEach, - executeShellScript, - getVariable -} from '../../utils/utils' +import { findTargetInConfiguration } from '../utils/config-utils' +import { asyncForEach, executeShellScript, getVariable } from '../utils/utils' import { isSasFile, isShellScript, readFile, folderExists, createFile -} from '../../utils/file-utils' +} from '../utils/file-utils' import { getAccessToken, isAccessTokenExpiring, refreshTokens -} from '../../utils/auth-utils' +} from '../utils/auth-utils' let targetToBuild = null let executionSession @@ -164,7 +160,7 @@ async function deployToSasViyaWithServicePack(buildTarget, isForced) { serverType: buildTarget.serverType }) - const CONSTANTS = require('../../constants') + const CONSTANTS = require('../constants') const buildDestinationFolder = CONSTANTS.buildDestinationFolder const finalFilePathJSON = path.join( buildDestinationFolder, diff --git a/src/commands/help/index.js b/src/commands/help.js similarity index 100% rename from src/commands/help/index.js rename to src/commands/help.js diff --git a/src/commands/index.js b/src/commands/index.js new file mode 100644 index 000000000..e52004d3a --- /dev/null +++ b/src/commands/index.js @@ -0,0 +1,38 @@ +export { addTarget } from './add' + +export { + build, + loadDependencies, + getServiceVars, + getBuildVars, + getProgramList, + validateProgramsList, + validateFileRef, + getProgramDependencies, + getDependencyPaths, + prioritiseDependencyOverrides +} from './build' + +export { processContext } from './context' + +export { create } from './create' + +export { buildDB } from './db' + +export { deploy } from './deploy' + +export { folder } from './folder' + +export { printHelpText } from './help' + +export { processJob } from './job' + +export { runSasJob } from './request' + +export { runSasCode } from './run' + +export { processServicepack } from './servicepack' + +export { printVersion } from './version' + +export { createWebAppServices } from './web' diff --git a/src/commands/request/index.js b/src/commands/request.js similarity index 92% rename from src/commands/request/index.js rename to src/commands/request.js index f95300c51..0144ea989 100644 --- a/src/commands/request/index.js +++ b/src/commands/request.js @@ -1,14 +1,14 @@ import path from 'path' import SASjs from '@sasjs/adapter/node' -import { findTargetInConfiguration } from '../../utils/config-utils' +import { findTargetInConfiguration } from '../utils/config-utils' import { readFile, folderExists, createFile, createFolder -} from '../../utils/file-utils' -import { getAccessToken } from '../../utils/config-utils' -import { displayResult } from '../../utils/displayResult' +} from '../utils/file-utils' +import { getAccessToken } from '../utils/config-utils' +import { displayResult } from '../utils/displayResult' export async function runSasJob( sasJobLocation, diff --git a/src/commands/run/index.js b/src/commands/run.js similarity index 96% rename from src/commands/run/index.js rename to src/commands/run.js index c1c9a2750..d5fb6b5cb 100644 --- a/src/commands/run/index.js +++ b/src/commands/run.js @@ -4,9 +4,9 @@ import SASjs from '@sasjs/adapter/node' import { findTargetInConfiguration, getAccessToken -} from '../../utils/config-utils' -import { readFile, createFile } from '../../utils/file-utils' -import { getVariable, generateTimestamp } from '../../utils/utils' +} from '../utils/config-utils' +import { readFile, createFile } from '../utils/file-utils' +import { getVariable, generateTimestamp } from '../utils/utils' /** * Runs SAS code from a given file on the specified target. diff --git a/src/commands/version/index.js b/src/commands/version.js similarity index 100% rename from src/commands/version/index.js rename to src/commands/version.js diff --git a/src/main.js b/src/main.js index 058ed15f9..a70275ce9 100644 --- a/src/main.js +++ b/src/main.js @@ -1,17 +1,19 @@ -import { build } from './commands/build' -import { deploy } from './commands/deploy' -import { processServicepack } from './commands/servicepack' -import { buildDB } from './commands/db' -import { create } from './commands/create' -import { printHelpText } from './commands/help' -import { printVersion } from './commands/version' -import { createWebAppServices } from './commands/web' -import { addTarget } from './commands/add' -import { runSasCode } from './commands/run' -import { runSasJob } from './commands/request' -import { processContext } from './commands/context' -import { folder } from './commands/folder' -import { processJob } from './commands/job' +import { + addTarget, + build, + processContext, + create, + buildDB, + deploy, + folder, + printHelpText, + processJob, + runSasJob, + runSasCode, + processServicepack, + printVersion, + createWebAppServices +} from './commands' import chalk from 'chalk' import { displayResult } from './utils/displayResult' From bb69ff3e16902e030bc80a6659f1a1eb20ded52a Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 4 Nov 2020 09:39:33 +0300 Subject: [PATCH 03/27] refactor(sasjs-command-tests): refactored file structure --- test/commands/{add => }/add.spec.js | 13 +++++-------- test/commands/{context => }/context.spec.js | 4 ++-- test/commands/{create => }/create.spec.js | 6 +++--- test/commands/{db => }/db.spec.js | 4 ++-- test/commands/{job => }/job.spec.js | 6 +++--- test/commands/{run => }/run.spec.js | 2 +- 6 files changed, 16 insertions(+), 19 deletions(-) rename test/commands/{add => }/add.spec.js (92%) rename test/commands/{context => }/context.spec.js (96%) rename test/commands/{create => }/create.spec.js (96%) rename test/commands/{db => }/db.spec.js (89%) rename test/commands/{job => }/job.spec.js (93%) rename test/commands/{run => }/run.spec.js (85%) diff --git a/test/commands/add/add.spec.js b/test/commands/add.spec.js similarity index 92% rename from test/commands/add/add.spec.js rename to test/commands/add.spec.js index 93c82157e..7fde0d1a7 100644 --- a/test/commands/add/add.spec.js +++ b/test/commands/add.spec.js @@ -1,12 +1,9 @@ import dotenv from 'dotenv' import path from 'path' -import { add } from '../../../src/main' -import { - getConfiguration, - getGlobalRcFile -} from '../../../src/utils/config-utils' -import { deleteFolder } from '../../../src/utils/file-utils' -import { generateTimestamp } from '../../../src/utils/utils' +import { add } from '../../src/main' +import { getConfiguration, getGlobalRcFile } from '../../src/utils/config-utils' +import { deleteFolder } from '../../src/utils/file-utils' +import { generateTimestamp } from '../../src/utils/utils' describe('sasjs add', () => { const testingAppFolder = 'cli-tests-add' @@ -53,7 +50,7 @@ describe('sasjs add', () => { await expect(add()).resolves.toEqual(true) - const buildSourceFolder = require('../../../src/constants') + const buildSourceFolder = require('../../src/constants') .buildSourceFolder const config = await getConfiguration( path.join(buildSourceFolder, 'sasjsconfig.json') diff --git a/test/commands/context/context.spec.js b/test/commands/context.spec.js similarity index 96% rename from test/commands/context/context.spec.js rename to test/commands/context.spec.js index eeee22140..7580de89d 100644 --- a/test/commands/context/context.spec.js +++ b/test/commands/context.spec.js @@ -1,11 +1,11 @@ import dotenv from 'dotenv' import path from 'path' -import { processContext } from '../../../src/commands/context/index' +import { processContext } from '../../src/commands' import { sanitizeFileName, readFile, createFile -} from '../../../src/utils/file-utils' +} from '../../src/utils/file-utils' let contexts let testContextConfig diff --git a/test/commands/create/create.spec.js b/test/commands/create.spec.js similarity index 96% rename from test/commands/create/create.spec.js rename to test/commands/create.spec.js index 9d3f4abd6..8b044aa2d 100644 --- a/test/commands/create/create.spec.js +++ b/test/commands/create.spec.js @@ -1,9 +1,9 @@ import dotenv from 'dotenv' import path from 'path' import rimraf from 'rimraf' -import { createFileStructure } from '../../../src/main' -import { createFolder } from '../../../src/utils/file-utils' -import { generateTimestamp } from '../../../src/utils/utils' +import { createFileStructure } from '../../src/main' +import { createFolder } from '../../src/utils/file-utils' +import { generateTimestamp } from '../../src/utils/utils' describe('sasjs create', () => { beforeAll(() => { diff --git a/test/commands/db/db.spec.js b/test/commands/db.spec.js similarity index 89% rename from test/commands/db/db.spec.js rename to test/commands/db.spec.js index efbbbfead..a26367085 100644 --- a/test/commands/db/db.spec.js +++ b/test/commands/db.spec.js @@ -1,7 +1,7 @@ import dotenv from 'dotenv' import path from 'path' -import { createFileStructure, buildDBs } from '../../../src/main' -import { createFolder, deleteFolder } from '../../../src/utils/file-utils' +import { createFileStructure, buildDBs } from '../../src/main' +import { createFolder, deleteFolder } from '../../src/utils/file-utils' describe('sasjs db', () => { const testingAppFolder = 'cli-tests-db' diff --git a/test/commands/job/job.spec.js b/test/commands/job.spec.js similarity index 93% rename from test/commands/job/job.spec.js rename to test/commands/job.spec.js index a2439321c..bb12fe5a2 100644 --- a/test/commands/job/job.spec.js +++ b/test/commands/job.spec.js @@ -1,8 +1,8 @@ import dotenv from 'dotenv' import path from 'path' -import { processJob } from '../../../src/commands/job/index' -import { processContext } from '../../../src/commands/context/index' -import { folderExists, fileExists } from '../../../src/utils/file-utils' +import { processJob } from '../../src/commands' +import { processContext } from '../../src/commands' +import { folderExists, fileExists } from '../../src/utils/file-utils' describe('sasjs job', () => { const targetName = 'cli-tests-job' diff --git a/test/commands/run/run.spec.js b/test/commands/run.spec.js similarity index 85% rename from test/commands/run/run.spec.js rename to test/commands/run.spec.js index 99209a004..cda51bb37 100644 --- a/test/commands/run/run.spec.js +++ b/test/commands/run.spec.js @@ -1,4 +1,4 @@ -import { runSasCode } from '../../../src/commands/run' +import { runSasCode } from '../../src/commands' describe('sasjs run', () => { describe('runSasCode', () => { From c551de32d077131e378ad387bd5ad7fe7561804b Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 4 Nov 2020 10:23:45 +0300 Subject: [PATCH 04/27] fix: removed unnecessary console.log --- src/utils/config-utils.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/utils/config-utils.js b/src/utils/config-utils.js index ec5137edd..847e476bf 100644 --- a/src/utils/config-utils.js +++ b/src/utils/config-utils.js @@ -15,8 +15,6 @@ export async function getConfiguration(pathToFile) { console.log(err) ) - console.log(`[config]`, config) - if (config) { const configJson = JSON.parse(config) return Promise.resolve(configJson.config ? configJson.config : configJson) From 9bbc4b30b2d340b84e941071114bc54b532cecce Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 4 Nov 2020 11:36:29 +0300 Subject: [PATCH 05/27] chore: removed unnecessary console.log --- src/utils/config-utils.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/config-utils.js b/src/utils/config-utils.js index 847e476bf..d9ac772e4 100644 --- a/src/utils/config-utils.js +++ b/src/utils/config-utils.js @@ -10,7 +10,6 @@ import path from 'path' import chalk from 'chalk' export async function getConfiguration(pathToFile) { - console.log(`[pathToFile]`, pathToFile) const config = await readFile(pathToFile, false, true).catch((err) => console.log(err) ) From 629b2816c5c362331e276ae55db0afc112f82fda Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 4 Nov 2020 11:44:14 +0300 Subject: [PATCH 06/27] feat: added 'getFlagValue' method --- src/utils/command.js | 11 ++++++++++- src/utils/utils.js | 3 +++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/utils/command.js b/src/utils/command.js index 75c8d1f9f..a79677bc0 100644 --- a/src/utils/command.js +++ b/src/utils/command.js @@ -1,4 +1,5 @@ import { displayResult } from './displayResult' +import { arrToObj } from './utils' const showInvalidCommandMessage = () => { displayResult( @@ -6,7 +7,6 @@ const showInvalidCommandMessage = () => { `Invalid command. Run 'sasjs help' to get the list of valid commands.` ) } -const arrToObj = (arr) => arr.reduce((o, key) => ({ ...o, [key]: key }), {}) const initialCommands = arrToObj([ ...new Set([ @@ -174,6 +174,15 @@ export class Command { } } } + + getFlagValue(flagName) { + const flag = this.flags.find((flag) => flag.name === flagName) + + if (!flag) return undefined + if (!flag.withValue) return null + + return flag.value + } } class Flag { diff --git a/src/utils/utils.js b/src/utils/utils.js index 2c6b93c44..709916bd6 100644 --- a/src/utils/utils.js +++ b/src/utils/utils.js @@ -218,3 +218,6 @@ export function generateTimestamp() { return timestamp } + +export const arrToObj = (arr) => + arr.reduce((o, key) => ({ ...o, [key]: key }), {}) From 6dc9811ba2fac409eef740410ecce8b73434eff9 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 4 Nov 2020 11:45:36 +0300 Subject: [PATCH 07/27] refactor(sasjs-context): refactored 'sasjs-context' command to use 'Command utility' --- src/commands/context/index.js | 95 +++++++++++------------------------ test/commands/context.spec.js | 33 ++---------- 2 files changed, 34 insertions(+), 94 deletions(-) diff --git a/src/commands/context/index.js b/src/commands/context/index.js index aebffce4d..473c96417 100644 --- a/src/commands/context/index.js +++ b/src/commands/context/index.js @@ -7,11 +7,13 @@ import { exportContext } from './export' import { fileExists, readFile } from '../../utils/file-utils' import { getBuildTarget, getAccessToken } from '../../utils/config-utils' import { displayResult } from '../../utils/displayResult' +import { Command } from '../../utils/command' import SASjs from '@sasjs/adapter/node' export async function processContext(commandLine) { - const command = commandLine[1] - const commands = { + const command = new Command(commandLine) + const subCommand = command.values.shift() + const subCommands = { create: 'create', edit: 'edit', delete: 'delete', @@ -19,11 +21,11 @@ export async function processContext(commandLine) { export: 'export' } - if (!commands.hasOwnProperty(command)) { + if (!subCommands.hasOwnProperty(subCommand)) { console.log( chalk.redBright( `Not supported context command. Supported commands are:\n${Object.keys( - commands + subCommands ).join('\n')}` ) ) @@ -31,56 +33,25 @@ export async function processContext(commandLine) { return } - const commandExample = - 'sasjs context --source ../contextConfig.json --target targetName' - - let targetName = [] - let targetNameFlagIndex = commandLine.indexOf('--target') - - if (targetNameFlagIndex === -1) - targetNameFlagIndex = commandLine.indexOf('-t') - - if (targetNameFlagIndex !== -1) { - for (let i = targetNameFlagIndex + 1; i < commandLine.length; i++) { - if (commandLine[i] === '--source' || commandLine[i] === '-s') { - throw `Target name has to be provided as the last argument (eg ${commandExample})` - } - - targetName.push(commandLine[i]) - } - } - - targetName = targetName.join(' ') - + const targetName = command.getFlagValue('target') const target = await getBuildTarget(targetName) - let configPath - let config - let parsedConfig - let configPathFlagIndex + const commandExample = + 'sasjs context --source ../contextConfig.json --target targetName' const getConfig = async () => { - configPathFlagIndex = commandLine.indexOf('--source') + const configPath = command.getFlagValue('source') - if (configPathFlagIndex === -1) - configPathFlagIndex = commandLine.indexOf('-s') + if (!configPath) { + const message = `'--source' flag is missing (eg '${commandExample}')` - if (configPathFlagIndex === -1) { - console.log( - chalk.redBright(`'--source' flag is missing (eg '${commandExample}')`) - ) + console.log(chalk.redBright(message)) return - } + } else if (!validateConfigPath(configPath)) { + const message = `Provide a path to context config file (eg '${commandExample}')` - configPath = commandLine[configPathFlagIndex + 1] - - if (!configPath || !validateConfigPath(configPath)) { - console.log( - chalk.redBright( - `Provide a path to context config file (eg '${commandExample}')` - ) - ) + console.log(chalk.redBright(message)) return } @@ -88,23 +59,13 @@ export async function processContext(commandLine) { return await readFile(configPath) } - const getContextName = (upToSourceFlag = false) => { - let contextName = '' + const getContextName = () => { + let contextName = command.values.join(' ') - if (targetNameFlagIndex === -1) { - contextName = commandLine.slice(2).join(' ') - } else { - contextName = commandLine - .slice(2, upToSourceFlag ? configPathFlagIndex : targetNameFlagIndex) - .join(' ') - } + if (!contextName) { + const message = `Provide a context name (eg 'sasjs context contextName')` - if (!contextName && !upToSourceFlag) { - console.log( - chalk.redBright( - `Provide a context name (eg 'sasjs context contextName')` - ) - ) + console.log(chalk.redBright(message)) return null } @@ -122,10 +83,12 @@ export async function processContext(commandLine) { displayResult(err) }) + let config + let parsedConfig let output - switch (command) { - case commands.create: + switch (subCommand) { + case subCommands.create: config = await getConfig() if (!config) break @@ -135,7 +98,7 @@ export async function processContext(commandLine) { output = await create(parsedConfig, sasjs, accessToken) break - case commands.edit: { + case subCommands.edit: { config = await getConfig() const contextName = getContextName(true) @@ -146,7 +109,7 @@ export async function processContext(commandLine) { break } - case commands.delete: { + case subCommands.delete: { const contextName = getContextName() if (contextName) { @@ -155,11 +118,11 @@ export async function processContext(commandLine) { break } - case commands.list: + case subCommands.list: output = list(target, sasjs, accessToken) break - case commands.export: { + case subCommands.export: { const contextName = getContextName() if (contextName) { diff --git a/test/commands/context.spec.js b/test/commands/context.spec.js index 7580de89d..78ff7e8be 100644 --- a/test/commands/context.spec.js +++ b/test/commands/context.spec.js @@ -37,9 +37,7 @@ describe('sasjs context', () => { it( 'should list accessible compute contexts', async () => { - const command = ['context', 'list', '-t', targetName] - - contexts = await processContext(command) + contexts = await processContext(`context list -t ${targetName}`) expect(contexts.length).toBeGreaterThan(0) }, @@ -52,7 +50,7 @@ describe('sasjs context', () => { 'should exports compute context', async () => { const contextName = contexts[0].name - const command = ['context', 'export', contextName, '-t', targetName] + const command = `context export ${contextName} -t ${targetName}` await expect(processContext(command)).resolves.toEqual(true) }, @@ -80,14 +78,7 @@ describe('sasjs context', () => { await createFile(testContextConfigPath, contextConfig) - const command = [ - 'context', - 'create', - '-s', - testContextConfigFile, - '-t', - targetName - ] + const command = `context create -s ${testContextConfigFile} -t targetName` await expect(processContext(command)).resolves.toEqual(true) }, @@ -105,15 +96,7 @@ describe('sasjs context', () => { await createFile(testContextConfigPath, contextConfig) - const command = [ - 'context', - 'edit', - ...testContextConfig.name.split(' '), - '-s', - testContextConfigFile, - '-t', - targetName - ] + const command = `context edit ${testContextConfig.name} -s ${testContextConfigFile} -t ${targetName}` await expect(processContext(command)).resolves.toEqual(true) }, @@ -125,13 +108,7 @@ describe('sasjs context', () => { it( 'should delete compute context', async () => { - const command = [ - 'context', - 'delete', - ...testContextConfig.name.split(' '), - '-t', - targetName - ] + const command = `context delete ${testContextConfig.name} -t targetName` await expect(processContext(command)).resolves.toEqual(true) }, From dfbbedaeee42e576149c9d1e17fd91ef59379cc4 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 4 Nov 2020 16:59:22 +0300 Subject: [PATCH 08/27] feat: added 'getFlag' and 'prefixAppLoc' methods --- src/utils/command.js | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/utils/command.js b/src/utils/command.js index a79677bc0..33321df01 100644 --- a/src/utils/command.js +++ b/src/utils/command.js @@ -175,14 +175,27 @@ export class Command { } } + getFlag(flagName) { + return this.flags.find((flag) => flag.name === flagName) + } + getFlagValue(flagName) { - const flag = this.flags.find((flag) => flag.name === flagName) + const flag = this.getFlag(flagName) if (!flag) return undefined if (!flag.withValue) return null return flag.value } + + prefixAppLoc(appLoc, path) { + if (!/^\//.test(appLoc)) appLoc = '/' + appLoc + + return path + .split(' ') + .map((p) => (/^\//.test(p) ? path : `${appLoc}/${p}`)) + .join(' ') + } } class Flag { From e3f94edc09bad0edbe390619c866e59e1f4e8e49 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Wed, 4 Nov 2020 17:00:29 +0300 Subject: [PATCH 09/27] refactor(sasjs folder): used command utility --- src/commands/folder/index.js | 72 ++++++++++-------------------------- 1 file changed, 20 insertions(+), 52 deletions(-) diff --git a/src/commands/folder/index.js b/src/commands/folder/index.js index 08be13feb..2d7ea8f71 100644 --- a/src/commands/folder/index.js +++ b/src/commands/folder/index.js @@ -5,65 +5,34 @@ import { displayResult } from '../../utils/displayResult' import { create } from './create' import { move } from './move' import { remove } from './remove' +import { Command } from '../../utils/command' export async function folder(commandLine) { - const command = commandLine[1] - const commands = { + const command = new Command(commandLine) + const subCommand = command.values.shift() + + const subCommands = { create: 'create', move: 'move', delete: 'delete' } - if (!commands.hasOwnProperty(command)) { - console.log( - chalk.redBright( - `Not supported folder command. Supported commands are:\n${Object.keys( - commands - ).join('\n')}` - ) - ) - - return - } - - let forceFlagIndex = commandLine.indexOf('-f') - - if (forceFlagIndex === -1) forceFlagIndex = commandLine.indexOf('--force') - - let targetName = [] - let targetFlagIndex = commandLine.indexOf('-t') - - if (targetFlagIndex === -1) targetFlagIndex = commandLine.indexOf('--target') + if (!subCommands.hasOwnProperty(subCommand)) { + const message = `Not supported folder command. Supported commands are:\n${Object.keys( + subCommands + ).join('\n')}` - if (targetFlagIndex !== -1) { - for (let i = targetFlagIndex + 1; i < commandLine.length; i++) { - if (i === forceFlagIndex) break + console.log(chalk.redBright(message)) - targetName.push(commandLine[i]) - } + return } - targetName = targetName.join(' ') + const forceFlag = command.getFlag('force') + const targetName = command.getFlagValue('target') + let folderPath = command.values.join(' ') const target = await getBuildTarget(targetName) - let folderPath = '' - - if (targetFlagIndex === -1 && forceFlagIndex === -1) { - folderPath = commandLine.slice(2).join(' ') - } else if (targetFlagIndex === -1) { - folderPath = commandLine.slice(2, forceFlagIndex).join(' ') - } else if (forceFlagIndex === -1) { - folderPath = commandLine.slice(2, targetFlagIndex).join(' ') - } else { - folderPath = commandLine - .slice( - 2, - targetFlagIndex > forceFlagIndex ? forceFlagIndex : targetFlagIndex - ) - .join(' ') - } - if (!folderPath) { console.log( chalk.redBright( @@ -74,8 +43,7 @@ export async function folder(commandLine) { return } - // Folder path should has prefix '/' - if (!/^\//.test(folderPath)) folderPath = '/' + folderPath + folderPath = command.prefixAppLoc(target.appLoc, folderPath) const sasjs = new SASjs({ serverUrl: target.serverUrl, @@ -86,16 +54,16 @@ export async function folder(commandLine) { displayResult(err) ) - switch (command) { - case commands.create: - create(folderPath, sasjs, accessToken, forceFlagIndex !== -1) + switch (subCommand) { + case subCommands.create: + create(folderPath, sasjs, accessToken, forceFlag !== undefined) break - case commands.delete: + case subCommands.delete: remove(folderPath, sasjs, accessToken) break - case commands.move: + case subCommands.move: move(folderPath, sasjs, accessToken) break From 2f47aec729aa8b58effdfe606c206b616c9069b4 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Thu, 5 Nov 2020 10:26:35 +0300 Subject: [PATCH 10/27] chore(command): improved 'prefixAppLoc' method --- src/utils/command.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/utils/command.js b/src/utils/command.js index 33321df01..147957ad4 100644 --- a/src/utils/command.js +++ b/src/utils/command.js @@ -183,14 +183,18 @@ export class Command { const flag = this.getFlag(flagName) if (!flag) return undefined - if (!flag.withValue) return null + if (!flag.withValue) return true return flag.value } - prefixAppLoc(appLoc, path) { + prefixAppLoc(appLoc = '', path = '') { + if (!path) return null + if (!/^\//.test(appLoc)) appLoc = '/' + appLoc + if (Array.isArray(path)) path = path.join(' ') + return path .split(' ') .map((p) => (/^\//.test(p) ? path : `${appLoc}/${p}`)) From b8b8fc80a8eb885ec282dd520b3196a06cc4b0b3 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Thu, 5 Nov 2020 10:28:03 +0300 Subject: [PATCH 11/27] refactor(sasjs-job): used command utility --- src/commands/job/execute.js | 2 +- src/commands/job/index.js | 25 +++++-------------------- test/commands/job.spec.js | 2 +- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/src/commands/job/execute.js b/src/commands/job/execute.js index 3d361f83c..2b7f6842a 100644 --- a/src/commands/job/execute.js +++ b/src/commands/job/execute.js @@ -69,7 +69,7 @@ export async function execute( : `Job session`) + ` can be found at ${target.serverUrl + sessionLink}` ) - if (output || log) { + if (output !== undefined || log) { try { const outputJson = JSON.stringify(submittedJob, null, 2) diff --git a/src/commands/job/index.js b/src/commands/job/index.js index 74716a663..19f661e7a 100644 --- a/src/commands/job/index.js +++ b/src/commands/job/index.js @@ -29,29 +29,14 @@ export async function processJob(commandLine) { return } - let targetName = command.flags.find((flag) => flag.name === 'target') - targetName = targetName ? targetName.value : '' - - const waitForJob = - command.flags.find((flag) => flag.name === 'wait') !== undefined - - let output = command.flags.find((flag) => flag.name === 'output') - output = output ? (output.value ? output.value : true) : null - - let log = command.flags.find((flag) => flag.name === 'log') - log = log ? (log.value ? log.value : true) : null + const targetName = command.getFlagValue('target') + const waitForJob = command.getFlagValue('wait') + const output = command.getFlagValue('output') + const log = command.getFlagValue('log') const target = await getBuildTarget(targetName) - let jobPath = command.values.join('') - - if (!/^\//.test(jobPath)) { - const { appLoc } = target - - jobPath = (/\/$/.test(appLoc) ? appLoc : appLoc + '/') + jobPath - } - - jobPath = sanitizeAppLoc(jobPath) + const jobPath = command.prefixAppLoc(target.appLoc, command.values) const sasjs = new SASjs({ serverUrl: target.serverUrl, diff --git a/test/commands/job.spec.js b/test/commands/job.spec.js index bb12fe5a2..ad030dc7c 100644 --- a/test/commands/job.spec.js +++ b/test/commands/job.spec.js @@ -101,7 +101,7 @@ describe('sasjs job', () => { it( 'should submit a job and create a file with job log', async () => { - const command = `job execute /Public/app/cli-tests/testJob -t ${targetName} -l testLog` + const command = `job execute testJob -t ${targetName} -l testLog` const folderPath = path.join(process.cwd(), 'testLog') const filePath = path.join(process.cwd(), 'testLog/testJob-log.json') From 26c8edcce1a41c28a74a93393190899a846ebfed Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Thu, 5 Nov 2020 12:06:17 +0300 Subject: [PATCH 12/27] refactor(sasjs-servicepack): used command utility --- src/commands/servicepack/index.js | 38 +++++++++---------------------- 1 file changed, 11 insertions(+), 27 deletions(-) diff --git a/src/commands/servicepack/index.js b/src/commands/servicepack/index.js index 5ef80c29b..b92755f71 100644 --- a/src/commands/servicepack/index.js +++ b/src/commands/servicepack/index.js @@ -1,23 +1,20 @@ import { servicePackDeploy } from './deploy' -import { - getCommandParameter, - getCommandParameterLastMultiWord, - isFlagPresent -} from '../../utils/command-utils' +import { Command } from '../../utils/command' import chalk from 'chalk' export async function processServicepack(commandLine) { - const command = commandLine[1] - const commands = { + const command = new Command(commandLine) + const subCommand = command.getSubCommand() + const subCommands = { deploy: 'deploy' } - if (!commands.hasOwnProperty(command)) { + if (!subCommands.hasOwnProperty(subCommand)) { console.log( chalk.redBright( `Not supported servicepack command. Supported commands are:\n${Object.keys( - commands + subCommands ).join('\n')}` ) ) @@ -25,27 +22,14 @@ export async function processServicepack(commandLine) { return } - const commandExample = - 'sasjs servicepack --source ../viyadeploy.json --target targetName' + const targetName = command.getFlagValue('target') + const jsonFilePath = command.getFlagValue('source') + const isForced = command.getFlagValue('force') let output - switch (command) { - case commands.deploy: - const targetName = getCommandParameterLastMultiWord( - '-t', - '--target', - commandLine, - commandExample - ) - const jsonFilePath = getCommandParameter( - '-s', - '--source', - commandLine, - commandExample - ) - const isForced = isFlagPresent('-f', commandLine) - + switch (subCommand) { + case subCommands.deploy: output = await servicePackDeploy(jsonFilePath, targetName, isForced) break From 35f6f971a04d8a78e4feefbe654e8815576757ef Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Thu, 5 Nov 2020 12:07:02 +0300 Subject: [PATCH 13/27] chore(command): added 'getSubCommand' method --- src/commands/context/index.js | 2 +- src/commands/folder/index.js | 2 +- src/commands/job/index.js | 2 +- src/utils/command-utils.js | 64 ---------------- src/utils/command.js | 6 +- test/command-utils.spec.js | 135 ---------------------------------- 6 files changed, 8 insertions(+), 203 deletions(-) delete mode 100644 src/utils/command-utils.js delete mode 100644 test/command-utils.spec.js diff --git a/src/commands/context/index.js b/src/commands/context/index.js index 473c96417..406f043ce 100644 --- a/src/commands/context/index.js +++ b/src/commands/context/index.js @@ -12,7 +12,7 @@ import SASjs from '@sasjs/adapter/node' export async function processContext(commandLine) { const command = new Command(commandLine) - const subCommand = command.values.shift() + const subCommand = command.getSubCommand() const subCommands = { create: 'create', edit: 'edit', diff --git a/src/commands/folder/index.js b/src/commands/folder/index.js index 2d7ea8f71..68a2c9976 100644 --- a/src/commands/folder/index.js +++ b/src/commands/folder/index.js @@ -9,7 +9,7 @@ import { Command } from '../../utils/command' export async function folder(commandLine) { const command = new Command(commandLine) - const subCommand = command.values.shift() + const subCommand = command.getSubCommand() const subCommands = { create: 'create', diff --git a/src/commands/job/index.js b/src/commands/job/index.js index 19f661e7a..81e864fd2 100644 --- a/src/commands/job/index.js +++ b/src/commands/job/index.js @@ -12,7 +12,7 @@ import { Command } from '../../utils/command' export async function processJob(commandLine) { const command = new Command(commandLine) - const subCommand = command.values.shift() + const subCommand = command.getSubCommand() const subCommands = { execute: 'execute' } diff --git a/src/utils/command-utils.js b/src/utils/command-utils.js deleted file mode 100644 index a0ddb8bf0..000000000 --- a/src/utils/command-utils.js +++ /dev/null @@ -1,64 +0,0 @@ -import chalk from 'chalk' - -export function isFlagPresent(flag, commandLine) { - return commandLine.indexOf(flag) > -1 -} - -export function getCommandParameter( - commandFlag, - commandFlagLong, - commandLine, - commandExample = '' -) { - let parameterValueFlagIndex = commandLine.indexOf(commandFlagLong) - - if (parameterValueFlagIndex === -1) - parameterValueFlagIndex = commandLine.indexOf(commandFlag) - - if (parameterValueFlagIndex === -1) { - const errorMessage = chalk.redBright( - `'${commandFlag || commandFlagLong}' flag is missing. ${ - commandExample ? "(eg '" + commandExample + "')" : '' - }` - ) - throw new Error(errorMessage) - - return - } - - let parameterValue = commandLine[parameterValueFlagIndex + 1] - - return parameterValue -} - -export function getCommandParameterLastMultiWord( - commandFlag, - commandFlagLong, - commandLine, - commandExample = '' -) { - let parameterValue = [] - let parameterFlagIndex = commandLine.indexOf(commandFlagLong) - - if (parameterFlagIndex === -1) - parameterFlagIndex = commandLine.indexOf(commandFlag) - - if (parameterFlagIndex !== -1) { - for (let i = parameterFlagIndex + 1; i < commandLine.length; i++) { - if (commandLine[i].startsWith('-')) { - const errorMessage = `Parameter '${ - commandFlagLong || commandFlag - }' has to be provided as the last argument ${ - commandExample ? "(eg '" + commandExample + "')" : '' - }` - throw new Error(errorMessage) - } - - parameterValue.push(commandLine[i]) - } - } - - parameterValue = parameterValue.join(' ') - - return parameterValue -} diff --git a/src/utils/command.js b/src/utils/command.js index 147957ad4..85edeaca7 100644 --- a/src/utils/command.js +++ b/src/utils/command.js @@ -78,7 +78,7 @@ const commandFlags = [ }, { command: initialCommands.servicepack, - flags: [initialFlags.target, initialFlags.source] + flags: [initialFlags.target, initialFlags.source, initialFlags.force] }, { command: initialCommands.run, flags: [initialFlags.target] }, { @@ -175,6 +175,10 @@ export class Command { } } + getSubCommand() { + return this.values.shift() + } + getFlag(flagName) { return this.flags.find((flag) => flag.name === flagName) } diff --git a/test/command-utils.spec.js b/test/command-utils.spec.js deleted file mode 100644 index a3443b197..000000000 --- a/test/command-utils.spec.js +++ /dev/null @@ -1,135 +0,0 @@ -import { - isFlagPresent, - getCommandParameter, - getCommandParameterLastMultiWord -} from '../src/utils/command-utils' - -const mockCommandLine = [ - 'test', - '-f', - '-t2', - 'testParam', - '--test3', - 'testLongParam', - '-l', - 'multi', - 'word' -] - -describe('isFlagPresent', () => { - test('passed existing flag', () => { - let flagPresent = isFlagPresent('-f', mockCommandLine) - - expect(flagPresent).toEqual(true) - }) - - test('passed non existing flag', () => { - let flagPresent = isFlagPresent('-a', mockCommandLine) - - expect(flagPresent).toEqual(false) - }) - - test('passed empty string', () => { - let flagPresent = isFlagPresent('', mockCommandLine) - - expect(flagPresent).toEqual(false) - }) - - test('passed null', () => { - let flagPresent = isFlagPresent(null, mockCommandLine) - - expect(flagPresent).toEqual(false) - }) - - test('passed undefined', () => { - let flagPresent = isFlagPresent(undefined, mockCommandLine) - - expect(flagPresent).toEqual(false) - }) -}) - -describe('getCommandParameter', () => { - test('passed short flag and long flag', () => { - let parameter = getCommandParameter('-t', '--test3', mockCommandLine) - - expect(parameter).toEqual('testLongParam') - }) - - test('passed short flag, without long flag', () => { - let parameter = getCommandParameter('-t2', null, mockCommandLine) - - expect(parameter).toEqual('testParam') - }) - - test('passed long flag, without short flag', () => { - let parameter = getCommandParameter(null, '--test3', mockCommandLine) - - expect(parameter).toEqual('testLongParam') - }) - - test('should throw and error when passed non-existing flags', () => { - expect(() => getCommandParameter('-n', '--non', mockCommandLine)).toThrow() - }) -}) - -describe('getCommandParameterLastMultiWord', () => { - test('passed short flag and long flag', () => { - let parameter = getCommandParameterLastMultiWord( - '-l', - '--last', - mockCommandLine - ) - - expect(parameter).toEqual('multi word') - }) - - test('passed short flag without long flag', () => { - let parameter = getCommandParameterLastMultiWord( - '-l', - null, - mockCommandLine - ) - - expect(parameter).toEqual('multi word') - }) - - test('passed long flag without short flag', () => { - let mockCommandLineCustom = [ - 'test', - '-f', - '-t2', - 'testParam', - '--test3', - 'testLongParam', - '--last', - 'multi', - 'word' - ] - - let parameter = getCommandParameterLastMultiWord( - null, - '--last', - mockCommandLineCustom - ) - - expect(parameter).toEqual('multi word') - }) - - test('throws an error when passed non-existing flags', () => { - let parameter = getCommandParameterLastMultiWord( - '-n', - '--non', - mockCommandLine - ) - - expect(parameter).toEqual('') - }) - - test('should throw and error when argument is not the last one', () => { - const input = ['-t', 'target', '-s', 'source'] - - expect(() => - getCommandParameterLastMultiWord('-t', '--target', input) - ).toThrow() - }) -}) From df76a3c60d6ef825308ab6b7f7ecffab5580d722 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Mon, 9 Nov 2020 10:24:04 +0300 Subject: [PATCH 14/27] chore: improved displaying error object --- src/utils/displayResult.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/displayResult.js b/src/utils/displayResult.js index 9cb8aad55..8bd002125 100644 --- a/src/utils/displayResult.js +++ b/src/utils/displayResult.js @@ -19,7 +19,9 @@ export function displayResult(err, failureMessage, successMessage) { console.log(chalk.redBright(failureMessage, err.body)) } } else { - console.log(chalk.redBright(failureMessage, err)) + console.log( + chalk.redBright(failureMessage, Object.keys(err).length ? err : '') + ) } } From a5543099515becd26815ea18811c945ece6effcf Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Mon, 9 Nov 2020 10:24:39 +0300 Subject: [PATCH 15/27] refactor(sasjs-web): used command utility --- src/cli.js | 2 +- src/commands/web/index.js | 14 ++++++++++++-- src/main.js | 4 ++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/cli.js b/src/cli.js index 6c8d0ccf7..d40c9b84c 100644 --- a/src/cli.js +++ b/src/cli.js @@ -207,7 +207,7 @@ export async function cli(args) { break } case 'web': { - await buildWebApp(command.parameters[1]) + await buildWebApp(command.parameters) break } case 'add': { diff --git a/src/commands/web/index.js b/src/commands/web/index.js index 04ce24c9b..d9df11ea4 100644 --- a/src/commands/web/index.js +++ b/src/commands/web/index.js @@ -16,6 +16,7 @@ import jsdom from 'jsdom' import base64img from 'base64-img' import { sasjsout } from './sasjsout' import btoa from 'btoa' +import { Command } from '../../utils/command' let buildDestinationFolder = '' const permittedServerTypes = { @@ -24,14 +25,23 @@ const permittedServerTypes = { } export async function createWebAppServices( - targetName = null, + commandLine = null, preTargetToBuild = null ) { - const CONSTANTS = require('../constants') + const command = new Command(commandLine) + + let targetName = command.getFlagValue('target') + targetName = targetName ? targetName : null + + const CONSTANTS = require('../../constants') buildDestinationFolder = CONSTANTS.buildDestinationFolder + console.log(chalk.greenBright('Building web app services...')) + await createBuildDestinationFolder() + let targetToBuild = null + if (preTargetToBuild) targetToBuild = preTargetToBuild else { const { target } = await findTargetInConfiguration(targetName) diff --git a/src/main.js b/src/main.js index a70275ce9..65e9dc11a 100644 --- a/src/main.js +++ b/src/main.js @@ -249,8 +249,8 @@ export async function buildDBs() { return result } -export async function buildWebApp(targetName) { - await createWebAppServices(targetName) +export async function buildWebApp(commandLine) { + await createWebAppServices(commandLine) .then(() => console.log( chalk.greenBright.bold.italic( From 3e4f5f466133d2badf75a70a4fc90540d037bf4d Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Mon, 9 Nov 2020 12:06:33 +0300 Subject: [PATCH 16/27] chore(dependencies): updated @sasjs/core --- package-lock.json | 6 +++--- package.json | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 2b1174680..29af8d264 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1899,9 +1899,9 @@ } }, "@sasjs/core": { - "version": "1.7.3", - "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-1.7.3.tgz", - "integrity": "sha512-iXlFQoYiJUIMISftOL03VLCW9LRWG2dmflA0xMWVnW03Oy0eldjnc5Uhf7nYYfrIz682pWG613Fz+ZDbP2XV/w==" + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/@sasjs/core/-/core-1.7.4.tgz", + "integrity": "sha512-biOOxz7+glYE7a1ZtkmONshyEjwixPVxYw92mD+psFOcNu7KJzfC18pevFzhQl52hveTWzLKVJJHs/rLglZguA==" }, "@semantic-release/commit-analyzer": { "version": "8.0.1", diff --git a/package.json b/package.json index b7a102bbc..e8454d04f 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ }, "dependencies": { "@sasjs/adapter": "^1.17.0", - "@sasjs/core": "^1.7.3", + "@sasjs/core": "^1.7.4", "base64-img": "^1.0.4", "btoa": "^1.2.1", "chalk": "^4.1.0", From 229dde23c5e7c61412053c0c8f299ad887c981b8 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Mon, 9 Nov 2020 12:12:57 +0300 Subject: [PATCH 17/27] chore(command): improved supperted flags logic --- src/utils/command.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/utils/command.js b/src/utils/command.js index 85edeaca7..21439ab67 100644 --- a/src/utils/command.js +++ b/src/utils/command.js @@ -141,9 +141,11 @@ export class Command { this.aliases = initialAliases.find((alias) => alias.name === this.name) this.aliases = this.aliases ? this.aliases.aliases : null - this.supportedFlags = commandFlags.filter( - (commandFlag) => commandFlag.command === this.name - )[0].flags + const supportedFlags = commandFlags.filter( + (flag) => flag.command === this.name + )[0] + + if (supportedFlags) this.supportedFlags = supportedFlags.flags for (let i = 0; i < commandLine.length; i++) { if (/^-/.test(commandLine[i]) && this.supportedFlags) { From 423834b73e40be4db650b3d63be5b1308c0863b6 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Mon, 9 Nov 2020 12:13:40 +0300 Subject: [PATCH 18/27] refactor(sasjs-add): used command utility --- src/cli.js | 2 +- src/main.js | 6 ++++-- test/commands/add.spec.js | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/cli.js b/src/cli.js index d40c9b84c..a5002d7c6 100644 --- a/src/cli.js +++ b/src/cli.js @@ -211,7 +211,7 @@ export async function cli(args) { break } case 'add': { - await add(command.parameters[1]) + await add(command.parameters) break } case 'run': { diff --git a/src/main.js b/src/main.js index 65e9dc11a..ddd2d35e9 100644 --- a/src/main.js +++ b/src/main.js @@ -16,6 +16,7 @@ import { } from './commands' import chalk from 'chalk' import { displayResult } from './utils/displayResult' +import { Command } from './utils/command' export async function createFileStructure(parentFolderName, appType) { let result @@ -270,10 +271,11 @@ export async function buildWebApp(commandLine) { }) } -export async function add(resourceType = 'target') { +export async function add(commandLine) { + const command = new Command(commandLine) let result = false - if (resourceType === 'target') { + if (command && command.name === 'add') { await addTarget() .then(() => { console.log(chalk.greenBright('Target successfully added!')) diff --git a/test/commands/add.spec.js b/test/commands/add.spec.js index 7fde0d1a7..aa52c113a 100644 --- a/test/commands/add.spec.js +++ b/test/commands/add.spec.js @@ -48,7 +48,7 @@ describe('sasjs add', () => { stdin.send([`1\r`]) }, 1000) - await expect(add()).resolves.toEqual(true) + await expect(add('add')).resolves.toEqual(true) const buildSourceFolder = require('../../src/constants') .buildSourceFolder @@ -102,7 +102,7 @@ describe('sasjs add', () => { stdin.send([`1\r`]) }, 1000) - await expect(add()).resolves.toEqual(true) + await expect(add('add')).resolves.toEqual(true) const config = await getGlobalRcFile() From 89bd25996f7f741d15d05720c8c9ff7c5640f302 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Fri, 13 Nov 2020 12:49:44 +0300 Subject: [PATCH 19/27] feat: added 'getTargetWithoutFlag' and 'showInvalidFlagMessage' methods --- src/utils/command.js | 51 ++++++++++++++++++++++++++++++++++++-------- 1 file changed, 42 insertions(+), 9 deletions(-) diff --git a/src/utils/command.js b/src/utils/command.js index 21439ab67..dc42cd7c9 100644 --- a/src/utils/command.js +++ b/src/utils/command.js @@ -1,5 +1,6 @@ import { displayResult } from './displayResult' import { arrToObj } from './utils' +import chalk from 'chalk' const showInvalidCommandMessage = () => { displayResult( @@ -8,6 +9,17 @@ const showInvalidCommandMessage = () => { ) } +const showInvalidFlagMessage = (flagMessage, supportedFlags) => { + displayResult( + {}, + `${flagMessage}${ + supportedFlags + ? ` Supported flags are:\n${supportedFlags.join('\n')}` + : '' + }` + ) +} + const initialCommands = arrToObj([ ...new Set([ 'create', @@ -156,20 +168,24 @@ export class Command { .filter((f) => regExp.test(f)) .filter((f) => this.supportedFlags.includes(f))[0] - flag = new Flag(flag) + try { + flag = new Flag(flag) - this.flags.push(flag) + this.flags.push(flag) - if (flag.withValue) { - if (/^-/.test(commandLine[i + 1])) continue + if (flag.withValue) { + if (/^-/.test(commandLine[i + 1])) continue - i++ + i++ - const value = commandLine[i] + const value = commandLine[i] - if (value) { - this.flags.find((f) => f.name === flag.name).setValue(value) + if (value) { + this.flags.find((f) => f.name === flag.name).setValue(value) + } } + } catch (error) { + showInvalidFlagMessage(error, this.supportedFlags) } } else { this.values.push(commandLine[i]) @@ -206,13 +222,30 @@ export class Command { .map((p) => (/^\//.test(p) ? path : `${appLoc}/${p}`)) .join(' ') } + + getTargetWithoutFlag() { + const deprecationDate = new Date(2021, 10, 2) + const today = new Date() + + if (today < deprecationDate && this.values.length) { + console.log( + chalk.yellowBright( + `WARNING: use --target or -t flag to specify the target name. Specifying the target name without a flag will not be supported starting from November 1, 2021.` + ) + ) + + return this.values.shift() + } + + return undefined + } } class Flag { value = null constructor(name) { - if (!name || typeof name !== 'string') throw 'Not valid flag name!' + if (!name || typeof name !== 'string') throw `Not valid flag name!` this.name = name this.longSyntax = '--' + name From 616d671082e60e5eea4b033956908b5bb6f3bcee Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Fri, 13 Nov 2020 12:50:27 +0300 Subject: [PATCH 20/27] refactor(build): used command utility --- src/cli.js | 2 +- src/main.js | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/cli.js b/src/cli.js index a5002d7c6..2589c523b 100644 --- a/src/cli.js +++ b/src/cli.js @@ -172,7 +172,7 @@ export async function cli(args) { break } case 'build': { - await buildServices(command.parameters[1]) + await buildServices(command.parameters) break } case 'deploy': { diff --git a/src/main.js b/src/main.js index ddd2d35e9..a420d4da3 100644 --- a/src/main.js +++ b/src/main.js @@ -51,7 +51,14 @@ export async function showVersion() { await printVersion() } -export async function buildServices(targetName) { +export async function buildServices(commandLine) { + const command = new Command(commandLine) + let targetName = command.getFlagValue('target') + + if (!targetName) { + targetName = command.getTargetWithoutFlag() + } + await build(targetName) .then(() => console.log( From d30efdf853a3322a63c505d113e1ed6f5b6e86ff Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Fri, 13 Nov 2020 15:59:38 +0300 Subject: [PATCH 21/27] refactor(create): used command utility --- src/cli.js | 5 +---- src/commands/create.js | 2 +- src/main.js | 12 ++++++++++-- test/commands/create.spec.js | 24 +++++++++++++++--------- 4 files changed, 27 insertions(+), 16 deletions(-) diff --git a/src/cli.js b/src/cli.js index 2589c523b..671e72a82 100644 --- a/src/cli.js +++ b/src/cli.js @@ -161,10 +161,7 @@ export async function cli(args) { } switch (command.name) { case 'create': { - const { projectName, appType } = processCreateParameters( - command.parameters - ) - await createFileStructure(projectName, appType) + await createFileStructure(command.parameters) break } case 'compile': { diff --git a/src/commands/create.js b/src/commands/create.js index 6b5058c64..c5dfb8fc9 100644 --- a/src/commands/create.js +++ b/src/commands/create.js @@ -17,7 +17,7 @@ import { } from '../utils/file-utils' import chalk from 'chalk' -export async function create(parentFolderName = '.', appType = '') { +export async function create(parentFolderName, appType) { const configPath = appType === 'sasonly' ? '../config-sasonly.json' : '../config.json' const config = await getConfiguration(path.join(__dirname, configPath)) diff --git a/src/main.js b/src/main.js index a420d4da3..e0a107bd1 100644 --- a/src/main.js +++ b/src/main.js @@ -18,11 +18,17 @@ import chalk from 'chalk' import { displayResult } from './utils/displayResult' import { Command } from './utils/command' -export async function createFileStructure(parentFolderName, appType) { +export async function createFileStructure(commandLine) { + const command = new Command(commandLine) + const template = command.getFlagValue('template') + const parentFolderName = command.values.shift() + let result - await create(parentFolderName, appType) + + await create(parentFolderName || '.', template) .then(() => { result = true + console.log( chalk.greenBright.bold.italic( `Project ${ @@ -33,6 +39,7 @@ export async function createFileStructure(parentFolderName, appType) { }) .catch((err) => { result = err + console.log( chalk.redBright( 'An error has occurred whilst creating your project.', @@ -40,6 +47,7 @@ export async function createFileStructure(parentFolderName, appType) { ) ) }) + return result } diff --git a/test/commands/create.spec.js b/test/commands/create.spec.js index 8b044aa2d..8eb5ce4d7 100644 --- a/test/commands/create.spec.js +++ b/test/commands/create.spec.js @@ -24,9 +24,7 @@ describe('sasjs create', () => { await createFolder(process.projectDir) - await expect( - createFileStructure(undefined, undefined) - ).resolves.toEqual(true) + await expect(createFileStructure('sasjs create')).resolves.toEqual(true) await verifyCreate({ parentFolderName: '.' }) }, @@ -46,7 +44,9 @@ describe('sasjs create', () => { await createFolder(process.projectDir) - await expect(createFileStructure('.', undefined)).resolves.toEqual(true) + await expect(createFileStructure('sasjs create .')).resolves.toEqual( + true + ) await verifyCreate({ parentFolderName: '.' }) }, @@ -66,7 +66,9 @@ describe('sasjs create', () => { await createFolder(process.projectDir) - await expect(createFileStructure('.', 'sasonly')).resolves.toEqual(true) + await expect( + createFileStructure('sasjs create . -t sasonly') + ).resolves.toEqual(true) await verifyCreate({ parentFolderName: '.', sasonly: true }) }, @@ -86,7 +88,7 @@ describe('sasjs create', () => { await createFolder(process.projectDir) - await expect(createFileStructure(undefined, 'react')).resolves.toEqual( + await expect(createFileStructure('create -t react')).resolves.toEqual( true ) @@ -111,7 +113,7 @@ describe('sasjs create', () => { await createFolder(process.projectDir) await expect( - createFileStructure(`${parentFolderNameTimeStamped}`, undefined) + createFileStructure(`create ${parentFolderNameTimeStamped}`) ).resolves.toEqual(true) await verifyCreate({ parentFolderName: parentFolderNameTimeStamped }) @@ -133,7 +135,9 @@ describe('sasjs create', () => { await createFolder(process.projectDir) await expect( - createFileStructure(`${parentFolderNameTimeStamped}`, 'minimal') + createFileStructure( + `create ${parentFolderNameTimeStamped} --template minimal` + ) ).resolves.toEqual(true) await verifyCreateWeb({ @@ -158,7 +162,9 @@ describe('sasjs create', () => { await createFolder(process.projectDir) await expect( - createFileStructure(`${parentFolderNameTimeStamped}`, 'angular') + createFileStructure( + `create ${parentFolderNameTimeStamped} -t angular` + ) ).resolves.toEqual(true) await verifyCreateWeb({ From 759e1751d8a226cec38674dd88c308961dd02eb0 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Fri, 13 Nov 2020 16:10:24 +0300 Subject: [PATCH 22/27] refactor(deploy): used command utility --- src/cli.js | 5 +---- src/main.js | 10 +++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/cli.js b/src/cli.js index 671e72a82..7eb711948 100644 --- a/src/cli.js +++ b/src/cli.js @@ -173,10 +173,7 @@ export async function cli(args) { break } case 'deploy': { - await deployServices( - command.parameters[1], - command.parameters[2] === '-f' - ) + await deployServices(command.parameters) break } case 'servicepack': { diff --git a/src/main.js b/src/main.js index e0a107bd1..231cae9b8 100644 --- a/src/main.js +++ b/src/main.js @@ -128,7 +128,15 @@ export async function compileServices(targetName) { }) } -export async function deployServices(targetName, isForced) { +export async function deployServices(commandLine) { + const command = new Command(commandLine) + let targetName = command.getFlagValue('target') + const isForced = command.getFlagValue('force') + + if (!targetName) { + targetName = command.getTargetWithoutFlag() + } + await deploy(targetName, null, isForced) .then(() => console.log( From 4b06273820254f020634fe678eda50737bc1c349 Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Fri, 13 Nov 2020 16:37:07 +0300 Subject: [PATCH 23/27] refactor(request): used command utility --- src/cli.js | 12 +-------- src/commands/request.js | 14 +++++----- src/main.js | 11 +++----- test/commands/request/request.spec.js | 38 +++++++++------------------ 4 files changed, 25 insertions(+), 50 deletions(-) diff --git a/src/cli.js b/src/cli.js index 7eb711948..af5278c92 100644 --- a/src/cli.js +++ b/src/cli.js @@ -35,9 +35,6 @@ function parseCommand(rawArgs) { case 'run': parameters = processRunParameters(args.slice(1)) return { name, parameters } - case 'request': - parameters = processRequestParameters(args.slice(1)) - return { name, parameters } } return { name, parameters: args } @@ -214,14 +211,7 @@ export async function cli(args) { break } case 'request': { - const { - sasJobLocation, - dataFilePath, - configFilePath, - targetName - } = command.parameters - - await runRequest(sasJobLocation, dataFilePath, configFilePath, targetName) + await runRequest(command.parameters) break } diff --git a/src/commands/request.js b/src/commands/request.js index 0144ea989..99681739a 100644 --- a/src/commands/request.js +++ b/src/commands/request.js @@ -9,13 +9,15 @@ import { } from '../utils/file-utils' import { getAccessToken } from '../utils/config-utils' import { displayResult } from '../utils/displayResult' +import { Command } from '../utils/command' + +export async function runSasJob(commandLine) { + const command = new Command(commandLine) + const sasJobLocation = command.values.shift() + const dataFilePath = command.getFlagValue('datafile') + const configFilePath = command.getFlagValue('configfile') + const targetName = command.getFlagValue('target') -export async function runSasJob( - sasJobLocation, - dataFilePath, - configFilePath, - targetName -) { const { target, isLocal } = await findTargetInConfiguration(targetName, true) if (!target) { throw new Error('Target not found! Please try again with another target.') diff --git a/src/main.js b/src/main.js index 231cae9b8..1e48ddc74 100644 --- a/src/main.js +++ b/src/main.js @@ -321,14 +321,10 @@ export async function run(filePath, targetName) { }) } -export async function runRequest( - sasJobLocation, - dataFilePath, - configFilePath, - targetName -) { +export async function runRequest(commandLine) { let result = false - await runSasJob(sasJobLocation, dataFilePath, configFilePath, targetName) + + await runSasJob(commandLine) .then((res) => (result = res)) .catch((err) => { result = err @@ -336,6 +332,7 @@ export async function runRequest( chalk.redBright('An error has occurred when running your SAS job', err) ) }) + return result } diff --git a/test/commands/request/request.spec.js b/test/commands/request/request.spec.js index 1efd98c95..2d4907284 100644 --- a/test/commands/request/request.spec.js +++ b/test/commands/request/request.spec.js @@ -108,10 +108,7 @@ describe('sasjs request', () => { async () => { await expect( runRequest( - '/Public/app/cli-tests/runRequest/sendArr', - dataPathRel, - 'default', - targetName + `request /Public/app/cli-tests/runRequest/sendArr -d ${dataPathRel} -c default -t ${targetName}` ) ).resolves.toEqual(true) const rawData = await readFile(`${process.projectDir}/output.json`) @@ -129,10 +126,7 @@ describe('sasjs request', () => { async () => { await expect( runRequest( - '/Public/app/cli-tests/runRequest/sendObj', - dataPathRel, - 'default', - targetName + `request /Public/app/cli-tests/runRequest/sendObj -d ${dataPathRel} -c default -t ${targetName}` ) ).resolves.toEqual(true) const rawData = await readFile(`${process.projectDir}/output.json`) @@ -151,7 +145,9 @@ describe('sasjs request', () => { `should execute service 'sendArr'`, async () => { await expect( - runRequest('runRequest/sendArr', dataPathRel, 'default', targetName) + runRequest( + `request runRequest/sendArr -d ${dataPathRel} -c default -t ${targetName}` + ) ).resolves.toEqual(true) const rawData = await readFile(`${process.projectDir}/output.json`) @@ -168,7 +164,9 @@ describe('sasjs request', () => { `should execute service sendObj`, async () => { await expect( - runRequest('runRequest/sendObj', dataPathRel, 'default', targetName) + runRequest( + `request runRequest/sendObj -d ${dataPathRel} -c default -t ${targetName}` + ) ).resolves.toEqual(true) const rawData = await readFile(`${process.projectDir}/output.json`) @@ -190,10 +188,7 @@ describe('sasjs request', () => { async () => { await expect( runRequest( - '/Public/app/cli-tests/runRequest/sendArr', - dataPathRel, - configPathRel, - targetName + `request /Public/app/cli-tests/runRequest/sendArr -d ${dataPathRel} -c ${configPathRel} -t ${targetName}` ) ).resolves.toEqual(true) @@ -212,10 +207,7 @@ describe('sasjs request', () => { async () => { await expect( runRequest( - '/Public/app/cli-tests/runRequest/sendObj', - dataPathRel, - configPathRel, - targetName + `request /Public/app/cli-tests/runRequest/sendObj -d ${dataPathRel} -c ${configPathRel} -t ${targetName}` ) ).resolves.toEqual(true) @@ -236,10 +228,7 @@ describe('sasjs request', () => { async () => { await expect( runRequest( - 'runRequest/sendArr', - dataPathRel, - configPathRel, - targetName + `request runRequest/sendArr -d ${dataPathRel} -c ${configPathRel} -t ${targetName}` ) ).resolves.toEqual(true) @@ -258,10 +247,7 @@ describe('sasjs request', () => { async () => { await expect( runRequest( - 'runRequest/sendObj', - dataPathRel, - configPathRel, - targetName + `request runRequest/sendObj -d ${dataPathRel} -c ${configPathRel} -t ${targetName}` ) ).resolves.toEqual(true) From c3dc23afc20f3834f7eabec8900b3be6305809ff Mon Sep 17 00:00:00 2001 From: Yury Shkoda Date: Fri, 13 Nov 2020 16:46:10 +0300 Subject: [PATCH 24/27] reafctor(run): used command utility --- src/cli.js | 10 +--------- src/commands/run.js | 7 ++++++- src/main.js | 4 ++-- test/commands/run.spec.js | 4 ++-- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/src/cli.js b/src/cli.js index af5278c92..bea26664a 100644 --- a/src/cli.js +++ b/src/cli.js @@ -29,13 +29,6 @@ function parseCommand(rawArgs) { if (args.length) { const name = getUnaliasedCommand(args[0]) - let parameters - - switch (name) { - case 'run': - parameters = processRunParameters(args.slice(1)) - return { name, parameters } - } return { name, parameters: args } } @@ -206,8 +199,7 @@ export async function cli(args) { break } case 'run': { - const { filePath, targetName } = command.parameters - await run(filePath, targetName) + await run(command.parameters) break } case 'request': { diff --git a/src/commands/run.js b/src/commands/run.js index d5fb6b5cb..e3af1b204 100644 --- a/src/commands/run.js +++ b/src/commands/run.js @@ -7,13 +7,18 @@ import { } from '../utils/config-utils' import { readFile, createFile } from '../utils/file-utils' import { getVariable, generateTimestamp } from '../utils/utils' +import { Command } from '../utils/command' /** * Runs SAS code from a given file on the specified target. * @param {string} filePath - the path to the file containing SAS code. * @param {string} targetName - the name of the target to run the SAS code on. */ -export async function runSasCode(filePath, targetName) { +export async function runSasCode(commandLine) { + const command = new Command(commandLine) + const filePath = command.values.shift() + const targetName = command.getFlagValue('target') + if (!/\.sas$/i.test(filePath)) { throw new Error(`'sasjs run' command supports only *.sas files.`) } diff --git a/src/main.js b/src/main.js index 1e48ddc74..5b8ce4877 100644 --- a/src/main.js +++ b/src/main.js @@ -313,8 +313,8 @@ export async function add(commandLine) { return result } -export async function run(filePath, targetName) { - await runSasCode(filePath, targetName).catch((err) => { +export async function run(commandLine) { + await runSasCode(commandLine).catch((err) => { console.log( chalk.redBright('An error has occurred when running your SAS code.', err) ) diff --git a/test/commands/run.spec.js b/test/commands/run.spec.js index cda51bb37..ae0a9ffdd 100644 --- a/test/commands/run.spec.js +++ b/test/commands/run.spec.js @@ -2,11 +2,11 @@ import { runSasCode } from '../../src/commands' describe('sasjs run', () => { describe('runSasCode', () => { - it('should throw if file type is not *.sas', async () => { + it('should throw an error if file type is not *.sas', async () => { const file = 'test.sas.txt' const error = new Error(`'sasjs run' command supports only *.sas files.`) - await expect(runSasCode(file, '')).rejects.toEqual(error) + await expect(runSasCode(`run ${file}`)).rejects.toEqual(error) }) }) }) From d5323bc0483032f7119e34c03533e011a47f6aa4 Mon Sep 17 00:00:00 2001 From: Saad Jutt Date: Fri, 13 Nov 2020 23:07:58 +0500 Subject: [PATCH 25/27] chore: refactor references updated --- src/commands/build.js | 2 +- test/commands/compile/loadDependencies.spec.js | 2 +- test/commands/request/request.spec.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/build.js b/src/commands/build.js index 35147e789..f3448e7ff 100644 --- a/src/commands/build.js +++ b/src/commands/build.js @@ -1,4 +1,4 @@ -import * as thisModule from './index' +import * as thisModule from './build' import find from 'find' import path from 'path' diff --git a/test/commands/compile/loadDependencies.spec.js b/test/commands/compile/loadDependencies.spec.js index 810d508ab..c7384d817 100644 --- a/test/commands/compile/loadDependencies.spec.js +++ b/test/commands/compile/loadDependencies.spec.js @@ -1,5 +1,5 @@ import path from 'path' -import * as compileModule from '../../../src/sasjs-build/index' +import * as compileModule from '../../../src/commands/build' const fakeServiceInit = `/** @file serviceinit.sas diff --git a/test/commands/request/request.spec.js b/test/commands/request/request.spec.js index 56a6b9b61..ba38e5cd2 100644 --- a/test/commands/request/request.spec.js +++ b/test/commands/request/request.spec.js @@ -9,7 +9,7 @@ import { deleteFolder, createFile } from '../../../src/utils/file-utils' -import { remove } from '../../../src/sasjs-folder/remove' +import { remove } from '../../../src/commands/folder/remove' import SASjs from '@sasjs/adapter/node' describe('sasjs request', () => { From 801d27186f433b9d339786f44b9ef9be7f7b30c6 Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Sun, 15 Nov 2020 18:39:22 +0000 Subject: [PATCH 26/27] chore(*): update file paths --- test/commands/folder/delete.spec.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/commands/folder/delete.spec.js b/test/commands/folder/delete.spec.js index 08b1bc7fa..3daccfbea 100644 --- a/test/commands/folder/delete.spec.js +++ b/test/commands/folder/delete.spec.js @@ -1,6 +1,6 @@ import dotenv from 'dotenv' import { folder } from '../../../src/sasjs-folder/index' -import * as removeModule from '../../../src/sasjs-folder/remove' +import * as removeModule from '../../../src/commands/folder/remove' import { generateTimestamp } from '../../../src/utils/utils' const createConfig = (targetName, timestamp) => ({ @@ -25,7 +25,7 @@ const createConfig = (targetName, timestamp) => ({ tgtDeployScripts: [] }) -jest.mock('../../../src/sasjs-folder/remove') +jest.mock('../../../src/commands/folder/remove') describe('sasjs folder delete', () => { let config From 8c3b720da51351698acf0aa1c65d5a5d3e5e1172 Mon Sep 17 00:00:00 2001 From: Krishna Acondy Date: Sun, 15 Nov 2020 18:47:11 +0000 Subject: [PATCH 27/27] chore(*): fix file path --- test/commands/folder/delete.spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/commands/folder/delete.spec.js b/test/commands/folder/delete.spec.js index 3daccfbea..aa0a1d7a9 100644 --- a/test/commands/folder/delete.spec.js +++ b/test/commands/folder/delete.spec.js @@ -1,5 +1,5 @@ import dotenv from 'dotenv' -import { folder } from '../../../src/sasjs-folder/index' +import { folder } from '../../../src/commands/folder/index' import * as removeModule from '../../../src/commands/folder/remove' import { generateTimestamp } from '../../../src/utils/utils'