env-flag 1.0.0

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 ADDED
@@ -0,0 +1,91 @@
1
+ # env-flag
2
+
3
+ A lightweight, customizable environment flag indicator for browser applications. Shows DEV/PROD/STAGING/TEST status as a badge in your app. Perfect for distinguishing environments at a glance!
4
+
5
+ ## 🚀 Features
6
+ - **Automatic environment detection** (development, production, staging, test)
7
+ - **Customizable colors, text, position, and size**
8
+ - **Accessible** (keyboard, screen reader, ARIA labels)
9
+ - **TypeScript support**
10
+ - **No dependencies**
11
+
12
+ ## 📦 Installation
13
+
14
+ ```sh
15
+ npm install env-flag
16
+ ```
17
+
18
+ or
19
+
20
+ ```sh
21
+ yarn add env-flag
22
+ ```
23
+
24
+ ## 🛠️ Usage
25
+
26
+ ### Basic
27
+ ```js
28
+ import EnvFlag from 'env-flag';
29
+
30
+ const flag = new EnvFlag();
31
+ flag.init();
32
+ ```
33
+
34
+ ### With Custom Options
35
+ ```js
36
+ import EnvFlag from 'env-flag';
37
+
38
+ const flag = new EnvFlag({
39
+ position: 'top-left',
40
+ size: 'large',
41
+ productionColor: '#27ae60',
42
+ developmentText: 'DEVS',
43
+ debug: true
44
+ });
45
+ flag.init();
46
+ ```
47
+
48
+ ### Force Environment (for testing)
49
+ ```js
50
+ const flag = new EnvFlag({
51
+ forceEnv: 'staging',
52
+ stagingColor: '#ff9800',
53
+ stagingText: 'STAGING'
54
+ });
55
+ flag.init();
56
+ ```
57
+
58
+ ## ⚙️ Configuration
59
+
60
+ | Option | Type | Default | Description |
61
+ |--------------------|----------|-----------------|----------------------------------------------|
62
+ | productionColor | string | `#e74c3c` | Prod badge color |
63
+ | developmentColor | string | `#3498db` | Dev badge color |
64
+ | stagingColor | string | `#f39c12` | Staging badge color |
65
+ | testColor | string | `#9b59b6` | Test badge color |
66
+ | productionText | string | `PROD` | Prod badge text |
67
+ | developmentText | string | `DEV` | Dev badge text |
68
+ | stagingText | string | `STAGING` | Staging badge text |
69
+ | testText | string | `TEST` | Test badge text |
70
+ | position | string | `bottom-right` | Badge position (`top-right`, `top-left`, `bottom-right`, `bottom-left`) |
71
+ | size | string | `medium` | Badge size (`small`, `medium`, `large`) |
72
+ | autoDetectEnv | boolean | `true` | Try to detect environment automatically |
73
+ | forceEnv | string | | Force environment (`production`, `development`, `staging`, `test`) |
74
+ | enabled | boolean | `true` | Show/hide the flag |
75
+ | debug | boolean | `false` | Enable debug logs |
76
+
77
+ ## 🧑‍💻 Contributing
78
+
79
+ 1. Fork the repo
80
+ 2. Create your feature branch (`git checkout -b feature/your-feature`)
81
+ 3. Commit your changes (`git commit -am 'Add new feature'`)
82
+ 4. Push to the branch (`git push origin feature/your-feature`)
83
+ 5. Open a Pull Request
84
+
85
+ ## 📄 License
86
+
87
+ ISC
88
+
89
+ ---
90
+
91
+ Made with ❤️ by Koray TUNCER
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Environment Flag - A lightweight library to display environment indicators
3
+ * @version 1.0.0
4
+ */
5
+ type Environment = 'development' | 'production' | 'staging' | 'test';
6
+ type Position = 'top-right' | 'top-left' | 'bottom-right' | 'bottom-left';
7
+ type Size = 'small' | 'medium' | 'large';
8
+ interface EnvFlagConfig {
9
+ readonly productionColor?: string;
10
+ readonly developmentColor?: string;
11
+ readonly stagingColor?: string;
12
+ readonly testColor?: string;
13
+ readonly productionText?: string;
14
+ readonly developmentText?: string;
15
+ readonly stagingText?: string;
16
+ readonly testText?: string;
17
+ readonly position?: Position;
18
+ readonly size?: Size;
19
+ readonly autoDetectEnv?: boolean;
20
+ readonly forceEnv?: Environment;
21
+ readonly enabled?: boolean;
22
+ readonly debug?: boolean;
23
+ }
24
+ declare class EnvFlag {
25
+ private static readonly DEFAULT_CONFIG;
26
+ private static readonly ELEMENT_ID;
27
+ private static readonly Z_INDEX;
28
+ private readonly config;
29
+ private flagElement;
30
+ private eventListeners;
31
+ constructor(config?: EnvFlagConfig);
32
+ /**
33
+ * Initialize the environment flag
34
+ */
35
+ init(): void;
36
+ /**
37
+ * Destroy the environment flag and clean up resources
38
+ */
39
+ destroy(): void;
40
+ /**
41
+ * Update configuration and recreate flag
42
+ */
43
+ updateConfig(newConfig: Partial<EnvFlagConfig>): void;
44
+ /**
45
+ * Get current environment
46
+ */
47
+ getCurrentEnvironment(): Environment;
48
+ private isValidEnvironment;
49
+ private detectEnvironment;
50
+ private createFlag;
51
+ private applyStyles;
52
+ private getEnvironmentStyles;
53
+ private getEnvironmentText;
54
+ private getSizeStyles;
55
+ private getPositionStyles;
56
+ private getPositionCoordinates;
57
+ private attachEventListeners;
58
+ private handleClick;
59
+ private handleMouseEnter;
60
+ private handleMouseLeave;
61
+ private handleKeydown;
62
+ private removeEventListeners;
63
+ private removeFlagElement;
64
+ }
65
+ export default EnvFlag;
66
+ export type { EnvFlagConfig, Environment, Position, Size };
package/dist/index.js ADDED
@@ -0,0 +1,317 @@
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
+ this.config = Object.assign(Object.assign({}, EnvFlag.DEFAULT_CONFIG), config);
10
+ if (this.config.debug) {
11
+ console.log('[EnvFlag] Initialized with config:', this.config);
12
+ }
13
+ }
14
+ /**
15
+ * Initialize the environment flag
16
+ */
17
+ init() {
18
+ try {
19
+ if (!this.isValidEnvironment()) {
20
+ if (this.config.debug) {
21
+ console.warn('[EnvFlag] Invalid environment, skipping initialization');
22
+ }
23
+ return;
24
+ }
25
+ if (!this.config.enabled) {
26
+ if (this.config.debug) {
27
+ console.log('[EnvFlag] Flag is disabled');
28
+ }
29
+ return;
30
+ }
31
+ this.destroy(); // Clean up any existing flag
32
+ this.createFlag();
33
+ if (this.config.debug) {
34
+ console.log('[EnvFlag] Flag created successfully');
35
+ }
36
+ }
37
+ catch (error) {
38
+ console.error('[EnvFlag] Failed to initialize:', error);
39
+ }
40
+ }
41
+ /**
42
+ * Destroy the environment flag and clean up resources
43
+ */
44
+ destroy() {
45
+ try {
46
+ this.removeEventListeners();
47
+ this.removeFlagElement();
48
+ if (this.config.debug) {
49
+ console.log('[EnvFlag] Flag destroyed and resources cleaned up');
50
+ }
51
+ }
52
+ catch (error) {
53
+ console.error('[EnvFlag] Error during cleanup:', error);
54
+ }
55
+ }
56
+ /**
57
+ * Update configuration and recreate flag
58
+ */
59
+ updateConfig(newConfig) {
60
+ Object.assign(this.config, newConfig);
61
+ this.init();
62
+ }
63
+ /**
64
+ * Get current environment
65
+ */
66
+ getCurrentEnvironment() {
67
+ if (this.config.forceEnv) {
68
+ return this.config.forceEnv;
69
+ }
70
+ if (!this.config.autoDetectEnv) {
71
+ return 'development';
72
+ }
73
+ return this.detectEnvironment();
74
+ }
75
+ isValidEnvironment() {
76
+ if (typeof window === 'undefined') {
77
+ if (this.config.debug) {
78
+ console.warn('[EnvFlag] Window is undefined, likely running in Node.js environment');
79
+ }
80
+ return false;
81
+ }
82
+ if (typeof document === 'undefined') {
83
+ if (this.config.debug) {
84
+ console.warn('[EnvFlag] Document is undefined');
85
+ }
86
+ return false;
87
+ }
88
+ return true;
89
+ }
90
+ detectEnvironment() {
91
+ var _a, _b, _c;
92
+ // Check various environment indicators
93
+ const indicators = {
94
+ hostname: ((_a = window.location) === null || _a === void 0 ? void 0 : _a.hostname) || '',
95
+ nodeEnv: ((_c = (_b = globalThis.process) === null || _b === void 0 ? void 0 : _b.env) === null || _c === void 0 ? void 0 : _c.NODE_ENV) || '',
96
+ userAgent: (navigator === null || navigator === void 0 ? void 0 : navigator.userAgent) || ''
97
+ };
98
+ if (this.config.debug) {
99
+ console.log('[EnvFlag] Environment indicators:', indicators);
100
+ }
101
+ // Production indicators
102
+ if (indicators.nodeEnv === 'production' ||
103
+ indicators.hostname.includes('prod') ||
104
+ !indicators.hostname.includes('localhost') &&
105
+ !indicators.hostname.includes('127.0.0.1') &&
106
+ !indicators.hostname.includes('dev') &&
107
+ !indicators.hostname.includes('staging')) {
108
+ return 'production';
109
+ }
110
+ // Staging indicators
111
+ if (indicators.nodeEnv === 'staging' ||
112
+ indicators.hostname.includes('staging') ||
113
+ indicators.hostname.includes('stage')) {
114
+ return 'staging';
115
+ }
116
+ // Test indicators
117
+ if (indicators.nodeEnv === 'test' ||
118
+ indicators.hostname.includes('test')) {
119
+ return 'test';
120
+ }
121
+ // Default to development
122
+ return 'development';
123
+ }
124
+ createFlag() {
125
+ const environment = this.getCurrentEnvironment();
126
+ this.flagElement = document.createElement('div');
127
+ this.flagElement.id = EnvFlag.ELEMENT_ID;
128
+ this.flagElement.setAttribute('data-env', environment);
129
+ this.flagElement.setAttribute('role', 'status');
130
+ this.flagElement.setAttribute('aria-label', `Environment: ${environment}`);
131
+ this.applyStyles(environment);
132
+ this.attachEventListeners();
133
+ // Use requestAnimationFrame for better performance
134
+ requestAnimationFrame(() => {
135
+ if (this.flagElement && document.body) {
136
+ document.body.appendChild(this.flagElement);
137
+ }
138
+ });
139
+ }
140
+ applyStyles(environment) {
141
+ if (!this.flagElement)
142
+ return;
143
+ const styles = this.getEnvironmentStyles(environment);
144
+ const positionStyles = this.getPositionStyles();
145
+ const sizeStyles = this.getSizeStyles();
146
+ // Apply base styles
147
+ Object.assign(this.flagElement.style, Object.assign(Object.assign({
148
+ // Content
149
+ textContent: this.getEnvironmentText(environment),
150
+ // Colors
151
+ backgroundColor: styles.backgroundColor, color: styles.color,
152
+ // Typography
153
+ fontFamily: '"Segoe UI", system-ui, -apple-system, sans-serif', fontWeight: '600', fontSize: sizeStyles.fontSize, lineHeight: '1', textAlign: 'center',
154
+ // Layout
155
+ position: 'fixed', zIndex: EnvFlag.Z_INDEX, padding: sizeStyles.padding,
156
+ // Visual
157
+ borderRadius: positionStyles.borderRadius, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)', backdropFilter: 'blur(8px)', opacity: styles.opacity,
158
+ // Interaction
159
+ cursor: 'pointer', userSelect: 'none', transition: 'all 0.2s ease-in-out' }, this.getPositionCoordinates()), {
160
+ // Accessibility
161
+ outline: 'none' }));
162
+ this.flagElement.textContent = this.getEnvironmentText(environment);
163
+ }
164
+ getEnvironmentStyles(environment) {
165
+ const colorMap = {
166
+ production: this.config.productionColor,
167
+ development: this.config.developmentColor,
168
+ staging: this.config.stagingColor,
169
+ test: this.config.testColor
170
+ };
171
+ return {
172
+ backgroundColor: colorMap[environment],
173
+ color: '#ffffff',
174
+ fontSize: this.getSizeStyles().fontSize,
175
+ padding: this.getSizeStyles().padding,
176
+ borderRadius: this.getPositionStyles().borderRadius,
177
+ opacity: '0.9'
178
+ };
179
+ }
180
+ getEnvironmentText(environment) {
181
+ const textMap = {
182
+ production: this.config.productionText,
183
+ development: this.config.developmentText,
184
+ staging: this.config.stagingText,
185
+ test: this.config.testText
186
+ };
187
+ return textMap[environment];
188
+ }
189
+ getSizeStyles() {
190
+ const sizeMap = {
191
+ small: { fontSize: '10px', padding: '4px 8px' },
192
+ medium: { fontSize: '12px', padding: '6px 12px' },
193
+ large: { fontSize: '14px', padding: '8px 16px' }
194
+ };
195
+ return sizeMap[this.config.size];
196
+ }
197
+ getPositionStyles() {
198
+ const radiusMap = {
199
+ 'top-right': '0 0 0 4px',
200
+ 'top-left': '0 0 4px 0',
201
+ 'bottom-left': '0 4px 0 0',
202
+ 'bottom-right': '4px 0 0 0'
203
+ };
204
+ return { borderRadius: radiusMap[this.config.position] };
205
+ }
206
+ getPositionCoordinates() {
207
+ const coordinateMap = {
208
+ 'top-right': { top: '0', right: '0' },
209
+ 'top-left': { top: '0', left: '0' },
210
+ 'bottom-left': { bottom: '0', left: '0' },
211
+ 'bottom-right': { bottom: '0', right: '0' }
212
+ };
213
+ return coordinateMap[this.config.position];
214
+ }
215
+ attachEventListeners() {
216
+ if (!this.flagElement)
217
+ return;
218
+ // Click to remove
219
+ const clickHandler = this.handleClick.bind(this);
220
+ this.flagElement.addEventListener('click', clickHandler);
221
+ this.eventListeners.push({
222
+ element: this.flagElement,
223
+ event: 'click',
224
+ handler: clickHandler
225
+ });
226
+ // Hover effects
227
+ const mouseEnterHandler = this.handleMouseEnter.bind(this);
228
+ const mouseLeaveHandler = this.handleMouseLeave.bind(this);
229
+ this.flagElement.addEventListener('mouseenter', mouseEnterHandler);
230
+ this.flagElement.addEventListener('mouseleave', mouseLeaveHandler);
231
+ this.eventListeners.push({
232
+ element: this.flagElement,
233
+ event: 'mouseenter',
234
+ handler: mouseEnterHandler
235
+ }, {
236
+ element: this.flagElement,
237
+ event: 'mouseleave',
238
+ handler: mouseLeaveHandler
239
+ });
240
+ // Keyboard accessibility
241
+ const keydownHandler = this.handleKeydown.bind(this);
242
+ this.flagElement.addEventListener('keydown', keydownHandler);
243
+ this.flagElement.setAttribute('tabindex', '0');
244
+ this.eventListeners.push({
245
+ element: this.flagElement,
246
+ event: 'keydown',
247
+ handler: keydownHandler
248
+ });
249
+ }
250
+ handleClick() {
251
+ this.destroy();
252
+ }
253
+ handleMouseEnter() {
254
+ if (this.flagElement) {
255
+ this.flagElement.style.opacity = '1';
256
+ this.flagElement.style.transform = 'scale(1.05)';
257
+ }
258
+ }
259
+ handleMouseLeave() {
260
+ if (this.flagElement) {
261
+ this.flagElement.style.opacity = '0.9';
262
+ this.flagElement.style.transform = 'scale(1)';
263
+ }
264
+ }
265
+ handleKeydown(event) {
266
+ const keyboardEvent = event;
267
+ if (keyboardEvent.key === 'Enter' || keyboardEvent.key === ' ') {
268
+ keyboardEvent.preventDefault();
269
+ this.destroy();
270
+ }
271
+ }
272
+ removeEventListeners() {
273
+ this.eventListeners.forEach(({ element, event, handler }) => {
274
+ element.removeEventListener(event, handler);
275
+ });
276
+ this.eventListeners = [];
277
+ }
278
+ removeFlagElement() {
279
+ var _a;
280
+ if (this.flagElement && ((_a = document.body) === null || _a === void 0 ? void 0 : _a.contains(this.flagElement))) {
281
+ document.body.removeChild(this.flagElement);
282
+ this.flagElement = null;
283
+ }
284
+ }
285
+ }
286
+ EnvFlag.DEFAULT_CONFIG = {
287
+ productionColor: '#e74c3c',
288
+ developmentColor: '#3498db',
289
+ stagingColor: '#f39c12',
290
+ testColor: '#9b59b6',
291
+ productionText: 'PROD',
292
+ developmentText: 'DEV',
293
+ stagingText: 'STAGING',
294
+ testText: 'TEST',
295
+ position: 'bottom-right',
296
+ size: 'medium',
297
+ autoDetectEnv: true,
298
+ enabled: true,
299
+ debug: false
300
+ };
301
+ EnvFlag.ELEMENT_ID = 'env-flag-indicator';
302
+ EnvFlag.Z_INDEX = '999999';
303
+ // Auto-initialization for immediate testing
304
+ if (typeof window !== 'undefined') {
305
+ const autoEnvFlag = new EnvFlag({
306
+ debug: true // Enable debug mode for testing
307
+ });
308
+ // Initialize when DOM is ready
309
+ if (document.readyState === 'loading') {
310
+ document.addEventListener('DOMContentLoaded', () => autoEnvFlag.init());
311
+ }
312
+ else {
313
+ // DOM is already ready
314
+ autoEnvFlag.init();
315
+ }
316
+ }
317
+ export default EnvFlag;
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "env-flag",
3
+ "version": "1.0.0",
4
+ "main": "dist/index.js",
5
+ "module": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "start": "tsc && node dist/index.js",
15
+ "test": "echo \"Error: no test specified\" && exit 1"
16
+ },
17
+ "keywords": [
18
+ "env",
19
+ "flag",
20
+ "environment",
21
+ "indicator",
22
+ "typescript",
23
+ "browser",
24
+ "badge"
25
+ ],
26
+ "author": "Koray TUNCER <ktuncerr@gmail.com>",
27
+ "license": "ISC",
28
+ "description": "A lightweight, customizable environment flag indicator for browser apps. Shows DEV/PROD/STAGING status as a badge.",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/ktuncerr/env-flag.git"
32
+ },
33
+ "devDependencies": {
34
+ "@types/node": "^24.0.11",
35
+ "typescript": "^5.8.3"
36
+ }
37
+ }