@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 +1 -1
- package/dist/bin/envmanager.js +404 -30
- package/dist/bin/envmanager.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
CHANGED
package/dist/bin/envmanager.js
CHANGED
|
@@ -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 = "
|
|
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
|
|
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 =
|
|
619
|
-
|
|
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
|
-
|
|
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 =
|
|
626
|
-
const plainCount =
|
|
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
|
|
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:
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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 (
|
|
752
|
-
console.log(chalk6.gray(` ${
|
|
1120
|
+
if (keysToUpdate.length > 0) {
|
|
1121
|
+
console.log(chalk6.gray(` ${keysToInsert.length} inserted, ${keysToUpdate.length} updated`));
|
|
753
1122
|
}
|
|
754
|
-
if (
|
|
755
|
-
console.log(chalk6.gray(`
|
|
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");
|