@envmanager-cli/cli 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -464,6 +464,147 @@ import { resolve } from "path";
464
464
  import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
465
465
  import { join as join2, dirname } from "path";
466
466
  import { z } from "zod";
467
+
468
+ // src/lib/formatters.ts
469
+ import * as yaml from "js-yaml";
470
+ var EXPORT_FORMATS = [
471
+ "dotenv",
472
+ "docker-compose",
473
+ "k8s-secret",
474
+ "k8s-configmap",
475
+ "vercel",
476
+ "railway",
477
+ "render"
478
+ ];
479
+ function sanitizeK8sName(name) {
480
+ return name.toLowerCase().replace(/[^a-z0-9-]/g, "-").replace(/^-+|-+$/g, "").substring(0, 63);
481
+ }
482
+ function toDotEnv(variables) {
483
+ return variables.map((v) => {
484
+ const needsQuotes = /[\s='"#\n\r]/.test(v.value);
485
+ const value = needsQuotes ? `"${v.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n")}"` : v.value;
486
+ return `${v.key}=${value}`;
487
+ }).join("\n");
488
+ }
489
+ function toDockerCompose(variables) {
490
+ const envData = {};
491
+ for (const v of variables) {
492
+ envData[v.key] = v.value;
493
+ }
494
+ const snippet = {
495
+ services: {
496
+ app: {
497
+ environment: envData
498
+ }
499
+ }
500
+ };
501
+ const yamlOutput = yaml.dump(snippet, { lineWidth: -1, quotingType: '"' });
502
+ return `# Docker Compose environment section
503
+ # Copy this into your docker-compose.yml and replace 'app' with your service name
504
+ ${yamlOutput}`;
505
+ }
506
+ function toKubernetesSecret(variables, config) {
507
+ const data = {};
508
+ for (const v of variables) {
509
+ data[v.key] = Buffer.from(v.value).toString("base64");
510
+ }
511
+ const manifest = {
512
+ apiVersion: "v1",
513
+ kind: "Secret",
514
+ metadata: {
515
+ name: config.name,
516
+ namespace: config.namespace
517
+ },
518
+ type: "Opaque",
519
+ data
520
+ };
521
+ return yaml.dump(manifest, { lineWidth: -1, quotingType: '"' });
522
+ }
523
+ function toKubernetesConfigMap(variables, config) {
524
+ const data = {};
525
+ for (const v of variables) {
526
+ data[v.key] = v.value;
527
+ }
528
+ const manifest = {
529
+ apiVersion: "v1",
530
+ kind: "ConfigMap",
531
+ metadata: {
532
+ name: config.name,
533
+ namespace: config.namespace
534
+ },
535
+ data
536
+ };
537
+ return yaml.dump(manifest, { lineWidth: -1, quotingType: '"' });
538
+ }
539
+ function toVercelCLI(variables) {
540
+ if (variables.length === 0) return "# No variables to export";
541
+ const header = `# Vercel CLI commands to add environment variables
542
+ # Run these commands in your project directory
543
+ # Docs: https://vercel.com/docs/cli/env
544
+ # Note: You may need to run 'vercel login' first
545
+
546
+ `;
547
+ const commands = variables.map((v) => {
548
+ if (v.value.includes("\n")) {
549
+ const escapedValue = v.value.replace(/'/g, "'\\''");
550
+ return `vercel env add ${v.key} production << 'EOF'
551
+ ${escapedValue}
552
+ EOF`;
553
+ } else {
554
+ const escapedValue = v.value.replace(/'/g, "'\\''");
555
+ return `echo '${escapedValue}' | vercel env add ${v.key} production`;
556
+ }
557
+ }).join("\n\n");
558
+ return header + commands;
559
+ }
560
+ function toRailwayCLI(variables) {
561
+ if (variables.length === 0) return "# No variables to export";
562
+ const header = `# Railway CLI commands to set environment variables
563
+ # Run these commands in your project directory
564
+ # Docs: https://docs.railway.app/reference/cli-api#variables-set
565
+ # Note: You may need to run 'railway login' first
566
+
567
+ `;
568
+ const commands = variables.map((v) => {
569
+ const escapedValue = v.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
570
+ return `railway variables set ${v.key}="${escapedValue}"`;
571
+ }).join("\n");
572
+ return header + commands;
573
+ }
574
+ function toRenderCLI(variables) {
575
+ if (variables.length === 0) return "# No variables to export";
576
+ const header = `# Render CLI commands to set environment variables
577
+ # Run these commands in your project directory
578
+ # Docs: https://render.com/docs/cli
579
+ # Note: You may need to authenticate first
580
+
581
+ `;
582
+ const commands = variables.map((v) => {
583
+ const escapedValue = v.value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
584
+ return `render env:set ${v.key}="${escapedValue}"`;
585
+ }).join("\n");
586
+ return header + commands;
587
+ }
588
+ function formatVariables(variables, format, k8sConfig) {
589
+ switch (format) {
590
+ case "dotenv":
591
+ return toDotEnv(variables);
592
+ case "docker-compose":
593
+ return toDockerCompose(variables);
594
+ case "k8s-secret":
595
+ return toKubernetesSecret(variables, k8sConfig ?? { name: "app-secrets", namespace: "default" });
596
+ case "k8s-configmap":
597
+ return toKubernetesConfigMap(variables, k8sConfig ?? { name: "app-config", namespace: "default" });
598
+ case "vercel":
599
+ return toVercelCLI(variables);
600
+ case "railway":
601
+ return toRailwayCLI(variables);
602
+ case "render":
603
+ return toRenderCLI(variables);
604
+ }
605
+ }
606
+
607
+ // src/lib/config.ts
467
608
  var ConfigSchema = z.object({
468
609
  project_id: z.string().uuid().optional(),
469
610
  project_name: z.string().optional(),
@@ -471,7 +612,10 @@ var ConfigSchema = z.object({
471
612
  environment_id: z.string().uuid().optional(),
472
613
  organization_id: z.string().uuid().optional(),
473
614
  output: z.string().default(".env"),
474
- api_url: z.string().url().optional()
615
+ api_url: z.string().url().optional(),
616
+ format: z.enum(EXPORT_FORMATS).optional(),
617
+ k8s_namespace: z.string().optional(),
618
+ k8s_name: z.string().optional()
475
619
  });
476
620
  var CONFIG_FILENAMES = ["envmanager.json", ".envmanagerrc"];
477
621
  function findConfigFile(startDir = process.cwd()) {
@@ -741,17 +885,23 @@ function resolveAll(variables) {
741
885
  }
742
886
 
743
887
  // src/commands/pull.ts
744
- var pullCommand = new Command4("pull").description("Pull environment variables from EnvManager to local .env file").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("-o, --output <file>", "Output file path (default: .env)").option("--no-secrets", "Exclude secret values (will be empty)").option("-f, --force", "Overwrite existing file without prompting").option("-r, --resolve-references", "Resolve ${VAR} references to their values").option("-F, --include-fallbacks", "Include fallback values for empty variables").option("-s, --show-sources", "Show value source as inline comments").action(async (options) => {
888
+ var pullCommand = new Command4("pull").description("Pull environment variables from EnvManager to local .env file").option("--org <name>", "Organization name (required if you belong to multiple)").option("-e, --environment <name>", 'Environment name (default: from config or "development")').option("-p, --project <id>", "Project ID (default: from config)").option("-o, --output <file>", "Output file path (default: .env)").option("--no-secrets", "Exclude secret values (will be empty)").option("-f, --force", "Overwrite existing file without prompting").option("-r, --resolve-references", "Resolve ${VAR} references to their values").option("-F, --include-fallbacks", "Include fallback values for empty variables").option("-s, --show-sources", "Show value source as inline comments").option("--format <type>", `Export format (${EXPORT_FORMATS.join(", ")})`).option("--k8s-namespace <ns>", 'Kubernetes namespace (default: "default")').option("--k8s-name <name>", "Kubernetes resource name").action(async (options) => {
745
889
  const spinner = ora3("Connecting to EnvManager...").start();
746
890
  try {
747
891
  const config = loadConfig();
748
892
  const projectInput = options.project || config?.project_id;
749
893
  const envName = options.environment || config?.environment || "development";
750
- const outputFile = resolve(options.output || ".env");
894
+ const outputFile = resolve(options.output || config?.output || ".env");
751
895
  const includeSecrets = options.secrets !== false;
752
896
  const shouldResolve = options.resolveReferences === true;
753
897
  const shouldFallback = options.includeFallbacks === true;
754
898
  const shouldShowSources = options.showSources === true;
899
+ const formatInput = options.format || config?.format || "dotenv";
900
+ if (!EXPORT_FORMATS.includes(formatInput)) {
901
+ spinner.fail(`Invalid format "${formatInput}". Valid formats: ${EXPORT_FORMATS.join(", ")}`);
902
+ process.exit(1);
903
+ }
904
+ const format = formatInput;
755
905
  if (!projectInput) {
756
906
  spinner.fail("No project specified");
757
907
  console.log(chalk4.yellow("\nSpecify a project with --project <id-or-name> or create envmanager.json"));
@@ -828,39 +978,63 @@ File ${outputFile} already exists.`));
828
978
  const resolved = resolveAll(inputs);
829
979
  resolvedMap = new Map(resolved.map((r) => [r.key, r]));
830
980
  }
831
- spinner.text = "Writing .env file...";
832
- const envContent = vars.map((v) => {
981
+ spinner.text = `Writing ${format} output...`;
982
+ const exportVars = vars.map((v) => {
833
983
  let value;
834
- let source = null;
835
984
  if (resolvedMap) {
836
- const resolved = resolvedMap.get(v.key);
837
- value = resolved.resolvedValue;
838
- source = resolved.source;
985
+ value = resolvedMap.get(v.key).resolvedValue;
839
986
  } else if (shouldFallback && (!v.value || v.value === "") && v.fallback_value) {
840
987
  value = v.fallback_value;
841
- source = "fallback";
842
988
  } else {
843
989
  value = v.value || "";
844
990
  }
845
- const needsQuotes = value.includes(" ") || value.includes("\n") || value.includes('"');
846
- const formattedValue = needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value;
847
- let line = `${v.key}=${formattedValue}`;
848
- if (shouldShowSources && resolvedMap) {
849
- const resolved = resolvedMap.get(v.key);
850
- if (resolved.references.length > 0 && resolved.source !== "empty") {
851
- line += ` # resolved from ${resolved.rawValue ?? v.value ?? ""}`;
852
- } else if (resolved.source === "fallback") {
991
+ return { key: v.key, value, isSecret: v.is_secret };
992
+ });
993
+ let content;
994
+ if (format === "dotenv" && (shouldShowSources || shouldResolve)) {
995
+ content = vars.map((v) => {
996
+ let value;
997
+ let source = null;
998
+ if (resolvedMap) {
999
+ const resolved = resolvedMap.get(v.key);
1000
+ value = resolved.resolvedValue;
1001
+ source = resolved.source;
1002
+ } else if (shouldFallback && (!v.value || v.value === "") && v.fallback_value) {
1003
+ value = v.fallback_value;
1004
+ source = "fallback";
1005
+ } else {
1006
+ value = v.value || "";
1007
+ }
1008
+ const needsQuotes = value.includes(" ") || value.includes("\n") || value.includes('"');
1009
+ const formattedValue = needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value;
1010
+ let line = `${v.key}=${formattedValue}`;
1011
+ if (shouldShowSources && resolvedMap) {
1012
+ const resolved = resolvedMap.get(v.key);
1013
+ if (resolved.references.length > 0 && resolved.source !== "empty") {
1014
+ line += ` # resolved from ${resolved.rawValue ?? v.value ?? ""}`;
1015
+ } else if (resolved.source === "fallback") {
1016
+ line += ` # fallback value`;
1017
+ }
1018
+ } else if (shouldShowSources && source === "fallback") {
853
1019
  line += ` # fallback value`;
854
1020
  }
855
- } else if (shouldShowSources && source === "fallback") {
856
- line += ` # fallback value`;
1021
+ return line;
1022
+ }).join("\n");
1023
+ } else {
1024
+ let k8sConfig;
1025
+ if (format === "k8s-secret" || format === "k8s-configmap") {
1026
+ const defaultName = format === "k8s-secret" ? "app-secrets" : "app-config";
1027
+ k8sConfig = {
1028
+ name: sanitizeK8sName(options.k8sName || config?.k8s_name || defaultName),
1029
+ namespace: options.k8sNamespace || config?.k8s_namespace || "default"
1030
+ };
857
1031
  }
858
- return line;
859
- }).join("\n");
860
- writeFileSync2(outputFile, envContent + "\n");
1032
+ content = formatVariables(exportVars, format, k8sConfig);
1033
+ }
1034
+ writeFileSync2(outputFile, content + "\n");
861
1035
  const secretCount = vars.filter((v) => v.is_secret).length;
862
1036
  const plainCount = vars.length - secretCount;
863
- spinner.succeed(`Pulled ${variables.length} variables to ${outputFile}`);
1037
+ spinner.succeed(`Pulled ${variables.length} variables to ${outputFile} (${format})`);
864
1038
  console.log(chalk4.gray(` ${plainCount} plain, ${secretCount} secrets`));
865
1039
  client.rpc("log_variable_access", {
866
1040
  p_environment_id: environmentId,
@@ -2003,9 +2177,9 @@ function validateAgainstTemplate(template, env) {
2003
2177
  }
2004
2178
 
2005
2179
  // src/lib/template-yaml.ts
2006
- import yaml from "js-yaml";
2180
+ import yaml2 from "js-yaml";
2007
2181
  function parseYamlTemplate(content) {
2008
- const parsed = yaml.load(content);
2182
+ const parsed = yaml2.load(content);
2009
2183
  if (!parsed || typeof parsed !== "object") {
2010
2184
  throw new Error("Invalid YAML template: must be an object");
2011
2185
  }
@@ -2150,7 +2324,7 @@ function generateYamlTemplate(variables, options = {}) {
2150
2324
  vars[v.key] = varConfig;
2151
2325
  }
2152
2326
  template.variables = vars;
2153
- return yaml.dump(template, { lineWidth: -1, quotingType: '"' });
2327
+ return yaml2.dump(template, { lineWidth: -1, quotingType: '"' });
2154
2328
  }
2155
2329
 
2156
2330
  // src/commands/init.ts