@envpilot/cli 1.3.3 → 1.4.0

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.
Files changed (2) hide show
  1. package/dist/index.js +393 -12
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ function initSentry() {
9
9
  Sentry.init({
10
10
  dsn,
11
11
  environment: "cli",
12
- release: true ? "1.3.3" : "0.0.0",
12
+ release: true ? "1.4.0" : "0.0.0",
13
13
  // Free tier: disable performance monitoring
14
14
  tracesSampleRate: 0,
15
15
  beforeSend(event) {
@@ -1384,10 +1384,356 @@ function printPostInit(selectedOrg, selectedProject) {
1384
1384
  import { Command as Command3 } from "commander";
1385
1385
  import chalk5 from "chalk";
1386
1386
  import inquirer2 from "inquirer";
1387
+
1388
+ // src/lib/format-converter.ts
1389
+ var ALL_FORMATS = [
1390
+ "env",
1391
+ "json",
1392
+ "yaml",
1393
+ "docker-compose",
1394
+ "aws",
1395
+ "vercel",
1396
+ "netlify"
1397
+ ];
1398
+ function serialize(vars, format, meta) {
1399
+ switch (format) {
1400
+ case "env":
1401
+ return serializeEnv(vars, meta);
1402
+ case "json":
1403
+ return serializeJson(vars);
1404
+ case "yaml":
1405
+ return serializeYaml(vars, meta);
1406
+ case "docker-compose":
1407
+ return serializeDockerCompose(vars, meta);
1408
+ case "aws":
1409
+ return serializeAws(vars, meta);
1410
+ case "vercel":
1411
+ return serializeVercel(vars);
1412
+ case "netlify":
1413
+ return serializeNetlify(vars, meta);
1414
+ }
1415
+ }
1416
+ function parse(content, format, options) {
1417
+ switch (format) {
1418
+ case "env":
1419
+ return parseEnvFile(content);
1420
+ case "json":
1421
+ return parseJson(content);
1422
+ case "yaml":
1423
+ return parseYaml(content);
1424
+ case "docker-compose":
1425
+ return parseDockerCompose(content);
1426
+ case "aws":
1427
+ return parseAws(content, options?.prefix);
1428
+ case "vercel":
1429
+ return parseVercel(content);
1430
+ case "netlify":
1431
+ return parseNetlify(content);
1432
+ }
1433
+ }
1434
+ function getDefaultFilename(format, environment) {
1435
+ const env = environment || "development";
1436
+ switch (format) {
1437
+ case "env":
1438
+ return env === "development" ? ".env.local" : `.env.${env}`;
1439
+ case "json":
1440
+ return `${env}.json`;
1441
+ case "yaml":
1442
+ return `${env}.yaml`;
1443
+ case "docker-compose":
1444
+ return "docker-compose.yml";
1445
+ case "aws":
1446
+ return `${env}.aws.json`;
1447
+ case "vercel":
1448
+ return `${env}.vercel.json`;
1449
+ case "netlify":
1450
+ return "netlify.toml";
1451
+ }
1452
+ }
1453
+ function serializeEnv(vars, meta) {
1454
+ const lines = [];
1455
+ if (meta?.environment) {
1456
+ lines.push(`# Environment: ${meta.environment}`);
1457
+ }
1458
+ if (meta?.projectName) {
1459
+ lines.push(`# Project: ${meta.projectName}`);
1460
+ }
1461
+ if (lines.length > 0) {
1462
+ lines.push(`# Exported: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1463
+ lines.push("");
1464
+ }
1465
+ return lines.join("\n") + stringifyEnv(vars, { sort: true });
1466
+ }
1467
+ function serializeJson(vars) {
1468
+ const sorted = Object.keys(vars).sort().reduce(
1469
+ (obj, key) => {
1470
+ obj[key] = vars[key];
1471
+ return obj;
1472
+ },
1473
+ {}
1474
+ );
1475
+ return JSON.stringify(sorted, null, 2) + "\n";
1476
+ }
1477
+ function parseJson(content) {
1478
+ const data = JSON.parse(content);
1479
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
1480
+ throw new Error("Expected a JSON object with string key-value pairs");
1481
+ }
1482
+ const result = {};
1483
+ for (const [key, value] of Object.entries(data)) {
1484
+ if (typeof value === "string") {
1485
+ result[key] = value;
1486
+ } else if (value !== null && value !== void 0) {
1487
+ result[key] = String(value);
1488
+ }
1489
+ }
1490
+ return result;
1491
+ }
1492
+ function yamlQuote(value) {
1493
+ if (value === "" || value === "true" || value === "false" || value === "null" || value === "~" || /^[0-9]/.test(value) || /[:#\[\]{}&*!|>'"%@`]/.test(value) || value.includes("\n") || value.startsWith(" ") || value.endsWith(" ")) {
1494
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1495
+ return `"${escaped}"`;
1496
+ }
1497
+ return value;
1498
+ }
1499
+ function serializeYaml(vars, meta) {
1500
+ const lines = [];
1501
+ if (meta?.environment) {
1502
+ lines.push(`# Environment: ${meta.environment}`);
1503
+ }
1504
+ if (meta?.projectName) {
1505
+ lines.push(`# Project: ${meta.projectName}`);
1506
+ }
1507
+ if (lines.length > 0) {
1508
+ lines.push("");
1509
+ }
1510
+ for (const key of Object.keys(vars).sort()) {
1511
+ lines.push(`${key}: ${yamlQuote(vars[key])}`);
1512
+ }
1513
+ return lines.join("\n") + "\n";
1514
+ }
1515
+ function parseYaml(content) {
1516
+ const result = {};
1517
+ for (const line of content.split("\n")) {
1518
+ const trimmed = line.trim();
1519
+ if (!trimmed || trimmed.startsWith("#")) continue;
1520
+ const colonIndex = trimmed.indexOf(": ");
1521
+ if (colonIndex === -1) {
1522
+ if (trimmed.endsWith(":")) {
1523
+ const key2 = trimmed.slice(0, -1).trim();
1524
+ if (key2 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key2)) {
1525
+ result[key2] = "";
1526
+ }
1527
+ }
1528
+ continue;
1529
+ }
1530
+ const key = trimmed.substring(0, colonIndex).trim();
1531
+ let value = trimmed.substring(colonIndex + 2).trim();
1532
+ if (line.startsWith(" ") || line.startsWith(" ")) continue;
1533
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1534
+ value = value.slice(1, -1);
1535
+ if (value.includes("\\")) {
1536
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
1537
+ }
1538
+ }
1539
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1540
+ result[key] = value;
1541
+ }
1542
+ }
1543
+ return result;
1544
+ }
1545
+ function serializeDockerCompose(vars, meta) {
1546
+ const lines = [];
1547
+ if (meta?.environment || meta?.projectName) {
1548
+ lines.push("# Docker Compose environment variables");
1549
+ if (meta.projectName) lines.push(`# Project: ${meta.projectName}`);
1550
+ if (meta.environment) lines.push(`# Environment: ${meta.environment}`);
1551
+ lines.push("");
1552
+ }
1553
+ lines.push("services:");
1554
+ lines.push(" app:");
1555
+ lines.push(" environment:");
1556
+ for (const key of Object.keys(vars).sort()) {
1557
+ lines.push(` ${key}: ${yamlQuote(vars[key])}`);
1558
+ }
1559
+ return lines.join("\n") + "\n";
1560
+ }
1561
+ function parseDockerCompose(content) {
1562
+ const result = {};
1563
+ const lines = content.split("\n");
1564
+ let inEnvironment = false;
1565
+ let environmentIndent = -1;
1566
+ for (const line of lines) {
1567
+ const trimmed = line.trim();
1568
+ if (!trimmed || trimmed.startsWith("#")) continue;
1569
+ const indent = line.length - line.trimStart().length;
1570
+ if (trimmed === "environment:") {
1571
+ inEnvironment = true;
1572
+ environmentIndent = indent;
1573
+ continue;
1574
+ }
1575
+ if (inEnvironment) {
1576
+ if (indent <= environmentIndent && trimmed !== "") {
1577
+ inEnvironment = false;
1578
+ environmentIndent = -1;
1579
+ continue;
1580
+ }
1581
+ if (trimmed.startsWith("- ")) {
1582
+ const entry = trimmed.slice(2);
1583
+ const eqIdx = entry.indexOf("=");
1584
+ if (eqIdx !== -1) {
1585
+ const key = entry.substring(0, eqIdx).trim();
1586
+ let value = entry.substring(eqIdx + 1).trim();
1587
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1588
+ value = value.slice(1, -1);
1589
+ }
1590
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1591
+ result[key] = value;
1592
+ }
1593
+ }
1594
+ continue;
1595
+ }
1596
+ const colonIndex = trimmed.indexOf(": ");
1597
+ if (colonIndex !== -1) {
1598
+ const key = trimmed.substring(0, colonIndex).trim();
1599
+ let value = trimmed.substring(colonIndex + 2).trim();
1600
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1601
+ value = value.slice(1, -1);
1602
+ if (value.includes("\\")) {
1603
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
1604
+ }
1605
+ }
1606
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1607
+ result[key] = value;
1608
+ }
1609
+ } else if (trimmed.endsWith(":")) {
1610
+ const key = trimmed.slice(0, -1).trim();
1611
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1612
+ result[key] = "";
1613
+ }
1614
+ }
1615
+ }
1616
+ }
1617
+ return result;
1618
+ }
1619
+ function serializeAws(vars, meta) {
1620
+ const prefix = meta?.prefix || `/${meta?.projectName || "app"}`;
1621
+ const parameters = Object.keys(vars).sort().map((key) => ({
1622
+ name: `${prefix}/${key}`,
1623
+ value: vars[key],
1624
+ type: "SecureString"
1625
+ }));
1626
+ return JSON.stringify({ parameters }, null, 2) + "\n";
1627
+ }
1628
+ function parseAws(content, prefix) {
1629
+ const data = JSON.parse(content);
1630
+ const result = {};
1631
+ const params = data.parameters || data.Parameters || [];
1632
+ if (!Array.isArray(params)) {
1633
+ throw new Error(
1634
+ "Expected AWS Parameter Store format with 'parameters' array"
1635
+ );
1636
+ }
1637
+ for (const param of params) {
1638
+ const name = param.name || param.Name || "";
1639
+ const value = param.value || param.Value || "";
1640
+ let key;
1641
+ if (prefix && name.startsWith(prefix + "/")) {
1642
+ key = name.substring(prefix.length + 1);
1643
+ } else {
1644
+ const lastSlash = name.lastIndexOf("/");
1645
+ key = lastSlash !== -1 ? name.substring(lastSlash + 1) : name;
1646
+ }
1647
+ if (key && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1648
+ result[key] = value;
1649
+ }
1650
+ }
1651
+ return result;
1652
+ }
1653
+ function serializeVercel(vars) {
1654
+ const sorted = Object.keys(vars).sort().reduce(
1655
+ (obj, key) => {
1656
+ obj[key] = vars[key];
1657
+ return obj;
1658
+ },
1659
+ {}
1660
+ );
1661
+ return JSON.stringify({ env: sorted }, null, 2) + "\n";
1662
+ }
1663
+ function parseVercel(content) {
1664
+ const data = JSON.parse(content);
1665
+ const result = {};
1666
+ const env = data.env;
1667
+ if (!env || typeof env !== "object" || Array.isArray(env)) {
1668
+ throw new Error("Expected Vercel format with 'env' object");
1669
+ }
1670
+ for (const [key, value] of Object.entries(env)) {
1671
+ if (typeof value === "string") {
1672
+ result[key] = value;
1673
+ } else if (value !== null && value !== void 0) {
1674
+ result[key] = String(value);
1675
+ }
1676
+ }
1677
+ return result;
1678
+ }
1679
+ function tomlQuote(value) {
1680
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1681
+ return `"${escaped}"`;
1682
+ }
1683
+ function serializeNetlify(vars, meta) {
1684
+ const lines = [];
1685
+ if (meta?.environment || meta?.projectName) {
1686
+ if (meta.projectName) lines.push(`# Project: ${meta.projectName}`);
1687
+ if (meta.environment) lines.push(`# Environment: ${meta.environment}`);
1688
+ lines.push("");
1689
+ }
1690
+ lines.push("[build.environment]");
1691
+ for (const key of Object.keys(vars).sort()) {
1692
+ lines.push(` ${key} = ${tomlQuote(vars[key])}`);
1693
+ }
1694
+ return lines.join("\n") + "\n";
1695
+ }
1696
+ function parseNetlify(content) {
1697
+ const result = {};
1698
+ const lines = content.split("\n");
1699
+ let inBuildEnv = false;
1700
+ for (const line of lines) {
1701
+ const trimmed = line.trim();
1702
+ if (trimmed.startsWith("[")) {
1703
+ inBuildEnv = trimmed === "[build.environment]" || trimmed === "[build.environment]";
1704
+ continue;
1705
+ }
1706
+ if (!inBuildEnv) continue;
1707
+ if (!trimmed || trimmed.startsWith("#")) continue;
1708
+ const eqIndex = trimmed.indexOf("=");
1709
+ if (eqIndex === -1) continue;
1710
+ const key = trimmed.substring(0, eqIndex).trim();
1711
+ let value = trimmed.substring(eqIndex + 1).trim();
1712
+ if (value.startsWith('"') && value.endsWith('"')) {
1713
+ value = value.slice(1, -1);
1714
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
1715
+ } else if (value.startsWith("'") && value.endsWith("'")) {
1716
+ value = value.slice(1, -1);
1717
+ }
1718
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1719
+ result[key] = value;
1720
+ }
1721
+ }
1722
+ return result;
1723
+ }
1724
+
1725
+ // src/commands/pull.ts
1387
1726
  var pullCommand = new Command3("pull").description("Download environment variables to local .env file").option(
1388
1727
  "-e, --env <environment>",
1389
1728
  "Environment (development, staging, production)"
1390
- ).option("-f, --file <path>", "Output file path (default: .env)").option("--force", "Overwrite without confirmation").option("--format <format>", "Output format: env, json", "env").option("--dry-run", "Show what would be downloaded without writing").option("--project <name-or-id>", "Pull a specific linked project").option("--all", "Pull all linked projects").action(async (options) => {
1729
+ ).option("-f, --file <path>", "Output file path (default: .env)").option("--force", "Overwrite without confirmation").option(
1730
+ "--format <format>",
1731
+ "Output format: env, json, yaml, docker-compose, aws, vercel, netlify",
1732
+ "env"
1733
+ ).option(
1734
+ "--prefix <prefix>",
1735
+ "AWS Parameter Store path prefix (default: /project-name)"
1736
+ ).option("--dry-run", "Show what would be downloaded without writing").option("--project <name-or-id>", "Pull a specific linked project").option("--all", "Pull all linked projects").action(async (options) => {
1391
1737
  try {
1392
1738
  if (!isAuthenticated()) {
1393
1739
  throw notAuthenticated();
@@ -1420,7 +1766,12 @@ var pullCommand = new Command3("pull").description("Download environment variabl
1420
1766
  if (!projectConfig) throw notInitialized();
1421
1767
  checkTrackedFiles();
1422
1768
  const environment = options.env || projectConfig.environment || "development";
1423
- const outputPath = options.file || getEnvPathForEnvironment(environment);
1769
+ const fmt = options.format || "env";
1770
+ if (!ALL_FORMATS.includes(fmt)) {
1771
+ error(`Unknown format: ${fmt}. Supported: ${ALL_FORMATS.join(", ")}`);
1772
+ process.exit(1);
1773
+ }
1774
+ const outputPath = options.file || (fmt === "env" ? getEnvPathForEnvironment(environment) : getDefaultFilename(fmt, environment));
1424
1775
  await pullProject(
1425
1776
  {
1426
1777
  projectId: projectConfig.projectId,
@@ -1479,7 +1830,8 @@ async function pullAllProjects(options) {
1479
1830
  async function pullSingleProject(project, options) {
1480
1831
  checkTrackedFiles();
1481
1832
  const environment = options.env || project.environment;
1482
- const outputPath = options.file || getEnvPathForEnvironment(environment);
1833
+ const fmt = options.format || "env";
1834
+ const outputPath = options.file || (fmt === "env" ? getEnvPathForEnvironment(environment) : getDefaultFilename(fmt, environment));
1483
1835
  await pullProject(
1484
1836
  {
1485
1837
  projectId: project.projectId,
@@ -1552,10 +1904,8 @@ async function pullProject(project, outputPath, options) {
1552
1904
  }
1553
1905
  } catch {
1554
1906
  }
1555
- if (options.format === "json") {
1556
- const fs = await import("fs");
1557
- fs.writeFileSync(outputPath, JSON.stringify(remoteVars, null, 2) + "\n");
1558
- } else {
1907
+ const fmt = options.format || "env";
1908
+ if (fmt === "env") {
1559
1909
  const comments = {};
1560
1910
  for (const variable of variables) {
1561
1911
  if (variable.description) {
@@ -1563,6 +1913,14 @@ async function pullProject(project, outputPath, options) {
1563
1913
  }
1564
1914
  }
1565
1915
  writeEnvFile(outputPath, remoteVars, { sort: true, comments });
1916
+ } else {
1917
+ const fs = await import("fs");
1918
+ const output = serialize(remoteVars, fmt, {
1919
+ projectName: project.projectId,
1920
+ environment: project.environment,
1921
+ prefix: options.prefix
1922
+ });
1923
+ fs.writeFileSync(outputPath, output, "utf-8");
1566
1924
  }
1567
1925
  const role = getRole();
1568
1926
  applyFileProtection(outputPath, role, metaProjectRole);
@@ -1658,7 +2016,14 @@ function validateEnvVars(vars) {
1658
2016
  var pushCommand = new Command4("push").description("Upload local .env file to cloud").option(
1659
2017
  "-e, --env <environment>",
1660
2018
  "Target environment (development, staging, production)"
1661
- ).option("-f, --file <path>", "Input file path (default: .env)").option("--merge", "Merge with existing variables (default)").option("--replace", "Replace all existing variables").option("--dry-run", "Show what would be uploaded without making changes").option("--force", "Skip confirmation").option("--project <name-or-id>", "Push to a specific linked project").action(async (options) => {
2019
+ ).option("-f, --file <path>", "Input file path (default: .env)").option("--merge", "Merge with existing variables (default)").option("--replace", "Replace all existing variables").option(
2020
+ "--format <format>",
2021
+ "Input format: env, json, yaml, docker-compose, aws, vercel, netlify",
2022
+ "env"
2023
+ ).option(
2024
+ "--prefix <prefix>",
2025
+ "AWS Parameter Store path prefix (default: /project-name)"
2026
+ ).option("--dry-run", "Show what would be uploaded without making changes").option("--force", "Skip confirmation").option("--project <name-or-id>", "Push to a specific linked project").action(async (options) => {
1662
2027
  try {
1663
2028
  if (!isAuthenticated()) {
1664
2029
  throw notAuthenticated();
@@ -1740,9 +2105,25 @@ var pushCommand = new Command4("push").description("Upload local .env file to cl
1740
2105
  }
1741
2106
  }
1742
2107
  const environment = options.env || defaultEnvironment || "development";
1743
- const inputPath = options.file || getEnvPathForEnvironment(environment);
2108
+ const fmt = options.format || "env";
2109
+ if (!ALL_FORMATS.includes(fmt)) {
2110
+ error(`Unknown format: ${fmt}. Supported: ${ALL_FORMATS.join(", ")}`);
2111
+ process.exit(1);
2112
+ }
2113
+ const inputPath = options.file || (fmt === "env" ? getEnvPathForEnvironment(environment) : getDefaultFilename(fmt, environment));
1744
2114
  const mode = options.replace ? "replace" : "merge";
1745
- const localVars = readEnvFile(inputPath);
2115
+ let localVars;
2116
+ if (fmt === "env") {
2117
+ localVars = readEnvFile(inputPath);
2118
+ } else {
2119
+ const { readFileSync: readFileSync4, existsSync: existsSync4 } = await import("fs");
2120
+ if (!existsSync4(inputPath)) {
2121
+ localVars = null;
2122
+ } else {
2123
+ const content = readFileSync4(inputPath, "utf-8");
2124
+ localVars = parse(content, fmt, { prefix: options.prefix });
2125
+ }
2126
+ }
1746
2127
  if (!localVars) {
1747
2128
  throw fileNotFound(inputPath);
1748
2129
  }
@@ -3141,7 +3522,7 @@ var usageCommand = new Command11("usage").description("Show plan usage and limit
3141
3522
  // src/lib/version-check.ts
3142
3523
  import chalk13 from "chalk";
3143
3524
  import Conf2 from "conf";
3144
- var CLI_VERSION = "1.3.1";
3525
+ var CLI_VERSION = true ? "1.4.0" : "0.0.0";
3145
3526
  var CHECK_INTERVAL = 60 * 60 * 1e3;
3146
3527
  var _cache = null;
3147
3528
  function getCache() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envpilot/cli",
3
- "version": "1.3.3",
3
+ "version": "1.4.0",
4
4
  "description": "Envpilot CLI — sync and manage environment variables from the terminal",
5
5
  "type": "module",
6
6
  "bin": {