@hairy/lnv
Version:
_description_
276 lines (273 loc) • 9.35 kB
JavaScript
import {createRequire as __createRequire} from 'module';var require=__createRequire(import.meta.url);
import {
context
} from "./chunk-4TCHCY3V.js";
import {
entryToFile,
uniq
} from "./chunk-PCQ4K4YG.js";
import {
vault
} from "./chunk-RZXDK7SO.js";
import {
readfile,
readfiles,
replaceLiteralQuantity
} from "./chunk-ANMSLEQF.js";
// src/internal/feature.ts
import fs from "node:fs";
import path from "node:path";
import process from "node:process";
import { intro, isCancel, outro, select, text } from "@clack/prompts";
import { colors } from "consola/utils";
import { config } from "dotenv";
import { createSpinner } from "nanospinner";
import { loadConfig } from "unconfig";
async function parseUserConfig() {
const { config: config2 = {} } = await loadConfig({
sources: [{ files: "lnv.config" }],
cwd: process.cwd(),
merge: true
});
context.script = config2.scripts?.[context.entries[0]];
context.entries = context.script ? context.entries.slice(1) : context.entries;
Object.assign(context.before, config2.injects?.before);
Object.assign(context.after, config2.injects?.after);
context.entries.unshift(...config2.injects?.entries || []);
context.depth = context.depth || config2.injects?.depth || false;
}
async function executionScript() {
if (!context.script)
return;
if (typeof context.script === "string") {
context.run = context.script;
return;
}
const {
prompts = [],
entries = [],
command: run,
depth = false,
message,
before,
after,
...selectOptions
} = context.script;
Object.assign(context.before, before);
Object.assign(context.after, after);
context.entries.unshift(...entries);
context.depth = context.depth || depth;
if (typeof run === "string" && message)
intro(message);
const parsed = {};
for (const prompt of prompts) {
let value2;
if (prompt.type === "handler") {
value2 = await prompt.handler(parsed);
}
if (prompt.type === "select") {
const choices = typeof prompt.options === "function" ? await prompt.options(parsed) : prompt.options;
value2 = await select({
message: message || `Please select ${prompt.key}`,
options: choices
});
}
if (prompt.type === "text") {
value2 = await text({
message: prompt.message || `Please enter ${prompt.key}`,
...selectOptions
});
}
if (isCancel(value2)) {
outro("Operation cancelled");
process.exit(0);
}
if (value2)
parsed[prompt.key] = value2;
}
Object.assign(context.parsed, parsed);
if (typeof run === "string") {
context.run = run;
return;
}
const value = await select({
message: message || "Please select a command",
options: run
});
if (isCancel(value)) {
outro("Operation cancelled");
process.exit(0);
}
context.run = value;
}
async function authEnvironment() {
const [environment] = context.sources.filter((source) => source.env.startsWith(".env.vault"));
if (!environment || !environment.files.length || process.env.DOTENV_KEY)
return;
const unauthorizedFilepaths = [];
const notSpecifiedFilepaths = [];
for (const file of environment.files) {
const dirpath = path.dirname(file.path);
if (fs.existsSync(path.join(dirpath, ".env.key")) || fs.existsSync(path.join(dirpath, ".env.keys")))
continue;
if (fs.existsSync(path.join(dirpath, ".env.me")))
notSpecifiedFilepaths.push(file.path);
else
unauthorizedFilepaths.push(file.path);
}
if (!unauthorizedFilepaths.length && !notSpecifiedFilepaths.length)
return;
if (unauthorizedFilepaths.length)
intro(`Found ${unauthorizedFilepaths.length} unauthorized directories, Please authorize them to access the vault environment variables.`);
for (const filepath of unauthorizedFilepaths) {
const dirpath = path.dirname(filepath);
console.log(`${colors.dim(`entry: `)}${filepath}`);
await vault("login", {
cwd: dirpath,
stdio: "inherit",
stderr: "inherit",
stdin: "inherit",
stdout: "inherit"
});
process.stdout.write("\x1B[1A");
process.stdout.write("\x1B[2K");
notSpecifiedFilepaths.push(filepath);
}
intro(`Found ${notSpecifiedFilepaths.length} directories not specified environment, Please select environment.`);
for (const filepath of notSpecifiedFilepaths) {
const dirpath = path.dirname(filepath);
const spinner = createSpinner();
spinner.start(" Loading dotenv environment...");
const { stdout } = await vault("keys", { cwd: dirpath });
spinner.stop();
const dotenvKeys = stdout.split("\n").filter((row) => row.includes("dotenv://")).map((row) => {
const [env, key] = row.split("dotenv://").map((part) => part.trim());
return { env, key: `dotenv://${key}` };
});
const value = await select({
message: filepath.replace(/\\\\/g, "/"),
options: [
{
value: "all",
label: "all",
hint: "Ask every time the script runs"
},
...dotenvKeys.map((key) => ({
value: key.env,
label: key.env
}))
]
});
if (isCancel(value)) {
outro("Operation cancelled");
process.exit(0);
}
if (value === "all") {
const content = [
`#/!!!!!!!!!!!!!!!!!! .env.keys !!!!!!!!!!!!!!!!!!!!!/`,
`#/ DOTENV_KEYs. DO NOT commit to source control /`,
`#/ [how it works](https://dotenv.org/env-keys) /`,
`#/--------------------------------------------------/`,
``,
...dotenvKeys.map((key) => `DOTENV_KEY_${key.env.toUpperCase()}="${key.key}"`)
];
fs.writeFileSync(path.join(dirpath, ".env.keys"), content.join("\n"));
} else {
const dotenvKey = dotenvKeys.find(({ env }) => env === value);
const content = [
`#/!!!!!!!!!!!!!!!!!!! .env.key !!!!!!!!!!!!!!!!!!!!!/`,
`#/ DOTENV_KEY. DO NOT commit to source control /`,
`#/ [how it works](https://dotenv.org/env-keys) /`,
`#/--------------------------------------------------/`,
``,
`DOTENV_KEY="${dotenvKey.key}"`
];
fs.writeFileSync(path.join(dirpath, ".env.key"), content.join("\n"));
}
}
}
async function readEnvironment() {
context.files = uniq(context.entries).filter(Boolean).map(entryToFile);
for (const file of context.files) {
const [env, defaultScope] = file.split(":");
const files = readfiles(process.cwd(), env, context.depth);
if (!files.length) {
const failedMessage = `Failed to loading ${env} file not found in all scopes`;
![".env", ".env.local"].includes(env) && console.log(failedMessage);
continue;
}
const fileDetails = files.map(async (filepath) => {
const keysPath = path.join(path.dirname(filepath), ".env.keys");
let scope = defaultScope;
if (env === ".env.vault" && fs.existsSync(keysPath)) {
const keys = config({ path: keysPath, processEnv: {} });
const scopes = Object.keys(keys.parsed || {}).map((key) => key.split('="dotenv')[0].replace("DOTENV_KEY_", "").toLowerCase());
const value = await select({
message: filepath.replace(/\\\\/g, "/"),
options: [
...scopes.map((key) => ({
value: key,
label: key
}))
]
});
if (isCancel(value)) {
outro("Operation cancelled");
process.exit(0);
}
scope = value;
}
return { path: filepath, scope };
});
context.sources.push({ env, files: await Promise.all(fileDetails) });
console.log(`Found ${files.length} ${env} files in all scopes`);
}
}
async function loadEnvironment() {
for (const { env, files } of context.sources) {
let exist = false;
for (const file of files) {
const output = parse(env, file.path, file.scope);
if (!output?.parsed)
continue;
exist = true;
Object.assign(context.parsed, output.parsed);
}
if (!exist)
console.log(`Failed to loading ${env} file not found in all scopes`);
}
}
function mergeParseEnvironment() {
for (const key in context.parsed)
context.parsed[key] = replaceLiteralQuantity(context.parsed[key], context.parsed);
}
function parse(env, filepath, scope) {
if (!env.startsWith(".env.vault"))
return config({ path: filepath });
const DOTENV_KEY = dokey(path.dirname(filepath), scope) || (scope ? process.env[`DOTENV_KEY_${scope.toUpperCase()}`] : process.env.DOTENV_KEY);
if (!DOTENV_KEY)
throw new Error("No DOTENV_KEY found in .env, .env.key or process.env");
delete process.env.DOTENV_KEY;
return config({ DOTENV_KEY, path: filepath });
}
function dokey(root, scope) {
const target = readfile(root, scope ? `.env.keys` : ".env.key");
if (!target)
return void 0;
const dirname = path.dirname(target);
if (root !== dirname && fs.existsSync(path.join(dirname, ".env.vault")))
return;
const parsed = config({ processEnv: {}, DOTENV_KEY: void 0, path: target }).parsed;
const value = scope ? parsed?.[`DOTENV_KEY_${scope.toUpperCase()}`] : parsed?.DOTENV_KEY;
return value;
}
export {
parseUserConfig,
executionScript,
authEnvironment,
readEnvironment,
loadEnvironment,
mergeParseEnvironment,
parse,
dokey
};