@paulduvall/claude-dev-toolkit 0.0.1-alpha.1

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,491 @@
1
+ /**
2
+ * Platform Utilities
3
+ *
4
+ * Shared platform detection and handling utilities to eliminate duplication
5
+ * across DependencyValidator and PermissionErrorHandler classes.
6
+ *
7
+ * Features:
8
+ * - Platform detection and normalization
9
+ * - Platform-specific command resolution
10
+ * - System path detection
11
+ * - Cross-platform compatibility helpers
12
+ */
13
+
14
+ const os = require('os');
15
+
16
+ class PlatformUtils {
17
+ constructor() {
18
+ this.config = {
19
+ platforms: {
20
+ WIN32: 'win32',
21
+ DARWIN: 'darwin',
22
+ LINUX: 'linux'
23
+ },
24
+ systemDirectories: this._initializeSystemDirectories(),
25
+ commandLookup: this._initializeCommandLookup()
26
+ };
27
+ }
28
+
29
+ /**
30
+ * Initialize system directories by platform
31
+ * @returns {Object} System directories configuration
32
+ * @private
33
+ */
34
+ _initializeSystemDirectories() {
35
+ return {
36
+ unix: [
37
+ '/usr', '/usr/local', '/usr/bin', '/usr/local/bin',
38
+ '/etc', '/opt', '/var', '/System', '/Applications'
39
+ ],
40
+ windows: [
41
+ 'C:\\Program Files', 'C:\\Windows', 'C:\\ProgramData',
42
+ 'C:\\Program Files (x86)'
43
+ ]
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Initialize command lookup utilities
49
+ * @returns {Object} Command lookup configuration
50
+ * @private
51
+ */
52
+ _initializeCommandLookup() {
53
+ return {
54
+ pathCommand: {
55
+ win32: 'where',
56
+ unix: 'which'
57
+ },
58
+ elevationMethod: {
59
+ win32: 'administrator',
60
+ unix: 'sudo'
61
+ }
62
+ };
63
+ }
64
+
65
+ /**
66
+ * Get current platform
67
+ * @returns {string} Current platform identifier
68
+ */
69
+ getCurrentPlatform() {
70
+ return process.platform;
71
+ }
72
+
73
+ /**
74
+ * Check if current platform is Windows
75
+ * @returns {boolean} True if Windows
76
+ */
77
+ isWindows() {
78
+ return this.getCurrentPlatform() === this.config.platforms.WIN32;
79
+ }
80
+
81
+ /**
82
+ * Check if current platform is macOS
83
+ * @returns {boolean} True if macOS
84
+ */
85
+ isDarwin() {
86
+ return this.getCurrentPlatform() === this.config.platforms.DARWIN;
87
+ }
88
+
89
+ /**
90
+ * Check if current platform is Linux
91
+ * @returns {boolean} True if Linux
92
+ */
93
+ isLinux() {
94
+ return this.getCurrentPlatform() === this.config.platforms.LINUX;
95
+ }
96
+
97
+ /**
98
+ * Check if current platform is Unix-like (Linux or macOS)
99
+ * @returns {boolean} True if Unix-like
100
+ */
101
+ isUnix() {
102
+ return this.isLinux() || this.isDarwin();
103
+ }
104
+
105
+ /**
106
+ * Get platform category (windows or unix)
107
+ * @param {string} platform - Platform identifier (optional, defaults to current)
108
+ * @returns {string} Platform category
109
+ */
110
+ getPlatformCategory(platform = this.getCurrentPlatform()) {
111
+ return platform === this.config.platforms.WIN32 ? 'windows' : 'unix';
112
+ }
113
+
114
+ /**
115
+ * Get system directories for platform
116
+ * @param {string} platform - Platform identifier (optional, defaults to current)
117
+ * @returns {Array<string>} System directories
118
+ */
119
+ getSystemDirectories(platform = this.getCurrentPlatform()) {
120
+ const category = this.getPlatformCategory(platform);
121
+ return this.config.systemDirectories[category] || [];
122
+ }
123
+
124
+ /**
125
+ * Check if path is a system-level path
126
+ * @param {string} path - Path to check
127
+ * @param {string} platform - Platform identifier (optional, defaults to current)
128
+ * @returns {boolean} True if system path
129
+ */
130
+ isSystemPath(path, platform = this.getCurrentPlatform()) {
131
+ if (!path) return false;
132
+
133
+ const systemDirs = this.getSystemDirectories(platform);
134
+ return systemDirs.some(sysDir => path.startsWith(sysDir));
135
+ }
136
+
137
+ /**
138
+ * Check if path is a user-level path
139
+ * @param {string} path - Path to check
140
+ * @returns {boolean} True if user path
141
+ */
142
+ isUserPath(path) {
143
+ if (!path) return false;
144
+
145
+ const homeDir = os.homedir();
146
+ return path.startsWith(homeDir);
147
+ }
148
+
149
+ /**
150
+ * Get appropriate command for finding executable paths
151
+ * @param {string} platform - Platform identifier (optional, defaults to current)
152
+ * @returns {string} Path command (where/which)
153
+ */
154
+ getPathCommand(platform = this.getCurrentPlatform()) {
155
+ return platform === this.config.platforms.WIN32 ?
156
+ this.config.commandLookup.pathCommand.win32 :
157
+ this.config.commandLookup.pathCommand.unix;
158
+ }
159
+
160
+ /**
161
+ * Get appropriate elevation method
162
+ * @param {string} platform - Platform identifier (optional, defaults to current)
163
+ * @returns {string} Elevation method (administrator/sudo)
164
+ */
165
+ getElevationMethod(platform = this.getCurrentPlatform()) {
166
+ return platform === this.config.platforms.WIN32 ?
167
+ this.config.commandLookup.elevationMethod.win32 :
168
+ this.config.commandLookup.elevationMethod.unix;
169
+ }
170
+
171
+ /**
172
+ * Get user home directory
173
+ * @returns {string} Home directory path
174
+ */
175
+ getHomeDirectory() {
176
+ return os.homedir();
177
+ }
178
+
179
+ /**
180
+ * Get system temporary directory
181
+ * @returns {string} Temporary directory path
182
+ */
183
+ getTempDirectory() {
184
+ return os.tmpdir();
185
+ }
186
+
187
+ /**
188
+ * Get system information
189
+ * @returns {Object} System information object
190
+ */
191
+ getSystemInfo() {
192
+ return {
193
+ platform: this.getCurrentPlatform(),
194
+ arch: process.arch,
195
+ nodeVersion: process.version,
196
+ totalMemory: os.totalmem(),
197
+ freeMemory: os.freemem(),
198
+ homeDir: this.getHomeDirectory(),
199
+ tempDir: this.getTempDirectory(),
200
+ platformCategory: this.getPlatformCategory()
201
+ };
202
+ }
203
+
204
+ /**
205
+ * Normalize platform-specific paths
206
+ * @param {string} path - Path to normalize
207
+ * @param {string} platform - Target platform (optional, defaults to current)
208
+ * @returns {string} Normalized path
209
+ */
210
+ normalizePath(path, platform = this.getCurrentPlatform()) {
211
+ if (!path) return path;
212
+
213
+ if (platform === this.config.platforms.WIN32) {
214
+ // Convert forward slashes to backslashes for Windows
215
+ return path.replace(/\//g, '\\');
216
+ } else {
217
+ // Convert backslashes to forward slashes for Unix-like
218
+ return path.replace(/\\/g, '/');
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Create platform-specific context object
224
+ * @param {string} platform - Platform identifier (optional, defaults to current)
225
+ * @returns {Object} Platform context
226
+ */
227
+ createPlatformContext(platform = this.getCurrentPlatform()) {
228
+ return {
229
+ platform,
230
+ category: this.getPlatformCategory(platform),
231
+ isWindows: platform === this.config.platforms.WIN32,
232
+ isUnix: platform !== this.config.platforms.WIN32,
233
+ systemDirectories: this.getSystemDirectories(platform),
234
+ pathCommand: this.getPathCommand(platform),
235
+ elevationMethod: this.getElevationMethod(platform),
236
+ systemInfo: platform === this.getCurrentPlatform() ? this.getSystemInfo() : null
237
+ };
238
+ }
239
+
240
+ /**
241
+ * Get human-readable platform name
242
+ * @param {string} platform - Platform identifier (optional, defaults to current)
243
+ * @returns {string} Human-readable platform name
244
+ */
245
+ getPlatformName(platform = this.getCurrentPlatform()) {
246
+ const names = {
247
+ 'win32': 'Windows',
248
+ 'darwin': 'macOS',
249
+ 'linux': 'Linux',
250
+ 'freebsd': 'FreeBSD',
251
+ 'openbsd': 'OpenBSD',
252
+ 'sunos': 'SunOS',
253
+ 'aix': 'AIX'
254
+ };
255
+ return names[platform] || platform;
256
+ }
257
+
258
+ /**
259
+ * Get supported platforms list
260
+ * @returns {Array<string>} List of supported platform identifiers
261
+ */
262
+ getSupportedPlatforms() {
263
+ return [
264
+ this.config.platforms.WIN32,
265
+ this.config.platforms.DARWIN,
266
+ this.config.platforms.LINUX
267
+ ];
268
+ }
269
+
270
+ /**
271
+ * Check if platform is supported
272
+ * @param {string} platform - Platform identifier to check
273
+ * @returns {boolean} True if platform is supported
274
+ */
275
+ isPlatformSupported(platform) {
276
+ return this.getSupportedPlatforms().includes(platform);
277
+ }
278
+
279
+ /**
280
+ * Get platform-specific configuration paths
281
+ * @param {string} platform - Platform identifier (optional, defaults to current)
282
+ * @returns {Object} Configuration paths for the platform
283
+ */
284
+ getPlatformConfigPaths(platform = this.getCurrentPlatform()) {
285
+ const homeDir = this.getHomeDirectory();
286
+
287
+ switch (platform) {
288
+ case this.config.platforms.WIN32:
289
+ return {
290
+ config: `${homeDir}\\AppData\\Roaming`,
291
+ localConfig: `${homeDir}\\AppData\\Local`,
292
+ cache: `${homeDir}\\AppData\\Local\\Temp`,
293
+ data: `${homeDir}\\AppData\\Roaming`
294
+ };
295
+ case this.config.platforms.DARWIN:
296
+ return {
297
+ config: `${homeDir}/.config`,
298
+ localConfig: `${homeDir}/.config`,
299
+ cache: `${homeDir}/Library/Caches`,
300
+ data: `${homeDir}/Library/Application Support`
301
+ };
302
+ default: // Linux and other Unix-like
303
+ return {
304
+ config: `${homeDir}/.config`,
305
+ localConfig: `${homeDir}/.config`,
306
+ cache: `${homeDir}/.cache`,
307
+ data: `${homeDir}/.local/share`
308
+ };
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Get platform-specific executable file extension
314
+ * @param {string} platform - Platform identifier (optional, defaults to current)
315
+ * @returns {string} Executable file extension (including dot)
316
+ */
317
+ getExecutableExtension(platform = this.getCurrentPlatform()) {
318
+ return platform === this.config.platforms.WIN32 ? '.exe' : '';
319
+ }
320
+
321
+ /**
322
+ * Get platform-specific shell executable
323
+ * @param {string} platform - Platform identifier (optional, defaults to current)
324
+ * @returns {string} Default shell executable name
325
+ */
326
+ getDefaultShell(platform = this.getCurrentPlatform()) {
327
+ switch (platform) {
328
+ case this.config.platforms.WIN32:
329
+ return 'cmd.exe';
330
+ case this.config.platforms.DARWIN:
331
+ return 'zsh'; // Modern macOS default
332
+ default:
333
+ return 'bash'; // Most Linux distributions
334
+ }
335
+ }
336
+
337
+ /**
338
+ * Get platform-specific path separator
339
+ * @param {string} platform - Platform identifier (optional, defaults to current)
340
+ * @returns {string} Path separator character
341
+ */
342
+ getPathSeparator(platform = this.getCurrentPlatform()) {
343
+ return platform === this.config.platforms.WIN32 ? '\\' : '/';
344
+ }
345
+
346
+ /**
347
+ * Get platform-specific PATH environment variable separator
348
+ * @param {string} platform - Platform identifier (optional, defaults to current)
349
+ * @returns {string} PATH separator character
350
+ */
351
+ getPathEnvSeparator(platform = this.getCurrentPlatform()) {
352
+ return platform === this.config.platforms.WIN32 ? ';' : ':';
353
+ }
354
+
355
+ /**
356
+ * Get platform-specific line ending
357
+ * @param {string} platform - Platform identifier (optional, defaults to current)
358
+ * @returns {string} Line ending characters
359
+ */
360
+ getLineEnding(platform = this.getCurrentPlatform()) {
361
+ return platform === this.config.platforms.WIN32 ? '\r\n' : '\n';
362
+ }
363
+
364
+ /**
365
+ * Get platform-specific package manager preference order
366
+ * @param {string} platform - Platform identifier (optional, defaults to current)
367
+ * @returns {Array<string>} Ordered list of preferred package managers
368
+ */
369
+ getPreferredPackageManagers(platform = this.getCurrentPlatform()) {
370
+ switch (platform) {
371
+ case this.config.platforms.WIN32:
372
+ return ['winget', 'chocolatey', 'scoop', 'npm'];
373
+ case this.config.platforms.DARWIN:
374
+ return ['brew', 'port', 'npm'];
375
+ case this.config.platforms.LINUX:
376
+ return ['apt', 'dnf', 'yum', 'pacman', 'snap', 'npm'];
377
+ default:
378
+ return ['npm'];
379
+ }
380
+ }
381
+
382
+ /**
383
+ * Resolve platform-specific command
384
+ * @param {string} command - Base command name
385
+ * @param {string} platform - Platform identifier (optional, defaults to current)
386
+ * @returns {string} Platform-specific command
387
+ */
388
+ resolveCommand(command, platform = this.getCurrentPlatform()) {
389
+ // Special cases for platform-specific commands
390
+ const commandMap = {
391
+ 'where': platform === this.config.platforms.WIN32 ? 'where' : 'which',
392
+ 'which': platform === this.config.platforms.WIN32 ? 'where' : 'which',
393
+ 'copy': platform === this.config.platforms.WIN32 ? 'copy' : 'cp',
394
+ 'move': platform === this.config.platforms.WIN32 ? 'move' : 'mv',
395
+ 'delete': platform === this.config.platforms.WIN32 ? 'del' : 'rm',
396
+ 'list': platform === this.config.platforms.WIN32 ? 'dir' : 'ls',
397
+ 'type': platform === this.config.platforms.WIN32 ? 'type' : 'cat'
398
+ };
399
+
400
+ return commandMap[command] || command + this.getExecutableExtension(platform);
401
+ }
402
+
403
+ /**
404
+ * Check if path requires elevation/sudo access
405
+ * @param {string} path - Path to check
406
+ * @param {string} platform - Platform identifier (optional, defaults to current)
407
+ * @returns {boolean} True if path requires elevation
408
+ */
409
+ requiresElevation(path, platform = this.getCurrentPlatform()) {
410
+ if (!path) return false;
411
+
412
+ // System paths generally require elevation
413
+ if (this.isSystemPath(path, platform)) {
414
+ return true;
415
+ }
416
+
417
+ // Platform-specific elevation requirements
418
+ const elevationPaths = {
419
+ [this.config.platforms.WIN32]: [
420
+ 'C:\\Windows', 'C:\\Program Files', 'C:\\Program Files (x86)'
421
+ ],
422
+ [this.config.platforms.DARWIN]: [
423
+ '/usr', '/System', '/Library', '/Applications'
424
+ ],
425
+ [this.config.platforms.LINUX]: [
426
+ '/usr', '/etc', '/opt', '/var', '/sys', '/proc'
427
+ ]
428
+ };
429
+
430
+ const platformPaths = elevationPaths[platform] || [];
431
+ return platformPaths.some(elevationPath => path.startsWith(elevationPath));
432
+ }
433
+
434
+ /**
435
+ * Generate platform compatibility report
436
+ * @param {Array<string>} requiredPlatforms - Platforms required for compatibility
437
+ * @returns {Object} Platform compatibility report
438
+ */
439
+ generateCompatibilityReport(requiredPlatforms = []) {
440
+ const currentPlatform = this.getCurrentPlatform();
441
+ const supportedPlatforms = this.getSupportedPlatforms();
442
+
443
+ return {
444
+ current: {
445
+ platform: currentPlatform,
446
+ name: this.getPlatformName(currentPlatform),
447
+ category: this.getPlatformCategory(currentPlatform),
448
+ supported: this.isPlatformSupported(currentPlatform)
449
+ },
450
+ requirements: {
451
+ platforms: requiredPlatforms,
452
+ satisfied: requiredPlatforms.length === 0 || requiredPlatforms.includes(currentPlatform),
453
+ missing: requiredPlatforms.filter(p => !supportedPlatforms.includes(p))
454
+ },
455
+ capabilities: {
456
+ supportedPlatforms: supportedPlatforms.map(platform => ({
457
+ platform,
458
+ name: this.getPlatformName(platform),
459
+ category: this.getPlatformCategory(platform)
460
+ })),
461
+ currentCapabilities: this.createPlatformContext(currentPlatform)
462
+ },
463
+ recommendations: this._generatePlatformRecommendations(requiredPlatforms, currentPlatform)
464
+ };
465
+ }
466
+
467
+ /**
468
+ * Generate platform-specific recommendations
469
+ * @param {Array<string>} requiredPlatforms - Required platforms
470
+ * @param {string} currentPlatform - Current platform
471
+ * @returns {Array<string>} Platform recommendations
472
+ * @private
473
+ */
474
+ _generatePlatformRecommendations(requiredPlatforms, currentPlatform) {
475
+ const recommendations = [];
476
+
477
+ if (requiredPlatforms.length > 0 && !requiredPlatforms.includes(currentPlatform)) {
478
+ recommendations.push(`Current platform ${this.getPlatformName(currentPlatform)} is not in required platforms: ${requiredPlatforms.map(p => this.getPlatformName(p)).join(', ')}`);
479
+ recommendations.push('Consider using a supported platform or updating compatibility requirements');
480
+ }
481
+
482
+ if (!this.isPlatformSupported(currentPlatform)) {
483
+ recommendations.push(`Platform ${this.getPlatformName(currentPlatform)} may have limited support`);
484
+ recommendations.push(`Consider using one of the fully supported platforms: ${this.getSupportedPlatforms().map(p => this.getPlatformName(p)).join(', ')}`);
485
+ }
486
+
487
+ return recommendations;
488
+ }
489
+ }
490
+
491
+ module.exports = PlatformUtils;