@buoy-gg/env 1.7.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 +388 -0
- package/lib/commonjs/env/EnvVariables.js +25 -0
- package/lib/commonjs/env/components/EnvStatsOverview.js +134 -0
- package/lib/commonjs/env/components/EnvVarRow.js +170 -0
- package/lib/commonjs/env/components/EnvVarSection.js +84 -0
- package/lib/commonjs/env/components/EnvVarsModal.js +315 -0
- package/lib/commonjs/env/hooks/useDynamicEnv.js +87 -0
- package/lib/commonjs/env/index.js +16 -0
- package/lib/commonjs/env/types/index.js +27 -0
- package/lib/commonjs/env/types/types.js +1 -0
- package/lib/commonjs/env/types/userTypes.js +1 -0
- package/lib/commonjs/env/utils/envTypeDetector.js +59 -0
- package/lib/commonjs/env/utils/helpers.js +119 -0
- package/lib/commonjs/env/utils/index.js +38 -0
- package/lib/commonjs/env/utils/utils.js +121 -0
- package/lib/commonjs/index.js +54 -0
- package/lib/commonjs/package.json +1 -0
- package/lib/commonjs/preset.js +90 -0
- package/lib/module/env/EnvVariables.js +11 -0
- package/lib/module/env/components/EnvStatsOverview.js +130 -0
- package/lib/module/env/components/EnvVarRow.js +166 -0
- package/lib/module/env/components/EnvVarSection.js +80 -0
- package/lib/module/env/components/EnvVarsModal.js +311 -0
- package/lib/module/env/hooks/useDynamicEnv.js +83 -0
- package/lib/module/env/index.js +3 -0
- package/lib/module/env/types/index.js +4 -0
- package/lib/module/env/types/types.js +1 -0
- package/lib/module/env/types/userTypes.js +1 -0
- package/lib/module/env/utils/envTypeDetector.js +55 -0
- package/lib/module/env/utils/helpers.js +114 -0
- package/lib/module/env/utils/index.js +5 -0
- package/lib/module/env/utils/utils.js +116 -0
- package/lib/module/index.js +13 -0
- package/lib/module/preset.js +85 -0
- package/lib/typescript/env/EnvVariables.d.ts +8 -0
- package/lib/typescript/env/EnvVariables.d.ts.map +1 -0
- package/lib/typescript/env/components/EnvStatsOverview.d.ts +20 -0
- package/lib/typescript/env/components/EnvStatsOverview.d.ts.map +1 -0
- package/lib/typescript/env/components/EnvVarRow.d.ts +9 -0
- package/lib/typescript/env/components/EnvVarRow.d.ts.map +1 -0
- package/lib/typescript/env/components/EnvVarSection.d.ts +10 -0
- package/lib/typescript/env/components/EnvVarSection.d.ts.map +1 -0
- package/lib/typescript/env/components/EnvVarsModal.d.ts +26 -0
- package/lib/typescript/env/components/EnvVarsModal.d.ts.map +1 -0
- package/lib/typescript/env/hooks/useDynamicEnv.d.ts +39 -0
- package/lib/typescript/env/hooks/useDynamicEnv.d.ts.map +1 -0
- package/lib/typescript/env/index.d.ts +2 -0
- package/lib/typescript/env/index.d.ts.map +1 -0
- package/lib/typescript/env/types/index.d.ts +3 -0
- package/lib/typescript/env/types/index.d.ts.map +1 -0
- package/lib/typescript/env/types/types.d.ts +67 -0
- package/lib/typescript/env/types/types.d.ts.map +1 -0
- package/lib/typescript/env/types/userTypes.d.ts +3 -0
- package/lib/typescript/env/types/userTypes.d.ts.map +1 -0
- package/lib/typescript/env/utils/envTypeDetector.d.ts +10 -0
- package/lib/typescript/env/utils/envTypeDetector.d.ts.map +1 -0
- package/lib/typescript/env/utils/helpers.d.ts +70 -0
- package/lib/typescript/env/utils/helpers.d.ts.map +1 -0
- package/lib/typescript/env/utils/index.d.ts +4 -0
- package/lib/typescript/env/utils/index.d.ts.map +1 -0
- package/lib/typescript/env/utils/utils.d.ts +24 -0
- package/lib/typescript/env/utils/utils.d.ts.map +1 -0
- package/lib/typescript/index.d.ts +5 -0
- package/lib/typescript/index.d.ts.map +1 -0
- package/lib/typescript/preset.d.ts +86 -0
- package/lib/typescript/preset.d.ts.map +1 -0
- package/package.json +66 -0
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
4
|
+
import { CompactRow, TypeBadge, buoyColors } from "@buoy-gg/shared-ui";
|
|
5
|
+
import { getEnvVarType } from "../utils/envTypeDetector";
|
|
6
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
7
|
+
const getStatusConfig = (status, expectedType) => {
|
|
8
|
+
switch (status) {
|
|
9
|
+
case "required_present":
|
|
10
|
+
return {
|
|
11
|
+
label: "Valid",
|
|
12
|
+
color: buoyColors.success,
|
|
13
|
+
sublabel: "Required"
|
|
14
|
+
};
|
|
15
|
+
case "required_missing":
|
|
16
|
+
return {
|
|
17
|
+
label: "Missing",
|
|
18
|
+
color: buoyColors.error,
|
|
19
|
+
sublabel: "Required"
|
|
20
|
+
};
|
|
21
|
+
case "required_wrong_value":
|
|
22
|
+
return {
|
|
23
|
+
label: "Wrong",
|
|
24
|
+
color: buoyColors.warning,
|
|
25
|
+
sublabel: "Invalid value"
|
|
26
|
+
};
|
|
27
|
+
case "required_wrong_type":
|
|
28
|
+
return {
|
|
29
|
+
label: "Type Error",
|
|
30
|
+
color: buoyColors.info,
|
|
31
|
+
sublabel: "Wrong type"
|
|
32
|
+
};
|
|
33
|
+
case "optional_present":
|
|
34
|
+
return {
|
|
35
|
+
label: "Set",
|
|
36
|
+
color: buoyColors.textSecondary,
|
|
37
|
+
sublabel: "Optional"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const formatEnvKey = key => {
|
|
42
|
+
// Format key similar to React Query: "section > subsection"
|
|
43
|
+
return key.split("_").join(" › ");
|
|
44
|
+
};
|
|
45
|
+
const formatValue = value => {
|
|
46
|
+
if (value === undefined || value === null) {
|
|
47
|
+
return "undefined";
|
|
48
|
+
}
|
|
49
|
+
const str = typeof value === "string" ? value : String(value);
|
|
50
|
+
return str;
|
|
51
|
+
};
|
|
52
|
+
export function EnvVarRow({
|
|
53
|
+
envVar,
|
|
54
|
+
isExpanded,
|
|
55
|
+
onPress
|
|
56
|
+
}) {
|
|
57
|
+
const config = getStatusConfig(envVar.status, envVar.expectedType);
|
|
58
|
+
const hasValue = envVar.value !== undefined && envVar.value !== null;
|
|
59
|
+
|
|
60
|
+
// Format primary text like React Query does: "section › subsection"
|
|
61
|
+
// For env vars, we'll show the key formatted nicely
|
|
62
|
+
const keyParts = envVar.key.split("_");
|
|
63
|
+
const primaryText = keyParts.map(part => part.toLowerCase()).join(" › ");
|
|
64
|
+
|
|
65
|
+
// Create expanded content for value and expected value
|
|
66
|
+
const expandedContent = /*#__PURE__*/_jsxs(View, {
|
|
67
|
+
style: styles.expandedContainer,
|
|
68
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
69
|
+
style: styles.expandedRow,
|
|
70
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
71
|
+
style: styles.expandedLabel,
|
|
72
|
+
children: "Value:"
|
|
73
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
74
|
+
style: styles.expandedValue,
|
|
75
|
+
numberOfLines: 3,
|
|
76
|
+
children: formatValue(envVar.value) || "undefined"
|
|
77
|
+
})]
|
|
78
|
+
}), envVar.status === "required_wrong_type" && envVar.expectedType && /*#__PURE__*/_jsxs(_Fragment, {
|
|
79
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
80
|
+
style: styles.expandedRow,
|
|
81
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
82
|
+
style: styles.expandedLabel,
|
|
83
|
+
children: "Type:"
|
|
84
|
+
}), /*#__PURE__*/_jsx(TypeBadge, {
|
|
85
|
+
type: getEnvVarType(envVar.value)
|
|
86
|
+
})]
|
|
87
|
+
}), /*#__PURE__*/_jsxs(View, {
|
|
88
|
+
style: styles.expandedRow,
|
|
89
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
90
|
+
style: styles.expandedLabel,
|
|
91
|
+
children: "Expected:"
|
|
92
|
+
}), /*#__PURE__*/_jsx(TypeBadge, {
|
|
93
|
+
type: envVar.expectedType
|
|
94
|
+
})]
|
|
95
|
+
})]
|
|
96
|
+
}), envVar.status === "required_wrong_value" && envVar.expectedValue && /*#__PURE__*/_jsxs(View, {
|
|
97
|
+
style: styles.expandedRow,
|
|
98
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
99
|
+
style: styles.expandedLabel,
|
|
100
|
+
children: "Expected:"
|
|
101
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
102
|
+
style: styles.expandedExpected,
|
|
103
|
+
children: String(envVar.expectedValue)
|
|
104
|
+
})]
|
|
105
|
+
}), envVar.description && /*#__PURE__*/_jsxs(View, {
|
|
106
|
+
style: styles.expandedRow,
|
|
107
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
108
|
+
style: styles.expandedLabel,
|
|
109
|
+
children: "Info:"
|
|
110
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
111
|
+
style: styles.expandedDescription,
|
|
112
|
+
children: envVar.description
|
|
113
|
+
})]
|
|
114
|
+
})]
|
|
115
|
+
});
|
|
116
|
+
return /*#__PURE__*/_jsx(CompactRow, {
|
|
117
|
+
statusDotColor: config.color,
|
|
118
|
+
statusLabel: config.label,
|
|
119
|
+
statusSublabel: config.sublabel,
|
|
120
|
+
primaryText: primaryText,
|
|
121
|
+
secondaryText: undefined // Don't show value inline anymore
|
|
122
|
+
,
|
|
123
|
+
expandedContent: expandedContent,
|
|
124
|
+
isExpanded: isExpanded,
|
|
125
|
+
expandedGlowColor: config.color,
|
|
126
|
+
customBadge: envVar.expectedType ? /*#__PURE__*/_jsx(TypeBadge, {
|
|
127
|
+
type: envVar.expectedType
|
|
128
|
+
}) : undefined,
|
|
129
|
+
showChevron: true,
|
|
130
|
+
onPress: onPress ? () => onPress(envVar) : undefined
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
const styles = StyleSheet.create({
|
|
134
|
+
expandedContainer: {
|
|
135
|
+
gap: 6
|
|
136
|
+
},
|
|
137
|
+
expandedRow: {
|
|
138
|
+
flexDirection: "row",
|
|
139
|
+
alignItems: "center",
|
|
140
|
+
gap: 8
|
|
141
|
+
},
|
|
142
|
+
expandedLabel: {
|
|
143
|
+
fontSize: 10,
|
|
144
|
+
color: buoyColors.textMuted,
|
|
145
|
+
fontWeight: "600",
|
|
146
|
+
minWidth: 60,
|
|
147
|
+
fontFamily: "monospace"
|
|
148
|
+
},
|
|
149
|
+
expandedValue: {
|
|
150
|
+
fontSize: 11,
|
|
151
|
+
color: buoyColors.textSecondary,
|
|
152
|
+
fontFamily: "monospace",
|
|
153
|
+
flex: 1
|
|
154
|
+
},
|
|
155
|
+
expandedExpected: {
|
|
156
|
+
fontSize: 11,
|
|
157
|
+
color: buoyColors.warning,
|
|
158
|
+
fontFamily: "monospace",
|
|
159
|
+
flex: 1
|
|
160
|
+
},
|
|
161
|
+
expandedDescription: {
|
|
162
|
+
fontSize: 11,
|
|
163
|
+
color: buoyColors.textSecondary,
|
|
164
|
+
flex: 1
|
|
165
|
+
}
|
|
166
|
+
});
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useCallback, useState } from "react";
|
|
4
|
+
import { View, Text, StyleSheet } from "react-native";
|
|
5
|
+
import { EnvVarRow } from "./EnvVarRow";
|
|
6
|
+
import { SectionHeader, buoyColors } from "@buoy-gg/shared-ui";
|
|
7
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
8
|
+
export function EnvVarSection({
|
|
9
|
+
title,
|
|
10
|
+
count,
|
|
11
|
+
vars,
|
|
12
|
+
emptyMessage
|
|
13
|
+
}) {
|
|
14
|
+
const [expandedVar, setExpandedVar] = useState(null);
|
|
15
|
+
const handleVarPress = useCallback(envVar => {
|
|
16
|
+
setExpandedVar(prev => prev === envVar.key ? null : envVar.key);
|
|
17
|
+
}, []);
|
|
18
|
+
if (vars.length === 0 && title === "Required Variables") {
|
|
19
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
20
|
+
style: styles.sectionContainer,
|
|
21
|
+
children: [/*#__PURE__*/_jsxs(SectionHeader, {
|
|
22
|
+
children: [/*#__PURE__*/_jsx(SectionHeader.Title, {
|
|
23
|
+
children: title
|
|
24
|
+
}), /*#__PURE__*/_jsx(SectionHeader.Badge, {
|
|
25
|
+
count: 0,
|
|
26
|
+
color: buoyColors.primary
|
|
27
|
+
})]
|
|
28
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
29
|
+
style: styles.emptySection,
|
|
30
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
31
|
+
style: styles.emptySectionText,
|
|
32
|
+
children: emptyMessage
|
|
33
|
+
})
|
|
34
|
+
})]
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (vars.length === 0) return null;
|
|
38
|
+
return /*#__PURE__*/_jsxs(View, {
|
|
39
|
+
style: styles.sectionContainer,
|
|
40
|
+
children: [title !== "" && /*#__PURE__*/_jsxs(SectionHeader, {
|
|
41
|
+
children: [/*#__PURE__*/_jsx(SectionHeader.Title, {
|
|
42
|
+
children: title
|
|
43
|
+
}), /*#__PURE__*/_jsx(SectionHeader.Badge, {
|
|
44
|
+
count: count,
|
|
45
|
+
color: buoyColors.primary
|
|
46
|
+
})]
|
|
47
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
48
|
+
style: styles.sectionContent,
|
|
49
|
+
children: vars.map(envVar => /*#__PURE__*/_jsx(EnvVarRow, {
|
|
50
|
+
envVar: envVar,
|
|
51
|
+
isExpanded: expandedVar === envVar.key,
|
|
52
|
+
onPress: handleVarPress
|
|
53
|
+
}, envVar.key))
|
|
54
|
+
})]
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const styles = StyleSheet.create({
|
|
58
|
+
sectionContainer: {
|
|
59
|
+
gap: 8
|
|
60
|
+
},
|
|
61
|
+
sectionContent: {
|
|
62
|
+
// No gap needed, EnvVarRow has its own margins
|
|
63
|
+
},
|
|
64
|
+
emptySection: {
|
|
65
|
+
padding: 20,
|
|
66
|
+
backgroundColor: buoyColors.primary + "08",
|
|
67
|
+
borderRadius: 8,
|
|
68
|
+
borderWidth: 1,
|
|
69
|
+
borderColor: buoyColors.primary + "20",
|
|
70
|
+
alignItems: "center"
|
|
71
|
+
},
|
|
72
|
+
emptySectionText: {
|
|
73
|
+
color: buoyColors.primary,
|
|
74
|
+
fontSize: 11,
|
|
75
|
+
textAlign: "center",
|
|
76
|
+
fontFamily: "monospace",
|
|
77
|
+
opacity: 0.8,
|
|
78
|
+
letterSpacing: 0.5
|
|
79
|
+
}
|
|
80
|
+
});
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { JsModal, devToolsStorageKeys, ModalHeader, HeaderSearchButton, Search, X, displayValue, buoyColors } from "@buoy-gg/shared-ui";
|
|
4
|
+
import { useCallback, useState, useRef, useEffect, useMemo } from "react";
|
|
5
|
+
import { View, TextInput, TouchableOpacity, StyleSheet, ScrollView, Text } from "react-native";
|
|
6
|
+
import { EnvStatsOverview } from "./EnvStatsOverview";
|
|
7
|
+
import { useDynamicEnv } from "../hooks/useDynamicEnv";
|
|
8
|
+
import { processEnvVars, calculateStats } from "../utils";
|
|
9
|
+
import { EnvVarSection } from "./EnvVarSection";
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
/**
|
|
12
|
+
* Full-screen modal for inspecting required environment variables. Handles automatic
|
|
13
|
+
* discovery, validation, filtering, and rich search so teams can quickly spot missing
|
|
14
|
+
* or misconfigured values during development sessions.
|
|
15
|
+
*/
|
|
16
|
+
export function EnvVarsModal({
|
|
17
|
+
visible,
|
|
18
|
+
onClose,
|
|
19
|
+
requiredEnvVars,
|
|
20
|
+
onBack,
|
|
21
|
+
onMinimize,
|
|
22
|
+
enableSharedModalDimensions = false
|
|
23
|
+
}) {
|
|
24
|
+
const [activeFilter, setActiveFilter] = useState("all");
|
|
25
|
+
const [isSearchActive, setIsSearchActive] = useState(false);
|
|
26
|
+
const [searchQuery, setSearchQuery] = useState("");
|
|
27
|
+
const searchInputRef = useRef(null);
|
|
28
|
+
const handleModeChange = useCallback(_mode => {
|
|
29
|
+
// Mode changes handled by JsModal
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
// Focus search input when search becomes active
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (isSearchActive && searchInputRef.current) {
|
|
35
|
+
searchInputRef.current.focus();
|
|
36
|
+
}
|
|
37
|
+
}, [isSearchActive]);
|
|
38
|
+
|
|
39
|
+
// Clear search when changing filters
|
|
40
|
+
useEffect(() => {
|
|
41
|
+
setSearchQuery("");
|
|
42
|
+
setIsSearchActive(false);
|
|
43
|
+
}, [activeFilter]);
|
|
44
|
+
|
|
45
|
+
// Auto-collect environment variables
|
|
46
|
+
const envResults = useDynamicEnv();
|
|
47
|
+
const autoCollectedEnvVars = useMemo(() => {
|
|
48
|
+
const envVars = {};
|
|
49
|
+
envResults.forEach(({
|
|
50
|
+
key,
|
|
51
|
+
data
|
|
52
|
+
}) => {
|
|
53
|
+
if (data !== undefined && data !== null) {
|
|
54
|
+
envVars[key] = typeof data === "string" ? data : displayValue(data);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
return envVars;
|
|
58
|
+
}, [envResults]);
|
|
59
|
+
|
|
60
|
+
// Process and categorize environment variables
|
|
61
|
+
const {
|
|
62
|
+
requiredVars,
|
|
63
|
+
optionalVars
|
|
64
|
+
} = useMemo(() => {
|
|
65
|
+
return processEnvVars(autoCollectedEnvVars, requiredEnvVars);
|
|
66
|
+
}, [autoCollectedEnvVars, requiredEnvVars]);
|
|
67
|
+
|
|
68
|
+
// Calculate statistics
|
|
69
|
+
const stats = useMemo(() => {
|
|
70
|
+
if (requiredEnvVars === undefined) {
|
|
71
|
+
return {
|
|
72
|
+
totalCount: 0,
|
|
73
|
+
requiredCount: 0,
|
|
74
|
+
optionalCount: 0,
|
|
75
|
+
presentRequiredCount: 0,
|
|
76
|
+
missingCount: 0,
|
|
77
|
+
wrongValueCount: 0,
|
|
78
|
+
wrongTypeCount: 0
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
return calculateStats(requiredVars, optionalVars, autoCollectedEnvVars);
|
|
82
|
+
}, [requiredEnvVars, requiredVars, optionalVars, autoCollectedEnvVars]);
|
|
83
|
+
|
|
84
|
+
// Combine all vars and sort by priority (issues first)
|
|
85
|
+
const allVars = useMemo(() => {
|
|
86
|
+
const combined = [...requiredVars, ...optionalVars];
|
|
87
|
+
|
|
88
|
+
// Sort by status priority: errors first, then warnings, then valid
|
|
89
|
+
return combined.sort((a, b) => {
|
|
90
|
+
const priorityMap = {
|
|
91
|
+
"required_missing": 1,
|
|
92
|
+
"required_wrong_type": 2,
|
|
93
|
+
"required_wrong_value": 3,
|
|
94
|
+
"required_present": 4,
|
|
95
|
+
"optional_present": 5
|
|
96
|
+
};
|
|
97
|
+
return (priorityMap[a.status] || 999) - (priorityMap[b.status] || 999);
|
|
98
|
+
});
|
|
99
|
+
}, [requiredVars, optionalVars]);
|
|
100
|
+
|
|
101
|
+
// Filter variables based on active filter and search
|
|
102
|
+
const filteredVars = useMemo(() => {
|
|
103
|
+
let vars = [];
|
|
104
|
+
switch (activeFilter) {
|
|
105
|
+
case "all":
|
|
106
|
+
vars = allVars;
|
|
107
|
+
break;
|
|
108
|
+
case "missing":
|
|
109
|
+
vars = allVars.filter(v => v.status === "required_missing");
|
|
110
|
+
break;
|
|
111
|
+
case "issues":
|
|
112
|
+
vars = allVars.filter(v => v.status === "required_wrong_type" || v.status === "required_wrong_value");
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Apply search filter
|
|
117
|
+
if (searchQuery) {
|
|
118
|
+
const query = searchQuery.toLowerCase();
|
|
119
|
+
vars = vars.filter(v => v.key.toLowerCase().includes(query) || v.description?.toLowerCase().includes(query) || typeof v.value === 'string' && v.value.toLowerCase().includes(query));
|
|
120
|
+
}
|
|
121
|
+
return vars;
|
|
122
|
+
}, [allVars, optionalVars, activeFilter, searchQuery]);
|
|
123
|
+
|
|
124
|
+
// Calculate health percentage
|
|
125
|
+
const healthPercentage = stats.requiredCount > 0 ? Math.round(stats.presentRequiredCount / stats.requiredCount * 100) : 100;
|
|
126
|
+
const healthStatus = healthPercentage === 100 ? "HEALTHY" : healthPercentage >= 75 ? "WARNING" : healthPercentage >= 50 ? "ERROR" : "CRITICAL";
|
|
127
|
+
const healthColor = healthPercentage === 100 ? buoyColors.success : healthPercentage >= 75 ? buoyColors.warning : healthPercentage >= 50 ? buoyColors.error : buoyColors.error;
|
|
128
|
+
if (!visible) return null;
|
|
129
|
+
const storagePrefix = enableSharedModalDimensions ? devToolsStorageKeys.modal.root() : devToolsStorageKeys.env.modal();
|
|
130
|
+
return /*#__PURE__*/_jsx(JsModal, {
|
|
131
|
+
visible: visible,
|
|
132
|
+
onClose: onClose,
|
|
133
|
+
onMinimize: onMinimize,
|
|
134
|
+
persistenceKey: storagePrefix,
|
|
135
|
+
header: {
|
|
136
|
+
customContent: /*#__PURE__*/_jsxs(ModalHeader, {
|
|
137
|
+
children: [onBack && /*#__PURE__*/_jsx(ModalHeader.Navigation, {
|
|
138
|
+
onBack: onBack
|
|
139
|
+
}), /*#__PURE__*/_jsx(ModalHeader.Content, {
|
|
140
|
+
title: isSearchActive ? "" : "Environment Variables",
|
|
141
|
+
noMargin: isSearchActive,
|
|
142
|
+
children: isSearchActive && /*#__PURE__*/_jsxs(View, {
|
|
143
|
+
style: styles.headerSearchContainer,
|
|
144
|
+
children: [/*#__PURE__*/_jsx(Search, {
|
|
145
|
+
size: 12,
|
|
146
|
+
color: buoyColors.textSecondary
|
|
147
|
+
}), /*#__PURE__*/_jsx(TextInput, {
|
|
148
|
+
ref: searchInputRef,
|
|
149
|
+
style: styles.headerSearchInput,
|
|
150
|
+
placeholder: "Search env keys...",
|
|
151
|
+
placeholderTextColor: buoyColors.textMuted,
|
|
152
|
+
value: searchQuery,
|
|
153
|
+
onChangeText: setSearchQuery,
|
|
154
|
+
autoCorrect: false,
|
|
155
|
+
autoCapitalize: "none"
|
|
156
|
+
}), /*#__PURE__*/_jsx(TouchableOpacity, {
|
|
157
|
+
onPress: () => {
|
|
158
|
+
setIsSearchActive(false);
|
|
159
|
+
setSearchQuery("");
|
|
160
|
+
},
|
|
161
|
+
style: styles.clearButton,
|
|
162
|
+
children: /*#__PURE__*/_jsx(X, {
|
|
163
|
+
size: 12,
|
|
164
|
+
color: buoyColors.textSecondary
|
|
165
|
+
})
|
|
166
|
+
})]
|
|
167
|
+
})
|
|
168
|
+
}), /*#__PURE__*/_jsx(ModalHeader.Actions, {
|
|
169
|
+
children: !isSearchActive && /*#__PURE__*/_jsx(HeaderSearchButton, {
|
|
170
|
+
onPress: () => setIsSearchActive(true)
|
|
171
|
+
})
|
|
172
|
+
})]
|
|
173
|
+
}),
|
|
174
|
+
showToggleButton: true
|
|
175
|
+
},
|
|
176
|
+
onModeChange: handleModeChange,
|
|
177
|
+
enablePersistence: true,
|
|
178
|
+
initialMode: "bottomSheet",
|
|
179
|
+
enableGlitchEffects: true,
|
|
180
|
+
styles: {},
|
|
181
|
+
children: /*#__PURE__*/_jsxs(ScrollView, {
|
|
182
|
+
style: styles.scrollContainer,
|
|
183
|
+
contentContainerStyle: styles.contentContainer,
|
|
184
|
+
showsVerticalScrollIndicator: false,
|
|
185
|
+
children: [/*#__PURE__*/_jsx(EnvStatsOverview, {
|
|
186
|
+
stats: stats,
|
|
187
|
+
healthPercentage: healthPercentage,
|
|
188
|
+
healthStatus: healthStatus,
|
|
189
|
+
healthColor: healthColor,
|
|
190
|
+
activeFilter: activeFilter,
|
|
191
|
+
onFilterChange: setActiveFilter
|
|
192
|
+
}), filteredVars.length > 0 ? /*#__PURE__*/_jsxs(View, {
|
|
193
|
+
style: styles.varsSection,
|
|
194
|
+
children: [/*#__PURE__*/_jsxs(View, {
|
|
195
|
+
style: styles.sectionHeader,
|
|
196
|
+
children: [/*#__PURE__*/_jsx(Text, {
|
|
197
|
+
style: styles.sectionTitle,
|
|
198
|
+
children: activeFilter === "all" ? "ALL VARIABLES" : activeFilter === "missing" ? "MISSING VARIABLES" : "ISSUES TO FIX"
|
|
199
|
+
}), /*#__PURE__*/_jsx(View, {
|
|
200
|
+
style: styles.countBadge,
|
|
201
|
+
children: /*#__PURE__*/_jsx(Text, {
|
|
202
|
+
style: styles.countText,
|
|
203
|
+
children: filteredVars.length
|
|
204
|
+
})
|
|
205
|
+
})]
|
|
206
|
+
}), /*#__PURE__*/_jsx(EnvVarSection, {
|
|
207
|
+
title: "",
|
|
208
|
+
count: 0,
|
|
209
|
+
vars: filteredVars,
|
|
210
|
+
emptyMessage: ""
|
|
211
|
+
})]
|
|
212
|
+
}) : /*#__PURE__*/_jsxs(View, {
|
|
213
|
+
style: styles.emptyState,
|
|
214
|
+
children: [/*#__PURE__*/_jsx(Search, {
|
|
215
|
+
size: 32,
|
|
216
|
+
color: buoyColors.textMuted
|
|
217
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
218
|
+
style: styles.emptyTitle,
|
|
219
|
+
children: searchQuery ? "No results found" : "No variables"
|
|
220
|
+
}), /*#__PURE__*/_jsx(Text, {
|
|
221
|
+
style: styles.emptySubtitle,
|
|
222
|
+
children: searchQuery ? `No variables matching "${searchQuery}"` : `No ${activeFilter === "all" ? "" : activeFilter} variables found`
|
|
223
|
+
})]
|
|
224
|
+
})]
|
|
225
|
+
})
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
const styles = StyleSheet.create({
|
|
229
|
+
scrollContainer: {
|
|
230
|
+
flex: 1,
|
|
231
|
+
backgroundColor: buoyColors.base
|
|
232
|
+
},
|
|
233
|
+
contentContainer: {
|
|
234
|
+
padding: 8,
|
|
235
|
+
paddingBottom: 24
|
|
236
|
+
},
|
|
237
|
+
headerSearchContainer: {
|
|
238
|
+
flexDirection: "row",
|
|
239
|
+
alignItems: "center",
|
|
240
|
+
flex: 1,
|
|
241
|
+
backgroundColor: buoyColors.input,
|
|
242
|
+
borderRadius: 6,
|
|
243
|
+
paddingHorizontal: 10,
|
|
244
|
+
paddingVertical: 4,
|
|
245
|
+
marginHorizontal: 12,
|
|
246
|
+
marginVertical: 4,
|
|
247
|
+
height: 32,
|
|
248
|
+
borderWidth: 1,
|
|
249
|
+
borderColor: buoyColors.border
|
|
250
|
+
},
|
|
251
|
+
headerSearchInput: {
|
|
252
|
+
flex: 1,
|
|
253
|
+
marginLeft: 8,
|
|
254
|
+
fontSize: 13,
|
|
255
|
+
color: buoyColors.text,
|
|
256
|
+
padding: 0,
|
|
257
|
+
height: '100%'
|
|
258
|
+
},
|
|
259
|
+
clearButton: {
|
|
260
|
+
padding: 4,
|
|
261
|
+
marginLeft: 4
|
|
262
|
+
},
|
|
263
|
+
varsSection: {
|
|
264
|
+
marginTop: 16
|
|
265
|
+
},
|
|
266
|
+
sectionHeader: {
|
|
267
|
+
flexDirection: "row",
|
|
268
|
+
justifyContent: "space-between",
|
|
269
|
+
alignItems: "center",
|
|
270
|
+
marginBottom: 8,
|
|
271
|
+
paddingHorizontal: 8
|
|
272
|
+
},
|
|
273
|
+
sectionTitle: {
|
|
274
|
+
fontSize: 10,
|
|
275
|
+
fontWeight: "700",
|
|
276
|
+
color: buoyColors.textMuted,
|
|
277
|
+
letterSpacing: 1.2,
|
|
278
|
+
fontFamily: "monospace"
|
|
279
|
+
},
|
|
280
|
+
countBadge: {
|
|
281
|
+
backgroundColor: buoyColors.primary + "15",
|
|
282
|
+
paddingHorizontal: 8,
|
|
283
|
+
paddingVertical: 2,
|
|
284
|
+
borderRadius: 9999,
|
|
285
|
+
borderWidth: 1,
|
|
286
|
+
borderColor: buoyColors.primary + "40"
|
|
287
|
+
},
|
|
288
|
+
countText: {
|
|
289
|
+
fontSize: 10,
|
|
290
|
+
fontWeight: "500",
|
|
291
|
+
color: buoyColors.primary,
|
|
292
|
+
fontFamily: "monospace"
|
|
293
|
+
},
|
|
294
|
+
emptyState: {
|
|
295
|
+
alignItems: "center",
|
|
296
|
+
justifyContent: "center",
|
|
297
|
+
paddingVertical: 48
|
|
298
|
+
},
|
|
299
|
+
emptyTitle: {
|
|
300
|
+
fontSize: 16,
|
|
301
|
+
fontWeight: "600",
|
|
302
|
+
color: buoyColors.text,
|
|
303
|
+
marginTop: 12,
|
|
304
|
+
marginBottom: 8
|
|
305
|
+
},
|
|
306
|
+
emptySubtitle: {
|
|
307
|
+
fontSize: 13,
|
|
308
|
+
color: buoyColors.textSecondary,
|
|
309
|
+
textAlign: "center"
|
|
310
|
+
}
|
|
311
|
+
});
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useMemo } from "react";
|
|
4
|
+
/**
|
|
5
|
+
* Hook that returns all available environment variables with parsed values
|
|
6
|
+
* Includes all available environment variables by default (only EXPO_PUBLIC_ prefixed vars are loaded by Expo)
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Get all available environment variables (only EXPO_PUBLIC_ prefixed)
|
|
10
|
+
* const envVars = useDynamicEnv();
|
|
11
|
+
* // Returns: [
|
|
12
|
+
* // { key: 'EXPO_PUBLIC_API_URL', data: 'https://api.example.com' },
|
|
13
|
+
* // { key: 'EXPO_PUBLIC_APP_NAME', data: 'MyApp' },
|
|
14
|
+
* // ...
|
|
15
|
+
* // ]
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Filter to specific variables
|
|
19
|
+
* const envVars = useDynamicEnv({
|
|
20
|
+
* envFilter: (key) => key.includes('API') || key.includes('URL')
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* // Filter by value content
|
|
25
|
+
* const envVars = useDynamicEnv({
|
|
26
|
+
* envFilter: (key, value) => value !== undefined && value.length > 0
|
|
27
|
+
* });
|
|
28
|
+
*/
|
|
29
|
+
export function useDynamicEnv({
|
|
30
|
+
envFilter = () => true // Default: include all available environment variables (EXPO_PUBLIC_ only)
|
|
31
|
+
} = {}) {
|
|
32
|
+
// Helper function to get a single environment variable value
|
|
33
|
+
const getEnvValue = useMemo(() => {
|
|
34
|
+
return key => {
|
|
35
|
+
// @ts-ignore thhis does exist
|
|
36
|
+
const value = process.env[key];
|
|
37
|
+
if (value === undefined) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Try to parse as JSON for complex values, fall back to string
|
|
42
|
+
try {
|
|
43
|
+
// Only attempt JSON parsing if it looks like JSON (starts with { or [)
|
|
44
|
+
if (value.startsWith("{") || value.startsWith("[")) {
|
|
45
|
+
return JSON.parse(value);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Parse boolean-like strings
|
|
49
|
+
if (value.toLowerCase() === "true") return true;
|
|
50
|
+
if (value.toLowerCase() === "false") return false;
|
|
51
|
+
|
|
52
|
+
// Parse number-like strings
|
|
53
|
+
if (/^\d+$/.test(value)) {
|
|
54
|
+
const num = parseInt(value, 10);
|
|
55
|
+
return !isNaN(num) ? num : value;
|
|
56
|
+
}
|
|
57
|
+
if (/^\d*\.\d+$/.test(value)) {
|
|
58
|
+
const num = parseFloat(value);
|
|
59
|
+
return !isNaN(num) ? num : value;
|
|
60
|
+
}
|
|
61
|
+
return value;
|
|
62
|
+
} catch {
|
|
63
|
+
return value;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}, []);
|
|
67
|
+
|
|
68
|
+
// Get all environment variables and process them
|
|
69
|
+
const envResults = useMemo(() => {
|
|
70
|
+
// @ts-ignore thhis does exist
|
|
71
|
+
const allEnvKeys = Object.keys(process.env);
|
|
72
|
+
const filteredKeys = allEnvKeys.filter(key => {
|
|
73
|
+
// @ts-ignore thhis does exist
|
|
74
|
+
const value = process.env[key];
|
|
75
|
+
return envFilter(key, value);
|
|
76
|
+
});
|
|
77
|
+
return filteredKeys.map(key => ({
|
|
78
|
+
key,
|
|
79
|
+
data: getEnvValue(key)
|
|
80
|
+
}));
|
|
81
|
+
}, [envFilter, getEnvValue]);
|
|
82
|
+
return envResults;
|
|
83
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"use strict";
|