import { detectJsPackageManager, logLines, fmt, installJsDependency } from './chunk-NEWKASA7.js';
import { scanCodeForEnvVars } from './chunk-C5ZGZA3T.js';
import { gracefulExit } from './chunk-XQSWW35O.js';
import { define } from './chunk-WIL4LMMA.js';
import { spawnAsync, envSpecUpdater, ParsedEnvSpecStaticValue, ansis_default, tryCatch, parseEnvSpecDotEnvFile, prompts_default, pathExists } from './chunk-LTYLEVHC.js';
import { Ee, q } from './chunk-7OJKCWSO.js';
import { __name } from './chunk-IBAHCH6O.js';
import path2, { dirname } from 'path';
import fs3 from 'fs/promises';

async function checkIsFileGitIgnored(path3, warnIfNotGitRepo = false) {
  try {
    await spawnAsync("git", ["check-ignore", path3, "-q"], { cwd: dirname(path3) });
    return true;
  } catch (err) {
    if (err.code === "ENOENT") return void 0;
    const errorOutput = err.data;
    if (err.exitCode === 127 || errorOutput?.includes("not found") || errorOutput?.includes("not recognized")) {
      return void 0;
    }
    if (errorOutput === "") return false;
    if (errorOutput?.includes("not a git repository")) {
      if (warnIfNotGitRepo) {
        console.log("\u{1F536} Your code is not currently in a git repository - run `git init` to initialize a new repo.");
      }
      return false;
    }
    if (errorOutput?.includes("is outside repository")) {
      return void 0;
    }
    throw err;
  }
}
__name(checkIsFileGitIgnored, "checkIsFileGitIgnored");
var PUBLIC_PREFIXES = [
  "PUBLIC",
  "VITE",
  "NEXT_PUBLIC",
  "NUXT_PUBLIC"
];
var PUBLIC_KEYWORDS = ["PUBLIC"];
var SENSITIVE_KEYWORDS = [
  "SECRET",
  "API_KEY",
  "PASSWORD",
  "TOKEN",
  "PRIVATE",
  "CREDENTIALS"
];
function isValidUrl(val) {
  try {
    const u = new URL(val);
    return true;
  } catch (err) {
    return false;
  }
}
__name(isValidUrl, "isValidUrl");
var EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
var VALID_NUMBER_REGEX = /^(0|([1-9][0-9]*))?(\.[0-9]+)?$/;
function inferItemDecorators(file, itemKey, valueStr = "") {
  let itemIsPublic = false;
  if (PUBLIC_PREFIXES.some((prefix) => itemKey.startsWith(prefix))) itemIsPublic = true;
  if (PUBLIC_KEYWORDS.some((keyword) => itemKey.includes(keyword))) itemIsPublic = true;
  let itemIsSensitive = false;
  if (SENSITIVE_KEYWORDS.some((keyword) => itemKey.includes(keyword))) itemIsSensitive = true;
  if (itemIsPublic) ; else if (itemIsSensitive) {
    envSpecUpdater.setItemDecorator(file, itemKey, "sensitive", "true");
  }
  if (itemKey === "PORT" || itemKey.endsWith("_PORT")) {
    envSpecUpdater.setItemDecorator(file, itemKey, "type", "port");
  } else if (itemKey.endsWith("_EMAIL")) {
    envSpecUpdater.setItemDecorator(file, itemKey, "type", "email");
  } else if (itemKey.endsWith("_URL") || itemKey.endsWith("_URI")) {
    envSpecUpdater.setItemDecorator(file, itemKey, "type", "url");
  } else if (valueStr) {
    if (valueStr.startsWith("<") && valueStr.endsWith(">")) {
      envSpecUpdater.setItemDecorator(file, itemKey, "example", valueStr);
    }
    if (valueStr === "true" || valueStr === "false") {
      envSpecUpdater.setItemDecorator(file, itemKey, "type", "boolean");
    } else if (EMAIL_REGEX.test(valueStr)) {
      envSpecUpdater.setItemDecorator(file, itemKey, "type", "email");
    } else if (valueStr !== "0" && valueStr !== "1" && VALID_NUMBER_REGEX.test(valueStr)) {
      envSpecUpdater.setItemDecorator(file, itemKey, "type", "number");
    } else if (isValidUrl(valueStr)) {
      envSpecUpdater.setItemDecorator(file, itemKey, "type", "url");
    }
  }
}
__name(inferItemDecorators, "inferItemDecorators");
function inferSchemaUpdates(file) {
  for (const item of file.configItems) {
    const valueStr = item.value instanceof ParsedEnvSpecStaticValue && item.value.value?.toString() || "";
    inferItemDecorators(file, item.key, valueStr);
  }
}
__name(inferSchemaUpdates, "inferSchemaUpdates");
function ensureAllItemsExist(schemaFile, otherFiles) {
  const addedItemKeys = [];
  for (const otherFile of otherFiles) {
    for (const item of otherFile.parsedFile.configItems) {
      const itemInSchema = schemaFile.configItems.find((i) => i.key === item.key);
      if (itemInSchema) continue;
      if (addedItemKeys.length === 0) {
        envSpecUpdater.injectFromStr(schemaFile, [
          "",
          "# items added to schema by `varlock init`",
          "# that were missing in example, but detected in other .env files",
          "# PLEASE REVIEW THESE!",
          "# ---",
          ""
        ].join("\n"), { location: "end" });
      }
      addedItemKeys.push(item.key);
      envSpecUpdater.injectFromStr(schemaFile, `${item.key}=`);
      const itemValue = item.value instanceof ParsedEnvSpecStaticValue && item.value.value?.toString() || "";
      inferItemDecorators(schemaFile, item.key, String(itemValue));
    }
  }
}
__name(ensureAllItemsExist, "ensureAllItemsExist");
async function detectRedundantValues(schemaFile, otherFiles, opts = {}) {
  const redundantItemsBySourcePath = {};
  const schemaValues = schemaFile.toSimpleObj();
  for (const otherFile of Object.values(otherFiles)) {
    if (otherFile.fileName.startsWith(".env.schema") || otherFile.fileName.startsWith(".env.example") || otherFile.fileName.startsWith(".env.sample") || otherFile.fileName.startsWith(".env.default")) continue;
    const otherFileValues = otherFile.parsedFile.toSimpleObj();
    for (const itemKey in otherFileValues) {
      if (!(itemKey in schemaValues)) continue;
      if (otherFileValues[itemKey] !== schemaValues[itemKey]) continue;
      redundantItemsBySourcePath[otherFile.fullPath] ||= [];
      redundantItemsBySourcePath[otherFile.fullPath].push(itemKey);
      if (opts.delete) {
        envSpecUpdater.deleteItem(otherFile.parsedFile, itemKey);
      }
    }
    if (opts.delete) {
      await fs3.writeFile(otherFile.fullPath, otherFile.parsedFile.toString(), "utf8");
    }
  }
  return redundantItemsBySourcePath;
}
__name(detectRedundantValues, "detectRedundantValues");
var SKIP_FILE_TYPES = [".md", ".d.ts"];
async function findEnvFiles(opts) {
  const cwd = opts?.cwd || process.cwd();
  const envFiles = [];
  const filesWithinDir = await fs3.readdir(cwd);
  for (const fileName of filesWithinDir) {
    if (fileName === ".env" || fileName.startsWith(".env.")) {
      let skip = false;
      for (const fileType of SKIP_FILE_TYPES) {
        if (fileName.endsWith(fileType)) skip = true;
      }
      if (skip) continue;
      envFiles.push(path2.join(cwd, fileName));
    }
  }
  return envFiles;
}
__name(findEnvFiles, "findEnvFiles");

// src/cli/commands/init.command.ts
var commandSpec = define({
  name: "init",
  description: "Set up varlock in the current project",
  args: {
    agent: {
      type: "boolean",
      description: "Run in non-interactive mode for agent/automation workflows"
    }
  },
  examples: `
This command starts an interactive onboarding process to help you get started with Varlock.
It will:
  - Scan for existing .env files in your project
  - Help create a .env.schema file from your .env.example or .env.sample file
  - Install varlock as a dependency in package.json (if applicable)

\u{1F4CD} Run this command in directories that contain .env or .env.* files

Examples:
  varlock init                    # Run in the current directory
  varlock init --agent            # Run non-interactively (agent/automation friendly)
  cd path/to/your/project && varlock init

For more information, visit https://varlock.dev/getting-started/installation
  `.trim()
});
var commandFn = /* @__PURE__ */ __name(async (ctx) => {
  const agentMode = Boolean(ctx.values.agent);
  const jsPackageManager = detectJsPackageManager();
  console.log("\u{1F9D9} Hello and welcome to Varlock \u{1F512}\u{1F525}\u2728");
  if (agentMode) {
    logLines([ansis_default.dim("Agent mode enabled: running init non-interactively with deterministic defaults.")]);
  }
  const envFilePaths = await findEnvFiles();
  const parsedEnvFiles = {};
  for (const filePath of envFilePaths) {
    const fileContents = await fs3.readFile(filePath, "utf-8");
    const fileName = path2.basename(filePath);
    const parsedFile = await tryCatch(async () => parseEnvSpecDotEnvFile(fileContents), () => {
      logLines([
        "",
        `Unable to parse ${fmt.filePath(filePath)}`,
        "This file will be skipped."
      ]);
    });
    if (!parsedFile) {
      continue;
    }
    parsedEnvFiles[fileName] = {
      fileName,
      fullPath: filePath,
      parsedFile
    };
  }
  const existingSchemaFile = parsedEnvFiles[".env.schema"];
  if (existingSchemaFile) {
    logLines([
      `It looks like you already have a ${fmt.fileName(".env.schema")} file \u{1F389}`,
      "This init helper is meant to help you get a new project set up.",
      "If you need to make changes to your schema or values, you can update your files directly.",
      "See more docs at https://varlock.dev/guides/schema"
    ]);
  } else {
    const allExampleFileNames = Object.keys(parsedEnvFiles).filter((fileName) => {
      return fileName.startsWith(".env.example") || fileName.startsWith(".env.sample");
    });
    let exampleFileToConvert;
    if (allExampleFileNames.length === 1) {
      exampleFileToConvert = parsedEnvFiles[allExampleFileNames[0]];
    } else if (allExampleFileNames.length > 1) {
      if (agentMode) {
        const sortedFileNames = [...allExampleFileNames].sort((a, b) => a.localeCompare(b));
        const preferredFileName = sortedFileNames.find((name) => name === ".env.example") ?? sortedFileNames.find((name) => name.startsWith(".env.example.")) ?? sortedFileNames.find((name) => name === ".env.sample") ?? sortedFileNames.find((name) => name.startsWith(".env.sample.")) ?? sortedFileNames[0];
        exampleFileToConvert = parsedEnvFiles[preferredFileName];
        logLines([
          "",
          ansis_default.dim(`Agent mode: auto-selected ${fmt.fileName(preferredFileName)} to generate ${fmt.fileName(".env.schema")}.`)
        ]);
      } else {
        console.log("");
        const selectedExample = await Ee({
          message: `We detected more than one example .env file. Which one should we use to create your new ${fmt.fileName(".env.schema")}?`,
          options: allExampleFileNames.map((fileName) => ({
            label: fileName,
            value: parsedEnvFiles[fileName]
          }))
        });
        if (q(selectedExample)) return gracefulExit(0);
        exampleFileToConvert = selectedExample;
      }
    }
    let scannedCodeEnvKeys = [];
    if (!exampleFileToConvert) {
      const scanResult = await scanCodeForEnvVars();
      scannedCodeEnvKeys = scanResult.keys;
    }
    const parsedEnvSchemaFile = exampleFileToConvert?.parsedFile || parseEnvSpecDotEnvFile("");
    if (!parsedEnvSchemaFile) throw new Error("expected parsed .env example file");
    envSpecUpdater.ensureHeader(parsedEnvSchemaFile, [
      "This env file uses @env-spec - see https://varlock.dev/env-spec for more info",
      ""
      // TODO: add env spec version? real links?
    ].join("\n"));
    envSpecUpdater.setRootDecorator(parsedEnvSchemaFile, "defaultRequired", "infer", { explicitTrue: true });
    envSpecUpdater.setRootDecorator(parsedEnvSchemaFile, "defaultSensitive", "false", { explicitTrue: true });
    envSpecUpdater.setRootDecorator(parsedEnvSchemaFile, "generateTypes", "lang=ts, path=env.d.ts", { bareFnArgs: true });
    envSpecUpdater.injectFromStr(parsedEnvSchemaFile, [
      "",
      "# example env variable injected by `varlock init` \u26A0\uFE0F DELETE THIS ITEM! \u26A0\uFE0F",
      '# @required @sensitive @example="example value"',
      'EXAMPLE_ITEM="delete me!"',
      ""
    ].join("\n"), { location: "after_header" });
    inferSchemaUpdates(parsedEnvSchemaFile);
    ensureAllItemsExist(parsedEnvSchemaFile, Object.values(parsedEnvFiles));
    const scannedCodeKeysToAdd = !exampleFileToConvert ? scannedCodeEnvKeys.filter((key) => !parsedEnvSchemaFile.configItems.find((i) => i.key === key)) : [];
    if (scannedCodeKeysToAdd.length > 0) {
      envSpecUpdater.injectFromStr(parsedEnvSchemaFile, [
        "",
        "# items added to schema by `varlock init`",
        "# detected by scanning your source code for env var references",
        "# PLEASE REVIEW THESE!",
        "# ---",
        ""
      ].join("\n"), { location: "end" });
      for (const key of scannedCodeKeysToAdd) {
        envSpecUpdater.injectFromStr(parsedEnvSchemaFile, `${key}=`);
        inferItemDecorators(parsedEnvSchemaFile, key, "");
      }
    }
    const schemaFilePath = path2.join(process.cwd(), ".env.schema");
    await fs3.writeFile(schemaFilePath, parsedEnvSchemaFile.toString());
    if (exampleFileToConvert) {
      logLines([
        "",
        `Your ${fmt.fileName(exampleFileToConvert.fileName)} has been used to generate your new ${fmt.fileName(".env.schema")}:`,
        fmt.filePath(schemaFilePath)
      ]);
    } else if (scannedCodeKeysToAdd.length > 0) {
      logLines([
        "",
        `Your new ${fmt.fileName(".env.schema")} file has been created from scanned source code references:`,
        fmt.filePath(schemaFilePath),
        ansis_default.dim(`Detected ${scannedCodeEnvKeys.length} env var key${scannedCodeEnvKeys.length === 1 ? "" : "s"} in your codebase.`)
      ]);
    } else {
      logLines([
        "",
        `Your new ${fmt.fileName(".env.schema")} file has been created:`,
        fmt.filePath(schemaFilePath)
      ]);
    }
    if (await checkIsFileGitIgnored(schemaFilePath)) {
      logLines([ansis_default.dim(`(and updated ${fmt.fileName(".gitignore")} to ensure it will be tracked by git)`)]);
      await fs3.appendFile(".gitignore", "\n!.env.schema");
    }
    if (agentMode) {
      logLines([
        "",
        ansis_default.bold("Agent setup instructions:"),
        "- Initial schema generation uses heuristics, and must be reviewed and confirmed explicitly with the user",
        "- Schema writing guide: https://varlock.dev/guides/schema",
        "- Confirm/add `@type` on items, default is string, so no `@type=string` necessary",
        "- Add `@required`/`@optional`, can adjust `@defaultRequired` root decorator to change default behavior",
        "- Confirm with user which items are `@sensitive` (secrets, private keys, etc) - see https://varlock.dev/guides/secrets/",
        "- Remove placeholder example values, move useful ones to `@example`",
        "- Add description comments when key itself is not self-explanitory",
        "- Add `@docs()` to attach docs links to items",
        "",
        "Help user review and update .env.schema file for them, validating that config is resolving correctly and valid.",
        "Schema file is safe to read, do not look directly at values in .env / .env.local files - tell user to edit those files if necessary.",
        `Validate the resolved config any time using ${fmt.command("varlock load --agent", { jsPackageManager: true })}. It prints JSON with sensitive values redacted so you can safely inspect output in logs or transcripts. Or drop the \`--agent\` flag to get human-readable output to show the user.`,
        "",
        "Advanced setup:",
        "- Can use multiple .env files or functions to manage variation per environment, see https://varlock.dev/guides/environments/",
        "- Complex/large files can be broken up and use `@import`, see https://varlock.dev/guides/import/",
        "- If user wants to load secrets from external sources, help install and configure plugins - see https://varlock.dev/guides/plugins/",
        "- If user has any sensitive values in plaintext, can use local encryption, see `https://varlock.dev/guides/local-encryption/",
        ""
      ]);
    } else {
      logLines([
        "",
        ansis_default.bold(`\u{1F6A7} Please review and update your new ${fmt.fileName(".env.schema")} file! \u{1F6A7}`),
        `We've done our best to get you started, but you must review and make sure it is correct!`,
        "",
        `\u{1F449} Some helpful pointers to get you started:`,
        `- add a description to each item when the name is not self explanatory - it will come through in generated types`,
        `- use ${fmt.decorator("@required")} (or ${fmt.decorator("@optional")}) to tag items that should fail validation when empty`,
        `- use ${fmt.decorator("@sensitive")} to tag items that contain sensitive secrets, and must be handled accordingly`,
        `- use ${fmt.decorator("@type")} to set an item's data type (if not a basic string), which affects validation and coercion logic`,
        `- if an item value is a ${ansis_default.italic("useful example")} rather than a default, use ${fmt.decorator("@example")}`,
        `- if an item value is just a dummy placeholder, delete it`
      ]);
      const confirmReviewed = await prompts_default.confirm({
        message: `Have you reviewed and updated your new ${fmt.fileName(".env.schema")} file?`
      });
      if (q(confirmReviewed)) return gracefulExit(0);
      const reloadedSchemaFile = await parseEnvSpecDotEnvFile(await fs3.readFile(schemaFilePath, "utf-8"));
      if (reloadedSchemaFile.configItems.find((i) => i.key === "EXAMPLE_ITEM")) {
        logLines([
          "",
          ansis_default.bold(`\u{1F6A8} Really? ${ansis_default.red("You didn't remove the EXAMPLE_ITEM!")}`),
          `Please make sure your schema is all correct before using it...`
        ]);
      }
    }
    if (exampleFileToConvert) {
      if (agentMode) {
        logLines([`- left ${exampleFileToConvert.fileName} in place (no destructive prompts) - confirm with user and remove for them`]);
      } else {
        const confirmDeleteExample = await prompts_default.confirm({
          message: `Should we delete your ${fmt.fileName(exampleFileToConvert.fileName)} file? ${ansis_default.italic.gray("(you can always do this yourself later)")}`
        });
        if (q(confirmDeleteExample)) return gracefulExit(0);
        if (confirmDeleteExample) {
          await fs3.unlink(exampleFileToConvert.fullPath);
        }
      }
    }
    const defaultsFile = Object.values(parsedEnvFiles).find((f) => {
      return f.fileName.startsWith(".env.default");
    });
    if (defaultsFile) {
      if (agentMode) {
        logLines([`- found ${defaultsFile.fileName} in project - tell user to migrate defaults to .env.schema and delete it`]);
      } else {
        logLines([
          "",
          `\u{1F6A7} We detected a ${fmt.fileName(defaultsFile.fileName)} file in your project`,
          `You should migrate these default values into ${fmt.fileName(".env.schema")} and delete it.`
        ]);
      }
    }
    const redundantInfo = await detectRedundantValues(parsedEnvSchemaFile, parsedEnvFiles);
    if (Object.keys(redundantInfo).length > 0) {
      if (agentMode) {
        logLines([`- found redundant values in other .env files - tell user to review and delete them`]);
      } else {
        logLines([
          "",
          ansis_default.bold("\u203C\uFE0F  Now that your schema contains defaults, some values in your other .env files are redundant:")
        ]);
        for (const [sourcePath, itemKeys] of Object.entries(redundantInfo)) {
          console.log(fmt.filePath(sourcePath));
          console.log("  ", itemKeys.map((k) => ansis_default.italic(k)).join(", "));
        }
        const confirmDeleteRedundant = await prompts_default.confirm({
          message: "Should we delete these redundant values from your other .env files?"
        });
        if (q(confirmDeleteRedundant)) return gracefulExit(0);
        if (confirmDeleteRedundant) {
          await detectRedundantValues(parsedEnvSchemaFile, parsedEnvFiles, { delete: true });
        }
      }
    }
    if (!agentMode) {
      logLines([
        "",
        ansis_default.bold("\u{1F389} Great!"),
        `You can run ${fmt.command("varlock load", { jsPackageManager })} to attempt loading your env vars validate against your new schema.`,
        "",
        "Check out our docs for more info about integrating into your application.",
        "",
        "\u{1F4D6} https://varlock.dev \u{1F448}",
        ""
      ]);
    }
  }
  if (jsPackageManager && await pathExists(path2.join(process.cwd(), "package.json"))) {
    const installResult = installJsDependency({
      packageManager: jsPackageManager.name,
      packageName: "varlock"
    });
    if (installResult) {
      logLines([
        "",
        `\u2705 Added ${fmt.packageName("varlock")} as a dependency in your package.json`
      ]);
    }
  }
  if (jsPackageManager?.name === "bun") {
    const bunfigPath = path2.join(process.cwd(), "bunfig.toml");
    const bunfigExists = await pathExists(bunfigPath);
    if (bunfigExists) {
      const bunfigContents = await fs3.readFile(bunfigPath, "utf-8");
      if (!/^\s*env\s*=\s*false\s*$/m.test(bunfigContents)) {
        await fs3.writeFile(bunfigPath, `env = false
${bunfigContents}`);
        logLines([
          "",
          `\u2705 Added ${ansis_default.bold("env = false")} to your existing ${fmt.fileName("bunfig.toml")}`,
          ansis_default.dim("This disables Bun's automatic .env loading, which conflicts with Varlock."),
          ansis_default.dim(`See ${ansis_default.underline("https://varlock.dev/integrations/bun")} for more info.`)
        ]);
      }
    } else {
      await fs3.writeFile(bunfigPath, "env = false\n");
      logLines([
        "",
        `\u2705 Created ${fmt.fileName("bunfig.toml")} with ${ansis_default.bold("env = false")}`,
        ansis_default.dim("This disables Bun's automatic .env loading, which conflicts with Varlock."),
        ansis_default.dim(`See ${ansis_default.underline("https://varlock.dev/integrations/bun")} for more info.`)
      ]);
    }
  }
  if (!agentMode) {
    logLines([
      "",
      `\u{1F4A1} Want tab completions for the varlock CLI? See ${ansis_default.underline("https://varlock.dev/guides/shell-completion")}`
    ]);
  }
}, "commandFn");

export { commandFn, commandSpec };
//# sourceMappingURL=chunk-G6QZTET7.js.map
//# sourceMappingURL=chunk-G6QZTET7.js.map