node-op
Version:
Interactive 1Password CLI and installer
239 lines (196 loc) • 7.53 kB
JavaScript
require('./chunk-1d702382.js');
require('events');
require('child_process');
var path = require('path');
require('fs');
var index = require('./chunk-e982b35b.js');
require('os');
var ensureError = require('./chunk-ec31973d.js');
var util = require('util');
var installOp = require('./chunk-07be731c.js');
require('constants');
require('stream');
require('assert');
var rethrowAsync = require('./chunk-7262e635.js');
var catchAsync = require('./chunk-31f10cfd.js');
require('https');
require('zlib');
require('crypto');
async function createDocument(props) {
const output = await ensureError.spawnAndCheck('op', ['create', 'document', props.file, props.title && `--title=${props.title}`, props.vault && `--vault=${props.vault}`].filter(util.isString), {
env: process.env,
verbosity: props.verbosity,
stdio: ['inherit', 'pipe', 'pipe']
});
const parsed = await catchAsync.catchAsync(() => JSON.parse(output));
if (parsed.error) {
throw new Error(`Couldn't create document, cannot parse output "${output}" as JSON`);
}
return parsed.result.uuid;
}
async function trashItem(props) {
await ensureError.spawnAndCheck('op', ['delete', 'item', props.uuid, props.vault && `--vault=${props.vault}`].filter(util.isString), {
env: process.env,
verbosity: props.verbosity,
stdio: ['inherit', 'pipe', 'pipe']
});
}
function findSingleFile(file, items) {
const title = path.basename(file);
const filtered = items.filter(item => {
var _item$overview;
return title === (item === null || item === void 0 ? void 0 : (_item$overview = item.overview) === null || _item$overview === void 0 ? void 0 : _item$overview.title);
});
if (filtered.length === 0) {
return null;
}
if (filtered.length > 1) {
throw new Error(`More than one document with title '${title}' found: [${filtered.map(item => `"${item.uuid}"`).join(', ')}]`);
}
return filtered[0];
}
async function validateFile(file, items, deps = {
stat: installOp.lib.stat
}) {
const result = await deps.stat(file);
if (!result.isFile()) {
throw new Error(`file at path '${file}' is not a file`);
}
return findSingleFile(file, items);
}
async function processFile(props, file, trashUuid, deps = {
createDocument,
trashItem,
unlink: installOp.lib.unlink
}) {
var _props$verbosity;
const verbosity = (_props$verbosity = props === null || props === void 0 ? void 0 : props.verbosity) !== null && _props$verbosity !== void 0 ? _props$verbosity : 0;
const newUuid = await rethrowAsync.rethrowAsync(() => deps.createDocument({
verbosity,
file,
...(props.vault && {
vault: props.vault
})
}), errInfo => errInfo.withMessage(`Cannot create new document in 1-Password vault from file "${file}"`));
const trash = async () => {
var _props$trash;
const shouldTrash = (_props$trash = props.trash) !== null && _props$trash !== void 0 ? _props$trash : true;
if (!trashUuid || !shouldTrash) {
return;
}
await rethrowAsync.rethrowAsync(() => deps.trashItem({
verbosity,
uuid: trashUuid,
...(props.vault && {
vault: props.vault
})
}), errInfo => errInfo.withMessage(`Cannot delete previous version of the document from 1-Password vault, with new document id "${newUuid}" and old document id "${trashUuid}"`));
};
const deleteLocal = async () => {
var _props$keepLocal;
const shouldKeepLocal = (_props$keepLocal = props.keepLocal) !== null && _props$keepLocal !== void 0 ? _props$keepLocal : false;
if (shouldKeepLocal) {
return;
}
await rethrowAsync.rethrowAsync(async () => {
if (verbosity > 0) {
console.log(`Deleting "${file}"`);
}
await deps.unlink(file);
}, errInfo => errInfo.withMessage(`Cannot delete local file at "${file}" after successfull 1-Password vault upload`));
};
await trash();
await deleteLocal();
}
async function vaultCheckin(props, deps = {
listItems: ensureError.listItems,
createDocument,
trashItem,
stat: installOp.lib.stat,
unlink: installOp.lib.unlink
}) {
var _props$verbosity2, _props$dryRun;
// tslint:disable-next-line: strict-boolean-expressions
if (!props || typeof props !== 'object') {
throw new TypeError('no properties passed');
}
if (typeof props.vault !== 'undefined' && typeof props.vault !== 'string') {
throw new TypeError('vault should be a string');
}
if (typeof props.trash !== 'undefined' && typeof props.trash !== 'boolean') {
throw new TypeError('trash should be a boolean');
}
if (typeof props.verbosity !== 'undefined' && (typeof props.verbosity !== 'number' || ![0, 1, 2].includes(props.verbosity))) {
throw new TypeError('verbosity should be a number: 0, 1 or 2');
}
if (!Array.isArray(props.files) || props.files.length === 0) {
throw new TypeError('files should be a non-empty array of strings');
}
const verbosity = (_props$verbosity2 = props === null || props === void 0 ? void 0 : props.verbosity) !== null && _props$verbosity2 !== void 0 ? _props$verbosity2 : 0;
const items = await rethrowAsync.rethrowAsync(() => deps.listItems({
verbosity,
...(props.vault && {
vault: props.vault
})
}), errInfo => errInfo.withMessage('Cannot list items in 1-Password vault'));
const filesAndItems = await Promise.all(props.files.map(file => validateFile(file, items, {
stat: deps.stat
}).then(item => ({
file,
uuid: item === null || item === void 0 ? void 0 : item.uuid
}))));
if (verbosity > 0) {
console.log('Will upload files', filesAndItems.map(item => item.file));
const toTrash = filesAndItems.filter(item => !!item.uuid);
console.log('Following 1-Password items going to be trashed', toTrash);
}
if ((_props$dryRun = props.dryRun) !== null && _props$dryRun !== void 0 ? _props$dryRun : false) {
return;
}
const results = await Promise.all(filesAndItems.map(pair => catchAsync.catchAsync(() => processFile(props, pair.file, pair.uuid, {
createDocument: deps.createDocument,
trashItem: deps.trashItem,
unlink: deps.unlink
}))));
const errorResults = results.map(item => item.error).filter(util.isError);
if (errorResults.length > 0) {
if (errorResults.length > 1) {
const [first, ...rest] = errorResults;
throw new ensureError.AggregateError(first, ...rest);
} else {
throw errorResults[0];
}
}
}
const program = new index.commander.Command();
const parsed = program.description('Upload one or more files to 1-Password vault from current directory and trash old files with same name').exitOverride(err => {
if (err.message === '(outputHelp)') {
return;
}
program.outputHelp();
process.exit(err.exitCode);
}).option('-v --vault <vault-name>', 'vault to use').option('--verbosity <0|1|2>', 'verbosity of stdout', (next, previous) => {
if (typeof previous !== 'undefined') {
throw new Error('Verbosity can be specified only once');
}
if (!['0', '1', '2'].includes(next)) {
throw new Error('verbosity can be only 0, 1 or 2');
}
return parseInt(next, 10);
}).requiredOption('-f --files <title>', 'list of files to checkin', (next, previous) => {
return [...(previous || []), next];
}).parse(process.argv);
async function run() {
await vaultCheckin({
vault: parsed.vault,
files: parsed.files,
verbosity: parsed.verbosity
});
}
run().then(() => {
process.exitCode = 0;
}).catch(err => {
console.error(err.message);
process.exitCode = 1;
});
;