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.
- package/dist/index.d.ts +86 -0
- package/dist/index.js +326 -0
- package/package.json +1 -1
package/dist/index.d.ts
ADDED
|
@@ -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;
|