UNPKG

@sidekick-coder/db

Version:

Cli Tool to manipulate data from diferent sources

389 lines (380 loc) 11.4 kB
import * as valibot from 'valibot'; import path, { dirname, resolve } from 'path'; import fs from 'fs'; import 'url'; import { parse as parse$1, stringify as stringify$1 } from 'yaml'; import qs from 'qs'; import * as inquirer from '@inquirer/prompts'; import crypto from 'crypto'; // src/core/validator/valibot.ts // src/utils/tryCatch.ts async function tryCatch(tryer) { try { const result = await tryer(); return [result, null]; } catch (error) { return [null, error]; } } tryCatch.sync = function(tryer) { try { const result = tryer(); return [result, null]; } catch (error) { return [null, error]; } }; var parse = parse$1; var stringify = stringify$1; var YAML = { parse, stringify }; function readFileSync(path4) { const [content, error] = tryCatch.sync(() => fs.readFileSync(path4)); if (error) { return null; } return new Uint8Array(content); } readFileSync.text = function(filepath, defaultValue = "") { const content = readFileSync(filepath); if (!content) { return defaultValue; } return new TextDecoder().decode(content); }; readFileSync.json = function(path4, options) { const content = readFileSync.text(path4); if (!content) { return (options == null ? void 0 : options.default) || null; } const [json, error] = tryCatch.sync(() => JSON.parse(content, options == null ? void 0 : options.reviver)); return error ? (options == null ? void 0 : options.default) || null : json; }; readFileSync.yaml = function(path4, options) { const content = readFileSync.text(path4); if (!content) { return (options == null ? void 0 : options.default) || null; } const [yml, error] = tryCatch.sync(() => YAML.parse(content, options == null ? void 0 : options.parseOptions)); return error ? (options == null ? void 0 : options.default) || null : yml; }; var filesystem = { readSync: readFileSync}; function createReviver(folder) { return (_, value) => { if (typeof value == "string" && value.startsWith("./")) { return resolve(dirname(folder), value); } return value; }; } var schema = valibot.optional( valibot.pipe( valibot.any(), valibot.transform((value) => { if (typeof value == "object") { return value; } if (/\.yml$/.test(value)) { const file = value.replace(/^@/, ""); const folder = dirname(file); return filesystem.readSync.yaml(value.replace(/^@/, ""), { reviver: createReviver(folder) }); } if (typeof value == "string" && value.includes("=")) { const result = qs.parse(value, { allowEmptyArrays: true }); return result; } if (typeof value == "string" && value.startsWith("{")) { return JSON.parse(value); } if (typeof value == "string" && value.startsWith("[")) { return JSON.parse(value); } return value; }), valibot.record(valibot.string(), valibot.any()) ) ); function createPathNode() { return { resolve: (...args) => path.resolve(...args), join: (...args) => path.join(...args), dirname: (args) => path.dirname(args), basename: (args) => path.basename(args) }; } // src/core/validator/valibot.ts var stringList = valibot.pipe( valibot.any(), valibot.transform((value) => { if (typeof value === "string") { return value.split(","); } if (Array.isArray(value)) { return value; } }), valibot.array(valibot.string()) ); function array2(s) { return valibot.pipe( v2.union([v2.array(s), s]), valibot.transform((value) => Array.isArray(value) ? value : [value]), valibot.array(s) ); } function path3(dirname2, path4 = createPathNode()) { return valibot.pipe( valibot.string(), valibot.transform((value) => path4.resolve(dirname2, value)) ); } function uint8() { return valibot.pipe( valibot.any(), valibot.check((value) => value instanceof Uint8Array), valibot.transform((value) => value) ); } var prompts = { password: (options) => valibot.optionalAsync(valibot.string(), () => { return inquirer.password({ message: "Enter password", ...options }); }) }; var extras = { array: array2, vars: schema, stringList, path: path3, uint8, number: valibot.pipe( valibot.any(), valibot.transform(Number), valibot.check((n) => !isNaN(n)), valibot.number() ) }; var vWithExtras = { ...valibot, extras, prompts }; var v2 = vWithExtras; // src/core/validator/validate.ts function validate(cb, payload) { const schema3 = typeof cb === "function" ? cb(v2) : cb; const { output, issues, success } = v2.safeParse(schema3, payload); if (!success) { const flatten = v2.flatten(issues); const messages = []; if (flatten.root) { messages.push(...flatten.root); } if (flatten.nested) { Object.entries(flatten.nested).forEach((entry) => { const [key, value] = entry; messages.push(...value.map((v3) => `${key}: ${v3}`)); }); } const message = messages.length ? messages.join(", ") : "Validation failed"; const error = new Error(message); error.name = "ValidationError"; Object.assign(error, { messages }); throw error; } return output; } validate.async = async function(cb, payload) { let schema3; if (typeof cb === "function") { schema3 = cb(v2); } else { schema3 = cb; } const { output, issues, success } = await v2.safeParseAsync(schema3, payload); if (!success) { const error = new Error("Validation failed"); const flatten = v2.flatten(issues); const details = { ...flatten.root, ...flatten.nested }; Object.assign(error, { details }); throw error; } return output; }; // src/providers/vault/encryption.ts var schema2 = v2.object({ value: v2.union([v2.string(), v2.extras.uint8()]), salt: v2.optional(v2.string(), crypto.randomBytes(16).toString("hex")), iv: v2.optional(v2.string(), crypto.randomBytes(16).toString("hex")), password: v2.string() }); function encrypt(payload) { const options = validate(schema2, payload); const salt = Buffer.from(options.salt, "hex"); const iv = Buffer.from(options.iv, "hex"); const key = crypto.scryptSync(options.password, salt, 32); const cipher = crypto.createCipheriv("aes-256-ctr", key, iv); if (options.value instanceof Uint8Array) { const buffer = Buffer.concat([cipher.update(options.value), cipher.final()]); return new Uint8Array(buffer); } if (typeof options.value === "string") { const encrypted = cipher.update(options.value, "utf8", "hex"); return encrypted + cipher.final("hex"); } throw new Error("Invalid type"); } function decrypt(payload) { const options = validate(schema2, payload); const salt = Buffer.from(options.salt, "hex"); const iv = Buffer.from(options.iv, "hex"); const key = crypto.scryptSync(options.password, salt, 32); const decipher = crypto.createDecipheriv("aes-256-ctr", key, iv); if (typeof options.value === "string") { let decrypted = decipher.update(options.value, "hex", "utf8"); decrypted += decipher.final("utf8"); return decrypted; } if (options.value instanceof Uint8Array) { const buffer = Buffer.concat([decipher.update(options.value), decipher.final()]); return new Uint8Array(buffer); } throw new Error("Invalid type"); } function createEncryption(payload) { const state = { salt: (payload == null ? void 0 : payload.salt) || crypto.randomBytes(16).toString("hex"), iv: (payload == null ? void 0 : payload.iv) || crypto.randomBytes(16).toString("hex"), password: (payload == null ? void 0 : payload.password) || "" }; const instance = { state, setSalt: function(salt) { state.salt = salt; return instance; }, setIv: function(iv) { state.iv = iv; return instance; }, setPassword: function(password2) { state.password = password2; return instance; }, encrypt: function(value) { return encrypt({ ...state, value }); }, decrypt: function(value) { return decrypt({ ...state, value }); } }; return instance; } function findMetadata(options) { const { filesystem: filesystem2, root, id } = options; const resolve3 = (...args) => filesystem2.path.resolve(root, ...args); const filepath = resolve3(id, ".db", "metadata.json"); const json = filesystem2.readSync.json(filepath, { default: { salt: crypto.randomBytes(16).toString("hex"), iv: crypto.randomBytes(16).toString("hex"), files: [] } }); const all = filesystem2.readdirSync(resolve3(id)); const files = []; all.filter((file) => file !== ".db").forEach((file) => { var _a; const meta = (_a = json.files) == null ? void 0 : _a.find((f) => f.name === file); files.push({ name: file, encrypted: false, ...meta }); }); json.files = files; const schema3 = v2.object({ salt: v2.string(), iv: v2.string(), files: v2.array(v2.object({ name: v2.string(), encrypted: v2.boolean() })) }); return validate(schema3, json); } // src/providers/vault/findPassword.ts function findPassword(options) { const { filesystem: filesystem2, root } = options; const resolve3 = (...args) => filesystem2.path.resolve(root, ...args); if (!filesystem2.existsSync(resolve3(".db", "password"))) { throw new Error("Not authenticated, please run db auth first"); } const password2 = filesystem2.readSync.text(resolve3(".db", "password")); const metadata = filesystem2.readSync.json(resolve3(".db", "password.json"), { schema: (v3) => v3.object({ salt: v3.string(), iv: v3.string(), test: v3.string() }) }); if (!password2 || !metadata) { throw new Error("Password not found"); } const encryption = createEncryption({ password: password2, salt: metadata.salt, iv: metadata.iv }); const decrypted = encryption.decrypt(metadata.test); if (!decrypted.endsWith("success")) { throw new Error("Password incorrect"); } return password2; } // src/providers/vault/lockItem.ts async function lockItem(options) { const { filesystem: filesystem2, root } = options; const resolve3 = (...args) => filesystem2.path.resolve(root, ...args); const id = validate((v3) => v3.string(), options.options.id); const filepath = resolve3(id); if (!filesystem2.existsSync(resolve3(id))) { throw new Error(`Item ${id} not found in ${filepath}`); } const password2 = findPassword({ filesystem: filesystem2, root }); const metadata = findMetadata({ id, filesystem: filesystem2, root }); const encryption = createEncryption({ password: password2, salt: metadata.salt, iv: metadata.iv }); for (const file of metadata.files) { if (file.encrypted) { continue; } const source_filename = file.name; const target_filename = encryption.encrypt(source_filename); const contents = filesystem2.readSync(resolve3(id, source_filename)); const encrypted = encryption.encrypt(contents); filesystem2.writeSync(resolve3(id, target_filename), encrypted); filesystem2.removeSync(resolve3(id, source_filename)); file.encrypted = true; file.name = target_filename; } filesystem2.writeSync.json(resolve3(id, ".db", "metadata.json"), metadata, { recursive: true }); return metadata.files; } export { lockItem };