envprobe 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/src/utils.js ADDED
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Shared utility functions for envcheck-cli
3
+ * @module utils
4
+ */
5
+
6
+ import path from 'path';
7
+
8
+ /**
9
+ * Normalize a file path to use forward slashes and remove redundant separators
10
+ * @param {string} filePath - The file path to normalize
11
+ * @returns {string} The normalized file path
12
+ */
13
+ export function normalizePath(filePath) {
14
+ if (!filePath || filePath === '') {
15
+ return '';
16
+ }
17
+
18
+ // Use path.normalize to handle platform-specific separators and redundant separators
19
+ // Then convert all backslashes to forward slashes for consistency
20
+ return path.normalize(filePath).replace(/\\/g, '/');
21
+ }
22
+
23
+ /**
24
+ * Convert an absolute path to a relative path from the base directory
25
+ * @param {string} absolutePath - The absolute path to convert
26
+ * @param {string} basePath - The base directory path
27
+ * @returns {string} The relative path
28
+ */
29
+ export function toRelativePath(absolutePath, basePath) {
30
+ if (!absolutePath || !basePath) {
31
+ return absolutePath || '';
32
+ }
33
+
34
+ // If paths are the same, return '.'
35
+ if (path.resolve(absolutePath) === path.resolve(basePath)) {
36
+ return '.';
37
+ }
38
+
39
+ // Use path.relative to compute relative path, then normalize to forward slashes
40
+ const relativePath = path.relative(basePath, absolutePath);
41
+ return relativePath.replace(/\\/g, '/');
42
+ }
43
+
44
+ /**
45
+ * Validate if a string is a valid environment variable name
46
+ * Must match pattern: ^[A-Z_][A-Z0-9_]*$
47
+ * @param {string} varName - The variable name to validate
48
+ * @returns {boolean} True if valid, false otherwise
49
+ */
50
+ export function isValidEnvVarName(varName) {
51
+ if (!varName || typeof varName !== 'string') {
52
+ return false;
53
+ }
54
+
55
+ const pattern = /^[A-Z_][A-Z0-9_]*$/;
56
+ return pattern.test(varName);
57
+ }
package/src/watch.js ADDED
@@ -0,0 +1,131 @@
1
+ /**
2
+ * Watch mode for continuous validation
3
+ * Monitors file changes and re-runs validation automatically
4
+ */
5
+
6
+ import { watch } from 'fs';
7
+ import { join } from 'path';
8
+
9
+ /**
10
+ * Watch mode manager
11
+ */
12
+ export class Watcher {
13
+ constructor(path, options, onChange) {
14
+ this.path = path;
15
+ this.options = options;
16
+ this.onChangeCallback = onChange;
17
+ this.watchers = [];
18
+ this.debounceTimer = null;
19
+ this.debounceDelay = 300;
20
+ this.isRunning = false;
21
+ }
22
+
23
+ start() {
24
+ console.log(`\nšŸ‘€ Watching for changes in ${this.path}...`);
25
+ console.log('Press Ctrl+C to stop\n');
26
+
27
+ try {
28
+ const watcher = watch(
29
+ this.path,
30
+ { recursive: true },
31
+ (eventType, filename) => {
32
+ if (filename && this.shouldProcess(filename)) {
33
+ this.handleChange(eventType, filename);
34
+ }
35
+ }
36
+ );
37
+
38
+ this.watchers.push(watcher);
39
+ this.isRunning = true;
40
+
41
+ // Initial run
42
+ this.triggerChange('initial', 'startup');
43
+
44
+ } catch (error) {
45
+ console.error(`Failed to start watch mode: ${error.message}`);
46
+ throw error;
47
+ }
48
+ }
49
+
50
+ shouldProcess(filename) {
51
+ // Ignore common non-source files
52
+ const ignorePatterns = [
53
+ /node_modules/,
54
+ /\.git/,
55
+ /dist/,
56
+ /build/,
57
+ /\.log$/,
58
+ /\.tmp$/,
59
+ /~$/,
60
+ ];
61
+
62
+ return !ignorePatterns.some(pattern => pattern.test(filename));
63
+ }
64
+
65
+ handleChange(eventType, filename) {
66
+ // Debounce rapid changes
67
+ if (this.debounceTimer) {
68
+ clearTimeout(this.debounceTimer);
69
+ }
70
+
71
+ this.debounceTimer = setTimeout(() => {
72
+ this.triggerChange(eventType, filename);
73
+ }, this.debounceDelay);
74
+ }
75
+
76
+ async triggerChange(eventType, filename) {
77
+ if (this.onChangeCallback) {
78
+ const timestamp = new Date().toLocaleTimeString();
79
+ console.log(`\n[${timestamp}] Change detected: ${filename}`);
80
+ console.log('─'.repeat(50));
81
+
82
+ try {
83
+ await this.onChangeCallback();
84
+ } catch (error) {
85
+ console.error(`Error during validation: ${error.message}`);
86
+ }
87
+
88
+ console.log('─'.repeat(50));
89
+ console.log('Waiting for changes...\n');
90
+ }
91
+ }
92
+
93
+ stop() {
94
+ this.isRunning = false;
95
+
96
+ for (const watcher of this.watchers) {
97
+ watcher.close();
98
+ }
99
+
100
+ this.watchers = [];
101
+
102
+ if (this.debounceTimer) {
103
+ clearTimeout(this.debounceTimer);
104
+ }
105
+
106
+ console.log('\nšŸ‘‹ Watch mode stopped');
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Start watch mode
112
+ */
113
+ export async function startWatchMode(path, options, runValidation) {
114
+ const watcher = new Watcher(path, options, runValidation);
115
+
116
+ // Handle graceful shutdown
117
+ process.on('SIGINT', () => {
118
+ watcher.stop();
119
+ process.exit(0);
120
+ });
121
+
122
+ process.on('SIGTERM', () => {
123
+ watcher.stop();
124
+ process.exit(0);
125
+ });
126
+
127
+ watcher.start();
128
+
129
+ // Keep process alive
130
+ return new Promise(() => {});
131
+ }