@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.
- package/dist/bin/envmanager.js +201 -27
- package/dist/bin/envmanager.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.js +18 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/bin/envmanager.js
CHANGED
|
@@ -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 =
|
|
832
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
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
|
-
|
|
856
|
-
|
|
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
|
-
|
|
859
|
-
}
|
|
860
|
-
writeFileSync2(outputFile,
|
|
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
|
|
2180
|
+
import yaml2 from "js-yaml";
|
|
2007
2181
|
function parseYamlTemplate(content) {
|
|
2008
|
-
const parsed =
|
|
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
|
|
2327
|
+
return yaml2.dump(template, { lineWidth: -1, quotingType: '"' });
|
|
2154
2328
|
}
|
|
2155
2329
|
|
|
2156
2330
|
// src/commands/init.ts
|