@sidekick-coder/db
Version:
Cli Tool to manipulate data from diferent sources
508 lines (499 loc) • 13.3 kB
JavaScript
import path, { dirname, resolve } from 'path';
import fs from 'fs';
import { parse as parse$1, stringify as stringify$1 } from 'yaml';
import * as valibot from 'valibot';
import 'url';
import qs from 'qs';
import * as inquirer from '@inquirer/prompts';
import { merge } from 'lodash-es';
import fg from 'fast-glob';
// src/core/drive/index.ts
var drive = {
exists: async (path4) => {
try {
await fs.promises.stat(path4);
return true;
} catch (error) {
return false;
}
},
existsSync: (path4) => {
try {
fs.statSync(path4);
return true;
} catch (error) {
return false;
}
},
list: async (path4, options) => {
if (!await drive.exists(path4)) {
return [];
}
const all = await fs.promises.readdir(path4, { withFileTypes: true });
if (options == null ? void 0 : options.onlyFiles) {
return all.filter((item) => item.isFile()).map((item) => item.name);
}
if (options == null ? void 0 : options.onlyDirs) {
return all.filter((item) => item.isDirectory()).map((item) => item.name);
}
return all.map((item) => item.name);
},
listSync: (path4, options) => {
if (!drive.existsSync(path4)) {
return [];
}
const all = fs.readdirSync(path4, { withFileTypes: true });
if (options == null ? void 0 : options.onlyFiles) {
return all.filter((item) => item.isFile()).map((item) => item.name);
}
if (options == null ? void 0 : options.onlyDirs) {
return all.filter((item) => item.isDirectory()).map((item) => item.name);
}
return all.map((item) => item.name);
},
read: async (path4) => {
if (!await drive.exists(path4)) {
throw new Error(`entry not found ${path4}`);
}
const stat = await fs.promises.stat(path4);
if (!stat.isFile()) {
throw new Error(`entry not file ${path4}`);
}
return fs.promises.readFile(path4, "utf-8");
},
readSync: (path4) => {
if (!drive.existsSync(path4)) {
throw new Error(`entry not found ${path4}`);
}
const stat = fs.statSync(path4);
if (!stat.isFile()) {
throw new Error(`entry not file ${path4}`);
}
return fs.readFileSync(path4, "utf-8");
},
write: async (path4, content, options) => {
if (options == null ? void 0 : options.recursive) {
await drive.mkdir(dirname(path4), { recursive: true });
}
return fs.promises.writeFile(path4, content, "utf-8");
},
writeSync: (path4, content, options) => {
if (options == null ? void 0 : options.recursive) {
const parent = dirname(path4);
drive.mkdirSync(parent, { recursive: true });
}
return fs.writeFileSync(path4, content, "utf-8");
},
mkdir: async (path4, options) => {
if (await drive.exists(path4)) return;
if (options == null ? void 0 : options.recursive) {
await drive.mkdir(dirname(path4));
}
return fs.promises.mkdir(path4);
},
mkdirSync: (path4, options) => {
if (drive.existsSync(path4)) return;
if (options == null ? void 0 : options.recursive) {
const parent = dirname(path4);
drive.mkdirSync(parent, { recursive: true });
}
return fs.mkdirSync(path4);
},
destroy: (path4) => {
return fs.promises.rm(path4, { recursive: true });
},
destroySync: (path4) => {
return fs.rmSync(path4, { recursive: true });
}
};
var parse = parse$1;
var stringify = stringify$1;
var YAML = {
parse,
stringify
};
// src/core/parsers/markdown.ts
function parse2(contents) {
let body = contents;
const result = {};
if (contents.startsWith("---")) {
const [, frontmatter, rest] = contents.split("---");
Object.assign(result, YAML.parse(frontmatter));
body = rest.trim();
}
result["body"] = body || "";
return result;
}
function stringify2(data) {
const { body, ...properties } = data;
let result = "";
if (properties && Object.keys(properties).length) {
result += `---
${YAML.stringify(properties)}---
`;
}
if (body) {
result += body;
}
return result;
}
var MD = {
parse: parse2,
stringify: stringify2
};
// src/core/parsers/all.ts
var parsers = [];
var json = {
name: "json",
ext: "json",
parse: JSON.parse,
stringify: (contents) => JSON.stringify(contents, null, 4)
};
var markdown = {
name: "markdown",
ext: "md",
parse: MD.parse,
stringify: (contents) => MD.stringify(contents)
};
parsers.push(json, markdown);
parsers.push({
name: "yaml",
ext: "yaml",
parse: YAML.parse,
stringify: (contents) => YAML.stringify(contents, null, 4)
});
parsers.push({
name: "yml",
ext: "yml",
parse: YAML.parse,
stringify: (contents) => YAML.stringify(contents, null, 4)
});
// 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];
}
};
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 [json2, error] = tryCatch.sync(() => JSON.parse(content, options == null ? void 0 : options.reviver));
return error ? (options == null ? void 0 : options.default) || null : json2;
};
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(dirname5, path4 = createPathNode()) {
return valibot.pipe(
valibot.string(),
valibot.transform((value) => path4.resolve(dirname5, 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;
};
var definition = (root) => v2.object({
dirs: v2.optional(v2.extras.array(v2.extras.path(root)), []),
files: v2.optional(v2.extras.array(v2.extras.path(root)), []),
patterns: v2.optional(v2.extras.array(v2.extras.path(root)), []),
items: v2.optional(v2.array(v2.any()), [])
});
var schema2 = (root) => v2.pipe(
definition(root),
v2.transform((value) => {
const items = [];
const files = [];
for (const item of value.items || []) {
items.push({
data: item
});
}
for (const file of value.files || []) {
files.push(resolve(root, file));
}
for (const dir of value.dirs || []) {
const entries = drive.listSync(dir);
entries.forEach((entry) => {
files.push(resolve(dir, entry));
});
}
for (const pattern of value.patterns || []) {
const path4 = fg.convertPathToPattern(pattern);
const entries = fg.sync(path4);
entries.forEach((entry) => {
files.push(resolve(entry));
});
}
const unique = files.filter((value2, index, self) => self.indexOf(value2) === index);
for (const file of unique) {
const content = drive.readSync(file);
const view2 = YAML.parse(content);
items.push({
filename: file,
dirname: dirname(file),
data: view2
});
}
return items;
})
);
// src/core/config/schemas.ts
var viewSources = (root) => v2.pipe(
schema2(root),
v2.transform((items) => items.map((i) => i.data)),
v2.transform((items) => {
return items.map((i) => {
if (i.extend) {
const parent = items.find((x) => x.name === i.extend);
return merge({}, parent, i);
}
return i;
});
})
);
var view = (root) => v2.object({
default: v2.optional(v2.string()),
sources: v2.optional(viewSources(root))
});
var database = (root) => v2.objectWithRest(
{
name: v2.string(),
provider: v2.object({
name: v2.string(),
config: v2.any()
}),
view: v2.optional(view(root))
},
v2.record(v2.string(), v2.any())
);
var config = (root) => v2.object({
databases: v2.object({
default: v2.optional(v2.string()),
sources: v2.pipe(
schema2(root),
v2.transform((i) => {
return i.map((i2) => {
if ("dirname" in i2) {
return {
filename: i2.filename,
dirname: i2.dirname,
data: validate(database(i2.dirname), i2.data)
};
}
return {
data: validate(database(root), i2.data)
};
});
})
)
})
});
function resolve4(filename) {
const root = dirname(filename);
const schema3 = v2.pipe(
v2.union([config(root), database(root)]),
v2.transform((value) => {
if ("databases" in value) {
return value;
}
const item = {
filename,
dirname: dirname(filename),
data: value
};
return {
databases: {
default: value.name,
sources: [item]
}
};
})
);
const text = drive.readSync(filename);
const yml = YAML.parse(text);
const result = validate(schema3, yml);
return {
dirname: root,
filename,
...result
};
}
export { config, database, resolve4 as resolve, view };