UNPKG

@hairy/lnv

Version:
276 lines (273 loc) 9.35 kB
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 };