UNPKG

node-op

Version:

Interactive 1Password CLI and installer

239 lines (196 loc) 7.53 kB
'use strict'; 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; });