New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

losant-cli

Package Overview
Dependencies
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

losant-cli - npm Package Compare versions

Comparing version 1.1.2 to 1.2.0

bin/losant-datatables.js

3

bin/losant.js

@@ -12,3 +12,4 @@ #!/usr/bin/env node

.command('experience', 'Manages your Losant Application\'s Experience Views, and Versions from the command line.')
.command('files', 'Manage Files on Losant for your Application.');
.command('files', 'Manage Files on Losant for your Application.')
.command('datatables', 'Manage Data Tables on Losant for your Application.');

@@ -15,0 +16,0 @@ const results = pgm.parse(process.argv);

@@ -10,3 +10,5 @@ const error = require('error/typed');

const params = require('../../lib/get-download-params');
const dtParams = require('../../lib/get-export-params').dataTables;
const getDownloader = require('../../lib/get-downloader');
const getExporter = require('../../lib/get-exporter');
const experienceBootstrap = require('../../lib/experience-bootstrap');

@@ -16,7 +18,9 @@ const inquirer = require('inquirer');

const filesDownload = getDownloader(params.files);
const dataTablesExport = getExporter(dtParams);
const {
saveConfig, logError, logResult, log, loadUserConfig, saveLocalMeta, hasBootstrapped
saveConfig, logError, logResult, log, loadUserConfig, saveLocalMeta, hasBootstrapped, getApiURL
} = require('../../lib/utils');
const DIRECTORIES_TO_GENERATE = [
'dataTables',
'files',

@@ -31,3 +35,3 @@ 'experience'

const getApplicationFunc = (api) => {
const getApplicationFunc = (api, appUrl) => {
return async () => {

@@ -48,3 +52,3 @@ const { filter } = await inquirer.prompt([

const choices = applications.items.map((appInfo) => {
const key = `${appInfo.name} https://app.losant.com/applications/${appInfo.id}`;
const key = `${appInfo.name} | ${appInfo.organizationName || 'My Sandbox'} | ${appUrl}/applications/${appInfo.id}`;
nameToId[key] = appInfo;

@@ -102,10 +106,9 @@ return key;

.action(async (command) => {
const userConfig = await loadUserConfig() || {};
if (!userConfig.apiToken) {
return logError('Must run losant login before running losant configure.');
}
let userConfig = await loadUserConfig() || {};
const apiUrl = await getApiURL(userConfig);
await Promise.all(DIRECTORIES_TO_GENERATE.map((dir) => { return ensureDir(dir); }));
await Promise.all(LOCAL_META_FILES.map((type) => { return saveLocalMeta(type, {}); }));
const api = await getApi({ apiToken: userConfig.apiToken });
const getApplication = getApplicationFunc(api);
userConfig = userConfig[apiUrl];
const api = await getApi({ url: apiUrl, apiToken: userConfig.apiToken });
const getApplication = getApplicationFunc(api, userConfig.appUrl);
let appInfo;

@@ -120,3 +123,3 @@ try {

}
const config = { applicationId: appInfo.id, applicationName: appInfo.name };
const config = { applicationId: appInfo.id, applicationName: appInfo.name, apiUrl };
try {

@@ -129,2 +132,3 @@ const file = await saveConfig(command.config, config);

const loadedConfig = merge(userConfig, config);
loadedConfig.api = api;
try {

@@ -164,2 +168,14 @@ const downloaded = await experienceDownload(null, {}, loadedConfig);

}
try {
const { canExportDataTables } = await inquirer.prompt([{ type: 'confirm', name: 'canExportDataTables', message: 'Export data tables now?' }]);
if (canExportDataTables) {
const exportedTables = await dataTablesExport(null, {}, loadedConfig);
if (exportedTables) {
logResult('success', 'Exported all data tables!', 'green');
}
}
} catch (e) {
console.error(e);
logError('Failed to export data tables.');
}
log('Configuration completed! :D');

@@ -166,0 +182,0 @@ });

@@ -41,3 +41,10 @@ const error = require('error/typed');

try {
const userFile = await saveUserConfig({ apiToken: api.getOption('accessToken') });
const wlInfo = await api.request({ method: 'get', url: '/whitelabels/domain' });
const userFile = await saveUserConfig({
[api.getOption('url')]: {
apiToken: api.getOption('accessToken'),
appUrl: wlInfo.appUrl,
endpointDomain: wlInfo.endpointDomain
}
});
logResult('success', `configuration written to ${c.bold(userFile)} with your user token!`, 'green');

@@ -44,0 +51,0 @@ } catch (e) {

@@ -7,3 +7,4 @@ const path = require('path');

localStatusParams: [ path.join(path.sep, '{components,layouts,pages}', '*.hbs') ],
remoteStatusParams: [ path.join('experience', '${viewType}s', '${name}.hbs'), 'body' ] // eslint-disable-line no-template-curly-in-string
// eslint-disable-next-line no-template-curly-in-string
remoteStatusParams: [ path.join('experience', '${viewType}s', '${name}.hbs'), 'body' ]
},

@@ -14,4 +15,12 @@ files: {

localStatusParams: [ path.join(path.sep, '**') ],
remoteStatusParams: [ 'files${parentDirectory}${name}', 's3etag', { skipMd5Creation: true } ] // eslint-disable-line no-template-curly-in-string
// eslint-disable-next-line no-template-curly-in-string
remoteStatusParams: [ 'files${parentDirectory}${name}', 's3etag', { skipMd5Creation: true } ]
},
dataTables: {
commandType: 'dataTables',
apiType: 'dataTables',
localStatusParams: [ path.join(path.sep, '*.csv' )],
// eslint-disable-next-line no-template-curly-in-string
remoteStatusParams: [ path.join('dataTables', '${name}-${id}.csv') ]
},
options: {

@@ -18,0 +27,0 @@ dryRun: [ '--dry-run', 'display actions but do not perform them' ],

@@ -7,3 +7,2 @@ const {

} = require('./utils');
const getApi = require('./get-api');
const { experience } = require('./get-download-params');

@@ -15,4 +14,3 @@ const getDownloader = require('./get-downloader');

module.exports = async (cmd, loadedConfig, applicationInfo) => {
const { apiToken, applicationId, applicationName } = loadedConfig ? loadedConfig : await loadConfig();
const api = await getApi({ apiToken });
const { apiToken, applicationId, applicationName, api, endpointDomain } = loadedConfig || await loadConfig();
try {

@@ -36,4 +34,4 @@ applicationInfo = applicationInfo || await api.application.get({ applicationId });

const results = await api.experience.bootstrap({ applicationId });
await downloader(null, {}, { apiToken, applicationId });
logResult('Experience URL', `https://${applicationInfo.endpointSlug}.onlosant.com/${results.resourceSuffix}`);
await downloader(null, {}, { apiToken, applicationId, api });
logResult('Experience URL', `https://${applicationInfo.endpointSlug}.${endpointDomain}/${results.resourceSuffix}`);
logResult('Bootstrap Username', `${results.email}`);

@@ -40,0 +38,0 @@ logResult('Bootstrap Password', `${results.password}`);

@@ -8,3 +8,2 @@ const {

const inquirer = require('inquirer');
const getApi = require('./get-api');
const paginate = require('./paginate-request');

@@ -18,5 +17,4 @@ const printTable = require('./print-table');

module.exports = async (page, command) => {
const { apiToken, applicationId } = await loadConfig();
const { apiToken, applicationId, api, appUrl } = await loadConfig();
if (!applicationId || !apiToken) { return; } // config did not lock or did not load correctly.
const api = await getApi({ apiToken });
const pageQuery = { applicationId, viewType: 'page' };

@@ -49,3 +47,3 @@ if (page || command.pattern) {

if (pageInfos.length === 1) {
log(`Only one page found that matched ${page}, ${pageInfos[0].name} https://app.losant.com/applications/${applicationId}/experience/versions/develop/views/pages/${pageInfos[0].id}`);
log(`Only one page found that matched ${page}, ${pageInfos[0].name} ${appUrl}/applications/${applicationId}/experience/versions/develop/views/pages/${pageInfos[0].id}`);
}

@@ -56,3 +54,3 @@ if (pageInfos.length > 1) {

// intentional, cause inquirer leaves no room in between the bullet point and the name, the space is important.
const key = ` ${info.name} https://app.losant.com/applications/${applicationId}/experience/versions/develop/views/pages/${info.id}`;
const key = ` ${info.name} ${appUrl}/applications/${applicationId}/experience/versions/develop/views/pages/${info.id}`;
pageToInfo[trim(key)] = info;

@@ -77,3 +75,3 @@ return key;

const choices = layouts.map(({ id, name }) => {
const key = `${name} https://app.losant.com/applications/${applicationId}/experience/versions/develop/views/layouts/${id}`;
const key = `${name} ${appUrl}/applications/${applicationId}/experience/versions/develop/views/layouts/${id}`;
keyToId[key] = id;

@@ -80,0 +78,0 @@ return key;

@@ -1,2 +0,1 @@

const getApi = require('./get-api');
const inquirer = require('inquirer');

@@ -14,3 +13,3 @@ const {

const listVersions = async (api, applicationId, filter) => {
const listVersions = async (api, applicationId, filter, endpointDomain) => {
const query = { applicationId };

@@ -33,3 +32,3 @@ if (filter) { query.filter = filter; }

if (config.slug) {
return `${config.slug}.onlosant.com`;
return `${config.slug}.${endpointDomain}`;
}

@@ -80,7 +79,6 @@ })).join('\n') : '';

module.exports = async (version, command) => {
const { apiToken, applicationId } = await loadConfig();
const { apiToken, applicationId, api, endpointDomain } = await loadConfig();
if (!applicationId || !apiToken) { return; } // config did not lock or did not load correctly.
const api = await getApi({ apiToken });
if (!version) {
await listVersions(api, applicationId, command.list);
await listVersions(api, applicationId, command.list, endpointDomain);
} else {

@@ -87,0 +85,0 @@ const domainChoiceMappings = choices('domainName', 'version', await getExperiencePart(api, 'domain', { applicationId }));

const losant = require('losant-rest');
const { trim } = require('omnibelt');
process.env.LOSANT_API_URL = process.env.LOSANT_API_URL || 'https://api.losant.com';
module.exports = async ({ email, password, twoFactorCode, apiToken }) => {
const api = losant.createClient({ url: process.env.LOSANT_API_URL });
module.exports = async ({ url, email, password, twoFactorCode, apiToken } = {}) => {
const api = losant.createClient({ url: url || process.env.LOSANT_API_URL || 'https://api.losant.com' });
if (email) {

@@ -9,0 +7,0 @@ // not try catching this, should only be used on configure and the errors will be handled there.

const request = require('request-promise');
const { experience, files } = require('./constants');
const { endsWith } = require('omnibelt');

@@ -15,4 +16,4 @@ const experienceParams = {

const filesParams = {
getData: async (file) => {
return request({ method: 'GET', uri: file.url, encoding: null, strictSSL: (process.env.LOSANT_API_URL !== 'https://api.losant.space') });
getData: async (file, api) => {
return request({ method: 'GET', uri: file.url, encoding: null, strictSSL: !endsWith('space', api.getOption('url')) });
},

@@ -19,0 +20,0 @@ apiType: files.apiType,

@@ -1,2 +0,1 @@

const getApi = require('./get-api');
const paginateRequest = require('./paginate-request');

@@ -34,6 +33,5 @@ const path = require('path');

let didDownload = false;
const { apiToken, applicationId } = loadedConfig ? loadedConfig : await loadConfig();
const { apiToken, applicationId, api } = loadedConfig ? loadedConfig : await loadConfig();
if (!apiToken || !applicationId) { return; }
if (commandType === 'experience' && !isValidExperienceOptions(command)) { return; }
const api = await getApi({ apiToken });
const meta = await loadLocalMeta(commandType) || {};

@@ -129,3 +127,3 @@ let items;

try {
data = await getData(itemsById[remoteStatus.id]);
data = await getData(itemsById[remoteStatus.id], api);
} catch (e) {

@@ -132,0 +130,0 @@ return logError(`An Error occurred when trying to download data for file ${remoteStatus.file} with the message ${e.message}`);

const path = require('path');
const getApi = require('./get-api');
const paginateRequest = require('./paginate-request');

@@ -52,5 +51,4 @@ const {

const getStatus = async () => {
const { applicationId, apiToken } = await loadConfig();
const { applicationId, apiToken, api } = await loadConfig();
if (!applicationId || !apiToken) { return; }
const api = await getApi({ apiToken });
let items;

@@ -57,0 +55,0 @@ try {

@@ -1,2 +0,1 @@

const getApi = require('./get-api');
const paginateRequest = require('./paginate-request');

@@ -27,3 +26,3 @@ const {

if (isEmpty(config)) { return; }
const api = await getApi({ apiToken: config.apiToken });
const api = config.api;
const meta = await loadLocalMeta(commandType) || {};

@@ -30,0 +29,0 @@ let items;

@@ -1,14 +0,19 @@

const { isNil, merge } = require('omnibelt');
module.exports = async (request, query) => {
const { merge } = require('omnibelt');
module.exports = async (request, query, shouldUseOffset = false) => {
let items = [];
if (!query.perPage) { query.perPage = 1000; }
if (isNil(query.page)) { query.page = 0; }
const results = await request(query) || { items: [] };
let extra = {};
const perPage = 1000;
let page = 0;
extra = (shouldUseOffset && { offset: page*perPage, limit: perPage })|| { page, perPage };
const results = await request(merge(query, extra)) || { items: [] };
if (results.count) { items = results.items; }
if (results.totalCount > 1000) {
const totalPages = Math.ceil(results.totalCount / 1000);
let page = query.page + 1; // since you already made one request above
const totalPages = Math.ceil(results.totalCount / perPage);
++page; // since you already made one request above
const requests = [];
while (page <= totalPages) {
requests.push(request(merge(query), { page }));
extra = (shouldUseOffset && { offset: page*perPage, limit: perPage })|| { page, perPage };
requests.push(request(merge(query, extra)));
++page;

@@ -15,0 +20,0 @@ }

@@ -0,1 +1,2 @@

const getApi = require('./get-api');
const path = require('path');

@@ -12,3 +13,3 @@ const findFile = require('find-file-up');

const {
keyBy, prop, merge, isEmpty, keys, union, find, propEq, propOr
keyBy, prop, mergeRight, isEmpty, keys, union, find, propEq, propOr, mapP
} = require('omnibelt');

@@ -21,3 +22,3 @@ const { rollbarLog } = require('./rollbar');

const resolveUserConfig = () => {
const directory = process.env.NODE_ENV !== 'test' ? (process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE) : './';
const directory = process.env.HOME || process.env.HOMEPATH || process.env.USERPROFILE;
return path.resolve(directory, path.join(`${CONFIG_DIR}`, '.credentials.yml'));

@@ -32,2 +33,4 @@ };

} = require('fs-extra');
const jwt = require('jsonwebtoken');
const inquirer = require('inquirer');

@@ -70,7 +73,34 @@ const utils = {

loadUserConfig: async () => {
loadUserConfig: async (checkIsEmpty = true) => {
const f = resolveUserConfig();
await ensureFile(f);
const config = yaml.safeLoad(await readFile(f)) || {};
if (isEmpty(config)) {
let config = yaml.safeLoad(await readFile(f)) || {};
if (config.apiToken) {
const token = config.apiToken;
config = {};
const decoded = jwt.decode(token, { complete: true }) || {};
if (decoded.payload && decoded.payload.iss) {
if (decoded.payload.iss === 'api.getstructure.io') {
decoded.payload.iss = 'api.losant.com';
}
const defaultUrl = `https://${decoded.payload.iss}`;
const api = await getApi({ apiToken: token, url: defaultUrl });
let wlInfo;
try {
wlInfo = await api.request({ method: 'get', url: '/whitelabels/domain' });
} catch (e) {
// could not get the white-label...
// just throw away the token they need to re login.
}
if (wlInfo) {
config[defaultUrl] = {
apiToken: token,
endpointDomain: wlInfo.endpointDomain,
appUrl: wlInfo.appUrl
};
await writeFile(f, yaml.safeDump(config));
}
}
}
if (checkIsEmpty && isEmpty(config)) {
utils.logError('User Configuration file missing, run losant login to generate this file.');

@@ -82,3 +112,17 @@ process.exit(1);

loadApplicationConfig: async (file) => {
getApiURL: async (userConfig) => {
const apiKeys = keys(userConfig);
if (apiKeys.length === 1) {
return apiKeys[0];
}
const { url } = await inquirer.prompt([{
type: 'list',
name: 'url',
message: 'Choose an API url:',
choices: apiKeys
}]);
return url;
},
loadApplicationConfig: async (file, userConfig) => {
const f = configName(file);

@@ -103,3 +147,7 @@ let foundFile = resolveConfig(file);

}
config.file = file;
if (!config.apiUrl) {
config.apiUrl = await utils.getApiURL(userConfig);
await writeFile(foundFile, yaml.safeDump(config));
}
config.file = foundFile;
}

@@ -110,5 +158,20 @@ return config;

loadConfig: async (file) => {
const userConfig = await utils.loadUserConfig(file);
const appConfig = await utils.loadApplicationConfig(file);
return merge(userConfig, appConfig);
let userConfig = await utils.loadUserConfig();
const appConfig = await utils.loadApplicationConfig(file, userConfig);
// this is handled by all other commands to return early if this function returns an empty object
// the commands assume something was logged
// originally this path was taken to make testing easier.
// TODO UPDATE how errors are logged and either rollbar and exit or continue if it's tests running.
if (!appConfig) { return {}; }
// backwards compatible in case the current applicationConfig does not have an apiUrl
const apiKey = appConfig.apiUrl;
userConfig = userConfig[apiKey];
if (!userConfig || isEmpty(userConfig)) {
utils.logError(`Could not find log in information for API ${apiKey}, please login again.`);
process.exit(1);
}
const api = await getApi({ url: apiKey, apiToken: userConfig.apiToken });
const config = mergeRight(userConfig, appConfig);
config.api = api;
return config;
},

@@ -157,2 +220,4 @@

saveUserConfig: async (config) => {
const userConfig = await utils.loadUserConfig(false);
config = mergeRight(userConfig, config);
const conf = yaml.safeDump(config);

@@ -199,2 +264,10 @@ const f = resolveUserConfig();

getShallowLocalStatus: async (dir, globPattern) => {
const dirPattern = path.resolve(path.join(dir, globPattern));
const files = glob.sync(dirPattern);
return new Map(await mapP(async (file) => {
return [path.relative('.', file), true];
}, files));
},
getLocalStatus: async (type, dir, globPattern) => {

@@ -204,3 +277,3 @@ if (!dir) { dir = type; } // the default pattern is that the directory matches the command type. e.g. files files/

const meta = await utils.loadLocalMeta(type) || {};
const metaFiles = new Set(Object.keys(meta));
const metaFiles = new Set(keys(meta));
const dirPattern = path.resolve(path.join(dir, globPattern));

@@ -248,2 +321,27 @@ const files = glob.sync(dirPattern);

getShallowRemoteStatus: async (localFiles, resources, pathTemplate) => {
if (!resources) { return {}; }
const statusByFile = {};
const compiledTemplate = template(pathTemplate);
resources.forEach((resource) => {
const file = path.normalize(compiledTemplate(resource));
const statObj = {
id: resource.id,
file,
name: resource.name,
status: 'found'
};
statusByFile[file] = statObj;
localFiles.delete(file);
});
const files = Array.from(localFiles.keys());
files.forEach((file) => {
statusByFile[file] = {
file,
status: 'missing'
};
});
return statusByFile;
},
getRemoteStatus: async (type, resources, pathTemplate, contentProperty, options = {}) => {

@@ -254,3 +352,3 @@ const statusByFile = {};

const metaFiles = new Set();
Object.keys(meta).forEach((file) => {
keys(meta).forEach((file) => {
const item = meta[file];

@@ -336,2 +434,21 @@ metaByFile[file] = Object.assign({}, item, { file: file });

getShallowStatus: async ({ items, remoteStatusParams, localStatusParams, dir, pattern }) => {
const localFiles = await utils.getShallowLocalStatus(dir, ...localStatusParams);
const remoteStatusByFile = await utils.getShallowRemoteStatus(new Map(localFiles), items, ...remoteStatusParams);
if (pattern) {
const dirPattern = path.join(dir, pattern);
const allFiles = union(keys(remoteStatusByFile), keys(localFiles));
const matchPattern = new RegExp(`^${dirPattern}.*$`, 'i');
allFiles.forEach((file) => {
if (!matchPattern.test(file)) {
delete remoteStatusByFile[file];
delete localFiles[file];
}
});
}
return { localStatusByFile: localFiles, remoteStatusByFile };
},
getStatus: async ({

@@ -338,0 +455,0 @@ commandType, items, remoteStatusParams, localStatusParams, pattern, type, dir

{
"name": "losant-cli",
"version": "1.1.2",
"version": "1.2.0",
"description": "Losant Command Line Interface",

@@ -40,2 +40,3 @@ "license": "MIT",

"commander": "^2.19.0",
"csv-stringify": "^5.3.0",
"death": "^1.1.0",

@@ -49,8 +50,9 @@ "error": "^7.0.2",

"js-yaml": "^3.12.0",
"jsonwebtoken": "^8.5.1",
"lodash-template": "^1.0.0",
"losant-rest": "2.3.5",
"losant-rest": "2.3.7",
"mime-types": "^2.1.21",
"minimatch": "^3.0.4",
"moment": "^2.22.2",
"omnibelt": "^1.2.0",
"omnibelt": "^1.3.1",
"pad": "^2.2.1",

@@ -61,2 +63,3 @@ "proper-lockfile": "^3.0.2",

"rollbar": "^2.5.0",
"sanitize-filename": "^1.6.2",
"single-line-log": "^1.1.2",

@@ -63,0 +66,0 @@ "update-notifier": "^2.3.0"

@@ -8,3 +8,3 @@ # Losant CLI

[Losant CLI](https://docs.losant.com/cli/overview) is a command line tool to help manage your [Losant Application](https://docs.losant.com/applications/overview/) and its resources.
It easily lets you manage [Experience Views](https://docs.losant.com/experiences/views/), [Experience Versions](https://docs.losant.com/experiences/versions/), and [Files](https://docs.losant.com/applications/files/) in your Applications.
It easily lets you manage [Experience Views](https://docs.losant.com/experiences/views/), [Experience Versions](https://docs.losant.com/experiences/versions/), [Files](https://docs.losant.com/applications/files/), and [Data Tables](https://docs.losant.com/data-tables/overview/) in your Applications.

@@ -31,2 +31,3 @@ ## Installation

* [files](#files)
* [datatables](#data-tables)

@@ -125,2 +126,17 @@ ### Login

### Data Tables
The `losant datatables` command is how you manage the data tables for a configured Application. It has the following subcommands:
* export
#### Data Tables Examples
* Export all data tables
`$ losant datatables export`
* Export all data tables whose names start with `Chicago`
`$ losant datatables export Chicago`
* Force a export of all data tables overwriting local modifications
`$ losant datatables export -f`
*****

@@ -130,2 +146,2 @@

<https://www.losant.com>
<https://www.losant.com>
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc