env-flag 1.0.2 → 1.0.3

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.
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Environment Flag - A lightweight library to display environment indicators
3
+ * @version 1.0.0
4
+ */
5
+ declare global {
6
+ interface ImportMeta {
7
+ readonly env: ImportMetaEnv;
8
+ }
9
+ interface ImportMetaEnv {
10
+ readonly NODE_ENV?: string;
11
+ readonly VITE_NODE_ENV?: string;
12
+ readonly VITE_APP_NODE_ENV?: string;
13
+ readonly VITE_APP_ENV?: string;
14
+ readonly VITE_ENV?: string;
15
+ readonly REACT_APP_NODE_ENV?: string;
16
+ readonly REACT_APP_ENV?: string;
17
+ readonly VUE_APP_NODE_ENV?: string;
18
+ readonly VUE_APP_ENV?: string;
19
+ readonly [key: string]: string | undefined;
20
+ }
21
+ }
22
+ type Environment = "development" | "production" | "staging" | "test";
23
+ type Position = "top-right" | "top-left" | "bottom-right" | "bottom-left";
24
+ type Size = "small" | "medium" | "large";
25
+ interface EnvFlagConfig {
26
+ readonly productionColor?: string;
27
+ readonly developmentColor?: string;
28
+ readonly stagingColor?: string;
29
+ readonly testColor?: string;
30
+ readonly productionText?: string;
31
+ readonly developmentText?: string;
32
+ readonly stagingText?: string;
33
+ readonly testText?: string;
34
+ readonly position?: Position;
35
+ readonly size?: Size;
36
+ readonly autoDetectEnv?: boolean;
37
+ readonly forceEnv?: Environment;
38
+ readonly enabled?: boolean;
39
+ readonly debug?: boolean;
40
+ }
41
+ declare class EnvFlag {
42
+ private static readonly DEFAULT_CONFIG;
43
+ private static readonly ELEMENT_ID;
44
+ private static readonly Z_INDEX;
45
+ private readonly config;
46
+ private flagElement;
47
+ private eventListeners;
48
+ constructor(config?: EnvFlagConfig);
49
+ /**
50
+ * Initialize the environment flag
51
+ */
52
+ init: () => void;
53
+ /**
54
+ * Destroy the environment flag and clean up resources
55
+ */
56
+ destroy: () => void;
57
+ /**
58
+ * Update configuration and recreate flag
59
+ */
60
+ updateConfig: (newConfig: Partial<EnvFlagConfig>) => void;
61
+ /**
62
+ * Get current environment
63
+ */
64
+ getCurrentEnvironment: () => Environment;
65
+ private isValidEnvironment;
66
+ private detectEnvironment;
67
+ private getNodeEnv;
68
+ private getCustomEnv;
69
+ private isValidEnvironmentString;
70
+ private createFlag;
71
+ private applyStyles;
72
+ private getEnvironmentStyles;
73
+ private getEnvironmentText;
74
+ private getSizeStyles;
75
+ private getPositionStyles;
76
+ private getPositionCoordinates;
77
+ private attachEventListeners;
78
+ private handleClick;
79
+ private handleMouseEnter;
80
+ private handleMouseLeave;
81
+ private handleKeydown;
82
+ private removeEventListeners;
83
+ private removeFlagElement;
84
+ }
85
+ export default EnvFlag;
86
+ export type { EnvFlagConfig, Environment, Position, Size };
package/dist/index.js ADDED
@@ -0,0 +1,326 @@
1
+ /**
2
+ * Environment Flag - A lightweight library to display environment indicators
3
+ * @version 1.0.0
4
+ */
5
+ class EnvFlag {
6
+ constructor(config = {}) {
7
+ this.flagElement = null;
8
+ this.eventListeners = [];
9
+ /**
10
+ * Initialize the environment flag
11
+ */
12
+ this.init = () => {
13
+ if (!this.isValidEnvironment())
14
+ return;
15
+ if (!this.config.enabled)
16
+ return;
17
+ this.destroy(); // Clean up any existing flag
18
+ this.createFlag();
19
+ };
20
+ /**
21
+ * Destroy the environment flag and clean up resources
22
+ */
23
+ this.destroy = () => {
24
+ this.removeEventListeners();
25
+ this.removeFlagElement();
26
+ };
27
+ /**
28
+ * Update configuration and recreate flag
29
+ */
30
+ this.updateConfig = (newConfig) => {
31
+ Object.assign(this.config, newConfig);
32
+ this.init();
33
+ };
34
+ /**
35
+ * Get current environment
36
+ */
37
+ this.getCurrentEnvironment = () => {
38
+ if (this.config.forceEnv)
39
+ return this.config.forceEnv;
40
+ if (!this.config.autoDetectEnv)
41
+ return "development";
42
+ return this.detectEnvironment();
43
+ };
44
+ this.isValidEnvironment = () => typeof window !== "undefined" && typeof document !== "undefined";
45
+ this.detectEnvironment = () => {
46
+ const indicators = {
47
+ hostname: window.location?.hostname || "",
48
+ nodeEnv: this.getNodeEnv(),
49
+ customEnv: this.getCustomEnv(),
50
+ userAgent: navigator?.userAgent || "",
51
+ };
52
+ // Priority 1: Custom environment variable (highest priority)
53
+ if (indicators.customEnv)
54
+ return indicators.customEnv;
55
+ // Priority 2: NODE_ENV from bundler
56
+ if (indicators.nodeEnv)
57
+ return indicators.nodeEnv;
58
+ // Priority 3: Hostname-based detection
59
+ // Production indicators
60
+ if (indicators.hostname.includes("prod") ||
61
+ (!indicators.hostname.includes("localhost") &&
62
+ !indicators.hostname.includes("127.0.0.1") &&
63
+ !indicators.hostname.includes("dev") &&
64
+ !indicators.hostname.includes("staging") &&
65
+ !indicators.hostname.includes("test") &&
66
+ indicators.hostname.length > 0))
67
+ return "production";
68
+ // Staging indicators
69
+ if (indicators.hostname.includes("staging") ||
70
+ indicators.hostname.includes("stage"))
71
+ return "staging";
72
+ // Test indicators
73
+ if (indicators.hostname.includes("test"))
74
+ return "test";
75
+ return "development";
76
+ };
77
+ this.getNodeEnv = () => {
78
+ // Vite environment (import.meta.env)
79
+ if (typeof import.meta !== "undefined" && import.meta.env) {
80
+ const viteEnv = import.meta.env.NODE_ENV || import.meta.env.VITE_NODE_ENV;
81
+ if (viteEnv && this.isValidEnvironmentString(viteEnv)) {
82
+ return viteEnv;
83
+ }
84
+ }
85
+ // Webpack/traditional bundler environment (process.env)
86
+ if (typeof globalThis !== "undefined" &&
87
+ globalThis.process?.env?.NODE_ENV) {
88
+ const nodeEnv = globalThis.process.env.NODE_ENV;
89
+ if (this.isValidEnvironmentString(nodeEnv))
90
+ return nodeEnv;
91
+ }
92
+ return "";
93
+ };
94
+ this.getCustomEnv = () => {
95
+ const customEnvVars = [
96
+ "VITE_APP_NODE_ENV",
97
+ "VITE_APP_ENV",
98
+ "VITE_ENV",
99
+ "REACT_APP_NODE_ENV",
100
+ "REACT_APP_ENV",
101
+ "VUE_APP_NODE_ENV",
102
+ "VUE_APP_ENV",
103
+ ];
104
+ // Vite environment
105
+ if (typeof import.meta !== "undefined" && import.meta.env) {
106
+ for (const envVar of customEnvVars) {
107
+ const value = import.meta.env[envVar];
108
+ if (value && this.isValidEnvironmentString(value)) {
109
+ if (this.config.debug) {
110
+ console.log(`[EnvFlag] Found custom env variable ${envVar}:`, value);
111
+ }
112
+ return value;
113
+ }
114
+ }
115
+ }
116
+ // Webpack/traditional bundler environment
117
+ if (typeof globalThis !== "undefined" && globalThis.process?.env) {
118
+ for (const envVar of customEnvVars) {
119
+ const value = globalThis.process.env[envVar];
120
+ if (value && this.isValidEnvironmentString(value)) {
121
+ if (this.config.debug) {
122
+ console.log(`[EnvFlag] Found custom env variable ${envVar}:`, value);
123
+ }
124
+ return value;
125
+ }
126
+ }
127
+ }
128
+ return "";
129
+ };
130
+ this.isValidEnvironmentString = (env) => ["development", "production", "staging", "test"].includes(env);
131
+ this.createFlag = () => {
132
+ const environment = this.getCurrentEnvironment();
133
+ this.flagElement = document.createElement("div");
134
+ this.flagElement.id = EnvFlag.ELEMENT_ID;
135
+ this.flagElement.setAttribute("data-env", environment);
136
+ this.flagElement.setAttribute("role", "status");
137
+ this.flagElement.setAttribute("aria-label", `Environment: ${environment}`);
138
+ this.applyStyles(environment);
139
+ this.attachEventListeners();
140
+ // Use requestAnimationFrame for better performance
141
+ requestAnimationFrame(() => {
142
+ if (this.flagElement && document.body) {
143
+ document.body.appendChild(this.flagElement);
144
+ }
145
+ });
146
+ };
147
+ this.applyStyles = (environment) => {
148
+ if (!this.flagElement)
149
+ return;
150
+ const styles = this.getEnvironmentStyles(environment);
151
+ const positionStyles = this.getPositionStyles();
152
+ const sizeStyles = this.getSizeStyles();
153
+ // Apply base styles
154
+ Object.assign(this.flagElement.style, {
155
+ // Content
156
+ textContent: this.getEnvironmentText(environment),
157
+ // Colors
158
+ backgroundColor: styles.backgroundColor,
159
+ color: styles.color,
160
+ // Typography
161
+ fontFamily: '"Segoe UI", system-ui, -apple-system, sans-serif',
162
+ fontWeight: "600",
163
+ fontSize: sizeStyles.fontSize,
164
+ lineHeight: "1",
165
+ textAlign: "center",
166
+ // Layout
167
+ position: "fixed",
168
+ zIndex: EnvFlag.Z_INDEX,
169
+ padding: sizeStyles.padding,
170
+ // Visual
171
+ borderRadius: positionStyles.borderRadius,
172
+ opacity: styles.opacity,
173
+ // Interaction
174
+ cursor: "pointer",
175
+ userSelect: "none",
176
+ transition: "all 0.2s ease-in-out",
177
+ // Positioning
178
+ ...this.getPositionCoordinates(),
179
+ // Accessibility
180
+ outline: "none",
181
+ });
182
+ this.flagElement.textContent = this.getEnvironmentText(environment);
183
+ };
184
+ this.getEnvironmentStyles = (environment) => {
185
+ const colorMap = {
186
+ production: this.config.productionColor,
187
+ development: this.config.developmentColor,
188
+ staging: this.config.stagingColor,
189
+ test: this.config.testColor,
190
+ };
191
+ return {
192
+ backgroundColor: colorMap[environment],
193
+ color: "#ffffff",
194
+ fontSize: this.getSizeStyles().fontSize,
195
+ padding: this.getSizeStyles().padding,
196
+ borderRadius: this.getPositionStyles().borderRadius,
197
+ opacity: "0.9",
198
+ };
199
+ };
200
+ this.getEnvironmentText = (environment) => ({
201
+ production: this.config.productionText,
202
+ development: this.config.developmentText,
203
+ staging: this.config.stagingText,
204
+ test: this.config.testText,
205
+ }[environment]);
206
+ this.getSizeStyles = () => ({
207
+ small: { fontSize: "10px", padding: "4px 8px" },
208
+ medium: { fontSize: "12px", padding: "6px 12px" },
209
+ large: { fontSize: "14px", padding: "8px 16px" },
210
+ }[this.config.size]);
211
+ this.getPositionStyles = () => ({
212
+ borderRadius: {
213
+ "top-right": "0 0 0 4px",
214
+ "top-left": "0 0 4px 0",
215
+ "bottom-left": "0 4px 0 0",
216
+ "bottom-right": "4px 0 0 0",
217
+ }[this.config.position],
218
+ });
219
+ this.getPositionCoordinates = () => ({
220
+ "top-right": { top: "0", right: "0" },
221
+ "top-left": { top: "0", left: "0" },
222
+ "bottom-left": { bottom: "0", left: "0" },
223
+ "bottom-right": { bottom: "0", right: "0" },
224
+ }[this.config.position]);
225
+ this.attachEventListeners = () => {
226
+ if (!this.flagElement)
227
+ return;
228
+ // Click to remove
229
+ const clickHandler = this.handleClick.bind(this);
230
+ this.flagElement.addEventListener("click", clickHandler);
231
+ this.eventListeners.push({
232
+ element: this.flagElement,
233
+ event: "click",
234
+ handler: clickHandler,
235
+ });
236
+ // Hover effects
237
+ const mouseEnterHandler = this.handleMouseEnter.bind(this);
238
+ const mouseLeaveHandler = this.handleMouseLeave.bind(this);
239
+ this.flagElement.addEventListener("mouseenter", mouseEnterHandler);
240
+ this.flagElement.addEventListener("mouseleave", mouseLeaveHandler);
241
+ this.eventListeners.push({
242
+ element: this.flagElement,
243
+ event: "mouseenter",
244
+ handler: mouseEnterHandler,
245
+ }, {
246
+ element: this.flagElement,
247
+ event: "mouseleave",
248
+ handler: mouseLeaveHandler,
249
+ });
250
+ // Keyboard accessibility
251
+ const keydownHandler = this.handleKeydown.bind(this);
252
+ this.flagElement.addEventListener("keydown", keydownHandler);
253
+ this.flagElement.setAttribute("tabindex", "0");
254
+ this.eventListeners.push({
255
+ element: this.flagElement,
256
+ event: "keydown",
257
+ handler: keydownHandler,
258
+ });
259
+ };
260
+ this.handleClick = () => this.destroy();
261
+ this.handleMouseEnter = () => {
262
+ if (this.flagElement) {
263
+ this.flagElement.style.opacity = "1";
264
+ this.flagElement.style.transform = "scale(1.05)";
265
+ }
266
+ };
267
+ this.handleMouseLeave = () => {
268
+ if (this.flagElement) {
269
+ this.flagElement.style.opacity = "0.9";
270
+ this.flagElement.style.transform = "scale(1)";
271
+ }
272
+ };
273
+ this.handleKeydown = (event) => {
274
+ const keyboardEvent = event;
275
+ if (keyboardEvent.key === "Enter" || keyboardEvent.key === " ") {
276
+ keyboardEvent.preventDefault();
277
+ this.destroy();
278
+ }
279
+ };
280
+ this.removeEventListeners = () => {
281
+ this.eventListeners.forEach(({ element, event, handler }) => {
282
+ element.removeEventListener(event, handler);
283
+ });
284
+ this.eventListeners = [];
285
+ };
286
+ this.removeFlagElement = () => {
287
+ if (this.flagElement && document.body?.contains(this.flagElement)) {
288
+ document.body.removeChild(this.flagElement);
289
+ this.flagElement = null;
290
+ }
291
+ };
292
+ this.config = { ...EnvFlag.DEFAULT_CONFIG, ...config };
293
+ }
294
+ }
295
+ EnvFlag.DEFAULT_CONFIG = {
296
+ productionColor: "#f8285a",
297
+ developmentColor: "#17c653",
298
+ stagingColor: "#f39c12",
299
+ testColor: "#9b59b6",
300
+ productionText: "PROD",
301
+ developmentText: "DEV",
302
+ stagingText: "STAGING",
303
+ testText: "TEST",
304
+ position: "bottom-right",
305
+ size: "medium",
306
+ autoDetectEnv: true,
307
+ enabled: true,
308
+ debug: false,
309
+ };
310
+ EnvFlag.ELEMENT_ID = "env-flag-indicator";
311
+ EnvFlag.Z_INDEX = "999999";
312
+ // Auto-initialization for immediate testing
313
+ if (typeof window !== "undefined") {
314
+ const autoEnvFlag = new EnvFlag({
315
+ debug: true, // Enable debug mode for testing
316
+ });
317
+ // Initialize when DOM is ready
318
+ if (document.readyState === "loading") {
319
+ document.addEventListener("DOMContentLoaded", () => autoEnvFlag.init());
320
+ }
321
+ else {
322
+ // DOM is already ready
323
+ autoEnvFlag.init();
324
+ }
325
+ }
326
+ export default EnvFlag;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "env-flag",
3
- "version": "1.0.2",
3
+ "version": "1.0.3",
4
4
  "main": "dist/index.js",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",