@envpilot/cli 1.3.4 → 1.4.1

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 +418 -20
  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.4" : "0.0.0",
12
+ release: true ? "1.4.1" : "0.0.0",
13
13
  // Free tier: disable performance monitoring
14
14
  tracesSampleRate: 0,
15
15
  beforeSend(event) {
@@ -748,6 +748,11 @@ var projectSchema = z.object({
748
748
  userRole: z.string().nullable().optional(),
749
749
  projectRole: z.string().nullable().optional()
750
750
  });
751
+ var variableTagSchema = z.object({
752
+ _id: z.string(),
753
+ name: z.string(),
754
+ color: z.string()
755
+ });
751
756
  var variableSchema = z.object({
752
757
  _id: z.string(),
753
758
  key: z.string(),
@@ -756,7 +761,8 @@ var variableSchema = z.object({
756
761
  projectId: z.string(),
757
762
  description: z.string().optional(),
758
763
  isSensitive: z.boolean().optional(),
759
- version: z.number().optional()
764
+ version: z.number().optional(),
765
+ tags: z.array(variableTagSchema).optional()
760
766
  });
761
767
  var environmentSchema = z.enum([
762
768
  "development",
@@ -1384,10 +1390,356 @@ function printPostInit(selectedOrg, selectedProject) {
1384
1390
  import { Command as Command3 } from "commander";
1385
1391
  import chalk5 from "chalk";
1386
1392
  import inquirer2 from "inquirer";
1393
+
1394
+ // src/lib/format-converter.ts
1395
+ var ALL_FORMATS = [
1396
+ "env",
1397
+ "json",
1398
+ "yaml",
1399
+ "docker-compose",
1400
+ "aws",
1401
+ "vercel",
1402
+ "netlify"
1403
+ ];
1404
+ function serialize(vars, format, meta) {
1405
+ switch (format) {
1406
+ case "env":
1407
+ return serializeEnv(vars, meta);
1408
+ case "json":
1409
+ return serializeJson(vars);
1410
+ case "yaml":
1411
+ return serializeYaml(vars, meta);
1412
+ case "docker-compose":
1413
+ return serializeDockerCompose(vars, meta);
1414
+ case "aws":
1415
+ return serializeAws(vars, meta);
1416
+ case "vercel":
1417
+ return serializeVercel(vars);
1418
+ case "netlify":
1419
+ return serializeNetlify(vars, meta);
1420
+ }
1421
+ }
1422
+ function parse(content, format, options) {
1423
+ switch (format) {
1424
+ case "env":
1425
+ return parseEnvFile(content);
1426
+ case "json":
1427
+ return parseJson(content);
1428
+ case "yaml":
1429
+ return parseYaml(content);
1430
+ case "docker-compose":
1431
+ return parseDockerCompose(content);
1432
+ case "aws":
1433
+ return parseAws(content, options?.prefix);
1434
+ case "vercel":
1435
+ return parseVercel(content);
1436
+ case "netlify":
1437
+ return parseNetlify(content);
1438
+ }
1439
+ }
1440
+ function getDefaultFilename(format, environment) {
1441
+ const env = environment || "development";
1442
+ switch (format) {
1443
+ case "env":
1444
+ return env === "development" ? ".env.local" : `.env.${env}`;
1445
+ case "json":
1446
+ return `${env}.json`;
1447
+ case "yaml":
1448
+ return `${env}.yaml`;
1449
+ case "docker-compose":
1450
+ return "docker-compose.yml";
1451
+ case "aws":
1452
+ return `${env}.aws.json`;
1453
+ case "vercel":
1454
+ return `${env}.vercel.json`;
1455
+ case "netlify":
1456
+ return "netlify.toml";
1457
+ }
1458
+ }
1459
+ function serializeEnv(vars, meta) {
1460
+ const lines = [];
1461
+ if (meta?.environment) {
1462
+ lines.push(`# Environment: ${meta.environment}`);
1463
+ }
1464
+ if (meta?.projectName) {
1465
+ lines.push(`# Project: ${meta.projectName}`);
1466
+ }
1467
+ if (lines.length > 0) {
1468
+ lines.push(`# Exported: ${(/* @__PURE__ */ new Date()).toISOString()}`);
1469
+ lines.push("");
1470
+ }
1471
+ return lines.join("\n") + stringifyEnv(vars, { sort: true });
1472
+ }
1473
+ function serializeJson(vars) {
1474
+ const sorted = Object.keys(vars).sort().reduce(
1475
+ (obj, key) => {
1476
+ obj[key] = vars[key];
1477
+ return obj;
1478
+ },
1479
+ {}
1480
+ );
1481
+ return JSON.stringify(sorted, null, 2) + "\n";
1482
+ }
1483
+ function parseJson(content) {
1484
+ const data = JSON.parse(content);
1485
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
1486
+ throw new Error("Expected a JSON object with string key-value pairs");
1487
+ }
1488
+ const result = {};
1489
+ for (const [key, value] of Object.entries(data)) {
1490
+ if (typeof value === "string") {
1491
+ result[key] = value;
1492
+ } else if (value !== null && value !== void 0) {
1493
+ result[key] = String(value);
1494
+ }
1495
+ }
1496
+ return result;
1497
+ }
1498
+ function yamlQuote(value) {
1499
+ if (value === "" || value === "true" || value === "false" || value === "null" || value === "~" || /^[0-9]/.test(value) || /[:#\[\]{}&*!|>'"%@`]/.test(value) || value.includes("\n") || value.startsWith(" ") || value.endsWith(" ")) {
1500
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1501
+ return `"${escaped}"`;
1502
+ }
1503
+ return value;
1504
+ }
1505
+ function serializeYaml(vars, meta) {
1506
+ const lines = [];
1507
+ if (meta?.environment) {
1508
+ lines.push(`# Environment: ${meta.environment}`);
1509
+ }
1510
+ if (meta?.projectName) {
1511
+ lines.push(`# Project: ${meta.projectName}`);
1512
+ }
1513
+ if (lines.length > 0) {
1514
+ lines.push("");
1515
+ }
1516
+ for (const key of Object.keys(vars).sort()) {
1517
+ lines.push(`${key}: ${yamlQuote(vars[key])}`);
1518
+ }
1519
+ return lines.join("\n") + "\n";
1520
+ }
1521
+ function parseYaml(content) {
1522
+ const result = {};
1523
+ for (const line of content.split("\n")) {
1524
+ const trimmed = line.trim();
1525
+ if (!trimmed || trimmed.startsWith("#")) continue;
1526
+ const colonIndex = trimmed.indexOf(": ");
1527
+ if (colonIndex === -1) {
1528
+ if (trimmed.endsWith(":")) {
1529
+ const key2 = trimmed.slice(0, -1).trim();
1530
+ if (key2 && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key2)) {
1531
+ result[key2] = "";
1532
+ }
1533
+ }
1534
+ continue;
1535
+ }
1536
+ const key = trimmed.substring(0, colonIndex).trim();
1537
+ let value = trimmed.substring(colonIndex + 2).trim();
1538
+ if (line.startsWith(" ") || line.startsWith(" ")) continue;
1539
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1540
+ value = value.slice(1, -1);
1541
+ if (value.includes("\\")) {
1542
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
1543
+ }
1544
+ }
1545
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1546
+ result[key] = value;
1547
+ }
1548
+ }
1549
+ return result;
1550
+ }
1551
+ function serializeDockerCompose(vars, meta) {
1552
+ const lines = [];
1553
+ if (meta?.environment || meta?.projectName) {
1554
+ lines.push("# Docker Compose environment variables");
1555
+ if (meta.projectName) lines.push(`# Project: ${meta.projectName}`);
1556
+ if (meta.environment) lines.push(`# Environment: ${meta.environment}`);
1557
+ lines.push("");
1558
+ }
1559
+ lines.push("services:");
1560
+ lines.push(" app:");
1561
+ lines.push(" environment:");
1562
+ for (const key of Object.keys(vars).sort()) {
1563
+ lines.push(` ${key}: ${yamlQuote(vars[key])}`);
1564
+ }
1565
+ return lines.join("\n") + "\n";
1566
+ }
1567
+ function parseDockerCompose(content) {
1568
+ const result = {};
1569
+ const lines = content.split("\n");
1570
+ let inEnvironment = false;
1571
+ let environmentIndent = -1;
1572
+ for (const line of lines) {
1573
+ const trimmed = line.trim();
1574
+ if (!trimmed || trimmed.startsWith("#")) continue;
1575
+ const indent = line.length - line.trimStart().length;
1576
+ if (trimmed === "environment:") {
1577
+ inEnvironment = true;
1578
+ environmentIndent = indent;
1579
+ continue;
1580
+ }
1581
+ if (inEnvironment) {
1582
+ if (indent <= environmentIndent && trimmed !== "") {
1583
+ inEnvironment = false;
1584
+ environmentIndent = -1;
1585
+ continue;
1586
+ }
1587
+ if (trimmed.startsWith("- ")) {
1588
+ const entry = trimmed.slice(2);
1589
+ const eqIdx = entry.indexOf("=");
1590
+ if (eqIdx !== -1) {
1591
+ const key = entry.substring(0, eqIdx).trim();
1592
+ let value = entry.substring(eqIdx + 1).trim();
1593
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1594
+ value = value.slice(1, -1);
1595
+ }
1596
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1597
+ result[key] = value;
1598
+ }
1599
+ }
1600
+ continue;
1601
+ }
1602
+ const colonIndex = trimmed.indexOf(": ");
1603
+ if (colonIndex !== -1) {
1604
+ const key = trimmed.substring(0, colonIndex).trim();
1605
+ let value = trimmed.substring(colonIndex + 2).trim();
1606
+ if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
1607
+ value = value.slice(1, -1);
1608
+ if (value.includes("\\")) {
1609
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
1610
+ }
1611
+ }
1612
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1613
+ result[key] = value;
1614
+ }
1615
+ } else if (trimmed.endsWith(":")) {
1616
+ const key = trimmed.slice(0, -1).trim();
1617
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1618
+ result[key] = "";
1619
+ }
1620
+ }
1621
+ }
1622
+ }
1623
+ return result;
1624
+ }
1625
+ function serializeAws(vars, meta) {
1626
+ const prefix = meta?.prefix || `/${meta?.projectName || "app"}`;
1627
+ const parameters = Object.keys(vars).sort().map((key) => ({
1628
+ name: `${prefix}/${key}`,
1629
+ value: vars[key],
1630
+ type: "SecureString"
1631
+ }));
1632
+ return JSON.stringify({ parameters }, null, 2) + "\n";
1633
+ }
1634
+ function parseAws(content, prefix) {
1635
+ const data = JSON.parse(content);
1636
+ const result = {};
1637
+ const params = data.parameters || data.Parameters || [];
1638
+ if (!Array.isArray(params)) {
1639
+ throw new Error(
1640
+ "Expected AWS Parameter Store format with 'parameters' array"
1641
+ );
1642
+ }
1643
+ for (const param of params) {
1644
+ const name = param.name || param.Name || "";
1645
+ const value = param.value || param.Value || "";
1646
+ let key;
1647
+ if (prefix && name.startsWith(prefix + "/")) {
1648
+ key = name.substring(prefix.length + 1);
1649
+ } else {
1650
+ const lastSlash = name.lastIndexOf("/");
1651
+ key = lastSlash !== -1 ? name.substring(lastSlash + 1) : name;
1652
+ }
1653
+ if (key && /^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1654
+ result[key] = value;
1655
+ }
1656
+ }
1657
+ return result;
1658
+ }
1659
+ function serializeVercel(vars) {
1660
+ const sorted = Object.keys(vars).sort().reduce(
1661
+ (obj, key) => {
1662
+ obj[key] = vars[key];
1663
+ return obj;
1664
+ },
1665
+ {}
1666
+ );
1667
+ return JSON.stringify({ env: sorted }, null, 2) + "\n";
1668
+ }
1669
+ function parseVercel(content) {
1670
+ const data = JSON.parse(content);
1671
+ const result = {};
1672
+ const env = data.env;
1673
+ if (!env || typeof env !== "object" || Array.isArray(env)) {
1674
+ throw new Error("Expected Vercel format with 'env' object");
1675
+ }
1676
+ for (const [key, value] of Object.entries(env)) {
1677
+ if (typeof value === "string") {
1678
+ result[key] = value;
1679
+ } else if (value !== null && value !== void 0) {
1680
+ result[key] = String(value);
1681
+ }
1682
+ }
1683
+ return result;
1684
+ }
1685
+ function tomlQuote(value) {
1686
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1687
+ return `"${escaped}"`;
1688
+ }
1689
+ function serializeNetlify(vars, meta) {
1690
+ const lines = [];
1691
+ if (meta?.environment || meta?.projectName) {
1692
+ if (meta.projectName) lines.push(`# Project: ${meta.projectName}`);
1693
+ if (meta.environment) lines.push(`# Environment: ${meta.environment}`);
1694
+ lines.push("");
1695
+ }
1696
+ lines.push("[build.environment]");
1697
+ for (const key of Object.keys(vars).sort()) {
1698
+ lines.push(` ${key} = ${tomlQuote(vars[key])}`);
1699
+ }
1700
+ return lines.join("\n") + "\n";
1701
+ }
1702
+ function parseNetlify(content) {
1703
+ const result = {};
1704
+ const lines = content.split("\n");
1705
+ let inBuildEnv = false;
1706
+ for (const line of lines) {
1707
+ const trimmed = line.trim();
1708
+ if (trimmed.startsWith("[")) {
1709
+ inBuildEnv = trimmed === "[build.environment]" || trimmed === "[build.environment]";
1710
+ continue;
1711
+ }
1712
+ if (!inBuildEnv) continue;
1713
+ if (!trimmed || trimmed.startsWith("#")) continue;
1714
+ const eqIndex = trimmed.indexOf("=");
1715
+ if (eqIndex === -1) continue;
1716
+ const key = trimmed.substring(0, eqIndex).trim();
1717
+ let value = trimmed.substring(eqIndex + 1).trim();
1718
+ if (value.startsWith('"') && value.endsWith('"')) {
1719
+ value = value.slice(1, -1);
1720
+ value = value.replace(/\\n/g, "\n").replace(/\\r/g, "\r").replace(/\\t/g, " ").replace(/\\"/g, '"').replace(/\\\\/g, "\\");
1721
+ } else if (value.startsWith("'") && value.endsWith("'")) {
1722
+ value = value.slice(1, -1);
1723
+ }
1724
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) {
1725
+ result[key] = value;
1726
+ }
1727
+ }
1728
+ return result;
1729
+ }
1730
+
1731
+ // src/commands/pull.ts
1387
1732
  var pullCommand = new Command3("pull").description("Download environment variables to local .env file").option(
1388
1733
  "-e, --env <environment>",
1389
1734
  "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) => {
1735
+ ).option("-f, --file <path>", "Output file path (default: .env)").option("--force", "Overwrite without confirmation").option(
1736
+ "--format <format>",
1737
+ "Output format: env, json, yaml, docker-compose, aws, vercel, netlify",
1738
+ "env"
1739
+ ).option(
1740
+ "--prefix <prefix>",
1741
+ "AWS Parameter Store path prefix (default: /project-name)"
1742
+ ).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
1743
  try {
1392
1744
  if (!isAuthenticated()) {
1393
1745
  throw notAuthenticated();
@@ -1420,7 +1772,12 @@ var pullCommand = new Command3("pull").description("Download environment variabl
1420
1772
  if (!projectConfig) throw notInitialized();
1421
1773
  checkTrackedFiles();
1422
1774
  const environment = options.env || projectConfig.environment || "development";
1423
- const outputPath = options.file || getEnvPathForEnvironment(environment);
1775
+ const fmt = options.format || "env";
1776
+ if (!ALL_FORMATS.includes(fmt)) {
1777
+ error(`Unknown format: ${fmt}. Supported: ${ALL_FORMATS.join(", ")}`);
1778
+ process.exit(1);
1779
+ }
1780
+ const outputPath = options.file || (fmt === "env" ? getEnvPathForEnvironment(environment) : getDefaultFilename(fmt, environment));
1424
1781
  await pullProject(
1425
1782
  {
1426
1783
  projectId: projectConfig.projectId,
@@ -1479,7 +1836,8 @@ async function pullAllProjects(options) {
1479
1836
  async function pullSingleProject(project, options) {
1480
1837
  checkTrackedFiles();
1481
1838
  const environment = options.env || project.environment;
1482
- const outputPath = options.file || getEnvPathForEnvironment(environment);
1839
+ const fmt = options.format || "env";
1840
+ const outputPath = options.file || (fmt === "env" ? getEnvPathForEnvironment(environment) : getDefaultFilename(fmt, environment));
1483
1841
  await pullProject(
1484
1842
  {
1485
1843
  projectId: project.projectId,
@@ -1552,10 +1910,8 @@ async function pullProject(project, outputPath, options) {
1552
1910
  }
1553
1911
  } catch {
1554
1912
  }
1555
- if (options.format === "json") {
1556
- const fs = await import("fs");
1557
- fs.writeFileSync(outputPath, JSON.stringify(remoteVars, null, 2) + "\n");
1558
- } else {
1913
+ const fmt = options.format || "env";
1914
+ if (fmt === "env") {
1559
1915
  const comments = {};
1560
1916
  for (const variable of variables) {
1561
1917
  if (variable.description) {
@@ -1563,6 +1919,14 @@ async function pullProject(project, outputPath, options) {
1563
1919
  }
1564
1920
  }
1565
1921
  writeEnvFile(outputPath, remoteVars, { sort: true, comments });
1922
+ } else {
1923
+ const fs = await import("fs");
1924
+ const output = serialize(remoteVars, fmt, {
1925
+ projectName: project.projectId,
1926
+ environment: project.environment,
1927
+ prefix: options.prefix
1928
+ });
1929
+ fs.writeFileSync(outputPath, output, "utf-8");
1566
1930
  }
1567
1931
  const role = getRole();
1568
1932
  applyFileProtection(outputPath, role, metaProjectRole);
@@ -1658,7 +2022,14 @@ function validateEnvVars(vars) {
1658
2022
  var pushCommand = new Command4("push").description("Upload local .env file to cloud").option(
1659
2023
  "-e, --env <environment>",
1660
2024
  "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) => {
2025
+ ).option("-f, --file <path>", "Input file path (default: .env)").option("--merge", "Merge with existing variables (default)").option("--replace", "Replace all existing variables").option(
2026
+ "--format <format>",
2027
+ "Input format: env, json, yaml, docker-compose, aws, vercel, netlify",
2028
+ "env"
2029
+ ).option(
2030
+ "--prefix <prefix>",
2031
+ "AWS Parameter Store path prefix (default: /project-name)"
2032
+ ).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
2033
  try {
1663
2034
  if (!isAuthenticated()) {
1664
2035
  throw notAuthenticated();
@@ -1740,9 +2111,25 @@ var pushCommand = new Command4("push").description("Upload local .env file to cl
1740
2111
  }
1741
2112
  }
1742
2113
  const environment = options.env || defaultEnvironment || "development";
1743
- const inputPath = options.file || getEnvPathForEnvironment(environment);
2114
+ const fmt = options.format || "env";
2115
+ if (!ALL_FORMATS.includes(fmt)) {
2116
+ error(`Unknown format: ${fmt}. Supported: ${ALL_FORMATS.join(", ")}`);
2117
+ process.exit(1);
2118
+ }
2119
+ const inputPath = options.file || (fmt === "env" ? getEnvPathForEnvironment(environment) : getDefaultFilename(fmt, environment));
1744
2120
  const mode = options.replace ? "replace" : "merge";
1745
- const localVars = readEnvFile(inputPath);
2121
+ let localVars;
2122
+ if (fmt === "env") {
2123
+ localVars = readEnvFile(inputPath);
2124
+ } else {
2125
+ const { readFileSync: readFileSync4, existsSync: existsSync4 } = await import("fs");
2126
+ if (!existsSync4(inputPath)) {
2127
+ localVars = null;
2128
+ } else {
2129
+ const content = readFileSync4(inputPath, "utf-8");
2130
+ localVars = parse(content, fmt, { prefix: options.prefix });
2131
+ }
2132
+ }
1746
2133
  if (!localVars) {
1747
2134
  throw fileNotFound(inputPath);
1748
2135
  }
@@ -2224,7 +2611,7 @@ var listCommand = new Command6("list").description("List resources").argument(
2224
2611
  "[resource]",
2225
2612
  "Resource type: projects, organizations, variables, linked",
2226
2613
  "projects"
2227
- ).option("-o, --organization <id>", "Organization ID (for projects/variables)").option("-p, --project <id>", "Project ID (for variables)").option("-e, --env <environment>", "Environment filter (for variables)").option("--show-values", "Show actual variable values (masked by default)").option("--json", "Output as JSON").action(async (resource, options) => {
2614
+ ).option("-o, --organization <id>", "Organization ID (for projects/variables)").option("-p, --project <id>", "Project ID (for variables)").option("-e, --env <environment>", "Environment filter (for variables)").option("-t, --tag <name>", "Filter by tag name (for variables)").option("--show-values", "Show actual variable values (masked by default)").option("--json", "Output as JSON").action(async (resource, options) => {
2228
2615
  try {
2229
2616
  if (!isAuthenticated()) {
2230
2617
  throw notAuthenticated();
@@ -2408,36 +2795,47 @@ async function listVariables(api, projectConfig, options) {
2408
2795
  metaProjectRole = response.meta?.projectRole;
2409
2796
  return response.data || [];
2410
2797
  });
2411
- if (variables.length === 0) {
2412
- info(`No variables found${environment ? ` for ${environment}` : ""}.`);
2798
+ const tagFilter = options.tag?.toLowerCase();
2799
+ const filtered = tagFilter ? variables.filter(
2800
+ (v) => v.tags?.some((t) => t.name.toLowerCase() === tagFilter)
2801
+ ) : variables;
2802
+ if (filtered.length === 0) {
2803
+ info(
2804
+ `No variables found${environment ? ` for ${environment}` : ""}${tagFilter ? ` with tag "${options.tag}"` : ""}.`
2805
+ );
2413
2806
  return;
2414
2807
  }
2415
2808
  if (options.json) {
2416
- const output = variables.map((v) => ({
2809
+ const output = filtered.map((v) => ({
2417
2810
  ...v,
2418
2811
  value: options.showValues ? v.value : maskValue(v.value)
2419
2812
  }));
2420
2813
  console.log(JSON.stringify(output, null, 2));
2421
2814
  return;
2422
2815
  }
2423
- header(`Variables${environment ? ` (${environment})` : ""}`);
2816
+ header(
2817
+ `Variables${environment ? ` (${environment})` : ""}${tagFilter ? ` [tag: ${options.tag}]` : ""}`
2818
+ );
2424
2819
  console.log();
2820
+ const hasTags = filtered.some((v) => v.tags && v.tags.length > 0);
2425
2821
  table(
2426
- variables.map((variable) => ({
2822
+ filtered.map((variable) => ({
2427
2823
  key: variable.key,
2428
2824
  value: options.showValues ? variable.value : maskValue(variable.value),
2429
2825
  sensitive: variable.isSensitive ? chalk8.yellow("\u25CF") : "",
2826
+ tags: hasTags ? variable.tags?.map((t) => t.name).join(", ") || chalk8.dim("-") : "",
2430
2827
  version: `v${variable.version}`
2431
2828
  })),
2432
2829
  [
2433
2830
  { key: "key", header: "Key" },
2434
2831
  { key: "value", header: "Value", width: 40 },
2435
2832
  { key: "sensitive", header: "" },
2833
+ ...hasTags ? [{ key: "tags", header: "Tags", width: 25 }] : [],
2436
2834
  { key: "version", header: "Ver" }
2437
2835
  ]
2438
2836
  );
2439
2837
  console.log();
2440
- console.log(chalk8.dim(`Total: ${variables.length} variables`));
2838
+ console.log(chalk8.dim(`Total: ${filtered.length} variables`));
2441
2839
  const role = getRole();
2442
2840
  if (role) {
2443
2841
  console.log(chalk8.dim(`Your org role: ${formatRole(role)}`));
@@ -3141,7 +3539,7 @@ var usageCommand = new Command11("usage").description("Show plan usage and limit
3141
3539
  // src/lib/version-check.ts
3142
3540
  import chalk13 from "chalk";
3143
3541
  import Conf2 from "conf";
3144
- var CLI_VERSION = true ? "1.3.4" : "0.0.0";
3542
+ var CLI_VERSION = true ? "1.4.1" : "0.0.0";
3145
3543
  var CHECK_INTERVAL = 60 * 60 * 1e3;
3146
3544
  var _cache = null;
3147
3545
  function getCache() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envpilot/cli",
3
- "version": "1.3.4",
3
+ "version": "1.4.1",
4
4
  "description": "Envpilot CLI — sync and manage environment variables from the terminal",
5
5
  "type": "module",
6
6
  "bin": {