@envmanager-cli/cli 0.1.1 → 0.1.2

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/README.md CHANGED
@@ -9,7 +9,7 @@ Official CLI for [EnvManager](https://envmanager.dev) - secure environment varia
9
9
  npm install -g @envmanager-cli/cli
10
10
 
11
11
  # Homebrew (macOS/Linux)
12
- brew install envmanager/tap/envmanager
12
+ brew install shintaii/tap/envmanager
13
13
  ```
14
14
 
15
15
  ## Quick Start
@@ -339,7 +339,7 @@ import ora2 from "ora";
339
339
  // src/lib/client.ts
340
340
  import { createClient as createSupabaseClient } from "@supabase/supabase-js";
341
341
  var DEFAULT_API_URL2 = "https://rhopfaburfflrdwpowcd.supabase.co";
342
- var DEFAULT_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InJob3BmYWJ1cmZmbHJkd3Bvd2NkIiwicm9sZSI6ImFub24iLCJpYXQiOjE3Mjk0NjkxNzMsImV4cCI6MjA0NTA0NTE3M30.hpEuNqNRJcbCQ_F5u_u8SYABC0Zr_pKCdHMn3lPXn4M";
342
+ var DEFAULT_ANON_KEY = "sb_publishable_Y2EpPiIN3KPjQMc1GLVXjw__ghRVLC4";
343
343
  var LOCAL_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";
344
344
  function getApiUrl2() {
345
345
  return process.env.ENVMANAGER_API_URL || getStoredApiUrl() || DEFAULT_API_URL2;
@@ -552,8 +552,166 @@ async function resolveOrganizationId(input, client) {
552
552
  return match.organization_id;
553
553
  }
554
554
 
555
+ // src/lib/variable-references.ts
556
+ var REFERENCE_PATTERN = /\$\{([A-Z_][A-Z0-9_]*)\}/g;
557
+ var MAX_DEPTH = 10;
558
+ function parseReferences(value) {
559
+ const cleaned = value.replace(/\\\$\{/g, "___ESCAPED___");
560
+ const refs = [];
561
+ const pattern = new RegExp(REFERENCE_PATTERN.source, "g");
562
+ let match;
563
+ while ((match = pattern.exec(cleaned)) !== null) {
564
+ refs.push(match[1]);
565
+ }
566
+ return refs;
567
+ }
568
+ function detectCircularReferences(variables) {
569
+ const graph = /* @__PURE__ */ new Map();
570
+ for (const v of variables) {
571
+ const base = v.value || v.fallbackValue || "";
572
+ graph.set(v.key, parseReferences(base));
573
+ }
574
+ const cycles = [];
575
+ const visited = /* @__PURE__ */ new Set();
576
+ const inStack = /* @__PURE__ */ new Set();
577
+ function dfs(key, stack) {
578
+ if (inStack.has(key)) {
579
+ const cycleStart = stack.indexOf(key);
580
+ cycles.push(stack.slice(cycleStart));
581
+ return;
582
+ }
583
+ if (visited.has(key)) return;
584
+ visited.add(key);
585
+ inStack.add(key);
586
+ stack.push(key);
587
+ const deps = graph.get(key) || [];
588
+ for (const dep of deps) {
589
+ if (graph.has(dep)) {
590
+ dfs(dep, stack);
591
+ }
592
+ }
593
+ stack.pop();
594
+ inStack.delete(key);
595
+ }
596
+ for (const key of graph.keys()) {
597
+ dfs(key, []);
598
+ }
599
+ return cycles;
600
+ }
601
+ function resolveValue(key, variables, depth = 0, _stack = /* @__PURE__ */ new Set()) {
602
+ const variable = variables.get(key);
603
+ if (!variable) {
604
+ return {
605
+ key,
606
+ rawValue: null,
607
+ resolvedValue: "",
608
+ source: "empty",
609
+ references: [],
610
+ referencedBy: [],
611
+ unresolvedRefs: [key],
612
+ hasCircularRef: false
613
+ };
614
+ }
615
+ if (variable.isSecret) {
616
+ const base2 = variable.value ?? variable.fallbackValue ?? "";
617
+ const source2 = variable.value ? "explicit" : variable.fallbackValue ? "fallback" : "empty";
618
+ return {
619
+ key,
620
+ rawValue: variable.value,
621
+ resolvedValue: base2,
622
+ source: source2,
623
+ references: parseReferences(base2),
624
+ referencedBy: [],
625
+ unresolvedRefs: [],
626
+ hasCircularRef: false
627
+ };
628
+ }
629
+ let base;
630
+ let source;
631
+ if (variable.value !== null && variable.value !== void 0 && variable.value !== "") {
632
+ base = variable.value;
633
+ source = "explicit";
634
+ } else if (variable.fallbackValue !== null && variable.fallbackValue !== void 0 && variable.fallbackValue !== "") {
635
+ base = variable.fallbackValue;
636
+ source = "fallback";
637
+ } else {
638
+ return {
639
+ key,
640
+ rawValue: variable.value,
641
+ resolvedValue: "",
642
+ source: "empty",
643
+ references: [],
644
+ referencedBy: [],
645
+ unresolvedRefs: [],
646
+ hasCircularRef: false
647
+ };
648
+ }
649
+ const references = parseReferences(base);
650
+ const unresolvedRefs = [];
651
+ let hasCircularRef = false;
652
+ let resolved = base.replace(/\\\$\{/g, "___ESCAPED___");
653
+ const pattern = new RegExp(REFERENCE_PATTERN.source, "g");
654
+ resolved = resolved.replace(pattern, (fullMatch, refKey) => {
655
+ if (_stack.has(refKey)) {
656
+ hasCircularRef = true;
657
+ unresolvedRefs.push(refKey);
658
+ return fullMatch;
659
+ }
660
+ if (depth >= MAX_DEPTH) {
661
+ unresolvedRefs.push(refKey);
662
+ return fullMatch;
663
+ }
664
+ if (!variables.has(refKey)) {
665
+ unresolvedRefs.push(refKey);
666
+ return fullMatch;
667
+ }
668
+ const newStack = new Set(_stack);
669
+ newStack.add(key);
670
+ const inner = resolveValue(refKey, variables, depth + 1, newStack);
671
+ if (inner.hasCircularRef) {
672
+ hasCircularRef = true;
673
+ }
674
+ unresolvedRefs.push(...inner.unresolvedRefs);
675
+ return inner.resolvedValue;
676
+ });
677
+ resolved = resolved.replace(/___ESCAPED___/g, "${");
678
+ return {
679
+ key,
680
+ rawValue: variable.value,
681
+ resolvedValue: resolved,
682
+ source,
683
+ references,
684
+ referencedBy: [],
685
+ unresolvedRefs: [...new Set(unresolvedRefs)],
686
+ hasCircularRef
687
+ };
688
+ }
689
+ function resolveAll(variables) {
690
+ const varMap = /* @__PURE__ */ new Map();
691
+ for (const v of variables) {
692
+ varMap.set(v.key, v);
693
+ }
694
+ const results = variables.map((v) => resolveValue(v.key, varMap));
695
+ const referencedByMap = /* @__PURE__ */ new Map();
696
+ for (const v of variables) {
697
+ referencedByMap.set(v.key, []);
698
+ }
699
+ for (const result of results) {
700
+ for (const ref of result.references) {
701
+ const existing = referencedByMap.get(ref);
702
+ if (existing) {
703
+ existing.push(result.key);
704
+ }
705
+ }
706
+ }
707
+ for (const result of results) {
708
+ result.referencedBy = referencedByMap.get(result.key) || [];
709
+ }
710
+ return results;
711
+ }
712
+
555
713
  // src/commands/pull.ts
556
- 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").action(async (options) => {
714
+ 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) => {
557
715
  const spinner = ora3("Connecting to EnvManager...").start();
558
716
  try {
559
717
  const config = loadConfig();
@@ -561,6 +719,9 @@ var pullCommand = new Command4("pull").description("Pull environment variables f
561
719
  const envName = options.environment || config?.environment || "development";
562
720
  const outputFile = resolve(options.output || ".env");
563
721
  const includeSecrets = options.secrets !== false;
722
+ const shouldResolve = options.resolveReferences === true;
723
+ const shouldFallback = options.includeFallbacks === true;
724
+ const shouldShowSources = options.showSources === true;
564
725
  if (!projectInput) {
565
726
  spinner.fail("No project specified");
566
727
  console.log(chalk4.yellow("\nSpecify a project with --project <id-or-name> or create envmanager.json"));
@@ -593,11 +754,13 @@ var pullCommand = new Command4("pull").description("Pull environment variables f
593
754
  }
594
755
  const environmentId = environments.id;
595
756
  spinner.text = "Fetching variables...";
596
- const { data: variables, error: varError } = await client.rpc("get_variables_for_sync", {
757
+ const rpcParams = {
597
758
  p_environment_id: environmentId,
598
759
  p_sync_secrets: includeSecrets,
599
- p_sync_variables: true
600
- });
760
+ p_sync_variables: true,
761
+ p_include_fallbacks: shouldFallback || false
762
+ };
763
+ const { data: variables, error: varError } = await client.rpc("get_variables_for_sync", rpcParams);
601
764
  if (varError) {
602
765
  spinner.fail("Failed to fetch variables");
603
766
  console.error(chalk4.red(varError.message));
@@ -614,18 +777,68 @@ File ${outputFile} already exists.`));
614
777
  console.log(chalk4.gray("Use --force to overwrite."));
615
778
  process.exit(1);
616
779
  }
780
+ const vars = variables.sort((a, b) => a.key.localeCompare(b.key));
781
+ let resolvedMap = null;
782
+ if (shouldResolve) {
783
+ spinner.text = "Resolving variable references...";
784
+ const inputs = vars.map((v) => ({
785
+ key: v.key,
786
+ value: v.value,
787
+ fallbackValue: v.fallback_value ?? null,
788
+ isSecret: v.is_secret
789
+ }));
790
+ const cycles = detectCircularReferences(inputs);
791
+ if (cycles.length > 0) {
792
+ spinner.stop();
793
+ for (const cycle of cycles) {
794
+ console.log(chalk4.yellow(`Warning: circular reference detected: ${cycle.join(" -> ")} -> ${cycle[0]}`));
795
+ }
796
+ spinner.start("Resolving variable references...");
797
+ }
798
+ const resolved = resolveAll(inputs);
799
+ resolvedMap = new Map(resolved.map((r) => [r.key, r]));
800
+ }
617
801
  spinner.text = "Writing .env file...";
618
- const envContent = variables.sort((a, b) => a.key.localeCompare(b.key)).map((v) => {
619
- const value = v.value || "";
802
+ const envContent = vars.map((v) => {
803
+ let value;
804
+ let source = null;
805
+ if (resolvedMap) {
806
+ const resolved = resolvedMap.get(v.key);
807
+ value = resolved.resolvedValue;
808
+ source = resolved.source;
809
+ } else if (shouldFallback && (!v.value || v.value === "") && v.fallback_value) {
810
+ value = v.fallback_value;
811
+ source = "fallback";
812
+ } else {
813
+ value = v.value || "";
814
+ }
620
815
  const needsQuotes = value.includes(" ") || value.includes("\n") || value.includes('"');
621
816
  const formattedValue = needsQuotes ? `"${value.replace(/"/g, '\\"')}"` : value;
622
- return `${v.key}=${formattedValue}`;
817
+ let line = `${v.key}=${formattedValue}`;
818
+ if (shouldShowSources && resolvedMap) {
819
+ const resolved = resolvedMap.get(v.key);
820
+ if (resolved.references.length > 0 && resolved.source !== "empty") {
821
+ line += ` # resolved from ${resolved.rawValue ?? v.value ?? ""}`;
822
+ } else if (resolved.source === "fallback") {
823
+ line += ` # fallback value`;
824
+ }
825
+ } else if (shouldShowSources && source === "fallback") {
826
+ line += ` # fallback value`;
827
+ }
828
+ return line;
623
829
  }).join("\n");
624
830
  writeFileSync2(outputFile, envContent + "\n");
625
- const secretCount = variables.filter((v) => v.is_secret).length;
626
- const plainCount = variables.length - secretCount;
831
+ const secretCount = vars.filter((v) => v.is_secret).length;
832
+ const plainCount = vars.length - secretCount;
627
833
  spinner.succeed(`Pulled ${variables.length} variables to ${outputFile}`);
628
834
  console.log(chalk4.gray(` ${plainCount} plain, ${secretCount} secrets`));
835
+ client.rpc("log_variable_access", {
836
+ p_environment_id: environmentId,
837
+ p_access_type: "cli_pull",
838
+ p_metadata: { cli_version: "0.1.1" }
839
+ }).then(() => {
840
+ }, () => {
841
+ });
629
842
  } catch (error) {
630
843
  spinner.fail("Pull failed");
631
844
  console.error(chalk4.red(error instanceof Error ? error.message : "Unknown error"));
@@ -669,6 +882,110 @@ function parseEnvFileAsArray(content) {
669
882
  return Array.from(map.entries()).map(([key, value]) => ({ key, value }));
670
883
  }
671
884
 
885
+ // src/lib/naming-conventions.ts
886
+ function isScreamingSnakeCase(name) {
887
+ return /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/.test(name);
888
+ }
889
+ function isSnakeCase(name) {
890
+ return /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/.test(name);
891
+ }
892
+ function isPascalCase(name) {
893
+ return /^[A-Z][a-zA-Z0-9]*$/.test(name);
894
+ }
895
+ function isCamelCase(name) {
896
+ return /^[a-z][a-zA-Z0-9]*$/.test(name);
897
+ }
898
+ function splitIntoWords(name) {
899
+ if (name.includes("_")) {
900
+ return name.split("_").filter(Boolean);
901
+ }
902
+ return name.replace(/([a-z0-9])([A-Z])/g, "$1_$2").split("_").filter(Boolean);
903
+ }
904
+ function convertToCase(name, targetCase) {
905
+ const words = splitIntoWords(name);
906
+ if (words.length === 0) return name;
907
+ switch (targetCase) {
908
+ case "SCREAMING_SNAKE_CASE":
909
+ return words.map((w) => w.toUpperCase()).join("_");
910
+ case "snake_case":
911
+ return words.map((w) => w.toLowerCase()).join("_");
912
+ case "PascalCase":
913
+ return words.map((w) => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase()).join("");
914
+ case "camelCase":
915
+ return words.map((w, i) => {
916
+ if (i === 0) return w.toLowerCase();
917
+ return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
918
+ }).join("");
919
+ default:
920
+ return name;
921
+ }
922
+ }
923
+ function validateVariableName(name, config) {
924
+ const issues = [];
925
+ if (config.rules.case) {
926
+ let caseValid = false;
927
+ switch (config.rules.case) {
928
+ case "SCREAMING_SNAKE_CASE":
929
+ caseValid = isScreamingSnakeCase(name);
930
+ break;
931
+ case "snake_case":
932
+ caseValid = isSnakeCase(name);
933
+ break;
934
+ case "PascalCase":
935
+ caseValid = isPascalCase(name);
936
+ break;
937
+ case "camelCase":
938
+ caseValid = isCamelCase(name);
939
+ break;
940
+ }
941
+ if (!caseValid) {
942
+ const suggestion = convertToCase(name, config.rules.case);
943
+ issues.push({
944
+ type: "case",
945
+ message: `Must be ${config.rules.case}`,
946
+ suggestion
947
+ });
948
+ }
949
+ }
950
+ if (config.rules.patterns) {
951
+ for (const pattern of config.rules.patterns) {
952
+ try {
953
+ const regex = new RegExp(pattern.match);
954
+ if (!regex.test(name)) {
955
+ issues.push({
956
+ type: "pattern",
957
+ message: pattern.description,
958
+ suggestion: pattern.example
959
+ });
960
+ }
961
+ } catch {
962
+ }
963
+ }
964
+ }
965
+ if (config.rules.forbidden) {
966
+ for (const rule of config.rules.forbidden) {
967
+ try {
968
+ const regex = new RegExp(rule.match, "i");
969
+ if (regex.test(name)) {
970
+ issues.push({
971
+ type: "forbidden",
972
+ message: rule.reason
973
+ });
974
+ }
975
+ } catch {
976
+ }
977
+ }
978
+ }
979
+ const isBlock = config.enforcement_mode === "block";
980
+ const suggestions = issues.map((i) => i.suggestion).filter((s) => !!s);
981
+ return {
982
+ valid: isBlock ? issues.length === 0 : true,
983
+ errors: isBlock ? issues : [],
984
+ warnings: isBlock ? [] : issues,
985
+ suggestions: [...new Set(suggestions)]
986
+ };
987
+ }
988
+
672
989
  // src/commands/push.ts
673
990
  var pushCommand = new Command5("push").description("Push local .env file to EnvManager").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("-i, --input <file>", "Input file path (default: .env)").option("--secrets <keys>", "Comma-separated list of keys to mark as secrets").option("--all-secrets", "Mark all variables as secrets").option("--dry-run", "Show what would be pushed without making changes").action(async (options) => {
674
991
  const spinner = ora4("Reading .env file...").start();
@@ -727,32 +1044,84 @@ var pushCommand = new Command5("push").description("Push local .env file to EnvM
727
1044
  spinner.fail(`Environment "${envName}" not found in project`);
728
1045
  process.exit(1);
729
1046
  }
1047
+ spinner.text = "Checking naming conventions...";
1048
+ const { data: namingRules } = await client.from("naming_conventions").select("*").eq("organization_id", organizationId).eq("project_id", projectId).maybeSingle();
1049
+ let namingConfig = null;
1050
+ if (namingRules) {
1051
+ namingConfig = {
1052
+ rules: namingRules.rules,
1053
+ enforcement_mode: namingRules.enforcement_mode,
1054
+ template_name: namingRules.template_name || void 0
1055
+ };
1056
+ } else {
1057
+ const { data: orgRules } = await client.from("naming_conventions").select("*").eq("organization_id", organizationId).is("project_id", null).maybeSingle();
1058
+ if (orgRules) {
1059
+ namingConfig = {
1060
+ rules: orgRules.rules,
1061
+ enforcement_mode: orgRules.enforcement_mode,
1062
+ template_name: orgRules.template_name || void 0
1063
+ };
1064
+ }
1065
+ }
1066
+ if (namingConfig) {
1067
+ const issues = [];
1068
+ for (const v of vars) {
1069
+ const result = validateVariableName(v.key, namingConfig);
1070
+ const allIssues = [...result.errors, ...result.warnings];
1071
+ for (const issue of allIssues) {
1072
+ issues.push({ key: v.key, message: issue.message, suggestion: issue.suggestion });
1073
+ }
1074
+ }
1075
+ if (issues.length > 0) {
1076
+ const isBlock = namingConfig.enforcement_mode === "block";
1077
+ console.log("");
1078
+ console.log(chalk6[isBlock ? "red" : "yellow"](` Naming convention ${isBlock ? "errors" : "warnings"}:`));
1079
+ for (const issue of issues) {
1080
+ const suggestion = issue.suggestion ? chalk6.gray(` \u2192 ${issue.suggestion}`) : "";
1081
+ console.log(chalk6[isBlock ? "red" : "yellow"](` ${issue.key}: ${issue.message}${suggestion}`));
1082
+ }
1083
+ console.log("");
1084
+ if (isBlock) {
1085
+ spinner.fail("Push blocked by naming convention errors");
1086
+ process.exit(1);
1087
+ }
1088
+ }
1089
+ }
730
1090
  spinner.text = "Pushing variables...";
731
- const variablesToInsert = vars.map((v) => ({
1091
+ const markAsSecrets = allSecrets || secretKeys.length > 0;
1092
+ const variablesData = vars.map((v) => ({
732
1093
  key: v.key,
733
- value: v.value,
734
- is_secret: allSecrets || secretKeys.includes(v.key)
1094
+ value: v.value
735
1095
  }));
736
- const { data: result, error: pushError } = await client.rpc("bulk_insert_variables", {
737
- p_environment_id: environment.id,
738
- p_organization_id: organizationId,
739
- p_variables: variablesToInsert,
740
- p_overwrite: true
1096
+ const { data: existingVars } = await client.from("variables").select("key").eq("environment_id", environment.id);
1097
+ const existingKeys = new Set((existingVars || []).map((v) => v.key));
1098
+ const keysToUpdate = variablesData.filter((v) => existingKeys.has(v.key));
1099
+ const keysToInsert = variablesData.filter((v) => !existingKeys.has(v.key));
1100
+ if (keysToUpdate.length > 0) {
1101
+ const { error: deleteError } = await client.from("variables").delete().eq("environment_id", environment.id).in("key", keysToUpdate.map((v) => v.key));
1102
+ if (deleteError) {
1103
+ spinner.fail("Failed to update existing variables");
1104
+ console.error(chalk6.red(deleteError.message));
1105
+ process.exit(1);
1106
+ }
1107
+ }
1108
+ const { error: pushError } = await client.rpc("bulk_insert_variables", {
1109
+ variables_data: variablesData,
1110
+ environment_id_param: environment.id,
1111
+ organization_id_param: organizationId,
1112
+ import_as_secrets: markAsSecrets
741
1113
  });
742
1114
  if (pushError) {
743
1115
  spinner.fail("Push failed");
744
1116
  console.error(chalk6.red(pushError.message));
745
1117
  process.exit(1);
746
1118
  }
747
- const insertedCount = result?.inserted || vars.length;
748
- const updatedCount = result?.updated || 0;
749
- const secretCount = variablesToInsert.filter((v) => v.is_secret).length;
750
1119
  spinner.succeed(`Pushed ${vars.length} variables to ${envName}`);
751
- if (updatedCount > 0) {
752
- console.log(chalk6.gray(` ${insertedCount} inserted, ${updatedCount} updated`));
1120
+ if (keysToUpdate.length > 0) {
1121
+ console.log(chalk6.gray(` ${keysToInsert.length} inserted, ${keysToUpdate.length} updated`));
753
1122
  }
754
- if (secretCount > 0) {
755
- console.log(chalk6.gray(` ${secretCount} marked as secrets`));
1123
+ if (markAsSecrets) {
1124
+ console.log(chalk6.gray(` All marked as secrets`));
756
1125
  }
757
1126
  } catch (error) {
758
1127
  spinner.fail("Push failed");
@@ -811,7 +1180,8 @@ var diffCommand = new Command6("diff").description("Show differences between loc
811
1180
  const { data: remoteVarsData, error: varError } = await client.rpc("get_variables_for_sync", {
812
1181
  p_environment_id: environment.id,
813
1182
  p_sync_secrets: true,
814
- p_sync_variables: true
1183
+ p_sync_variables: true,
1184
+ p_include_fallbacks: false
815
1185
  });
816
1186
  if (varError) {
817
1187
  spinner.fail("Failed to fetch remote variables");
@@ -1150,7 +1520,8 @@ async function fetchAllVariables(environmentId, includeSecrets = true) {
1150
1520
  const { data: variables, error } = await client.rpc("get_variables_for_sync", {
1151
1521
  p_environment_id: environmentId,
1152
1522
  p_sync_secrets: includeSecrets,
1153
- p_sync_variables: true
1523
+ p_sync_variables: true,
1524
+ p_include_fallbacks: false
1154
1525
  });
1155
1526
  if (error) {
1156
1527
  throw new Error(`Failed to fetch variables: ${error.message}`);
@@ -1770,7 +2141,8 @@ Environment "${envName}" not found. Available:`));
1770
2141
  const { data: variables, error: varError } = await client.rpc("get_variables_for_sync", {
1771
2142
  p_environment_id: environment.id,
1772
2143
  p_sync_secrets: false,
1773
- p_sync_variables: true
2144
+ p_sync_variables: true,
2145
+ p_include_fallbacks: false
1774
2146
  });
1775
2147
  if (varError) {
1776
2148
  spinner.fail("Failed to fetch variables");
@@ -1884,7 +2256,8 @@ File ${outputPath} already exists. Use --force to overwrite.`));
1884
2256
  const { data: variables, error: varError } = await client.rpc("get_variables_for_sync", {
1885
2257
  p_environment_id: environment.id,
1886
2258
  p_sync_secrets: true,
1887
- p_sync_variables: true
2259
+ p_sync_variables: true,
2260
+ p_include_fallbacks: false
1888
2261
  });
1889
2262
  if (varError) {
1890
2263
  spinner.fail("Failed to fetch variables");
@@ -1975,7 +2348,8 @@ templateCommand.command("sync").description("Compare template with EnvManager an
1975
2348
  const { data: variables, error: varError } = await client.rpc("get_variables_for_sync", {
1976
2349
  p_environment_id: environment.id,
1977
2350
  p_sync_secrets: false,
1978
- p_sync_variables: true
2351
+ p_sync_variables: true,
2352
+ p_include_fallbacks: false
1979
2353
  });
1980
2354
  if (varError) {
1981
2355
  spinner.fail("Failed to fetch variables");