Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

horticulturalist

Package Overview
Dependencies
Maintainers
10
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

horticulturalist - npm Package Compare versions

Comparing version 0.12.7 to 0.13.0

.eslintrc

11

package.json
{
"name": "horticulturalist",
"version": "0.12.7",
"version": "0.13.0",
"description": "A fancy gardener",

@@ -15,4 +15,5 @@ "repository": "https://github.com/medic/horticulturalist",

"start": "node src/index.js --dev",
"test": "npm run unit-tests && npm run int-tests",
"unit-tests": "jshint src/ tests/ && TESTING=1 mocha ./tests/unit/**.js",
"eslint": "eslint src/**/* tests/**/*",
"test": "npm run eslint && npm run unit-tests && npm run int-tests",
"unit-tests": "TESTING=1 mocha ./tests/unit/**.js",
"int-tests": "mocha --full-trace -s 10000 -t 65536 ./tests/int/tests/**.js"

@@ -36,7 +37,7 @@ },

"chai-as-promised": "^7.1.1",
"jshint": "^2.9.4",
"eslint": "^5.12.1",
"medic-builds-repo": "^0.3.0",
"mocha": "^5.2.0",
"sinon": "4.0.1"
"sinon": "^7.2.5"
}
}
Horticulturalist
================
Deploys and manages [Medic](github.com/medic/medic-webapp).
Deploys and manages [Medic](https://github.com/medic/medic).
For more detailed documentation on how to start Medic using Horticulturalist, [see this guide](https://github.com/medic/medic-webapp#deploy-locally-using-horticulturalist-beta).
For more detailed documentation on how to start Medic using Horticulturalist, [see this guide](https://github.com/medic/medic#deploy-locally-using-horticulturalist-beta).

@@ -64,6 +64,2 @@ # Usage

# Development
Most development is managed in [the horticulturalist tag in medic/medic-webapp](https://github.com/medic/medic-webapp/issues?q=is%3Aopen+is%3Aissue+label%3Ahorticulturalist).
## Releasing

@@ -70,0 +66,0 @@

@@ -8,3 +8,4 @@ const request = require('request-promise-native'),

const { info } = require('./log');
const { info, error } = require('./log'),
help = require('./help');

@@ -24,3 +25,11 @@ const DEFAULT_BUILDS_URL = 'https://staging.dev.medicmobile.org/_couch/builds';

} else {
const COUCH_URL = new URL(process.env.COUCH_URL);
let COUCH_URL;
try {
COUCH_URL = new URL(process.env.COUCH_URL);
} catch (err) {
help.outputHelp();
error('You must define the COUCH_URL environment variable, pointing to the DB you wish to deploy into');
process.exit(-1);
}
COUCH_URL.pathname = '/';

@@ -42,3 +51,5 @@

const DEPLOY_URL = process.env.COUCH_URL;
if(!DEPLOY_URL) throw new Error('COUCH_URL env var not set.');
if (!DEPLOY_URL) {
throw new Error('COUCH_URL env var not set.');
}

@@ -45,0 +56,0 @@ module.exports = {

const fs = require('fs'),
path = require('path');
const {info} = require('./log');
const pluckOptionsFromReadme = () => {
const readmePath = path.join(__dirname, '..', 'README.md');
let readmeString = fs.readFileSync(readmePath, 'utf8');
let readmeString = '\n' + fs.readFileSync(readmePath, 'utf8');

@@ -19,9 +21,8 @@ // Everything before options

const package = require('../package');
console.log(`Horticulturalist ${package.version}`);
info(`Horticulturalist ${package.version}`);
},
outputHelp: () => {
module.exports.outputVersion();
console.log();
console.log(pluckOptionsFromReadme());
info(pluckOptionsFromReadme());
}
};

@@ -63,8 +63,2 @@ #!/usr/bin/env node

if (active(argv.dev, argv.local, argv['medic-os'], argv.test).length !== 1) {
help.outputHelp();
error('You must pick one mode to run in.');
process.exit(-1);
}
const mode = argv.dev ? MODES.development :

@@ -76,3 +70,2 @@ argv.test ? MODES.test :

if (argv.version || argv.v) {

@@ -83,3 +76,3 @@ help.outputVersion();

if (!mode || argv.help || argv.h) {
if (argv.help || argv.h) {
help.outputHelp();

@@ -89,2 +82,8 @@ return;

if (active(argv.dev, argv.local, argv['medic-os'], argv.test).length !== 1) {
help.outputHelp();
error('You must pick one mode to run in.');
process.exit(-1);
}
if (active(argv.install, argv.stage, argv['complete-install']).length > 1) {

@@ -108,3 +107,3 @@ help.outputHelp();

if (version === true) {
version = 'medic:medic:master';
version = '@medic:medic:release';
}

@@ -130,3 +129,3 @@

process.on('unhandledRejection', (err) => {
console.error(err);
error(err);
fatality('Unhandled rejection, please raise this as a bug!');

@@ -133,0 +132,0 @@ });

@@ -18,2 +18,15 @@ const decompress = require('decompress');

const appNotCurrent = app => {
const currentPath = app.deployPath('current');
if (!fs.existsSync(currentPath)) {
return true;
}
const linkString = fs.readlinkSync(currentPath);
if(!fs.existsSync(linkString)) {
return true;
}
return linkString !== app.deployPath();
};
const getApps = () => {

@@ -39,4 +52,4 @@ if (ddoc.node_modules) {

debug(`Found ${JSON.stringify(changedApps)}`);
changedApps = changedApps.filter(appNotAlreadyUnzipped);
debug(`Apps that aren't unzipped: ${JSON.stringify(changedApps)}`);
changedApps = changedApps.filter(appNotCurrent);
debug(`Apps that are changed: ${JSON.stringify(changedApps)}`);

@@ -46,4 +59,5 @@ return changedApps;

const unzipChangedApps = (changedApps) =>
Promise.all(changedApps.map(app => {
const unzipChangedApps = (changedApps) => {
const appsToUnzip = changedApps.filter(appNotAlreadyUnzipped);
return Promise.all(appsToUnzip.map(app => {
const attachment = ddoc._attachments[app.attachmentName].data;

@@ -57,2 +71,3 @@ return decompress(attachment, app.deployPath(), {

}));
};

@@ -59,0 +74,0 @@ return {

@@ -95,3 +95,3 @@ const fs = require('fs-extra'),

const deployStagedDdocs = () => {
info(`Deploying staged ddocs`);
info('Deploying staged ddocs');

@@ -118,3 +118,5 @@ return moduleWithContext._loadStagedDdocs()

fs.symlinkSync(linkString, oldLinkString);
} else debug(`Old app not found at ${linkString}.`);
} else {
debug(`Old app not found at ${linkString}.`);
}

@@ -121,0 +123,0 @@ fs.unlinkSync(livePath);

@@ -1,12 +0,20 @@

const { info, debug, stage: stageLog } = require('../log'),
const { info, debug, stage: stageLog, error } = require('../log'),
DB = require('../dbs'),
fs = require('fs-extra'),
utils = require('../utils'),
ddocWrapper = require('./ddocWrapper');
ddocWrapper = require('./ddocWrapper'),
warmViews = require('./warmViews');
const ACTIVE_TASK_QUERY_INTERVAL = 10 * 1000; // 10 seconds
const stager = deployDoc => (key, message) => {
stageLog(message);
return utils.appendDeployLog(deployDoc, {key: key, message: message});
const stageRunner = deployDoc => (key, message, stageFn) => {
return utils.readyStage(deployDoc, key, message)
.then(stageShouldRun => {
if (stageFn && !stageShouldRun) {
// Mark stages with executable content against them as skipped if we
// don't think we should run them again
stageLog(`Skipping: ${message}`);
} else {
stageLog(message);
return stageFn && stageFn();
}
});
};

@@ -20,2 +28,27 @@

const appId = deployDoc => `_design/${deployDoc.build_info.application}`;
const findDownloadedBuild = deployDoc => {
debug(`Locating already downloaded ${keyFromDeployDoc(deployDoc)}`);
const id = utils.getStagedDdocId(appId(deployDoc));
return DB.app.get(id, {
attachments: true,
binary: true
})
.catch(err => {
// Two reasons this might be happening (as well as "CouchDB is down etc"):
// - We are trying to `--complete-install` without `--stage`ing first, and so there is no
// ddoc to pick up from. This is highly unlikely as we check for the deploy doc being in the
// right state before getting here.
// - This deploy failed on or after the staged ddocs are deleted. This is highly unlikely
// because (as of writing) this is the very last stage-- postCleanup.
//
// The solution for both of these problems would be to start the installation again
error(`Failed to find existing staged ddoc: ${err.message}`);
throw err;
});
};
const downloadBuild = deployDoc => {

@@ -27,3 +60,3 @@ debug(`Downloading ${keyFromDeployDoc(deployDoc)}, this may take some time…`);

deployable._id = `_design/${deployDoc.build_info.application}`;
deployable._id = appId(deployDoc);
utils.stageDdoc(deployable);

@@ -64,137 +97,2 @@ deployable.deploy_info = {

const warmViews = (deployDoc) => {
let viewsWarmed = false;
const writeProgress = () => {
return DB.activeTasks()
.then(tasks => {
const relevantTasks = tasks.filter(task =>
task.type === 'indexer' && task.design_document.includes(':staged:'));
return updateIndexers(relevantTasks);
});
};
// logs indexer progress in the console
// _design/doc [||||||||||29%||||||||||_________________________________________________________]
const logIndexersProgress = (indexers) => {
if (!indexers || !indexers.length) {
return;
}
const logProgress = (indexer) => {
// progress bar stretches to match console width.
// 60 is roughly the nbr of chars displayed around the bar (ddoc name + debug padding)
const barLength = process.stdout.columns - 60,
progress = `${indexer.progress}%`,
filledBarLength = (indexer.progress / 100 * barLength),
bar = progress
.padStart((filledBarLength + progress.length) / 2, '|')
.padEnd(filledBarLength, '|')
.padEnd(barLength, '_'),
ddocName = indexer.design_document.padEnd(35, ' ');
debug(`${ddocName}[${bar}]`);
};
debug('View indexer progress');
indexers.forEach(logProgress);
};
// Groups tasks by `design_document` and calculates the average progress per ddoc
// When a task is finished, it disappears from _active_tasks
const updateIndexers = (runningTasks) => {
const entry = deployDoc.log[deployDoc.log.length - 1],
indexers = entry.indexers || [];
// We assume all previous tasks have finished.
indexers.forEach(setTasksToComplete);
// If a task is new or still running, it's progress is updated
updateRunningTasks(indexers, runningTasks);
indexers.forEach(calculateAverageProgress);
entry.indexers = indexers;
logIndexersProgress(indexers);
return utils.update(deployDoc);
};
const setTasksToComplete = (indexer) => {
Object
.keys(indexer.tasks)
.forEach(pid => {
indexer.tasks[pid] = 100;
});
};
const calculateAverageProgress = (indexer) => {
const tasks = Object.keys(indexer.tasks);
indexer.progress = Math.round(tasks.reduce((progress, pid) => progress + indexer.tasks[pid], 0) / tasks.length);
};
const updateRunningTasks = (indexers, activeTasks = []) => {
activeTasks.forEach(task => {
let indexer = indexers.find(indexer => indexer.design_document === task.design_document);
if (!indexer) {
indexer = {
design_document: task.design_document,
tasks: {},
};
indexers.push(indexer);
}
indexer.tasks[`${task.node}-${task.pid}`] = task.progress;
});
};
// Query _active_tasks every 10 seconds until `viewsWarmed` is true
const writeProgressTimeout = () => {
setTimeout(() => {
if (viewsWarmed) {
return;
}
writeProgress().then(writeProgressTimeout);
}, ACTIVE_TASK_QUERY_INTERVAL);
};
const probeViews = viewlist => {
return Promise
.all(viewlist.map(view => DB.app.query(view, { limit: 1 })))
.then(() => {
viewsWarmed = true;
info('Warming views complete');
return updateIndexers();
})
.catch(err => {
if (err.error !== 'timeout') {
throw err;
}
return probeViews(viewlist);
});
};
const firstView = ddoc =>
`${ddoc._id.replace('_design/', '')}/${Object.keys(ddoc.views).find(k => k !== 'lib')}`;
return utils.getStagedDdocs(true)
.then(ddocs => {
debug(`Got ${ddocs.length} staged ddocs`);
const queries = ddocs
.filter(ddoc => ddoc.views && Object.keys(ddoc.views).length)
.map(firstView);
info('Beginning view warming');
deployDoc.log.push({
type: 'warm_log'
});
return utils.update(deployDoc)
.then(() => {
writeProgressTimeout();
return probeViews(queries);
});
});
};
const clearStagedDdocs = () => {

@@ -222,3 +120,5 @@ debug('Clear existing staged DBs');

fs.removeSync(linkString);
} else debug(`Old app not found at ${linkString}.`);
} else {
debug(`Old app not found at ${linkString}.`);
}

@@ -262,3 +162,3 @@ fs.unlinkSync(oldPath);

const predeploySteps = (deployDoc) => {
const stage = stager(deployDoc);
const stage = stageRunner(deployDoc);

@@ -268,11 +168,11 @@ let ddoc;

return stage('horti.stage.init', `Horticulturalist deployment of '${keyFromDeployDoc(deployDoc)}' initialising`)
.then(() => stage('horti.stage.preCleanup', 'Pre-deploy cleanup'))
.then(() => preCleanup())
.then(() => stage('horti.stage.download', 'Downloading and staging install'))
.then(() => downloadBuild(deployDoc))
.then(() => stage('horti.stage.preCleanup', 'Pre-deploy cleanup', preCleanup))
.then(() => stage('horti.stage.download', 'Downloading and staging install', () => downloadBuild(deployDoc)))
.then(stagedDdoc => {
// If we're resuming a deployment and we skip the above stage we need to find the ddoc manually
return stagedDdoc || findDownloadedBuild(deployDoc);
})
.then(stagedDdoc => ddoc = stagedDdoc)
.then(() => stage('horti.stage.extractingDdocs', 'Extracting ddocs'))
.then(() => extractDdocs(ddoc))
.then(() => stage('horti.stage.warmingViews', 'Warming views'))
.then(() => warmViews(deployDoc))
.then(() => stage('horti.stage.extractingDdocs', 'Extracting ddocs', () => extractDdocs(ddoc)))
.then(() => stage('horti.stage.warmingViews', 'Warming views', () => warmViews().warm(deployDoc)))
.then(() => stage('horti.stage.readyToDeploy', 'View warming complete, ready to deploy'))

@@ -290,20 +190,12 @@ .then(() => ddoc);

} else {
debug('Loading application ddoc');
const ddocId = utils.getStagedDdocId(`_design/${deployDoc.build_info.application}`);
return DB.app.get(ddocId, {
attachments: true,
binary: true
});
return findDownloadedBuild(deployDoc);
}
};
const stage = stager(deployDoc);
const stage = stageRunner(deployDoc);
return stage('horti.stage.initDeploy', 'Initiating deployment')
.then(getApplicationDdoc)
.then(ddoc => {
return stage('horti.stage.deploying', 'Deploying new installation')
.then(() => performDeploy(mode, deployDoc, ddoc, firstRun))
.then(() => stage('horti.stage.postCleanup', 'Post-deploy cleanup, installation complete'))
.then(() => postCleanup(ddocWrapper(ddoc, mode), deployDoc));
});
.then(stagedDdoc => ddoc = stagedDdoc)
.then(() => stage('horti.stage.deploying', 'Deploying new installation', () => performDeploy(mode, deployDoc, ddoc, firstRun)))
.then(() => stage('horti.stage.postCleanup', 'Post-deploy cleanup, installation complete', () => postCleanup(ddocWrapper(ddoc, mode), deployDoc)));
};

@@ -343,5 +235,4 @@

_extractDdocs: extractDdocs,
_warmViews: warmViews,
_deploySteps: deploySteps,
_postCleanup: postCleanup
};

@@ -14,6 +14,3 @@ const lockfile = require('lockfile');

new Promise((resolve, reject) => {
lockfile.lock(LOCK_FILE, err => {
if(err) reject(err);
else resolve();
});
lockfile.lock(LOCK_FILE, err => err ? reject(err) : resolve());
});

@@ -20,0 +17,0 @@

@@ -5,4 +5,4 @@ const debug = require('debug');

module.exports.stage = debug('horti:stage');
module.exports.info = console.log;
module.exports.error = console.error;
module.exports.info = console.log; //eslint-disable-line
module.exports.error = console.error; //eslint-disable-line

@@ -9,0 +9,0 @@ if (!process.env.TESTING) {

@@ -105,3 +105,3 @@ const { debug } = require('./log');

},
appendDeployLog: (deployDoc, message, type='stage') => {
readyStage: (deployDoc, key, message) => {
if (!deployDoc.log) {

@@ -111,9 +111,25 @@ deployDoc.log = [];

deployDoc.log.push({
type: type,
datetime: new Date().getTime(),
message: message
});
const existingStages = deployDoc.log.filter(entry => entry.type === 'stage');
const thisStageIdx = existingStages.findIndex(entry => entry.key === key);
return module.exports.update(deployDoc);
if (thisStageIdx === -1) {
// We have not attempted this stage before
deployDoc.log.push({
type: 'stage',
datetime: new Date().getTime(),
key: key,
message: { message, key }
});
return module.exports.update(deployDoc)
.then(() => true);
} else if (thisStageIdx === existingStages.length - 1) {
// Attempted and not passed, so try again
return Promise.resolve(true);
} else {
// Attemped and passed, so skip
return Promise.resolve(false);
}
},

@@ -141,3 +157,3 @@ update: doc => {

if (err.code === 'EPIPE') {
err.horticulturalist = `Failed to perform bulk docs, you may need to increase CouchDB's max_http_request_size`;
err.horticulturalist = 'Failed to perform bulk docs, you may need to increase CouchDB\'s max_http_request_size';
throw err;

@@ -144,0 +160,0 @@ }

Sorry, the diff of this file is not supported yet

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