#!/usr/bin/env node
import { g as getGitenvs, a as getCwd, b as getPassphraseEnvName, c as getIsLatestGitenvsVersion, G as GITENVS_STAGE_ENV_NAME, d as getGitenvsVersion, l as latestGitenvsVersion, e as Gitenvs1, s as saveGitenvs, f as checkGitenvsJsonExists } from '../gitenvs-4DxW9Ige.mjs';
import { execSync } from 'child_process';
import { Command } from 'commander';
import { randomBytes } from 'crypto';
import { readFile, writeFile, mkdir } from 'fs/promises';
import { join, dirname } from 'path';
import { fileURLToPath } from 'url';
import { z } from 'zod';
import { base64ToString, base64ToUint8Array } from 'uint8array-extras';
import 'fs';

const getIsGitenvsExisting = async () => {
  try {
    await getGitenvs();
    return true;
  } catch (error) {
    if (process.env.NODE_ENV === "development") {
      console.error(error);
    }
    return false;
  }
};

const PASSPHRASE_FILE_NAME = "gitenvs.passphrases.json";
const getPassphrase = async ({
  stage,
  passphrase,
  passphrasePath = join(getCwd(), PASSPHRASE_FILE_NAME)
}) => {
  const getFromEnvVars = () => {
    const envName = getPassphraseEnvName({ stage });
    return process.env[envName];
  };
  const getFromFile = async () => {
    const currentFileContent = await readFile(passphrasePath, "utf-8").then((res) => JSON.parse(res)).catch(() => []);
    const requestedPassphrase = currentFileContent.find(
      (p) => p.stageName === stage
    )?.passphrase;
    return requestedPassphrase;
  };
  return passphrase ?? getFromEnvVars() ?? await getFromFile();
};

const decryptEnvVar = async ({
  encrypted,
  encryptedPrivateKey: privateKeyData,
  passphrase
}) => {
  try {
    const { iv, encryptedPrivateKey } = JSON.parse(
      base64ToString(privateKeyData)
    );
    const passphraseKey = await globalThis.crypto.subtle.importKey(
      "raw",
      base64ToUint8Array(passphrase),
      {
        name: "AES-GCM",
        length: 256
      },
      false,
      ["unwrapKey"]
    );
    const privateKey = await globalThis.crypto.subtle.unwrapKey(
      "pkcs8",
      base64ToUint8Array(encryptedPrivateKey),
      passphraseKey,
      {
        name: "AES-GCM",
        iv: base64ToUint8Array(iv)
      },
      {
        name: "RSA-OAEP",
        hash: { name: "SHA-256" }
      },
      false,
      ["unwrapKey"]
    );
    const secretData = JSON.parse(base64ToString(encrypted));
    const symmetricKey = await globalThis.crypto.subtle.unwrapKey(
      "raw",
      base64ToUint8Array(secretData.encryptedSymmetricKey),
      privateKey,
      {
        name: "RSA-OAEP"
      },
      {
        name: "AES-GCM",
        length: 256
      },
      false,
      ["decrypt"]
    );
    const decrypted = await globalThis.crypto.subtle.decrypt(
      {
        name: "AES-GCM",
        iv: base64ToUint8Array(secretData.iv)
      },
      symmetricKey,
      base64ToUint8Array(secretData.encryptedValue)
    );
    const plaintext = new TextDecoder().decode(decrypted);
    return plaintext;
  } catch (error) {
    console.log("error", error);
  }
};

const getFileContent = async ({
  gitenvs,
  envFile,
  envStage: envStage2,
  passphrase: passphrase2
}) => {
  const resolvedEnvVars = (await Promise.all(
    gitenvs.envVars.filter((envVar2) => envVar2.fileIds.includes(envFile.id)).map(async (envVar2) => resolveEnvVar({ envVar: envVar2, envStage: envStage2, passphrase: passphrase2 }))
  )).filter((dotenvVar) => !!dotenvVar.value);
  const lines = resolvedEnvVars.map((envVar2) => {
    console.log(`\u{1F512} Gitenvs: Writing "${envVar2.key}" to ${envFile.filePath}`);
    switch (envFile.type) {
      case "dotenv":
        return getDotenvLine(envVar2);
      case ".ts":
        return getTsLine(envVar2);
    }
  });
  const fileContent = lines.join("\n");
  return fileContent;
};
const resolveEnvVar = async ({
  envVar,
  envStage,
  passphrase
}) => {
  const config = envVar.values[envStage.name];
  if (!config) return { key: envVar.key, value: "" };
  let value = config?.value ?? "";
  if (config.encrypted) {
    value = await decryptEnvVar({
      encrypted: value,
      encryptedPrivateKey: envStage.encryptedPrivateKey,
      passphrase
    }) ?? "";
  }
  if (config.isFunction) {
    try {
      value = eval(value);
      if (typeof value !== "string") {
        value = String(value);
      }
    } catch (error) {
      console.error(`\u274C Gitenvs: Error evaluating function "${envVar.key}"`);
      console.error(error);
      process.exit(1);
    }
  }
  return {
    key: envVar.key,
    value
  };
};
const getDotenvLine = ({ key, value: value2 }) => {
  const includesCommentCharacter = value2.includes("#");
  const includesDoubleQuote = value2.includes('"');
  const includesSingleQuote = value2.includes("'");
  let wrapWith = "";
  if (includesCommentCharacter) {
    if (includesSingleQuote && includesDoubleQuote) {
      throw new Error(
        `\u274C Gitenvs: "${key}" includes both single and double quotes and a comment character. This is not supported and will result in unexpected values.`
      );
    }
    if (includesDoubleQuote) {
      wrapWith = "'";
    } else {
      wrapWith = '"';
    }
    console.log(
      `\u26A0\uFE0F Gitenvs: wrapping "${key}" with ${wrapWith} quotes because it includes a comment character`
    );
  }
  return `${key}=${wrapWith}${value2}${wrapWith}`;
};
const getTsLine = ({ key, value: value2 }) => {
  const includesDoubleQuote = value2.includes('"');
  const includesSingleQuote = value2.includes("'");
  const includesBacktick = value2.includes("`");
  let wrapWith = "'";
  if (includesSingleQuote && includesDoubleQuote && includesBacktick) {
    throw new Error(
      `\u274C Gitenvs: "${key}" includes all string encasing characters. This is not supported and will result in unexpected values.`
    );
  }
  if (includesSingleQuote) {
    if (!includesBacktick) {
      wrapWith = "`";
    } else {
      wrapWith = '"';
    }
  }
  return `export const ${key} = process.env.${key} ?? ${wrapWith}${value2}${wrapWith}`;
};

const createCommandSchema = z.object({
  stage: z.string(),
  passphrase: z.string().optional(),
  passphrasePath: z.string().optional()
});
const createCommand = async (options) => {
  const gitenvsExists = await getIsGitenvsExisting();
  if (!gitenvsExists) {
    console.error("\u274C Gitenvs: gitenvs.json not found");
    process.exit(1);
  }
  const isLatestGitenvsVersion = await getIsLatestGitenvsVersion();
  if (!isLatestGitenvsVersion) {
    console.error(
      `\u274C Gitenvs: Version is not latest. Please run \`gitenvs migrate\` to migrate to the latest version.`
    );
    process.exit(1);
  }
  const gitenvs = await getGitenvs();
  const stage = process.env[GITENVS_STAGE_ENV_NAME] ?? options.stage;
  if (!stage) {
    console.error(
      `Stage is required. Set it with --stage <stage> or with env var: ${GITENVS_STAGE_ENV_NAME}`
    );
    process.exit(1);
  }
  const envStage = gitenvs.envStages.find((envStage2) => envStage2.name === stage);
  if (!envStage) {
    console.error(`Env stage ${stage} not found`);
    process.exit(1);
  }
  const passphrase = await getPassphrase({
    stage,
    passphrase: options.passphrase,
    passphrasePath: options.passphrasePath
  });
  if (!passphrase) {
    console.error(
      `Requested passphrase for stage ${stage} not found in ${options.passphrasePath ?? join(getCwd(), PASSPHRASE_FILE_NAME)}`
    );
    process.exit(1);
  }
  const filePromises = gitenvs.envFiles.map(async (envFile) => {
    console.log(`\u{1F512} Gitenvs: Creating ${envFile.name} file for stage: ${stage}`);
    const fileContent = await getFileContent({
      gitenvs,
      envFile,
      envStage,
      passphrase
    });
    return writeFile(join(getCwd(), envFile.filePath), fileContent, "utf-8");
  });
  await Promise.allSettled(filePromises);
};

const [major] = process.versions.node.split(".").map(Number);
if (major && major < 20) {
  console.error("\u274C Gitenvs: Node version must be >= 20");
  process.exit(1);
}
const getGitenvsUiEnvVars = () => ({
  ...process.env,
  GITENVS_DIR: getCwd(),
  GITENVS_ENCRYPTION_TOKEN: randomBytes(32).toString("hex"),
  PORT: "1337"
});
const checkGitenvsVersion = async () => {
  const gitenvsExists = checkGitenvsJsonExists();
  if (gitenvsExists) {
    const isLatestGitenvsVersion = await getIsLatestGitenvsVersion();
    if (!isLatestGitenvsVersion) {
      console.error(
        `\u274C Gitenvs: Version is not latest. Please run \`gitenvs migrate\` to migrate to the latest version.`
      );
      process.exit(1);
    }
  }
};
const program = new Command();
program.name("gitenvs").description("Save your env variables in git \u2013 encrypted!");
program.command("migrate").action(async () => {
  let isLatestVersion = false;
  while (!isLatestVersion) {
    let currentVersion;
    try {
      currentVersion = await getGitenvsVersion();
    } catch (error) {
      console.error("\u274C Gitenvs: cannot read version");
      process.exit(1);
    }
    if (currentVersion === latestGitenvsVersion) {
      isLatestVersion = true;
      console.log(`\u2705 Gitenvs: On latest version v${latestGitenvsVersion}`);
      break;
    }
    console.log(
      `\u{1F504} Gitenvs: Migrating from v${currentVersion} to v${currentVersion + 1}`
    );
    const gitenvsContent = await readFile(
      join(getCwd(), "gitenvs.json"),
      "utf-8"
    );
    const jsonParsedGitenvsContent = JSON.parse(gitenvsContent);
    if (currentVersion === 1) {
      const gitenvs1 = Gitenvs1.parse(jsonParsedGitenvsContent);
      const migrated = {
        ...gitenvs1,
        version: "2",
        envVars: gitenvs1.envVars.map((envVar) => {
          const { fileId, id, key, values } = envVar;
          return {
            id,
            fileIds: [fileId],
            key,
            values
          };
        })
      };
      await saveGitenvs(migrated);
    }
  }
});
program.command("create").description("Creates env files").option(
  "--stage <stage>",
  "Example: production, staging, development",
  "development"
).option("--passphrase <passphrase>").option("--passphrasePath <passphrasePath>").action(async (options) => {
  await checkGitenvsVersion();
  const parsed = createCommandSchema.safeParse(options);
  if (!parsed.success) {
    console.error("\u274C Gitenvs: Invalid options");
    console.error(parsed.error.message);
    process.exit(1);
  }
  await createCommand(parsed.data);
});
program.command("dev-ui").description("Starts a browser UI to edit env vars").action(async () => {
  await checkGitenvsVersion();
  execSync("pnpm run dev-next", {
    stdio: "inherit",
    env: getGitenvsUiEnvVars()
  });
});
program.command("init-playground").description("Initializes the playground").action(async () => {
  await mkdir("playground", { recursive: true });
  await writeFile(
    "playground/package.json",
    JSON.stringify(
      {
        name: "playground",
        version: "1.0.0",
        private: true
      },
      null,
      2
    ),
    "utf-8"
  );
});
program.command("ui", { isDefault: true }).description("Starts a browser UI to edit env vars").action(async () => {
  await checkGitenvsVersion();
  const nodePath = process.argv0;
  const currentDir = dirname(fileURLToPath(import.meta.url));
  execSync(`${nodePath} ${currentDir}/next/server.js`, {
    stdio: "inherit",
    env: getGitenvsUiEnvVars()
  });
});
program.parse();
