@buoy-gg/env 7.0.41 → 7.0.43

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 (51) hide show
  1. package/README.md +11 -87
  2. package/lib/commonjs/env/components/EnvStatsOverview.js +1 -159
  3. package/lib/commonjs/env/components/EnvVarRow.js +1 -189
  4. package/lib/commonjs/env/components/EnvVarSection.js +1 -84
  5. package/lib/commonjs/env/components/EnvVarsModal.js +1 -322
  6. package/lib/commonjs/env/hooks/useDynamicEnv.js +1 -92
  7. package/lib/commonjs/env/types/index.js +1 -27
  8. package/lib/commonjs/env/types/types.js +1 -1
  9. package/lib/commonjs/env/types/userTypes.js +1 -1
  10. package/lib/commonjs/env/utils/envTypeDetector.js +1 -59
  11. package/lib/commonjs/env/utils/helpers.js +1 -119
  12. package/lib/commonjs/env/utils/index.js +1 -38
  13. package/lib/commonjs/env/utils/remoteEnv.js +1 -24
  14. package/lib/commonjs/env/utils/utils.js +1 -121
  15. package/lib/commonjs/index.js +1 -77
  16. package/lib/commonjs/preset.js +1 -81
  17. package/lib/commonjs/sync/envSyncAdapter.js +1 -51
  18. package/lib/module/env/components/EnvStatsOverview.js +1 -155
  19. package/lib/module/env/components/EnvVarRow.js +1 -185
  20. package/lib/module/env/components/EnvVarSection.js +1 -80
  21. package/lib/module/env/components/EnvVarsModal.js +1 -317
  22. package/lib/module/env/hooks/useDynamicEnv.js +1 -88
  23. package/lib/module/env/types/index.js +1 -4
  24. package/lib/module/env/types/types.js +1 -1
  25. package/lib/module/env/types/userTypes.js +1 -1
  26. package/lib/module/env/utils/envTypeDetector.js +1 -55
  27. package/lib/module/env/utils/helpers.js +1 -114
  28. package/lib/module/env/utils/index.js +1 -5
  29. package/lib/module/env/utils/remoteEnv.js +1 -19
  30. package/lib/module/env/utils/utils.js +1 -116
  31. package/lib/module/index.js +1 -21
  32. package/lib/module/preset.js +1 -78
  33. package/lib/module/sync/envSyncAdapter.js +1 -46
  34. package/lib/web/index.mjs +1 -2
  35. package/package.json +4 -7
  36. package/lib/typescript/env/components/EnvStatsOverview.d.ts.map +0 -1
  37. package/lib/typescript/env/components/EnvVarRow.d.ts.map +0 -1
  38. package/lib/typescript/env/components/EnvVarSection.d.ts.map +0 -1
  39. package/lib/typescript/env/components/EnvVarsModal.d.ts.map +0 -1
  40. package/lib/typescript/env/hooks/useDynamicEnv.d.ts.map +0 -1
  41. package/lib/typescript/env/types/index.d.ts.map +0 -1
  42. package/lib/typescript/env/types/types.d.ts.map +0 -1
  43. package/lib/typescript/env/types/userTypes.d.ts.map +0 -1
  44. package/lib/typescript/env/utils/envTypeDetector.d.ts.map +0 -1
  45. package/lib/typescript/env/utils/helpers.d.ts.map +0 -1
  46. package/lib/typescript/env/utils/index.d.ts.map +0 -1
  47. package/lib/typescript/env/utils/remoteEnv.d.ts.map +0 -1
  48. package/lib/typescript/env/utils/utils.d.ts.map +0 -1
  49. package/lib/typescript/index.d.ts.map +0 -1
  50. package/lib/typescript/preset.d.ts.map +0 -1
  51. package/lib/typescript/sync/envSyncAdapter.d.ts.map +0 -1
@@ -1,185 +1 @@
1
- "use strict";
2
-
3
- import { View, Text, StyleSheet } from "react-native";
4
- import { CompactRow, TypeBadge, buoyColors, night, monoFont } 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
- // The key is the row's identity everywhere a handle is needed. The visible
66
- // primary text is the prettified "expo › public › api › url" form, which is
67
- // no use to a test (or to VoiceOver) trying to find one specific variable.
68
- const rowID = `buoy-env-row-${envVar.key}`;
69
-
70
- // Create expanded content for value and expected value
71
- const expandedContent = /*#__PURE__*/_jsxs(View, {
72
- style: styles.expandedContainer,
73
- children: [/*#__PURE__*/_jsxs(View, {
74
- style: styles.expandedRow,
75
- children: [/*#__PURE__*/_jsx(Text, {
76
- style: styles.expandedLabel,
77
- children: "Value:"
78
- }), /*#__PURE__*/_jsx(Text, {
79
- style: styles.expandedValue,
80
- numberOfLines: 3,
81
- testID: `${rowID}-value`,
82
- children: formatValue(envVar.value) || "undefined"
83
- })]
84
- }), envVar.status === "required_wrong_type" && envVar.expectedType && /*#__PURE__*/_jsxs(_Fragment, {
85
- children: [/*#__PURE__*/_jsxs(View, {
86
- style: styles.expandedRow,
87
- children: [/*#__PURE__*/_jsx(Text, {
88
- style: styles.expandedLabel,
89
- children: "Type:"
90
- }), /*#__PURE__*/_jsx(TypeBadge, {
91
- type: getEnvVarType(envVar.value),
92
- testID: `${rowID}-actual-type`
93
- })]
94
- }), /*#__PURE__*/_jsxs(View, {
95
- style: styles.expandedRow,
96
- children: [/*#__PURE__*/_jsx(Text, {
97
- style: styles.expandedLabel,
98
- children: "Expected:"
99
- }), /*#__PURE__*/_jsx(TypeBadge, {
100
- type: envVar.expectedType,
101
- testID: `${rowID}-expected-type`
102
- })]
103
- })]
104
- }), envVar.status === "required_wrong_value" && envVar.expectedValue && /*#__PURE__*/_jsxs(View, {
105
- style: styles.expandedRow,
106
- children: [/*#__PURE__*/_jsx(Text, {
107
- style: styles.expandedLabel,
108
- children: "Expected:"
109
- }), /*#__PURE__*/_jsx(Text, {
110
- style: styles.expandedExpected,
111
- testID: `${rowID}-expected-value`,
112
- children: String(envVar.expectedValue)
113
- })]
114
- }), envVar.description && /*#__PURE__*/_jsxs(View, {
115
- style: styles.expandedRow,
116
- children: [/*#__PURE__*/_jsx(Text, {
117
- style: styles.expandedLabel,
118
- children: "Info:"
119
- }), /*#__PURE__*/_jsx(Text, {
120
- style: styles.expandedDescription,
121
- testID: `${rowID}-description`,
122
- children: envVar.description
123
- })]
124
- })]
125
- });
126
- return /*#__PURE__*/_jsx(CompactRow, {
127
- statusDotColor: config.color,
128
- statusLabel: config.label,
129
- statusSublabel: config.sublabel,
130
- primaryText: primaryText,
131
- secondaryText: undefined // Don't show value inline anymore
132
- ,
133
- expandedContent: expandedContent,
134
- isExpanded: isExpanded,
135
- expandedGlowColor: config.color,
136
- customBadge: envVar.expectedType ? /*#__PURE__*/_jsx(TypeBadge, {
137
- type: envVar.expectedType,
138
- testID: `${rowID}-type-badge`
139
- }) : undefined,
140
- showChevron: true,
141
- onPress: onPress ? () => onPress(envVar) : undefined,
142
- testID: rowID,
143
- statusTestID: `${rowID}-status`,
144
- statusSublabelTestID: `${rowID}-category`
145
- // The status is carried by a coloured dot and a two-word label that the
146
- // row's container merges away on Fabric — spelled out here so the verdict
147
- // reaches assistive tech (and Maestro) with the key it belongs to.
148
- ,
149
- accessibilityLabel: `${envVar.key}, ${config.label}, ${config.sublabel}`
150
- });
151
- }
152
- const styles = StyleSheet.create({
153
- expandedContainer: {
154
- gap: 6
155
- },
156
- expandedRow: {
157
- flexDirection: "row",
158
- alignItems: "center",
159
- gap: 8
160
- },
161
- expandedLabel: {
162
- fontSize: 10,
163
- color: night.color.textTertiary,
164
- fontWeight: "600",
165
- minWidth: 60,
166
- fontFamily: monoFont
167
- },
168
- expandedValue: {
169
- fontSize: 11,
170
- color: night.color.textSecondary,
171
- fontFamily: monoFont,
172
- flex: 1
173
- },
174
- expandedExpected: {
175
- fontSize: 11,
176
- color: buoyColors.warning,
177
- fontFamily: monoFont,
178
- flex: 1
179
- },
180
- expandedDescription: {
181
- fontSize: 11,
182
- color: night.color.textSecondary,
183
- flex: 1
184
- }
185
- });
1
+ "use strict";import{View as s,Text as l,StyleSheet as m}from"react-native";import{CompactRow as h,TypeBadge as c,buoyColors as a,night as p,monoFont as u}from"@buoy-gg/shared-ui";import{getEnvVarType as w}from"../utils/envTypeDetector";import{jsx as n,jsxs as d,Fragment as T}from"react/jsx-runtime";const _=(e,i)=>{switch(e){case"required_present":return{label:"Valid",color:a.success,sublabel:"Required"};case"required_missing":return{label:"Missing",color:a.error,sublabel:"Required"};case"required_wrong_value":return{label:"Wrong",color:a.warning,sublabel:"Invalid value"};case"required_wrong_type":return{label:"Type Error",color:a.info,sublabel:"Wrong type"};case"optional_present":return{label:"Set",color:a.textSecondary,sublabel:"Optional"}}};const R=e=>{return e.split("_").join(" \u203A ")};const S=e=>{if(e===void 0||e===null){return"undefined"}const i=typeof e==="string"?e:String(e);return i};export function EnvVarRow({envVar:e,isExpanded:i,onPress:x}){const r=_(e.status,e.expectedType);const D=e.value!==void 0&&e.value!==null;const y=e.key.split("_");const b=y.map(g=>g.toLowerCase()).join(" \u203A ");const o=`buoy-env-row-${e.key}`;const f=d(s,{style:t.expandedContainer,children:[d(s,{style:t.expandedRow,children:[n(l,{style:t.expandedLabel,children:"Value:"}),n(l,{style:t.expandedValue,numberOfLines:3,testID:`${o}-value`,children:S(e.value)||"undefined"})]}),e.status==="required_wrong_type"&&e.expectedType&&d(T,{children:[d(s,{style:t.expandedRow,children:[n(l,{style:t.expandedLabel,children:"Type:"}),n(c,{type:w(e.value),testID:`${o}-actual-type`})]}),d(s,{style:t.expandedRow,children:[n(l,{style:t.expandedLabel,children:"Expected:"}),n(c,{type:e.expectedType,testID:`${o}-expected-type`})]})]}),e.status==="required_wrong_value"&&e.expectedValue&&d(s,{style:t.expandedRow,children:[n(l,{style:t.expandedLabel,children:"Expected:"}),n(l,{style:t.expandedExpected,testID:`${o}-expected-value`,children:String(e.expectedValue)})]}),e.description&&d(s,{style:t.expandedRow,children:[n(l,{style:t.expandedLabel,children:"Info:"}),n(l,{style:t.expandedDescription,testID:`${o}-description`,children:e.description})]})]});return n(h,{statusDotColor:r.color,statusLabel:r.label,statusSublabel:r.sublabel,primaryText:b,secondaryText:void 0,expandedContent:f,isExpanded:i,expandedGlowColor:r.color,customBadge:e.expectedType?n(c,{type:e.expectedType,testID:`${o}-type-badge`}):void 0,showChevron:true,onPress:x?()=>x(e):void 0,testID:o,statusTestID:`${o}-status`,statusSublabelTestID:`${o}-category`,accessibilityLabel:`${e.key}, ${r.label}, ${r.sublabel}`})}const t=m.create({expandedContainer:{gap:6},expandedRow:{flexDirection:"row",alignItems:"center",gap:8},expandedLabel:{fontSize:10,color:p.color.textTertiary,fontWeight:"600",minWidth:60,fontFamily:u},expandedValue:{fontSize:11,color:p.color.textSecondary,fontFamily:u,flex:1},expandedExpected:{fontSize:11,color:a.warning,fontFamily:u,flex:1},expandedDescription:{fontSize:11,color:p.color.textSecondary,flex:1}});
@@ -1,80 +1 @@
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, night, monoFont } 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: night.color.accent
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: night.color.accent
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: night.color.accent + "08",
67
- borderRadius: 8,
68
- borderWidth: 1,
69
- borderColor: night.color.accent + "20",
70
- alignItems: "center"
71
- },
72
- emptySectionText: {
73
- color: night.color.accent,
74
- fontSize: 11,
75
- textAlign: "center",
76
- fontFamily: monoFont,
77
- opacity: 0.8,
78
- letterSpacing: 0.5
79
- }
80
- });
1
+ "use strict";import{useCallback as u,useState as x}from"react";import{View as i,Text as g,StyleSheet as S}from"react-native";import{EnvVarRow as f}from"./EnvVarRow";import{SectionHeader as n,night as o,monoFont as C}from"@buoy-gg/shared-ui";import{jsx as e,jsxs as l}from"react/jsx-runtime";export function EnvVarSection({title:c,count:a,vars:s,emptyMessage:d}){const[m,p]=x(null);const y=u(t=>{p(h=>h===t.key?null:t.key)},[]);if(s.length===0&&c==="Required Variables"){return l(i,{style:r.sectionContainer,children:[l(n,{children:[e(n.Title,{children:c}),e(n.Badge,{count:0,color:o.color.accent})]}),e(i,{style:r.emptySection,children:e(g,{style:r.emptySectionText,children:d})})]})}if(s.length===0)return null;return l(i,{style:r.sectionContainer,children:[c!==""&&l(n,{children:[e(n.Title,{children:c}),e(n.Badge,{count:a,color:o.color.accent})]}),e(i,{style:r.sectionContent,children:s.map(t=>e(f,{envVar:t,isExpanded:m===t.key,onPress:y},t.key))})]})}const r=S.create({sectionContainer:{gap:8},sectionContent:{},emptySection:{padding:20,backgroundColor:o.color.accent+"08",borderRadius:8,borderWidth:1,borderColor:o.color.accent+"20",alignItems:"center"},emptySectionText:{color:o.color.accent,fontSize:11,textAlign:"center",fontFamily:C,opacity:.8,letterSpacing:.5}});
@@ -1,317 +1 @@
1
- "use strict";
2
-
3
- import { JsModal, devToolsStorageKeys, ModalHeader, HeaderSearchButton, displayValue, buoyColors, night, windowDragProps, ToolEmptyState, resolveListEmptyState, monoFont } from "@buoy-gg/shared-ui";
4
- import { useCallback, useState, useRef, useEffect, useMemo } from "react";
5
- import { View, 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
- inline = false
24
- }) {
25
- const [activeFilter, setActiveFilter] = useState("all");
26
- const [isSearchActive, setIsSearchActive] = useState(false);
27
- const [searchQuery, setSearchQuery] = useState("");
28
- const searchInputRef = useRef(null);
29
- const handleModeChange = useCallback(_mode => {
30
- // Mode changes handled by JsModal
31
- }, []);
32
-
33
- // Focus search input when search becomes active
34
- useEffect(() => {
35
- if (isSearchActive && searchInputRef.current) {
36
- searchInputRef.current.focus();
37
- }
38
- }, [isSearchActive]);
39
-
40
- // Clear search when changing filters
41
- useEffect(() => {
42
- setSearchQuery("");
43
- setIsSearchActive(false);
44
- }, [activeFilter]);
45
-
46
- // Auto-collect environment variables
47
- const envResults = useDynamicEnv();
48
- const autoCollectedEnvVars = useMemo(() => {
49
- const envVars = {};
50
- envResults.forEach(({
51
- key,
52
- data
53
- }) => {
54
- if (data !== undefined && data !== null) {
55
- envVars[key] = typeof data === "string" ? data : displayValue(data);
56
- }
57
- });
58
- return envVars;
59
- }, [envResults]);
60
-
61
- // Process and categorize environment variables
62
- const {
63
- requiredVars,
64
- optionalVars
65
- } = useMemo(() => {
66
- return processEnvVars(autoCollectedEnvVars, requiredEnvVars);
67
- }, [autoCollectedEnvVars, requiredEnvVars]);
68
-
69
- // Calculate statistics
70
- const stats = useMemo(() => {
71
- if (requiredEnvVars === undefined) {
72
- return {
73
- totalCount: 0,
74
- requiredCount: 0,
75
- optionalCount: 0,
76
- presentRequiredCount: 0,
77
- missingCount: 0,
78
- wrongValueCount: 0,
79
- wrongTypeCount: 0
80
- };
81
- }
82
- return calculateStats(requiredVars, optionalVars, autoCollectedEnvVars);
83
- }, [requiredEnvVars, requiredVars, optionalVars, autoCollectedEnvVars]);
84
-
85
- // Combine all vars and sort by priority (issues first)
86
- const allVars = useMemo(() => {
87
- const combined = [...requiredVars, ...optionalVars];
88
-
89
- // Sort by status priority: errors first, then warnings, then valid
90
- return combined.sort((a, b) => {
91
- const priorityMap = {
92
- "required_missing": 1,
93
- "required_wrong_type": 2,
94
- "required_wrong_value": 3,
95
- "required_present": 4,
96
- "optional_present": 5
97
- };
98
- return (priorityMap[a.status] || 999) - (priorityMap[b.status] || 999);
99
- });
100
- }, [requiredVars, optionalVars]);
101
-
102
- // Filter variables based on active filter and search
103
- const filteredVars = useMemo(() => {
104
- let vars = [];
105
- switch (activeFilter) {
106
- case "all":
107
- vars = allVars;
108
- break;
109
- case "missing":
110
- vars = allVars.filter(v => v.status === "required_missing");
111
- break;
112
- case "issues":
113
- vars = allVars.filter(v => v.status === "required_wrong_type" || v.status === "required_wrong_value");
114
- break;
115
- }
116
-
117
- // Apply search filter
118
- if (searchQuery) {
119
- const query = searchQuery.toLowerCase();
120
- vars = vars.filter(v => v.key.toLowerCase().includes(query) || v.description?.toLowerCase().includes(query) || typeof v.value === 'string' && v.value.toLowerCase().includes(query));
121
- }
122
- return vars;
123
- }, [allVars, optionalVars, activeFilter, searchQuery]);
124
-
125
- // Calculate health percentage
126
- const healthPercentage = stats.requiredCount > 0 ? Math.round(stats.presentRequiredCount / stats.requiredCount * 100) : 100;
127
- const healthStatus = healthPercentage === 100 ? "HEALTHY" : healthPercentage >= 75 ? "WARNING" : healthPercentage >= 50 ? "ERROR" : "CRITICAL";
128
- const healthColor = healthPercentage === 100 ? buoyColors.success : healthPercentage >= 75 ? buoyColors.warning : healthPercentage >= 50 ? buoyColors.error : buoyColors.error;
129
- if (!visible) return null;
130
- const storagePrefix = enableSharedModalDimensions ? devToolsStorageKeys.modal.root() : devToolsStorageKeys.env.modal();
131
- const header = /*#__PURE__*/_jsxs(ModalHeader, {
132
- children: [onBack && /*#__PURE__*/_jsx(ModalHeader.Navigation, {
133
- onBack: onBack
134
- }), /*#__PURE__*/_jsx(ModalHeader.Content, {
135
- title: isSearchActive ? "" : "Environment Variables",
136
- noMargin: isSearchActive,
137
- children: isSearchActive && /*#__PURE__*/_jsx(ModalHeader.Search, {
138
- value: searchQuery,
139
- onChangeText: setSearchQuery,
140
- onCancel: () => {
141
- setIsSearchActive(false);
142
- setSearchQuery("");
143
- },
144
- placeholder: "Search env keys...",
145
- accessibilityLabel: "Search env keys",
146
- testID: "buoy-env-search"
147
- // The field itself is a handle Detox can use but XCUITest
148
- // cannot: on this RN version a TextInput's testID never
149
- // reaches the XCUITest element tree — iOS folds the input
150
- // into the header's accessibility container. This wrapper
151
- // handle is the one the a11y lane can actually see.
152
- ,
153
- containerTestID: "buoy-env-search-field",
154
- clearTestID: "buoy-env-search-clear"
155
- })
156
- }), /*#__PURE__*/_jsx(ModalHeader.Actions, {
157
- children: !isSearchActive && /*#__PURE__*/_jsx(HeaderSearchButton, {
158
- onPress: () => setIsSearchActive(true),
159
- testID: "buoy-env-search-open",
160
- accessibilityLabel: "Search env keys"
161
- })
162
- })]
163
- });
164
- const body = /*#__PURE__*/_jsxs(ScrollView, {
165
- style: styles.scrollContainer,
166
- contentContainerStyle: styles.contentContainer,
167
- showsVerticalScrollIndicator: false,
168
- testID: "buoy-env-scroll",
169
- children: [/*#__PURE__*/_jsx(EnvStatsOverview, {
170
- stats: stats,
171
- healthPercentage: healthPercentage,
172
- healthStatus: healthStatus,
173
- healthColor: healthColor,
174
- activeFilter: activeFilter,
175
- onFilterChange: setActiveFilter
176
- }), filteredVars.length > 0 ? /*#__PURE__*/_jsxs(View, {
177
- style: styles.varsSection,
178
- children: [/*#__PURE__*/_jsxs(View, {
179
- style: styles.sectionHeader,
180
- children: [/*#__PURE__*/_jsx(Text, {
181
- style: styles.sectionTitle,
182
- testID: "buoy-env-section-title",
183
- children: activeFilter === "all" ? "ALL VARIABLES" : activeFilter === "missing" ? "MISSING VARIABLES" : "ISSUES TO FIX"
184
- }), /*#__PURE__*/_jsx(View, {
185
- style: styles.countBadge,
186
- children: /*#__PURE__*/_jsx(Text, {
187
- style: styles.countText,
188
- testID: "buoy-env-section-count",
189
- children: filteredVars.length
190
- })
191
- })]
192
- }), /*#__PURE__*/_jsx(EnvVarSection, {
193
- title: "",
194
- count: 0,
195
- vars: filteredVars,
196
- emptyMessage: ""
197
- })]
198
- }) : /*#__PURE__*/_jsx(ToolEmptyState, {
199
- testID: "buoy-env-empty",
200
- ...resolveListEmptyState({
201
- subject: "variables",
202
- verb: "declared",
203
- search: searchQuery,
204
- total: allVars.length,
205
- waiting: {
206
- // A missing .env is a config problem, not a stream that has
207
- // not started — `keys` is the env-and-secrets state.
208
- otto: "keys",
209
- title: "No variables",
210
- message: `No ${activeFilter === "all" ? "" : activeFilter} variables found`
211
- }
212
- })
213
- })]
214
- });
215
-
216
- // The ONLY difference between the phone and the desktop dashboard is which
217
- // shell the header and body go in. Everything above this line is shared.
218
- if (inline) {
219
- return /*#__PURE__*/_jsxs(View, {
220
- style: styles.inlineRoot,
221
- children: [/*#__PURE__*/_jsx(View, {
222
- style: styles.inlineHeader,
223
- ...windowDragProps,
224
- children: header
225
- }), body]
226
- });
227
- }
228
- return /*#__PURE__*/_jsx(JsModal, {
229
- visible: visible,
230
- onClose: onClose,
231
- onMinimize: onMinimize,
232
- persistenceKey: storagePrefix,
233
- header: {
234
- customContent: header,
235
- showToggleButton: true
236
- },
237
- onModeChange: handleModeChange,
238
- enablePersistence: true,
239
- initialMode: "bottomSheet",
240
- enableGlitchEffects: true,
241
- styles: {},
242
- children: body
243
- });
244
- }
245
-
246
- /**
247
- * The same env tool as `EnvVarsModal`, rendered INLINE for hosts that place
248
- * tools in a panel. Exists so there is exactly one implementation — the desktop
249
- * dashboard used to ship its own copy of this component's internals.
250
- */
251
- export function EnvToolView({
252
- requiredEnvVars
253
- }) {
254
- return /*#__PURE__*/_jsx(EnvVarsModal, {
255
- inline: true,
256
- visible: true,
257
- requiredEnvVars: requiredEnvVars,
258
- onClose: () => {}
259
- });
260
- }
261
- const styles = StyleSheet.create({
262
- inlineRoot: {
263
- flex: 1,
264
- minHeight: 0,
265
- // Transparent so the desktop workbench's hoisted sky (one field spanning
266
- // sidebar + panel) shows through — the shell paints its base beneath it.
267
- backgroundColor: "transparent"
268
- },
269
- inlineHeader: {
270
- paddingHorizontal: 12,
271
- paddingVertical: 8,
272
- borderBottomWidth: 1,
273
- borderBottomColor: buoyColors.border,
274
- // Clear, like Bench — the host's hoisted sky shows through the header.
275
- backgroundColor: "transparent"
276
- },
277
- scrollContainer: {
278
- flex: 1,
279
- // Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
280
- backgroundColor: "transparent"
281
- },
282
- contentContainer: {
283
- padding: 8,
284
- paddingBottom: 24
285
- },
286
- varsSection: {
287
- marginTop: 16
288
- },
289
- sectionHeader: {
290
- flexDirection: "row",
291
- justifyContent: "space-between",
292
- alignItems: "center",
293
- marginBottom: 8,
294
- paddingHorizontal: 8
295
- },
296
- sectionTitle: {
297
- fontSize: 10,
298
- fontWeight: "700",
299
- color: night.color.textTertiary,
300
- letterSpacing: 1.2,
301
- fontFamily: monoFont
302
- },
303
- countBadge: {
304
- backgroundColor: night.color.accent + "15",
305
- paddingHorizontal: 8,
306
- paddingVertical: 2,
307
- borderRadius: 9999,
308
- borderWidth: 1,
309
- borderColor: night.color.accent + "40"
310
- },
311
- countText: {
312
- fontSize: 10,
313
- fontWeight: "500",
314
- color: night.color.accent,
315
- fontFamily: monoFont
316
- }
317
- });
1
+ "use strict";import{JsModal as O,devToolsStorageKeys as M,ModalHeader as d,HeaderSearchButton as K,displayValue as Q,buoyColors as h,night as S,windowDragProps as J,ToolEmptyState as U,resolveListEmptyState as X,monoFont as q}from"@buoy-gg/shared-ui";import{useCallback as Y,useState as V,useRef as $,useEffect as A,useMemo as g}from"react";import{View as f,StyleSheet as Z,ScrollView as ee,Text as H}from"react-native";import{EnvStatsOverview as te}from"./EnvStatsOverview";import{useDynamicEnv as oe}from"../hooks/useDynamicEnv";import{processEnvVars as re,calculateStats as ne}from"../utils";import{EnvVarSection as se}from"./EnvVarSection";import{jsx as e,jsxs as y}from"react/jsx-runtime";export function EnvVarsModal({visible:p,onClose:D,requiredEnvVars:m,onBack:E,onMinimize:B,enableSharedModalDimensions:F=false,inline:j=false}){const[s,z]=V("all");const[a,w]=V(false);const[l,I]=V("");const x=$(null);const N=Y(t=>{},[]);A(()=>{if(a&&x.current){x.current.focus()}},[a]);A(()=>{I("");w(false)},[s]);const R=oe();const C=g(()=>{const t={};R.forEach(({key:o,data:r})=>{if(r!==void 0&&r!==null){t[o]=typeof r==="string"?r:Q(r)}});return t},[R]);const{requiredVars:b,optionalVars:c}=g(()=>{return re(C,m)},[C,m]);const v=g(()=>{if(m===void 0){return{totalCount:0,requiredCount:0,optionalCount:0,presentRequiredCount:0,missingCount:0,wrongValueCount:0,wrongTypeCount:0}}return ne(b,c,C)},[m,b,c,C]);const u=g(()=>{const t=[...b,...c];return t.sort((o,r)=>{const L={"required_missing":1,"required_wrong_type":2,"required_wrong_value":3,"required_present":4,"optional_present":5};return(L[o.status]||999)-(L[r.status]||999)})},[b,c]);const T=g(()=>{let t=[];switch(s){case"all":t=u;break;case"missing":t=u.filter(o=>o.status==="required_missing");break;case"issues":t=u.filter(o=>o.status==="required_wrong_type"||o.status==="required_wrong_value");break}if(l){const o=l.toLowerCase();t=t.filter(r=>r.key.toLowerCase().includes(o)||r.description?.toLowerCase().includes(o)||typeof r.value==="string"&&r.value.toLowerCase().includes(o))}return t},[u,c,s,l]);const i=v.requiredCount>0?Math.round(v.presentRequiredCount/v.requiredCount*100):100;const P=i===100?"HEALTHY":i>=75?"WARNING":i>=50?"ERROR":"CRITICAL";const W=i===100?h.success:i>=75?h.warning:i>=50?h.error:h.error;if(!p)return null;const G=F?M.modal.root():M.env.modal();const _=y(d,{children:[E&&e(d.Navigation,{onBack:E}),e(d.Content,{title:a?"":"Environment Variables",noMargin:a,children:a&&e(d.Search,{value:l,onChangeText:I,onCancel:()=>{w(false);I("")},placeholder:"Search env keys...",accessibilityLabel:"Search env keys",testID:"buoy-env-search",containerTestID:"buoy-env-search-field",clearTestID:"buoy-env-search-clear"})}),e(d.Actions,{children:!a&&e(K,{onPress:()=>w(true),testID:"buoy-env-search-open",accessibilityLabel:"Search env keys"})})]});const k=y(ee,{style:n.scrollContainer,contentContainerStyle:n.contentContainer,showsVerticalScrollIndicator:false,testID:"buoy-env-scroll",children:[e(te,{stats:v,healthPercentage:i,healthStatus:P,healthColor:W,activeFilter:s,onFilterChange:z}),T.length>0?y(f,{style:n.varsSection,children:[y(f,{style:n.sectionHeader,children:[e(H,{style:n.sectionTitle,testID:"buoy-env-section-title",children:s==="all"?"ALL VARIABLES":s==="missing"?"MISSING VARIABLES":"ISSUES TO FIX"}),e(f,{style:n.countBadge,children:e(H,{style:n.countText,testID:"buoy-env-section-count",children:T.length})})]}),e(se,{title:"",count:0,vars:T,emptyMessage:""})]}):e(U,{testID:"buoy-env-empty",...X({subject:"variables",verb:"declared",search:l,total:u.length,waiting:{otto:"keys",title:"No variables",message:`No ${s==="all"?"":s} variables found`}})})]});if(j){return y(f,{style:n.inlineRoot,children:[e(f,{style:n.inlineHeader,...J,children:_}),k]})}return e(O,{visible:p,onClose:D,onMinimize:B,persistenceKey:G,header:{customContent:_,showToggleButton:true},onModeChange:N,enablePersistence:true,initialMode:"bottomSheet",enableGlitchEffects:true,styles:{},children:k})}export function EnvToolView({requiredEnvVars:p}){return e(EnvVarsModal,{inline:true,visible:true,requiredEnvVars:p,onClose:()=>{}})}const n=Z.create({inlineRoot:{flex:1,minHeight:0,backgroundColor:"transparent"},inlineHeader:{paddingHorizontal:12,paddingVertical:8,borderBottomWidth:1,borderBottomColor:h.border,backgroundColor:"transparent"},scrollContainer:{flex:1,backgroundColor:"transparent"},contentContainer:{padding:8,paddingBottom:24},varsSection:{marginTop:16},sectionHeader:{flexDirection:"row",justifyContent:"space-between",alignItems:"center",marginBottom:8,paddingHorizontal:8},sectionTitle:{fontSize:10,fontWeight:"700",color:S.color.textTertiary,letterSpacing:1.2,fontFamily:q},countBadge:{backgroundColor:S.color.accent+"15",paddingHorizontal:8,paddingVertical:2,borderRadius:9999,borderWidth:1,borderColor:S.color.accent+"40"},countText:{fontSize:10,fontWeight:"500",color:S.color.accent,fontFamily:q}});
@@ -1,88 +1 @@
1
- "use strict";
2
-
3
- import { useMemo } from "react";
4
- import { getRemoteEnv } from "../utils/remoteEnv";
5
- /**
6
- * Hook that returns all available environment variables with parsed values
7
- * Includes all available environment variables by default (only EXPO_PUBLIC_ prefixed vars are loaded by Expo)
8
- *
9
- * @example
10
- * // Get all available environment variables (only EXPO_PUBLIC_ prefixed)
11
- * const envVars = useDynamicEnv();
12
- * // Returns: [
13
- * // { key: 'EXPO_PUBLIC_API_URL', data: 'https://api.example.com' },
14
- * // { key: 'EXPO_PUBLIC_APP_NAME', data: 'MyApp' },
15
- * // ...
16
- * // ]
17
- *
18
- * @example
19
- * // Filter to specific variables
20
- * const envVars = useDynamicEnv({
21
- * envFilter: (key) => key.includes('API') || key.includes('URL')
22
- * });
23
- *
24
- * @example
25
- * // Filter by value content
26
- * const envVars = useDynamicEnv({
27
- * envFilter: (key, value) => value !== undefined && value.length > 0
28
- * });
29
- */
30
- export function useDynamicEnv({
31
- envFilter = () => true // Default: include all available environment variables (EXPO_PUBLIC_ only)
32
- } = {}) {
33
- // In remote mirror mode (desktop dashboard) env values come from the
34
- // synced device instead of the local process.env
35
- const envSource =
36
- // @ts-ignore process.env does exist
37
- getRemoteEnv() ?? (typeof process === "undefined" ? {} : process.env);
38
-
39
- // Helper function to get a single environment variable value
40
- const getEnvValue = useMemo(() => {
41
- return key => {
42
- const value = envSource[key];
43
- if (value === undefined) {
44
- return null;
45
- }
46
-
47
- // Try to parse as JSON for complex values, fall back to string
48
- try {
49
- // Only attempt JSON parsing if it looks like JSON (starts with { or [)
50
- if (value.startsWith("{") || value.startsWith("[")) {
51
- return JSON.parse(value);
52
- }
53
-
54
- // Parse boolean-like strings
55
- if (value.toLowerCase() === "true") return true;
56
- if (value.toLowerCase() === "false") return false;
57
-
58
- // Parse number-like strings
59
- if (/^\d+$/.test(value)) {
60
- const num = parseInt(value, 10);
61
- return !isNaN(num) ? num : value;
62
- }
63
- if (/^\d*\.\d+$/.test(value)) {
64
- const num = parseFloat(value);
65
- return !isNaN(num) ? num : value;
66
- }
67
- return value;
68
- } catch {
69
- return value;
70
- }
71
- };
72
- }, [envSource]);
73
-
74
- // Get all environment variables and process them
75
- const envResults = useMemo(() => {
76
- const allEnvKeys = Object.keys(envSource);
77
- const filteredKeys = allEnvKeys.filter(key => {
78
- const value = envSource[key];
79
- return envFilter(key, value);
80
- });
81
- return filteredKeys.map(key => ({
82
- key,
83
- data: getEnvValue(key)
84
- }));
85
- // eslint-disable-next-line react-hooks/exhaustive-deps
86
- }, [envFilter, getEnvValue, envSource]);
87
- return envResults;
88
- }
1
+ "use strict";import{useMemo as o}from"react";import{getRemoteEnv as i}from"../utils/remoteEnv";export function useDynamicEnv({envFilter:s=()=>true}={}){const r=i()??(typeof process==="undefined"?{}:process.env);const u=o(()=>{return n=>{const t=r[n];if(t===void 0){return null}try{if(t.startsWith("{")||t.startsWith("[")){return JSON.parse(t)}if(t.toLowerCase()==="true")return true;if(t.toLowerCase()==="false")return false;if(/^\d+$/.test(t)){const e=parseInt(t,10);return!isNaN(e)?e:t}if(/^\d*\.\d+$/.test(t)){const e=parseFloat(t);return!isNaN(e)?e:t}return t}catch{return t}}},[r]);const a=o(()=>{const n=Object.keys(r);const t=n.filter(e=>{const c=r[e];return s(e,c)});return t.map(e=>({key:e,data:u(e)}))},[s,u,r]);return a}